diff --git a/lib/controllers/room_chat_controller.dart b/lib/controllers/room_chat_controller.dart index 35b8aea..a1ee835 100644 --- a/lib/controllers/room_chat_controller.dart +++ b/lib/controllers/room_chat_controller.dart @@ -104,7 +104,7 @@ class RoomChatController extends AsyncNotifier { ); if (oldMessage == null || message == null || !ref.mounted) return; - return await updateMessage( + return await controller.updateMessage( oldMessage, message.copyWith( id: oldMessage.id, @@ -225,9 +225,6 @@ class RoomChatController extends AsyncNotifier { ); } - Future updateMessage(Message message, Message newMessage) async => - (await future).updateMessage(message, newMessage); - Future send( String message, { bool shouldMention = true, diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart index 2d12532..011f359 100644 --- a/lib/widgets/chat_page/room_chat.dart +++ b/lib/widgets/chat_page/room_chat.dart @@ -4,11 +4,9 @@ import "package:flutter/material.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_chat_ui/flutter_chat_ui.dart"; import "package:flutter_hooks/flutter_hooks.dart"; -import "package:flutter_link_previewer/flutter_link_previewer.dart"; import "package:flyer_chat_file_message/flyer_chat_file_message.dart"; import "package:flyer_chat_image_message/flyer_chat_image_message.dart"; import "package:flyer_chat_system_message/flyer_chat_system_message.dart"; -import "package:flyer_chat_text_message/flyer_chat_text_message.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/client_controller.dart"; import "package:nexus/controllers/client_state_controller.dart"; @@ -21,10 +19,10 @@ import "package:nexus/helpers/extensions/show_context_menu.dart"; import "package:nexus/models/relation_type.dart"; import "package:nexus/models/requests/report_request.dart"; import "package:nexus/widgets/chat_page/chat_box.dart"; -import "package:nexus/widgets/chat_page/html/html.dart"; import "package:nexus/widgets/chat_page/member_list.dart"; import "package:nexus/widgets/chat_page/message_wrapper.dart"; import "package:nexus/widgets/chat_page/room_appbar.dart"; +import "package:nexus/widgets/chat_page/text_message_wrapper.dart"; import "package:nexus/widgets/chat_page/top_widget.dart"; import "package:nexus/widgets/form_text_input.dart"; import "package:nexus/widgets/loading.dart"; @@ -187,6 +185,13 @@ class RoomChat extends HookConsumerWidget { ]; } + final chatTheme = ChatTheme.fromThemeData(theme).copyWith( + colors: ChatColors.fromThemeData(theme).copyWith( + primary: theme.colorScheme.primaryContainer, + onPrimary: theme.colorScheme.onPrimaryContainer, + ), + ); + return Scaffold( appBar: RoomAppbar( room, @@ -208,12 +213,7 @@ class RoomChat extends HookConsumerWidget { .betterWhen( data: (controller) => Chat( currentUserId: userId, - theme: ChatTheme.fromThemeData(theme).copyWith( - colors: ChatColors.fromThemeData(theme).copyWith( - primary: theme.colorScheme.primaryContainer, - onPrimary: theme.colorScheme.onPrimaryContainer, - ), - ), + theme: chatTheme, onMessageSecondaryTap: ( context, @@ -359,180 +359,123 @@ class RoomChat extends HookConsumerWidget { index, { required bool isSentByMe, MessageGroupStatus? groupStatus, - }) { - final image = - message.metadata?["image"] - as ImageMessage?; - return MessageWrapper( + }) => MessageWrapper( + message, + TextMessageWrapper( message, + content: message.text, + groupStatus: groupStatus, + onTapReply: notifier.scrollToMessage, + updateMessage: controller.updateMessage, + isSentByMe: isSentByMe, + ), + + groupStatus, + ), + imageMessageBuilder: + ( + context, + message, + index, { + required bool isSentByMe, + MessageGroupStatus? groupStatus, + }) { + final text = + message.metadata?["text"] as TextMessage?; + return MessageWrapper( + text ?? message, Column( spacing: 4, crossAxisAlignment: CrossAxisAlignment.start, children: [ - FlyerChatTextMessage( - showTime: true, - showStatus: false, - customWidget: Column( - spacing: 4, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Html( - textStyle: - message.metadata?["big"] == - true - ? TextStyle( - fontSize: 32, - ) - : null, - message.text - .replaceAllMapped( - RegExp( - "(]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)", - caseSensitive: - false, + TextMessageWrapper( + message, + content: message.text, + groupStatus: groupStatus, + onTapReply: notifier.scrollToMessage, + updateMessage: + controller.updateMessage, + isSentByMe: isSentByMe, + extra: InkWell( + onTap: () => showDialog( + context: context, + builder: (_) => LayoutBuilder( + builder: + ( + context, + constraints, + ) => Dialog( + backgroundColor: + Colors.transparent, + insetPadding: + EdgeInsets.all( + constraints + .maxWidth / + 100, ), - (m) { - // If it's already an tag, leave it unchanged - if (m.group(1) != - null) { - return m.group( - 1, - )!; - } - - // Otherwise, wrap the bare URL - final url = m.group( - 2, - )!; - return "$url"; - }, - ) - .replaceAll( - "\n", - "
", - ), - ), - if (message.editedAt != null) - Text( - "(edited)", - style: theme - .textTheme - .labelSmall, - ), - ], - ), - if (image != null) - InkWell( - onTap: () => showDialog( - context: context, - builder: (_) => LayoutBuilder( - builder: (context, constraints) => Dialog( - backgroundColor: - Colors.transparent, - insetPadding: - EdgeInsets.all( - constraints - .maxWidth / - 100, + child: ConstrainedBox( + constraints: + BoxConstraints( + minWidth: min( + constraints + .maxWidth, + 1000, + ), ), - child: ConstrainedBox( - constraints: - BoxConstraints( - minWidth: min( - constraints - .maxWidth, - 1000, - ), - ), - child: InteractiveViewer( - child: Image( - fit: BoxFit - .contain, - image: CachedNetworkImage( - image.source, - ref.watch( - CrossCacheController - .provider, - ), - headers: - ref.headers, + child: InteractiveViewer( + child: Image( + fit: BoxFit.contain, + image: CachedNetworkImage( + message.source, + ref.watch( + CrossCacheController + .provider, ), + headers: + ref.headers, ), ), ), ), ), + ), + ), + child: FlyerChatImageMessage( + customImageProvider: + CachedNetworkImage( + message.source, + ref.watch( + CrossCacheController + .provider, + ), + headers: ref.headers, ), - child: FlyerChatImageMessage( - customImageProvider: - CachedNetworkImage( - image.source, - ref.watch( - CrossCacheController - .provider, - ), - headers: ref.headers, - ), - errorBuilder: - ( + errorBuilder: + ( + context, + error, + stackTrace, + ) => Center( + child: Text( + "Image Failed to Load", + style: TextStyle( + color: Theme.of( context, - error, - stackTrace, - ) => Center( - child: Text( - "Image Failed to Load", - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.error, - ), - ), - ), - message: image, - index: index, + ).colorScheme.error, + ), + ), ), - ), - ], + message: message, + index: index, + ), ), - topWidget: TopWidget( - message, - groupStatus: groupStatus, - onTapReply: - notifier.scrollToMessage, - ), - message: message, - index: index, ), ], ), groupStatus, ); }, - linkPreviewBuilder: (_, message, isSentByMe) => - LinkPreview( - text: message.text, - backgroundColor: isSentByMe - ? theme.colorScheme.inversePrimary - : theme.colorScheme.surfaceContainerLow, - insidePadding: EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, - ), - linkPreviewData: message.linkPreviewData, - onLinkPreviewDataFetched: (linkPreviewData) => - notifier.updateMessage( - message, - message.copyWith( - linkPreviewData: linkPreviewData, - ), - ), - ), fileMessageBuilder: ( _, diff --git a/lib/widgets/chat_page/text_message_wrapper.dart b/lib/widgets/chat_page/text_message_wrapper.dart new file mode 100644 index 0000000..5f2a9f3 --- /dev/null +++ b/lib/widgets/chat_page/text_message_wrapper.dart @@ -0,0 +1,105 @@ +import "package:flutter/material.dart"; +import "package:flutter_chat_core/flutter_chat_core.dart"; +import "package:flutter_link_previewer/flutter_link_previewer.dart"; +import "package:nexus/widgets/chat_page/html/html.dart"; +import "package:nexus/widgets/chat_page/top_widget.dart"; + +class TextMessageWrapper extends StatelessWidget { + final Message message; + final String? content; + final MessageGroupStatus? groupStatus; + final Future Function(Message oldMessage, Message newMessage) + updateMessage; + final bool isSentByMe; + final Widget? extra; + final OnTapReply onTapReply; + + const TextMessageWrapper( + this.message, { + this.content, + this.onTapReply, + required this.updateMessage, + required this.groupStatus, + required this.isSentByMe, + this.extra, + super.key, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + final textMessage = message is TextMessage ? message as TextMessage : null; + + return ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(8)), + child: Container( + padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), + decoration: BoxDecoration( + color: isSentByMe + ? colorScheme.primaryContainer + : colorScheme.surfaceContainer, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TopWidget( + message, + groupStatus: groupStatus, + onTapReply: onTapReply, + ), + if (content != null) + Html( + textStyle: message.metadata?["big"] == true + ? TextStyle(fontSize: 32) + : null, + content! + .replaceAllMapped( + RegExp( + "(]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)", + caseSensitive: false, + ), + (m) { + // If it's already an tag, leave it unchanged + if (m.group(1) != null) { + return m.group(1)!; + } + + // Otherwise, wrap the bare URL + final url = m.group(2)!; + return "$url"; + }, + ) + .replaceAll("\n", "
"), + ), + if (textMessage?.editedAt != null) + Text("(edited)", style: theme.textTheme.labelSmall), + if (textMessage != null) + LinkPreview( + text: textMessage.text, + backgroundColor: isSentByMe + ? colorScheme.inversePrimary + : colorScheme.surfaceContainerLow, + outsidePadding: EdgeInsets.only(top: 4), + insidePadding: EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), + linkPreviewData: message.metadata?["linkPreviewData"], + onLinkPreviewDataFetched: (linkPreviewData) => updateMessage( + message, + message.copyWith( + metadata: { + ...(message.metadata ?? {}), + "linkPreviewData": linkPreviewData, + }, + ), + ), + ), + if (extra != null) extra!, + ], + ), + ), + ); + } +} diff --git a/lib/widgets/chat_page/top_widget.dart b/lib/widgets/chat_page/top_widget.dart index 2f4286a..55a56a3 100644 --- a/lib/widgets/chat_page/top_widget.dart +++ b/lib/widgets/chat_page/top_widget.dart @@ -5,11 +5,13 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/chat_page/html/quoted.dart"; +typedef OnTapReply = void Function(Message message)?; + class TopWidget extends ConsumerWidget { final Message message; final bool alwaysShow; final MessageGroupStatus? groupStatus; - final void Function(Message message)? onTapReply; + final OnTapReply onTapReply; const TopWidget( this.message, { required this.groupStatus, diff --git a/pubspec.lock b/pubspec.lock index aec6fdc..da5de89 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -509,14 +509,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_math_fork: - dependency: transitive - description: - name: flutter_math_fork - sha256: "6d5f2f1aa57ae539ffb0a04bb39d2da67af74601d685a161aff7ce5bda5fa407" - url: "https://pub.dev" - source: hosted - version: "0.7.4" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -591,15 +583,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" - flyer_chat_text_message: - dependency: "direct main" - description: - path: "packages/flyer_chat_text_message" - ref: HEAD - resolved-ref: "03be67c8c81c8f637672ee03dd8f082d2c223627" - url: "https://github.com/Henry-Hiles/flutter_chat_ui" - source: git - version: "2.6.0" freezed: dependency: "direct dev" description: @@ -640,14 +623,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" - gpt_markdown: - dependency: transitive - description: - name: gpt_markdown - sha256: "9b88dfaffea644070b648c204ca4a55745a49f4ad0b58ed0ab70913ad593c7a1" - url: "https://pub.dev" - source: hosted - version: "1.1.5" graphs: dependency: transitive description: @@ -1381,14 +1356,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.0+1" - tuple: - dependency: transitive - description: - name: tuple - sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 - url: "https://pub.dev" - source: hosted - version: "2.0.2" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3c50656..3c0198d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,10 +42,6 @@ dependencies: flyer_chat_image_message: ^2.2.2 flyer_chat_system_message: ^2.1.13 flyer_chat_file_message: ^2.3.1 - flyer_chat_text_message: - git: - url: https://github.com/Henry-Hiles/flutter_chat_ui - path: packages/flyer_chat_text_message flutter_chat_ui: git: url: https://github.com/Henry-Hiles/flutter_chat_ui