Compare commits
2 Commits
a2cc55696f
...
1fbaac8d88
Author | SHA1 | Date | |
---|---|---|---|
1fbaac8d88
|
|||
b9dc724f0b
|
@@ -541,7 +541,10 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
Widget chatMessageListWidget(List<LocalChatMessage> messageList) =>
|
Widget chatMessageListWidget(List<LocalChatMessage> messageList) =>
|
||||||
SuperListView.builder(
|
SuperListView.builder(
|
||||||
listController: listController,
|
listController: listController,
|
||||||
padding: EdgeInsets.symmetric(vertical: 16),
|
padding: EdgeInsets.only(
|
||||||
|
top: 16,
|
||||||
|
bottom: 96 + MediaQuery.of(context).padding.bottom,
|
||||||
|
),
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
reverse: true, // Show newest messages at the bottom
|
reverse: true, // Show newest messages at the bottom
|
||||||
itemCount: messageList.length,
|
itemCount: messageList.length,
|
||||||
@@ -735,17 +738,15 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
// Messages
|
||||||
children: [
|
Positioned.fill(
|
||||||
Expanded(
|
|
||||||
child: messages.when(
|
child: messages.when(
|
||||||
data:
|
data:
|
||||||
(messageList) =>
|
(messageList) =>
|
||||||
messageList.isEmpty
|
messageList.isEmpty
|
||||||
? Center(child: Text('No messages yet'.tr()))
|
? Center(child: Text('No messages yet'.tr()))
|
||||||
: chatMessageListWidget(messageList),
|
: chatMessageListWidget(messageList),
|
||||||
loading:
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
() => const Center(child: CircularProgressIndicator()),
|
|
||||||
error:
|
error:
|
||||||
(error, _) => ResponseErrorWidget(
|
(error, _) => ResponseErrorWidget(
|
||||||
error: error,
|
error: error,
|
||||||
@@ -753,7 +754,12 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
chatRoom.when(
|
// Input
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: chatRoom.when(
|
||||||
data:
|
data:
|
||||||
(room) => Column(
|
(room) => Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -880,12 +886,12 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
attachmentProgress: attachmentProgress.value,
|
attachmentProgress: attachmentProgress.value,
|
||||||
),
|
),
|
||||||
|
Gap(MediaQuery.of(context).padding.bottom),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
error: (_, _) => const SizedBox.shrink(),
|
error: (_, _) => const SizedBox.shrink(),
|
||||||
loading: () => const SizedBox.shrink(),
|
loading: () => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
import "dart:async";
|
import "dart:async";
|
||||||
import "dart:io";
|
|
||||||
import "package:easy_localization/easy_localization.dart";
|
import "package:easy_localization/easy_localization.dart";
|
||||||
import "package:flutter/foundation.dart";
|
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter/services.dart";
|
import "package:flutter/services.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
@@ -56,10 +54,6 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final inputFocusNode = useFocusNode();
|
final inputFocusNode = useFocusNode();
|
||||||
|
|
||||||
final enterToSend = ref.watch(appSettingsNotifierProvider).enterToSend;
|
|
||||||
|
|
||||||
final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
|
|
||||||
|
|
||||||
void send() {
|
void send() {
|
||||||
onSend.call();
|
onSend.call();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
@@ -67,6 +61,18 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void insertNewLine() {
|
||||||
|
final text = messageController.text;
|
||||||
|
final selection = messageController.selection;
|
||||||
|
final start = selection.start >= 0 ? selection.start : text.length;
|
||||||
|
final end = selection.end >= 0 ? selection.end : text.length;
|
||||||
|
final newText = text.replaceRange(start, end, '\n');
|
||||||
|
messageController.value = TextEditingValue(
|
||||||
|
text: newText,
|
||||||
|
selection: TextSelection.collapsed(offset: start + 1),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> handlePaste() async {
|
Future<void> handlePaste() async {
|
||||||
final clipboard = await Pasteboard.image;
|
final clipboard = await Pasteboard.image;
|
||||||
if (clipboard == null) return;
|
if (clipboard == null) return;
|
||||||
@@ -80,36 +86,43 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleKeyPress(
|
inputFocusNode.onKeyEvent = (node, event) {
|
||||||
BuildContext context,
|
if (event is! KeyDownEvent) return KeyEventResult.ignored;
|
||||||
WidgetRef ref,
|
|
||||||
RawKeyEvent event,
|
|
||||||
) {
|
|
||||||
if (event is! RawKeyDownEvent) return;
|
|
||||||
|
|
||||||
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
final isPaste = event.logicalKey == LogicalKeyboardKey.keyV;
|
||||||
final isModifierPressed = event.isMetaPressed || event.isControlPressed;
|
final isModifierPressed =
|
||||||
|
HardwareKeyboard.instance.isMetaPressed ||
|
||||||
|
HardwareKeyboard.instance.isControlPressed;
|
||||||
|
|
||||||
if (isPaste && isModifierPressed) {
|
if (isPaste && isModifierPressed) {
|
||||||
handlePaste();
|
handlePaste();
|
||||||
return;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
final enterToSend = ref.read(appSettingsNotifierProvider).enterToSend;
|
final enterToSend = ref.read(appSettingsNotifierProvider).enterToSend;
|
||||||
final isEnter = event.logicalKey == LogicalKeyboardKey.enter;
|
final isEnter = event.logicalKey == LogicalKeyboardKey.enter;
|
||||||
|
|
||||||
if (isEnter) {
|
if (isEnter) {
|
||||||
if (enterToSend && !isModifierPressed) {
|
if (isModifierPressed) {
|
||||||
|
insertNewLine();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else if (enterToSend) {
|
||||||
send();
|
send();
|
||||||
} else if (!enterToSend && isModifierPressed) {
|
return KeyEventResult.handled;
|
||||||
send();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Material(
|
return KeyEventResult.ignored;
|
||||||
elevation: 8,
|
};
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(16),
|
||||||
|
child: Material(
|
||||||
|
elevation: 2,
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
|
borderRadius: BorderRadius.circular(32),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (attachments.isNotEmpty)
|
if (attachments.isNotEmpty)
|
||||||
@@ -143,12 +156,20 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
messageForwardingTo != null ||
|
messageForwardingTo != null ||
|
||||||
messageEditingTo != null)
|
messageEditingTo != null)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(
|
||||||
decoration: BoxDecoration(
|
horizontal: 16,
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
vertical: 4,
|
||||||
borderRadius: BorderRadius.circular(8),
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
borderRadius: BorderRadius.circular(32),
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.only(
|
||||||
|
left: 8,
|
||||||
|
right: 8,
|
||||||
|
top: 8,
|
||||||
|
bottom: 4,
|
||||||
),
|
),
|
||||||
margin: const EdgeInsets.only(left: 8, right: 8, top: 8),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
@@ -173,20 +194,18 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
SizedBox(
|
||||||
icon: const Icon(Icons.close, size: 20),
|
width: 28,
|
||||||
onPressed: onClear,
|
height: 28,
|
||||||
padding: EdgeInsets.zero,
|
child: InkWell(
|
||||||
style: ButtonStyle(
|
onTap: onClear,
|
||||||
minimumSize: WidgetStatePropertyAll(Size(28, 28)),
|
child: const Icon(Icons.close, size: 20).center(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Row(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -260,32 +279,10 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: RawKeyboardListener(
|
|
||||||
focusNode: FocusNode(),
|
|
||||||
onKey: (event) => handleKeyPress(context, ref, event),
|
|
||||||
child: TextField(
|
child: TextField(
|
||||||
focusNode: inputFocusNode,
|
focusNode: inputFocusNode,
|
||||||
controller: messageController,
|
controller: messageController,
|
||||||
onSubmitted:
|
keyboardType: TextInputType.multiline,
|
||||||
(enterToSend && isMobile)
|
|
||||||
? (_) {
|
|
||||||
send();
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
keyboardType:
|
|
||||||
(enterToSend && isMobile)
|
|
||||||
? TextInputType.text
|
|
||||||
: TextInputType.multiline,
|
|
||||||
textInputAction: TextInputAction.send,
|
|
||||||
inputFormatters: [
|
|
||||||
if (enterToSend && !isMobile)
|
|
||||||
TextInputFormatter.withFunction((oldValue, newValue) {
|
|
||||||
if (newValue.text.endsWith('\n')) {
|
|
||||||
return oldValue;
|
|
||||||
}
|
|
||||||
return newValue;
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText:
|
hintText:
|
||||||
(chatRoom.type == 1 && chatRoom.name == null)
|
(chatRoom.type == 1 && chatRoom.name == null)
|
||||||
@@ -314,17 +311,17 @@ class ChatInput extends HookConsumerWidget {
|
|||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.send),
|
icon: const Icon(Icons.send),
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
onPressed: send,
|
onPressed: send,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(bottom: MediaQuery.of(context).padding.bottom),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -56,7 +56,7 @@ class MessageContent extends StatelessWidget {
|
|||||||
case 'messages.update.links':
|
case 'messages.update.links':
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Symbols.edit,
|
Symbols.edit,
|
||||||
@@ -64,10 +64,11 @@ class MessageContent extends StatelessWidget {
|
|||||||
color: Theme.of(
|
color: Theme.of(
|
||||||
context,
|
context,
|
||||||
).colorScheme.onSurfaceVariant.withOpacity(0.6),
|
).colorScheme.onSurfaceVariant.withOpacity(0.6),
|
||||||
),
|
).padding(top: 2),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
if (item.meta['previous_content'] is String)
|
if (item.meta['previous_content'] is String)
|
||||||
PrettyDiffText(
|
Flexible(
|
||||||
|
child: PrettyDiffText(
|
||||||
oldText: item.meta['previous_content'],
|
oldText: item.meta['previous_content'],
|
||||||
newText: item.content ?? 'Edited a message',
|
newText: item.content ?? 'Edited a message',
|
||||||
defaultTextStyle: Theme.of(
|
defaultTextStyle: Theme.of(
|
||||||
@@ -86,6 +87,7 @@ class MessageContent extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
).colorScheme.onSurfaceVariant.withOpacity(0.7),
|
).colorScheme.onSurfaceVariant.withOpacity(0.7),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Text(
|
Text(
|
||||||
@@ -104,11 +106,13 @@ class MessageContent extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
MarkdownTextContent(
|
Flexible(
|
||||||
|
child: MarkdownTextContent(
|
||||||
content: item.content ?? '*${item.type} has no content*',
|
content: item.content ?? '*${item.type} has no content*',
|
||||||
isSelectable: true,
|
isSelectable: true,
|
||||||
linesMargin: EdgeInsets.zero,
|
linesMargin: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
if (translatedText?.isNotEmpty ?? false)
|
if (translatedText?.isNotEmpty ?? false)
|
||||||
...([
|
...([
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
|
Reference in New Issue
Block a user