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,
|
||||
),
|
||||
"flashing": false,
|
||||
"timelineId": event.timelineRowId,
|
||||
"big": event.localContent?.bigEmoji == true,
|
||||
"eventType": type,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:collection/collection.dart";
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.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 {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,34 +9,46 @@ class MessageWrapper extends StatelessWidget {
|
|||
const MessageWrapper(this.message, this.child, this.groupStatus, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Row(
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
groupStatus?.isFirst != false
|
||||
? AvatarOrHash(
|
||||
Uri.parse(message.metadata?["avatarUrl"] ?? ""),
|
||||
height: 40,
|
||||
message.metadata?["displayName"] ?? "",
|
||||
)
|
||||
: SizedBox(width: 40),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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,
|
||||
],
|
||||
),
|
||||
Widget build(BuildContext context) => ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
child: AnimatedContainer(
|
||||
padding: message.metadata?["flashing"] == true
|
||||
? EdgeInsets.all(8)
|
||||
: EdgeInsets.all(0),
|
||||
color: message.metadata?["flashing"] == true
|
||||
? Theme.of(context).colorScheme.onSurface.withAlpha(50)
|
||||
: Colors.transparent,
|
||||
duration: Duration(milliseconds: 250),
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
groupStatus?.isFirst != false
|
||||
? AvatarOrHash(
|
||||
Uri.parse(message.metadata?["avatarUrl"] ?? ""),
|
||||
height: 40,
|
||||
message.metadata?["displayName"] ?? "",
|
||||
)
|
||||
: SizedBox(width: 40),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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, {
|
||||
required bool isSentByMe,
|
||||
MessageGroupStatus? groupStatus,
|
||||
}) => MessageWrapper(
|
||||
}) => TextMessageWrapper(
|
||||
message,
|
||||
TextMessageWrapper(
|
||||
message,
|
||||
content: message.text,
|
||||
groupStatus: groupStatus,
|
||||
onTapReply: notifier.scrollToMessage,
|
||||
updateMessage: controller.updateMessage,
|
||||
isSentByMe: isSentByMe,
|
||||
),
|
||||
|
||||
groupStatus,
|
||||
content: message.text,
|
||||
groupStatus: groupStatus,
|
||||
onTapReply: notifier.scrollToMessage,
|
||||
updateMessage: controller.updateMessage,
|
||||
isSentByMe: isSentByMe,
|
||||
),
|
||||
|
||||
imageMessageBuilder:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ 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/message_wrapper.dart";
|
||||
import "package:nexus/widgets/chat_page/top_widget.dart";
|
||||
|
||||
class TextMessageWrapper extends StatelessWidget {
|
||||
|
|
@ -31,75 +32,79 @@ class TextMessageWrapper extends StatelessWidget {
|
|||
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\"/>"),
|
||||
return MessageWrapper(
|
||||
message,
|
||||
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 (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,
|
||||
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\"/>"),
|
||||
),
|
||||
linkPreviewData: message.metadata?["linkPreviewData"],
|
||||
onLinkPreviewDataFetched: (linkPreviewData) => updateMessage(
|
||||
message,
|
||||
message.copyWith(
|
||||
metadata: {
|
||||
...(message.metadata ?? {}),
|
||||
"linkPreviewData": linkPreviewData,
|
||||
},
|
||||
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!,
|
||||
],
|
||||
if (extra != null) extra!,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
groupStatus,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue