💄 Optimize chat input a step further

This commit is contained in:
2025-09-27 15:31:57 +08:00
parent b9dc724f0b
commit 1fbaac8d88
3 changed files with 379 additions and 357 deletions

View File

@@ -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,

View File

@@ -115,9 +115,14 @@ class ChatInput extends HookConsumerWidget {
return KeyEventResult.ignored; return KeyEventResult.ignored;
}; };
return Material( return Container(
elevation: 8, margin: const EdgeInsets.all(16),
color: Theme.of(context).colorScheme.surface, 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)
@@ -151,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(
@@ -181,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,
@@ -306,10 +317,11 @@ class ChatInput extends HookConsumerWidget {
onPressed: send, onPressed: send,
), ),
], ],
).padding(bottom: MediaQuery.of(context).padding.bottom),
), ),
], ],
), ),
),
),
); );
} }
} }

View File

@@ -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(