💄 Optimize message indicator

This commit is contained in:
2025-10-10 21:38:53 +08:00
parent 598c51bc1a
commit a201f20793
3 changed files with 271 additions and 281 deletions

View File

@@ -9,6 +9,7 @@ class MessageIndicators extends StatelessWidget {
final MessageStatus? status; final MessageStatus? status;
final bool isCurrentUser; final bool isCurrentUser;
final Color textColor; final Color textColor;
final EdgeInsets padding;
const MessageIndicators({ const MessageIndicators({
super.key, super.key,
@@ -16,26 +17,43 @@ class MessageIndicators extends StatelessWidget {
this.status, this.status,
required this.isCurrentUser, required this.isCurrentUser,
required this.textColor, required this.textColor,
this.padding = const EdgeInsets.only(left: 6),
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( final children = <Widget>[];
spacing: 4,
mainAxisSize: MainAxisSize.min, if (editedAt != null) {
children: [ children.add(
if (editedAt != null) Text(
Text( 'edited'.tr().toLowerCase(),
'edited'.tr().toLowerCase(), style: TextStyle(fontSize: 11, color: textColor.withOpacity(0.7)),
style: TextStyle(fontSize: 11, color: textColor.withOpacity(0.7)), ),
), );
if (isCurrentUser && status != null) }
_buildStatusIcon(
context, if (isCurrentUser && status != null && status != MessageStatus.sent) {
status!, children.add(
textColor.withOpacity(0.7), _buildStatusIcon(
).padding(bottom: 3), context,
], status!,
textColor.withOpacity(0.7),
).padding(bottom: 4),
);
}
if (children.isEmpty) {
return const SizedBox.shrink();
}
return Padding(
padding: padding,
child: Row(
spacing: 4,
mainAxisSize: MainAxisSize.min,
children: children,
),
); );
} }
@@ -46,9 +64,18 @@ class MessageIndicators extends StatelessWidget {
) { ) {
switch (status) { switch (status) {
case MessageStatus.pending: case MessageStatus.pending:
return Icon(Icons.access_time, size: 12, color: textColor); return SizedBox(
width: 10,
height: 10,
child: CircularProgressIndicator(
padding: EdgeInsets.zero,
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(textColor),
),
);
case MessageStatus.sent: case MessageStatus.sent:
return Icon(Icons.check, size: 12, color: textColor); // Sent status is hidden
return const SizedBox.shrink();
case MessageStatus.failed: case MessageStatus.failed:
return Consumer( return Consumer(
builder: builder:

View File

@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:intl/intl.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -714,45 +715,12 @@ class MessageItemDisplayBubble extends HookConsumerWidget {
renderingPadding: EdgeInsets.zero, renderingPadding: EdgeInsets.zero,
maxWidth: 480, maxWidth: 480,
), ),
if (progress != null && progress!.isNotEmpty) FileUploadProgressWidget(
Column( progress: progress,
crossAxisAlignment: CrossAxisAlignment.stretch, textColor: textColor,
spacing: 8, hasContent:
children: [ remoteMessage.content?.isNotEmpty ?? false,
if ((remoteMessage.content?.isNotEmpty ?? false)) ),
const Gap(0),
for (var entry in progress!.entries)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'fileUploadingProgress'.tr(
args: [
(entry.key + 1).toString(),
entry.value.toStringAsFixed(1),
],
),
style: TextStyle(
fontSize: 12,
color: textColor.withOpacity(0.8),
),
),
const Gap(4),
LinearProgressIndicator(
value: entry.value / 100,
backgroundColor:
Theme.of(
context,
).colorScheme.surfaceVariant,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
),
],
),
const Gap(0),
],
),
], ],
), ),
), ),
@@ -833,81 +801,64 @@ class MessageItemDisplayIRC extends HookConsumerWidget {
), ),
const Gap(8), const Gap(8),
Expanded( Expanded(
child: Column( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
if (remoteMessage.repliedMessageId != null) Flexible(
MessageQuoteWidget( child: Column(
message: message, crossAxisAlignment: CrossAxisAlignment.start,
textColor: textColor,
isReply: true,
).padding(vertical: 4),
if (remoteMessage.forwardedMessageId != null)
MessageQuoteWidget(
message: message,
textColor: textColor,
isReply: false,
).padding(vertical: 4),
if (MessageContent.hasContent(remoteMessage))
MessageContent(
item: remoteMessage,
translatedText: translatedText,
),
if (remoteMessage.attachments.isNotEmpty)
LayoutBuilder(
builder: (context, constraints) {
return CloudFileList(
files: remoteMessage.attachments,
maxWidth: constraints.maxWidth,
padding: EdgeInsets.symmetric(vertical: 4),
);
},
),
if (remoteMessage.meta['embeds'] != null &&
kMessageEnableEmbedTypes.contains(message.type))
EmbedListWidget(
embeds: remoteMessage.meta['embeds'] as List<dynamic>,
isInteractive: true,
isFullPost: false,
renderingPadding: EdgeInsets.zero,
maxWidth: 480,
),
if (progress != null && progress!.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 8,
children: [ children: [
if ((remoteMessage.content?.isNotEmpty ?? false)) if (remoteMessage.repliedMessageId != null)
const SizedBox.shrink(), MessageQuoteWidget(
for (var entry in progress!.entries) message: message,
Column( textColor: textColor,
crossAxisAlignment: CrossAxisAlignment.start, isReply: true,
children: [ ).padding(vertical: 4),
Text( if (remoteMessage.forwardedMessageId != null)
'fileUploadingProgress'.tr( MessageQuoteWidget(
args: [ message: message,
(entry.key + 1).toString(), textColor: textColor,
entry.value.toStringAsFixed(1), isReply: false,
], ).padding(vertical: 4),
), if (MessageContent.hasContent(remoteMessage))
style: TextStyle( MessageContent(
fontSize: 12, item: remoteMessage,
color: textColor.withOpacity(0.8), translatedText: translatedText,
),
),
const Gap(4),
LinearProgressIndicator(
value: entry.value / 100,
backgroundColor:
Theme.of(context).colorScheme.surfaceVariant,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
),
],
), ),
if (remoteMessage.attachments.isNotEmpty)
LayoutBuilder(
builder: (context, constraints) {
return CloudFileList(
files: remoteMessage.attachments,
maxWidth: constraints.maxWidth,
padding: EdgeInsets.symmetric(vertical: 4),
);
},
),
if (remoteMessage.meta['embeds'] != null &&
kMessageEnableEmbedTypes.contains(message.type))
EmbedListWidget(
embeds: remoteMessage.meta['embeds'] as List<dynamic>,
isInteractive: true,
isFullPost: false,
renderingPadding: EdgeInsets.zero,
maxWidth: 480,
),
FileUploadProgressWidget(
progress: progress,
textColor: textColor,
hasContent: remoteMessage.content?.isNotEmpty ?? false,
),
], ],
), ),
),
MessageIndicators(
editedAt: remoteMessage.editedAt,
status: message.status,
isCurrentUser: isCurrentUser,
textColor: textColor,
),
], ],
), ),
), ),
@@ -971,175 +922,133 @@ class MessageItemDisplayDiscord extends HookConsumerWidget {
), ),
], ],
), ),
Column( Row(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
if (remoteMessage.repliedMessageId != null) Flexible(
MessageQuoteWidget( child: Column(
message: message, crossAxisAlignment: CrossAxisAlignment.start,
textColor: textColor,
isReply: true,
).padding(vertical: 4),
if (remoteMessage.forwardedMessageId != null)
MessageQuoteWidget(
message: message,
textColor: textColor,
isReply: false,
).padding(vertical: 4),
if (MessageContent.hasContent(remoteMessage))
MessageContent(
item: remoteMessage,
translatedText: translatedText,
),
if (remoteMessage.attachments.isNotEmpty)
LayoutBuilder(
builder: (context, constraints) {
return CloudFileList(
files: remoteMessage.attachments,
maxWidth: constraints.maxWidth,
padding: EdgeInsets.symmetric(vertical: 4),
);
},
),
if (remoteMessage.meta['embeds'] != null &&
kMessageEnableEmbedTypes.contains(message.type))
EmbedListWidget(
embeds: remoteMessage.meta['embeds'] as List<dynamic>,
isInteractive: true,
isFullPost: false,
renderingPadding: EdgeInsets.zero,
maxWidth: 480,
),
if (progress != null && progress!.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 8,
children: [ children: [
if ((remoteMessage.content?.isNotEmpty ?? false)) if (remoteMessage.repliedMessageId != null)
const SizedBox.shrink(), MessageQuoteWidget(
for (var entry in progress!.entries) message: message,
Column( textColor: textColor,
crossAxisAlignment: CrossAxisAlignment.start, isReply: true,
children: [ ).padding(vertical: 4),
Text( if (remoteMessage.forwardedMessageId != null)
'fileUploadingProgress'.tr( MessageQuoteWidget(
args: [ message: message,
(entry.key + 1).toString(), textColor: textColor,
entry.value.toStringAsFixed(1), isReply: false,
], ).padding(vertical: 4),
), if (MessageContent.hasContent(remoteMessage))
style: TextStyle( MessageContent(
fontSize: 12, item: remoteMessage,
color: textColor.withOpacity(0.8), translatedText: translatedText,
),
),
const Gap(4),
LinearProgressIndicator(
value: entry.value / 100,
backgroundColor:
Theme.of(
context,
).colorScheme.surfaceVariant,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
),
],
), ),
if (remoteMessage.attachments.isNotEmpty)
LayoutBuilder(
builder: (context, constraints) {
return CloudFileList(
files: remoteMessage.attachments,
maxWidth: constraints.maxWidth,
padding: EdgeInsets.symmetric(vertical: 4),
);
},
),
if (remoteMessage.meta['embeds'] != null &&
kMessageEnableEmbedTypes.contains(message.type))
EmbedListWidget(
embeds:
remoteMessage.meta['embeds']
as List<dynamic>,
isInteractive: true,
isFullPost: false,
renderingPadding: EdgeInsets.zero,
maxWidth: 480,
),
FileUploadProgressWidget(
progress: progress,
textColor: textColor,
hasContent:
remoteMessage.content?.isNotEmpty ?? false,
),
], ],
), ),
),
MessageIndicators(
editedAt: remoteMessage.editedAt,
status: message.status,
isCurrentUser: isCurrentUser,
textColor: textColor,
),
], ],
).padding(left: kAvatarRadius * 2 + 8), ).padding(left: kAvatarRadius * 2 + 8),
], ],
) )
: Padding( : Padding(
padding: EdgeInsets.only(left: kAvatarRadius * 2 + 8), padding: EdgeInsets.only(left: kAvatarRadius * 2 + 8),
child: Column( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
if (showAvatar) Flexible(
MessageSenderInfo( child: Column(
sender: sender, crossAxisAlignment: CrossAxisAlignment.start,
createdAt: message.createdAt,
textColor: textColor,
showAvatar: false,
isCompact: true,
),
if (remoteMessage.repliedMessageId != null)
MessageQuoteWidget(
message: message,
textColor: textColor,
isReply: true,
).padding(vertical: 4),
if (remoteMessage.forwardedMessageId != null)
MessageQuoteWidget(
message: message,
textColor: textColor,
isReply: false,
).padding(vertical: 4),
if (MessageContent.hasContent(remoteMessage))
MessageContent(
item: remoteMessage,
translatedText: translatedText,
),
if (remoteMessage.attachments.isNotEmpty)
LayoutBuilder(
builder: (context, constraints) {
return CloudFileList(
files: remoteMessage.attachments,
maxWidth: constraints.maxWidth,
padding: EdgeInsets.symmetric(vertical: 4),
);
},
),
if (remoteMessage.meta['embeds'] != null &&
kMessageEnableEmbedTypes.contains(message.type))
EmbedListWidget(
embeds: remoteMessage.meta['embeds'] as List<dynamic>,
isInteractive: true,
isFullPost: false,
renderingPadding: EdgeInsets.zero,
maxWidth: 480,
),
if (progress != null && progress!.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 8,
children: [ children: [
if ((remoteMessage.content?.isNotEmpty ?? false)) if (remoteMessage.repliedMessageId != null)
const Gap(0), MessageQuoteWidget(
for (var entry in progress!.entries) message: message,
Column( textColor: textColor,
crossAxisAlignment: CrossAxisAlignment.start, isReply: true,
children: [ ).padding(vertical: 4),
Text( if (remoteMessage.forwardedMessageId != null)
'fileUploadingProgress'.tr( MessageQuoteWidget(
args: [ message: message,
(entry.key + 1).toString(), textColor: textColor,
entry.value.toStringAsFixed(1), isReply: false,
], ).padding(vertical: 4),
), if (MessageContent.hasContent(remoteMessage))
style: TextStyle( MessageContent(
fontSize: 12, item: remoteMessage,
color: textColor.withOpacity(0.8), translatedText: translatedText,
),
),
const Gap(4),
LinearProgressIndicator(
value: entry.value / 100,
backgroundColor:
Theme.of(
context,
).colorScheme.surfaceVariant,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
),
],
), ),
const Gap(0), if (remoteMessage.attachments.isNotEmpty)
LayoutBuilder(
builder: (context, constraints) {
return CloudFileList(
files: remoteMessage.attachments,
maxWidth: constraints.maxWidth,
padding: EdgeInsets.symmetric(vertical: 4),
);
},
),
if (remoteMessage.meta['embeds'] != null &&
kMessageEnableEmbedTypes.contains(message.type))
EmbedListWidget(
embeds:
remoteMessage.meta['embeds'] as List<dynamic>,
isInteractive: true,
isFullPost: false,
renderingPadding: EdgeInsets.zero,
maxWidth: 480,
),
FileUploadProgressWidget(
progress: progress,
textColor: textColor,
hasContent:
remoteMessage.content?.isNotEmpty ?? false,
),
], ],
), ),
),
MessageIndicators(
editedAt: remoteMessage.editedAt,
status: message.status,
isCurrentUser: isCurrentUser,
textColor: textColor,
),
], ],
), ),
), ),
@@ -1249,3 +1158,57 @@ class MessageQuoteWidget extends HookConsumerWidget {
); );
} }
} }
class FileUploadProgressWidget extends StatelessWidget {
final Map<int, double>? progress;
final Color textColor;
final bool hasContent;
const FileUploadProgressWidget({
super.key,
required this.progress,
required this.textColor,
required this.hasContent,
});
@override
Widget build(BuildContext context) {
if (progress == null || progress!.isEmpty) return const SizedBox.shrink();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 8,
children: [
if (hasContent) const Gap(0),
for (var entry in progress!.entries)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'fileUploadingProgress'.tr(
args: [
(entry.key + 1).toString(),
entry.value.toStringAsFixed(1),
],
),
style: TextStyle(
fontSize: 12,
color: textColor.withOpacity(0.8),
),
),
const Gap(4),
LinearProgressIndicator(
value: entry.value,
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
trackGap: 0,
),
],
),
const Gap(0),
],
);
}
}

View File

@@ -52,7 +52,7 @@ class UploadMenu extends StatelessWidget {
leadingIcon: Icon(item.icon), leadingIcon: Icon(item.icon),
style: ButtonStyle( style: ButtonStyle(
padding: WidgetStatePropertyAll( padding: WidgetStatePropertyAll(
EdgeInsets.symmetric(horizontal: 16, vertical: 20), EdgeInsets.only(left: 12, right: 16, top: 20, bottom: 20),
), ),
), ),
child: Text(item.textKey.tr()), child: Text(item.textKey.tr()),