forked from Henry-Hiles/nexus
custom text message wrapper
This commit is contained in:
parent
983f6d6c18
commit
3e0d8304b6
6 changed files with 210 additions and 200 deletions
|
|
@ -104,7 +104,7 @@ class RoomChatController extends AsyncNotifier<InMemoryChatController> {
|
|||
);
|
||||
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<InMemoryChatController> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> updateMessage(Message message, Message newMessage) async =>
|
||||
(await future).updateMessage(message, newMessage);
|
||||
|
||||
Future<void> send(
|
||||
String message, {
|
||||
bool shouldMention = true,
|
||||
|
|
|
|||
|
|
@ -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\\b[^>]*>.*?<\\/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 <a> tag, leave it unchanged
|
||||
if (m.group(1) !=
|
||||
null) {
|
||||
return m.group(
|
||||
1,
|
||||
)!;
|
||||
}
|
||||
|
||||
// Otherwise, wrap the bare URL
|
||||
final url = m.group(
|
||||
2,
|
||||
)!;
|
||||
return "<a href=\"$url\">$url</a>";
|
||||
},
|
||||
)
|
||||
.replaceAll(
|
||||
"\n",
|
||||
"<br class=\"fake-break\"/>",
|
||||
),
|
||||
),
|
||||
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:
|
||||
(
|
||||
_,
|
||||
|
|
|
|||
105
lib/widgets/chat_page/text_message_wrapper.dart
Normal file
105
lib/widgets/chat_page/text_message_wrapper.dart
Normal file
|
|
@ -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<void> 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\\b[^>]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)",
|
||||
caseSensitive: false,
|
||||
),
|
||||
(m) {
|
||||
// If it's already an <a> tag, leave it unchanged
|
||||
if (m.group(1) != null) {
|
||||
return m.group(1)!;
|
||||
}
|
||||
|
||||
// Otherwise, wrap the bare URL
|
||||
final url = m.group(2)!;
|
||||
return "<a href=\"$url\">$url</a>";
|
||||
},
|
||||
)
|
||||
.replaceAll("\n", "<br class=\"fake-break\"/>"),
|
||||
),
|
||||
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!,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
33
pubspec.lock
33
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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue