✨ Provide three styles of message
This commit is contained in:
@@ -1021,5 +1021,6 @@
|
|||||||
"currentEmbed": "Current Embed",
|
"currentEmbed": "Current Embed",
|
||||||
"noEmbed": "No embed yet",
|
"noEmbed": "No embed yet",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"webView": "Web View"
|
"webView": "Web View",
|
||||||
|
"messageActions": "Message Actions"
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,32 @@ class MessageContent extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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) {
|
switch (item.type) {
|
||||||
case 'call.start':
|
case 'call.start':
|
||||||
case 'call.ended':
|
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':
|
case 'text':
|
||||||
default:
|
default:
|
||||||
return Column(
|
return Column(
|
||||||
|
@@ -15,16 +15,20 @@ import 'package:island/pods/messages_notifier.dart';
|
|||||||
import 'package:island/pods/translate.dart';
|
import 'package:island/pods/translate.dart';
|
||||||
import 'package:island/screens/chat/room.dart';
|
import 'package:island/screens/chat/room.dart';
|
||||||
import 'package:island/utils/mapping.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/app_scaffold.dart';
|
||||||
import 'package:island/widgets/chat/message_content.dart';
|
import 'package:island/widgets/chat/message_content.dart';
|
||||||
import 'package:island/widgets/chat/message_indicators.dart';
|
import 'package:island/widgets/chat/message_indicators.dart';
|
||||||
import 'package:island/widgets/chat/message_sender_info.dart';
|
import 'package:island/widgets/chat/message_sender_info.dart';
|
||||||
import 'package:island/widgets/content/alert.native.dart';
|
import 'package:island/widgets/content/alert.native.dart';
|
||||||
import 'package:island/widgets/content/cloud_file_collection.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:island/widgets/content/embed/link.dart';
|
||||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
import 'package:styled_widget/styled_widget.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 {
|
class MessageItemAction {
|
||||||
static const String edit = "edit";
|
static const String edit = "edit";
|
||||||
@@ -51,6 +55,188 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
required this.onJump,
|
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
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final textColor =
|
final textColor =
|
||||||
@@ -108,111 +294,7 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
final remoteMessage = message.toRemoteMessage();
|
final remoteMessage = message.toRemoteMessage();
|
||||||
final sender = remoteMessage.sender;
|
final sender = remoteMessage.sender;
|
||||||
|
|
||||||
final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
|
return Material(
|
||||||
|
|
||||||
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(
|
|
||||||
children: [
|
|
||||||
if (isCurrentUser)
|
|
||||||
MenuAction(
|
|
||||||
title: 'edit'.tr(),
|
|
||||||
image: MenuImage.icon(Symbols.edit),
|
|
||||||
callback: () {
|
|
||||||
onAction!.call(MessageItemAction.edit);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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:
|
color:
|
||||||
hasBackground
|
hasBackground
|
||||||
? Colors.transparent
|
? Colors.transparent
|
||||||
@@ -267,7 +349,7 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
if (MessageContent.hasContent(remoteMessage))
|
if (MessageContent.hasContent(remoteMessage))
|
||||||
MessageContent(
|
MessageContent(
|
||||||
item: remoteMessage,
|
item: remoteMessage,
|
||||||
translatedText: translatedText.value,
|
translatedText: translatedText,
|
||||||
),
|
),
|
||||||
if (remoteMessage.attachments.isNotEmpty)
|
if (remoteMessage.attachments.isNotEmpty)
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
@@ -281,9 +363,7 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
if (remoteMessage.meta['embeds'] != null)
|
if (remoteMessage.meta['embeds'] != null)
|
||||||
...((remoteMessage.meta['embeds'] as List<dynamic>)
|
...((remoteMessage.meta['embeds'] as List<dynamic>)
|
||||||
.map(
|
.map((embed) => convertMapKeysToSnakeCase(embed))
|
||||||
(embed) => convertMapKeysToSnakeCase(embed),
|
|
||||||
)
|
|
||||||
.where((embed) => embed['type'] == 'link')
|
.where((embed) => embed['type'] == 'link')
|
||||||
.map((embed) => SnScrappedLink.fromJson(embed))
|
.map((embed) => SnScrappedLink.fromJson(embed))
|
||||||
.map(
|
.map(
|
||||||
@@ -308,13 +388,11 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
if ((remoteMessage.content?.isNotEmpty ??
|
if ((remoteMessage.content?.isNotEmpty ?? false))
|
||||||
false))
|
|
||||||
const Gap(0),
|
const Gap(0),
|
||||||
for (var entry in progress!.entries)
|
for (var entry in progress!.entries)
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'fileUploadingProgress'.tr(
|
'fileUploadingProgress'.tr(
|
||||||
@@ -335,11 +413,8 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
Theme.of(
|
Theme.of(
|
||||||
context,
|
context,
|
||||||
).colorScheme.surfaceVariant,
|
).colorScheme.surfaceVariant,
|
||||||
valueColor:
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
AlwaysStoppedAnimation<Color>(
|
Theme.of(context).colorScheme.primary,
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.primary,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -362,6 +437,311 @@ class MessageItem extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: [
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
AccountPfcGestureDetector(
|
||||||
|
uname: sender.account.name,
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
file: sender.account.profile.picture,
|
||||||
|
radius: kAvatarRadius,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MessageSenderInfo(
|
||||||
|
sender: sender,
|
||||||
|
createdAt: message.createdAt,
|
||||||
|
textColor: textColor,
|
||||||
|
showAvatar: false,
|
||||||
|
isCompact: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
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 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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -41,7 +41,7 @@ class MessageListTile extends StatelessWidget {
|
|||||||
sender: sender,
|
sender: sender,
|
||||||
createdAt: message.createdAt,
|
createdAt: message.createdAt,
|
||||||
textColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
textColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
compact: true,
|
showAvatar: false,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
MessageContent(item: remoteMessage),
|
MessageContent(item: remoteMessage),
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:island/models/chat.dart';
|
import 'package:island/models/chat.dart';
|
||||||
import 'package:island/widgets/account/account_name.dart';
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
import 'package:island/widgets/account/account_pfc.dart';
|
import 'package:island/widgets/account/account_pfc.dart';
|
||||||
@@ -9,14 +10,16 @@ class MessageSenderInfo extends StatelessWidget {
|
|||||||
final SnChatMember sender;
|
final SnChatMember sender;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final Color textColor;
|
final Color textColor;
|
||||||
final bool compact;
|
final bool showAvatar;
|
||||||
|
final bool isCompact;
|
||||||
|
|
||||||
const MessageSenderInfo({
|
const MessageSenderInfo({
|
||||||
super.key,
|
super.key,
|
||||||
required this.sender,
|
required this.sender,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.textColor,
|
required this.textColor,
|
||||||
this.compact = false,
|
this.showAvatar = true,
|
||||||
|
this.isCompact = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -28,11 +31,42 @@ class MessageSenderInfo extends StatelessWidget {
|
|||||||
? DateFormat('MM/dd HH:mm').format(createdAt.toLocal())
|
? DateFormat('MM/dd HH:mm').format(createdAt.toLocal())
|
||||||
: DateFormat('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(
|
return Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
if (!compact)
|
if (!showAvatar)
|
||||||
AccountPfcGestureDetector(
|
AccountPfcGestureDetector(
|
||||||
uname: sender.account.name,
|
uname: sender.account.name,
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
@@ -84,6 +118,7 @@ class MessageSenderInfo extends StatelessWidget {
|
|||||||
spacing: 8,
|
spacing: 8,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
if (showAvatar)
|
||||||
AccountPfcGestureDetector(
|
AccountPfcGestureDetector(
|
||||||
uname: sender.account.name,
|
uname: sender.account.name,
|
||||||
child: ProfilePictureWidget(
|
child: ProfilePictureWidget(
|
||||||
|
Reference in New Issue
Block a user