forked from Henry-Hiles/nexus
make message flash when tapping reply
This commit is contained in:
parent
0ac714f1e9
commit
a6aee7565a
5 changed files with 127 additions and 102 deletions
|
|
@ -66,6 +66,7 @@ class MessageController extends AsyncNotifier<Message?> {
|
||||||
),
|
),
|
||||||
).future,
|
).future,
|
||||||
),
|
),
|
||||||
|
"flashing": false,
|
||||||
"timelineId": event.timelineRowId,
|
"timelineId": event.timelineRowId,
|
||||||
"big": event.localContent?.bigEmoji == true,
|
"big": event.localContent?.bigEmoji == true,
|
||||||
"eventType": type,
|
"eventType": type,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import "dart:async";
|
||||||
|
|
||||||
import "package:collection/collection.dart";
|
import "package:collection/collection.dart";
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
|
|
@ -282,6 +284,16 @@ class RoomChatController extends AsyncNotifier<InMemoryChatController> {
|
||||||
|
|
||||||
Future<void> scrollToMessage(Message message) async {
|
Future<void> scrollToMessage(Message message) async {
|
||||||
final controller = await future;
|
final controller = await future;
|
||||||
|
Future<void> setFlashing(bool flashing) => controller.updateMessage(
|
||||||
|
message,
|
||||||
|
message.copyWith(
|
||||||
|
metadata: {...(message.metadata ?? {}), "flashing": flashing},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await setFlashing(true);
|
||||||
|
Timer(Duration(seconds: 1), () => setFlashing(false));
|
||||||
|
|
||||||
return await controller.scrollToMessage(message.id);
|
return await controller.scrollToMessage(message.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,34 +9,46 @@ class MessageWrapper extends StatelessWidget {
|
||||||
const MessageWrapper(this.message, this.child, this.groupStatus, {super.key});
|
const MessageWrapper(this.message, this.child, this.groupStatus, {super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Row(
|
Widget build(BuildContext context) => ClipRRect(
|
||||||
spacing: 8,
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: AnimatedContainer(
|
||||||
children: [
|
padding: message.metadata?["flashing"] == true
|
||||||
groupStatus?.isFirst != false
|
? EdgeInsets.all(8)
|
||||||
? AvatarOrHash(
|
: EdgeInsets.all(0),
|
||||||
Uri.parse(message.metadata?["avatarUrl"] ?? ""),
|
color: message.metadata?["flashing"] == true
|
||||||
height: 40,
|
? Theme.of(context).colorScheme.onSurface.withAlpha(50)
|
||||||
message.metadata?["displayName"] ?? "",
|
: Colors.transparent,
|
||||||
)
|
duration: Duration(milliseconds: 250),
|
||||||
: SizedBox(width: 40),
|
child: Row(
|
||||||
Expanded(
|
spacing: 8,
|
||||||
child: Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
spacing: 4,
|
groupStatus?.isFirst != false
|
||||||
children: [
|
? AvatarOrHash(
|
||||||
if (groupStatus?.isFirst != false)
|
Uri.parse(message.metadata?["avatarUrl"] ?? ""),
|
||||||
Text(
|
height: 40,
|
||||||
message.metadata?["displayName"] ?? message.authorId,
|
message.metadata?["displayName"] ?? "",
|
||||||
overflow: TextOverflow.ellipsis,
|
)
|
||||||
style: Theme.of(
|
: SizedBox(width: 40),
|
||||||
context,
|
Expanded(
|
||||||
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
child: Column(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child,
|
spacing: 4,
|
||||||
],
|
children: [
|
||||||
),
|
if (groupStatus?.isFirst != false)
|
||||||
|
Text(
|
||||||
|
message.metadata?["displayName"] ?? message.authorId,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -259,18 +259,13 @@ class RoomChat extends HookConsumerWidget {
|
||||||
index, {
|
index, {
|
||||||
required bool isSentByMe,
|
required bool isSentByMe,
|
||||||
MessageGroupStatus? groupStatus,
|
MessageGroupStatus? groupStatus,
|
||||||
}) => MessageWrapper(
|
}) => TextMessageWrapper(
|
||||||
message,
|
message,
|
||||||
TextMessageWrapper(
|
content: message.text,
|
||||||
message,
|
groupStatus: groupStatus,
|
||||||
content: message.text,
|
onTapReply: notifier.scrollToMessage,
|
||||||
groupStatus: groupStatus,
|
updateMessage: controller.updateMessage,
|
||||||
onTapReply: notifier.scrollToMessage,
|
isSentByMe: isSentByMe,
|
||||||
updateMessage: controller.updateMessage,
|
|
||||||
isSentByMe: isSentByMe,
|
|
||||||
),
|
|
||||||
|
|
||||||
groupStatus,
|
|
||||||
),
|
),
|
||||||
|
|
||||||
imageMessageBuilder:
|
imageMessageBuilder:
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:flutter_link_previewer/flutter_link_previewer.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/html/html.dart";
|
||||||
|
import "package:nexus/widgets/chat_page/message_wrapper.dart";
|
||||||
import "package:nexus/widgets/chat_page/top_widget.dart";
|
import "package:nexus/widgets/chat_page/top_widget.dart";
|
||||||
|
|
||||||
class TextMessageWrapper extends StatelessWidget {
|
class TextMessageWrapper extends StatelessWidget {
|
||||||
|
|
@ -31,75 +32,79 @@ class TextMessageWrapper extends StatelessWidget {
|
||||||
final colorScheme = theme.colorScheme;
|
final colorScheme = theme.colorScheme;
|
||||||
final textMessage = message is TextMessage ? message as TextMessage : null;
|
final textMessage = message is TextMessage ? message as TextMessage : null;
|
||||||
|
|
||||||
return ClipRRect(
|
return MessageWrapper(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
message,
|
||||||
child: Container(
|
ClipRRect(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
decoration: BoxDecoration(
|
child: Container(
|
||||||
color: isSentByMe
|
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||||
? colorScheme.primaryContainer
|
decoration: BoxDecoration(
|
||||||
: colorScheme.surfaceContainer,
|
color: isSentByMe
|
||||||
),
|
? colorScheme.primaryContainer
|
||||||
child: Column(
|
: colorScheme.surfaceContainer,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
),
|
||||||
children: [
|
child: Column(
|
||||||
TopWidget(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
message,
|
children: [
|
||||||
groupStatus: groupStatus,
|
TopWidget(
|
||||||
onTapReply: onTapReply,
|
message,
|
||||||
),
|
groupStatus: groupStatus,
|
||||||
if (content != null)
|
onTapReply: onTapReply,
|
||||||
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)
|
if (content != null)
|
||||||
Text("(edited)", style: theme.textTheme.labelSmall),
|
Html(
|
||||||
if (textMessage != null)
|
textStyle: message.metadata?["big"] == true
|
||||||
LinkPreview(
|
? TextStyle(fontSize: 32)
|
||||||
text: textMessage.text,
|
: null,
|
||||||
backgroundColor: isSentByMe
|
content!
|
||||||
? colorScheme.inversePrimary
|
.replaceAllMapped(
|
||||||
: colorScheme.surfaceContainerLow,
|
RegExp(
|
||||||
outsidePadding: EdgeInsets.only(top: 4),
|
"(<a\\b[^>]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)",
|
||||||
insidePadding: EdgeInsets.symmetric(
|
caseSensitive: false,
|
||||||
vertical: 8,
|
),
|
||||||
horizontal: 16,
|
(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\"/>"),
|
||||||
),
|
),
|
||||||
linkPreviewData: message.metadata?["linkPreviewData"],
|
if (textMessage?.editedAt != null)
|
||||||
onLinkPreviewDataFetched: (linkPreviewData) => updateMessage(
|
Text("(edited)", style: theme.textTheme.labelSmall),
|
||||||
message,
|
if (textMessage != null)
|
||||||
message.copyWith(
|
LinkPreview(
|
||||||
metadata: {
|
text: textMessage.text,
|
||||||
...(message.metadata ?? {}),
|
backgroundColor: isSentByMe
|
||||||
"linkPreviewData": linkPreviewData,
|
? 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!,
|
||||||
if (extra != null) extra!,
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
groupStatus,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue