✨ Provide three styles of message
This commit is contained in:
@@ -18,6 +18,32 @@ class MessageContent extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (item.type == 'messages.delete' || item.deletedAt != null) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.delete,
|
||||
size: 14,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant.withOpacity(0.6),
|
||||
),
|
||||
const Gap(4),
|
||||
Text(
|
||||
item.content ?? 'Deleted a message',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant.withOpacity(0.6),
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
switch (item.type) {
|
||||
case 'call.start':
|
||||
case 'call.ended':
|
||||
@@ -71,30 +97,6 @@ class MessageContent extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
);
|
||||
case 'messages.delete':
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.delete,
|
||||
size: 14,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant.withOpacity(0.6),
|
||||
),
|
||||
const Gap(4),
|
||||
Text(
|
||||
item.content ?? 'Deleted a message',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant.withOpacity(0.6),
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
case 'text':
|
||||
default:
|
||||
return Column(
|
||||
|
@@ -15,16 +15,20 @@ import 'package:island/pods/messages_notifier.dart';
|
||||
import 'package:island/pods/translate.dart';
|
||||
import 'package:island/screens/chat/room.dart';
|
||||
import 'package:island/utils/mapping.dart';
|
||||
import 'package:island/widgets/account/account_pfc.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/widgets/chat/message_content.dart';
|
||||
import 'package:island/widgets/chat/message_indicators.dart';
|
||||
import 'package:island/widgets/chat/message_sender_info.dart';
|
||||
import 'package:island/widgets/content/alert.native.dart';
|
||||
import 'package:island/widgets/content/cloud_file_collection.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/content/embed/link.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:super_context_menu/super_context_menu.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
|
||||
const kChatMessageStyle = 'discord';
|
||||
|
||||
class MessageItemAction {
|
||||
static const String edit = "edit";
|
||||
@@ -51,6 +55,188 @@ class MessageItem extends HookConsumerWidget {
|
||||
required this.onJump,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final remoteMessage = message.toRemoteMessage();
|
||||
|
||||
final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
|
||||
|
||||
final messageLanguage =
|
||||
remoteMessage.content != null
|
||||
? ref.watch(detectStringLanguageProvider(remoteMessage.content!))
|
||||
: null;
|
||||
|
||||
final currentLanguage = context.locale.toString();
|
||||
final translatableLanguage =
|
||||
messageLanguage != null
|
||||
? messageLanguage.substring(0, 2) != currentLanguage.substring(0, 2)
|
||||
: false;
|
||||
|
||||
final translating = useState(false);
|
||||
final translatedText = useState<String?>(null);
|
||||
|
||||
Future<void> translate() async {
|
||||
if (translatedText.value != null) {
|
||||
translatedText.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (translating.value) return;
|
||||
if (remoteMessage.content == null) return;
|
||||
translating.value = true;
|
||||
try {
|
||||
final text = await ref.watch(
|
||||
translateStringProvider(
|
||||
TranslateQuery(
|
||||
text: remoteMessage.content!,
|
||||
lang: currentLanguage.substring(0, 2),
|
||||
),
|
||||
).future,
|
||||
);
|
||||
translatedText.value = text;
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
translating.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void showActionMenu() {
|
||||
if (onAction == null) return;
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder:
|
||||
(context) => SheetScaffold(
|
||||
titleText: 'messageActions'.tr(),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
if (isCurrentUser)
|
||||
ListTile(
|
||||
leading: Icon(Symbols.edit),
|
||||
title: Text('edit'.tr()),
|
||||
onTap: () {
|
||||
onAction!.call(MessageItemAction.edit);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
if (isCurrentUser)
|
||||
ListTile(
|
||||
leading: Icon(Symbols.delete),
|
||||
title: Text('delete'.tr()),
|
||||
onTap: () {
|
||||
onAction!.call(MessageItemAction.delete);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
if (isCurrentUser) Divider(),
|
||||
ListTile(
|
||||
leading: Icon(Symbols.reply),
|
||||
title: Text('reply'.tr()),
|
||||
onTap: () {
|
||||
onAction!.call(MessageItemAction.reply);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(Symbols.forward),
|
||||
title: Text('forward'.tr()),
|
||||
onTap: () {
|
||||
onAction!.call(MessageItemAction.forward);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
if (translatableLanguage) Divider(),
|
||||
if (translatableLanguage)
|
||||
ListTile(
|
||||
leading: Icon(Symbols.translate),
|
||||
title: Text(
|
||||
translatedText.value == null
|
||||
? 'translate'.tr()
|
||||
: translating.value
|
||||
? 'translating'.tr()
|
||||
: 'translated'.tr(),
|
||||
),
|
||||
onTap: () {
|
||||
translate();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
if (isMobile) Divider(),
|
||||
if (isMobile)
|
||||
ListTile(
|
||||
leading: Icon(Symbols.copy_all),
|
||||
title: Text('copyMessage'.tr()),
|
||||
onTap: () {
|
||||
Clipboard.setData(
|
||||
ClipboardData(text: remoteMessage.content ?? ''),
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onLongPress: showActionMenu,
|
||||
onSecondaryTap: showActionMenu,
|
||||
child: switch (kChatMessageStyle) {
|
||||
'irc' => MessageItemDisplayIRC(
|
||||
message: message,
|
||||
isCurrentUser: isCurrentUser,
|
||||
progress: progress,
|
||||
showAvatar: showAvatar,
|
||||
onJump: onJump,
|
||||
translatedText: translatedText.value,
|
||||
translating: translating.value,
|
||||
),
|
||||
'discord' => MessageItemDisplayDiscord(
|
||||
message: message,
|
||||
isCurrentUser: isCurrentUser,
|
||||
progress: progress,
|
||||
showAvatar: showAvatar,
|
||||
onJump: onJump,
|
||||
translatedText: translatedText.value,
|
||||
translating: translating.value,
|
||||
),
|
||||
_ => MessageItemDisplayBubble(
|
||||
message: message,
|
||||
isCurrentUser: isCurrentUser,
|
||||
progress: progress,
|
||||
showAvatar: showAvatar,
|
||||
onJump: onJump,
|
||||
translatedText: translatedText.value,
|
||||
translating: translating.value,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MessageItemDisplayBubble extends HookConsumerWidget {
|
||||
final LocalChatMessage message;
|
||||
final bool isCurrentUser;
|
||||
final Map<int, double>? progress;
|
||||
final bool showAvatar;
|
||||
final Function(String messageId) onJump;
|
||||
final String? translatedText;
|
||||
final bool translating;
|
||||
|
||||
const MessageItemDisplayBubble({
|
||||
super.key,
|
||||
required this.message,
|
||||
required this.isCurrentUser,
|
||||
required this.progress,
|
||||
required this.showAvatar,
|
||||
required this.onJump,
|
||||
required this.translatedText,
|
||||
required this.translating,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final textColor =
|
||||
@@ -108,261 +294,455 @@ class MessageItem extends HookConsumerWidget {
|
||||
final remoteMessage = message.toRemoteMessage();
|
||||
final sender = remoteMessage.sender;
|
||||
|
||||
final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
|
||||
|
||||
final messageLanguage =
|
||||
remoteMessage.content != null
|
||||
? ref.watch(detectStringLanguageProvider(remoteMessage.content!))
|
||||
: null;
|
||||
|
||||
final currentLanguage = context.locale.toString();
|
||||
final translatableLanguage =
|
||||
messageLanguage != null
|
||||
? messageLanguage.substring(0, 2) != currentLanguage.substring(0, 2)
|
||||
: false;
|
||||
|
||||
final translating = useState(false);
|
||||
final translatedText = useState<String?>(null);
|
||||
|
||||
Future<void> translate() async {
|
||||
if (translatedText.value != null) {
|
||||
translatedText.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (translating.value) return;
|
||||
if (remoteMessage.content == null) return;
|
||||
translating.value = true;
|
||||
try {
|
||||
final text = await ref.watch(
|
||||
translateStringProvider(
|
||||
TranslateQuery(
|
||||
text: remoteMessage.content!,
|
||||
lang: currentLanguage.substring(0, 2),
|
||||
),
|
||||
).future,
|
||||
);
|
||||
translatedText.value = text;
|
||||
} catch (err) {
|
||||
showErrorAlert(err);
|
||||
} finally {
|
||||
translating.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return ContextMenuWidget(
|
||||
menuProvider: (_) {
|
||||
if (onAction == null) return Menu(children: []);
|
||||
return Menu(
|
||||
return Material(
|
||||
color:
|
||||
hasBackground
|
||||
? Colors.transparent
|
||||
: Theme.of(context).colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isCurrentUser)
|
||||
MenuAction(
|
||||
title: 'edit'.tr(),
|
||||
image: MenuImage.icon(Symbols.edit),
|
||||
callback: () {
|
||||
onAction!.call(MessageItemAction.edit);
|
||||
},
|
||||
if (showAvatar) ...[
|
||||
const Gap(8),
|
||||
MessageSenderInfo(
|
||||
sender: sender,
|
||||
createdAt: message.createdAt,
|
||||
textColor: textColor,
|
||||
),
|
||||
if (isCurrentUser)
|
||||
MenuAction(
|
||||
title: 'delete'.tr(),
|
||||
image: MenuImage.icon(Symbols.delete),
|
||||
callback: () {
|
||||
onAction!.call(MessageItemAction.delete);
|
||||
},
|
||||
),
|
||||
if (isCurrentUser) MenuSeparator(),
|
||||
MenuAction(
|
||||
title: 'reply'.tr(),
|
||||
image: MenuImage.icon(Symbols.reply),
|
||||
callback: () {
|
||||
onAction!.call(MessageItemAction.reply);
|
||||
},
|
||||
),
|
||||
MenuAction(
|
||||
title: 'forward'.tr(),
|
||||
image: MenuImage.icon(Symbols.forward),
|
||||
callback: () {
|
||||
onAction!.call(MessageItemAction.forward);
|
||||
},
|
||||
),
|
||||
if (translatableLanguage) MenuSeparator(),
|
||||
if (translatableLanguage)
|
||||
MenuAction(
|
||||
title:
|
||||
translatedText.value == null
|
||||
? 'translate'.tr()
|
||||
: translating.value
|
||||
? 'translating'.tr()
|
||||
: 'translated'.tr(),
|
||||
image: MenuImage.icon(Symbols.translate),
|
||||
callback: translate,
|
||||
),
|
||||
if (isMobile) MenuSeparator(),
|
||||
if (isMobile)
|
||||
MenuAction(
|
||||
title: 'copyMessage'.tr(),
|
||||
image: MenuImage.icon(Symbols.copy_all),
|
||||
callback: () {
|
||||
Clipboard.setData(
|
||||
ClipboardData(text: remoteMessage.content ?? ''),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Material(
|
||||
color:
|
||||
hasBackground
|
||||
? Colors.transparent
|
||||
: Theme.of(context).colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (showAvatar) ...[
|
||||
const Gap(8),
|
||||
MessageSenderInfo(
|
||||
sender: sender,
|
||||
createdAt: message.createdAt,
|
||||
const Gap(4),
|
||||
],
|
||||
const Gap(2),
|
||||
Row(
|
||||
spacing: 4,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
color: flashColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
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)
|
||||
...((remoteMessage.meta['embeds'] as List<dynamic>)
|
||||
.map((embed) => convertMapKeysToSnakeCase(embed))
|
||||
.where((embed) => embed['type'] == 'link')
|
||||
.map((embed) => SnScrappedLink.fromJson(embed))
|
||||
.map(
|
||||
(link) => LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return EmbedLinkWidget(
|
||||
link: link,
|
||||
maxWidth: math.min(
|
||||
constraints.maxWidth,
|
||||
480,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList()),
|
||||
if (progress != null && progress!.isNotEmpty)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
spacing: 8,
|
||||
children: [
|
||||
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),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
MessageIndicators(
|
||||
editedAt: remoteMessage.editedAt,
|
||||
status: message.status,
|
||||
isCurrentUser: isCurrentUser,
|
||||
textColor: textColor,
|
||||
),
|
||||
const Gap(4),
|
||||
],
|
||||
const Gap(2),
|
||||
Row(
|
||||
spacing: 4,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MessageItemDisplayIRC extends HookConsumerWidget {
|
||||
final LocalChatMessage message;
|
||||
final bool isCurrentUser;
|
||||
final Map<int, double>? progress;
|
||||
final bool showAvatar;
|
||||
final Function(String messageId) onJump;
|
||||
final String? translatedText;
|
||||
final bool translating;
|
||||
|
||||
const MessageItemDisplayIRC({
|
||||
super.key,
|
||||
required this.message,
|
||||
required this.isCurrentUser,
|
||||
required this.progress,
|
||||
required this.showAvatar,
|
||||
required this.onJump,
|
||||
required this.translatedText,
|
||||
required this.translating,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final remoteMessage = message.toRemoteMessage();
|
||||
final sender = remoteMessage.sender;
|
||||
final textColor = Theme.of(context).colorScheme.onSurfaceVariant;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
DateFormat('HH:mm').format(message.createdAt),
|
||||
style: TextStyle(color: textColor.withOpacity(0.7), fontSize: 12),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'<${sender.account.nick}>',
|
||||
style: TextStyle(color: Colors.blue),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
translatedText ?? remoteMessage.content ?? '',
|
||||
style: TextStyle(color: textColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MessageItemDisplayDiscord extends HookConsumerWidget {
|
||||
final LocalChatMessage message;
|
||||
final bool isCurrentUser;
|
||||
final Map<int, double>? progress;
|
||||
final bool showAvatar;
|
||||
final Function(String messageId) onJump;
|
||||
final String? translatedText;
|
||||
final bool translating;
|
||||
|
||||
const MessageItemDisplayDiscord({
|
||||
super.key,
|
||||
required this.message,
|
||||
required this.isCurrentUser,
|
||||
required this.progress,
|
||||
required this.showAvatar,
|
||||
required this.onJump,
|
||||
required this.translatedText,
|
||||
required this.translating,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final textColor = Theme.of(context).colorScheme.onSurfaceVariant;
|
||||
final remoteMessage = message.toRemoteMessage();
|
||||
final sender = remoteMessage.sender;
|
||||
|
||||
const kAvatarRadius = 12.0;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child:
|
||||
showAvatar
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
color: flashColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
AccountPfcGestureDetector(
|
||||
uname: sender.account.name,
|
||||
child: ProfilePictureWidget(
|
||||
file: sender.account.profile.picture,
|
||||
radius: kAvatarRadius,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
MessageSenderInfo(
|
||||
sender: sender,
|
||||
createdAt: message.createdAt,
|
||||
textColor: textColor,
|
||||
showAvatar: false,
|
||||
isCompact: true,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
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.value,
|
||||
),
|
||||
if (remoteMessage.attachments.isNotEmpty)
|
||||
LayoutBuilder(
|
||||
],
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
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)
|
||||
...((remoteMessage.meta['embeds'] as List<dynamic>)
|
||||
.map((embed) => convertMapKeysToSnakeCase(embed))
|
||||
.where((embed) => embed['type'] == 'link')
|
||||
.map((embed) => SnScrappedLink.fromJson(embed))
|
||||
.map(
|
||||
(link) => LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return EmbedLinkWidget(
|
||||
link: link,
|
||||
maxWidth: math.min(
|
||||
constraints.maxWidth,
|
||||
480,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList()),
|
||||
if (progress != null && progress!.isNotEmpty)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
spacing: 8,
|
||||
children: [
|
||||
if ((remoteMessage.content?.isNotEmpty ?? false))
|
||||
const SizedBox.shrink(),
|
||||
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),
|
||||
],
|
||||
),
|
||||
],
|
||||
).padding(left: kAvatarRadius * 2 + 8),
|
||||
],
|
||||
)
|
||||
: Padding(
|
||||
padding: EdgeInsets.only(left: kAvatarRadius * 2 + 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (showAvatar)
|
||||
MessageSenderInfo(
|
||||
sender: sender,
|
||||
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)
|
||||
...((remoteMessage.meta['embeds'] as List<dynamic>)
|
||||
.map((embed) => convertMapKeysToSnakeCase(embed))
|
||||
.where((embed) => embed['type'] == 'link')
|
||||
.map((embed) => SnScrappedLink.fromJson(embed))
|
||||
.map(
|
||||
(link) => LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return CloudFileList(
|
||||
files: remoteMessage.attachments,
|
||||
maxWidth: constraints.maxWidth,
|
||||
padding: EdgeInsets.symmetric(vertical: 4),
|
||||
return EmbedLinkWidget(
|
||||
link: link,
|
||||
maxWidth: math.min(constraints.maxWidth, 480),
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (remoteMessage.meta['embeds'] != null)
|
||||
...((remoteMessage.meta['embeds'] as List<dynamic>)
|
||||
.map(
|
||||
(embed) => convertMapKeysToSnakeCase(embed),
|
||||
)
|
||||
.where((embed) => embed['type'] == 'link')
|
||||
.map((embed) => SnScrappedLink.fromJson(embed))
|
||||
.map(
|
||||
(link) => LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return EmbedLinkWidget(
|
||||
link: link,
|
||||
maxWidth: math.min(
|
||||
constraints.maxWidth,
|
||||
480,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList()),
|
||||
if (progress != null && progress!.isNotEmpty)
|
||||
)
|
||||
.toList()),
|
||||
if (progress != null && progress!.isNotEmpty)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
spacing: 8,
|
||||
children: [
|
||||
if ((remoteMessage.content?.isNotEmpty ?? false))
|
||||
const Gap(0),
|
||||
for (var entry in progress!.entries)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'fileUploadingProgress'.tr(
|
||||
args: [
|
||||
(entry.key + 1).toString(),
|
||||
entry.value.toStringAsFixed(1),
|
||||
],
|
||||
),
|
||||
const Gap(0),
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
MessageIndicators(
|
||||
editedAt: remoteMessage.editedAt,
|
||||
status: message.status,
|
||||
isCurrentUser: isCurrentUser,
|
||||
textColor: textColor,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -41,7 +41,7 @@ class MessageListTile extends StatelessWidget {
|
||||
sender: sender,
|
||||
createdAt: message.createdAt,
|
||||
textColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
compact: true,
|
||||
showAvatar: false,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
MessageContent(item: remoteMessage),
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:island/models/chat.dart';
|
||||
import 'package:island/widgets/account/account_name.dart';
|
||||
import 'package:island/widgets/account/account_pfc.dart';
|
||||
@@ -9,14 +10,16 @@ class MessageSenderInfo extends StatelessWidget {
|
||||
final SnChatMember sender;
|
||||
final DateTime createdAt;
|
||||
final Color textColor;
|
||||
final bool compact;
|
||||
final bool showAvatar;
|
||||
final bool isCompact;
|
||||
|
||||
const MessageSenderInfo({
|
||||
super.key,
|
||||
required this.sender,
|
||||
required this.createdAt,
|
||||
required this.textColor,
|
||||
this.compact = false,
|
||||
this.showAvatar = true,
|
||||
this.isCompact = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -28,11 +31,42 @@ class MessageSenderInfo extends StatelessWidget {
|
||||
? DateFormat('MM/dd HH:mm').format(createdAt.toLocal())
|
||||
: DateFormat('HH:mm').format(createdAt.toLocal());
|
||||
|
||||
if (compact) {
|
||||
if (isCompact) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
if (showAvatar)
|
||||
AccountPfcGestureDetector(
|
||||
uname: sender.account.name,
|
||||
child: ProfilePictureWidget(
|
||||
fileId: sender.account.profile.picture?.id,
|
||||
radius: 14,
|
||||
),
|
||||
),
|
||||
if (showAvatar) const Gap(4),
|
||||
AccountName(
|
||||
account: sender.account,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: textColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const Gap(6),
|
||||
Text(
|
||||
timestamp,
|
||||
style: TextStyle(fontSize: 10, color: textColor.withOpacity(0.7)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (showAvatar) {
|
||||
return Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (!compact)
|
||||
if (!showAvatar)
|
||||
AccountPfcGestureDetector(
|
||||
uname: sender.account.name,
|
||||
child: ProfilePictureWidget(
|
||||
@@ -84,13 +118,14 @@ class MessageSenderInfo extends StatelessWidget {
|
||||
spacing: 8,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AccountPfcGestureDetector(
|
||||
uname: sender.account.name,
|
||||
child: ProfilePictureWidget(
|
||||
fileId: sender.account.profile.picture?.id,
|
||||
radius: 16,
|
||||
if (showAvatar)
|
||||
AccountPfcGestureDetector(
|
||||
uname: sender.account.name,
|
||||
child: ProfilePictureWidget(
|
||||
fileId: sender.account.profile.picture?.id,
|
||||
radius: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 2,
|
||||
|
Reference in New Issue
Block a user