M lib/client_controller.dart => lib/client_controller.dart +27 -1
@@ 724,7 724,8 @@ class ClientController {
var entries = messages.map((msg) => MessageEntry(msg, buf!.id)).toList();
await _db.storeMessages(entries);
if (buf.messageHistoryLoaded) {
- buf.addMessages(entries.map((entry) => MessageModel(entry: entry)), append: !isHistory);
+ var models = await buildMessageModelList(_db, entries);
+ buf.addMessages(models, append: !isHistory);
}
String t = entries.first.time;
@@ 1068,3 1069,28 @@ IrcUri? _uriFromBouncerNetworkModel(BouncerNetworkModel bouncerNetwork) {
port: bouncerNetwork.port,
);
}
+
+Future<Iterable<MessageModel>> buildMessageModelList(DB db, List<MessageEntry> entries) async {
+ if (entries.isEmpty) {
+ return [];
+ }
+
+ List<String> msgids = [];
+ for (var entry in entries) {
+ var parentMsgid = entry.msg.tags['+draft/reply'];
+ if (parentMsgid != null) {
+ msgids.add(parentMsgid);
+ }
+ }
+
+ var bufferId = entries.first.buffer;
+ var parents = await db.fetchMessageSetByNetworkMsgid(bufferId, msgids);
+ return entries.map((entry) {
+ MessageEntry? replyTo;
+ var parentMsgid = entry.msg.tags['+draft/reply'];
+ if (parentMsgid != null) {
+ replyTo = parents[parentMsgid];
+ }
+ return MessageModel(entry: entry, replyTo: replyTo);
+ });
+}
M lib/database.dart => lib/database.dart +15 -0
@@ 671,6 671,21 @@ class DB {
return l;
}
+ Future<Map<String, MessageEntry>> fetchMessageSetByNetworkMsgid(int buffer, List<String> msgids) async {
+ var inList = List.filled(msgids.length, '?').join(', ');
+ var entries = await _db.rawQuery('''
+ SELECT *
+ FROM Message
+ WHERE buffer = ? AND network_msgid IN ($inList)
+ ''', <Object>[buffer] + msgids);
+ Map<String, MessageEntry> messages = {};
+ for (var m in entries) {
+ var entry = MessageEntry.fromMap(m);
+ messages[entry.networkMsgid!] = entry;
+ }
+ return messages;
+ }
+
Future<void> storeMessages(List<MessageEntry> entries) async {
await _db.transaction((txn) async {
await Future.wait(entries.map((entry) async {
M lib/models.dart => lib/models.dart +2 -1
@@ 549,8 549,9 @@ int _compareMessageModels(MessageModel a, MessageModel b) {
class MessageModel {
final MessageEntry entry;
+ final MessageEntry? replyTo;
- MessageModel({ required this.entry }) {
+ MessageModel({ required this.entry, this.replyTo }) {
assert(entry.id != null);
}
M lib/page/buffer.dart => lib/page/buffer.dart +29 -7
@@ 136,9 136,8 @@ class _BufferPageState extends State<BufferPage> with WidgetsBindingObserver {
var limit = 1000;
var entries = await db.listMessagesBefore(buffer.id, firstMsgId, limit);
- buffer.populateMessageHistory(entries.map((entry) {
- return MessageModel(entry: entry);
- }).toList());
+ var models = await buildMessageModelList(db, entries);
+ buffer.populateMessageHistory(models.toList());
if (entries.length >= limit) {
return;
@@ 673,10 672,35 @@ class _MessageItem extends StatelessWidget {
linkify(context, actionText, linkStyle: linkStyle),
];
} else {
- var body = stripAnsiFormatting(ircMsg.params[1]);
+ var body = ircMsg.params[1];
+ WidgetSpan? replyChip;
+ if (msg.replyTo != null && msg.replyTo!.msg.source != null) {
+ var replyNickname = msg.replyTo!.msg.source!.name;
+
+ var replyPrefix = '$replyNickname: ';
+ if (body.startsWith(replyPrefix)) {
+ body = body.replaceFirst(replyPrefix, '');
+ }
+
+ replyChip = WidgetSpan(
+ alignment: PlaceholderAlignment.middle,
+ child: SelectionContainer.disabled(child: Chip(
+ avatar: Icon(Icons.reply, size: 16, color: textColor),
+ label: Text(replyNickname),
+ labelPadding: EdgeInsets.only(right: 4),
+ backgroundColor: Color.alphaBlend(textColor.withOpacity(0.15), boxColor),
+ labelStyle: TextStyle(color: textColor),
+ visualDensity: VisualDensity(vertical: -4),
+ )),
+ );
+ }
+
+ body = stripAnsiFormatting(body);
content = [
if (isFirstInGroup) senderTextSpan,
if (isFirstInGroup) TextSpan(text: '\n'),
+ if (replyChip != null) replyChip,
+ if (replyChip != null) WidgetSpan(child: SizedBox(width: 5, height: 5)),
linkify(context, body, linkStyle: linkStyle),
];
@@ 696,9 720,7 @@ class _MessageItem extends StatelessWidget {
}
}
- Widget inner = SelectableText.rich(TextSpan(
- children: content,
- ));
+ Widget inner = SelectionArea(child: Text.rich(TextSpan(children: content)));
if (showTime) {
var hh = localDateTime.hour.toString().padLeft(2, '0');
M lib/widget/composer.dart => lib/widget/composer.dart +2 -5
@@ 4,6 4,7 @@ import 'package:flutter_flipped_autocomplete/flutter_flipped_autocomplete.dart';
import 'package:provider/provider.dart';
import '../client.dart';
+import '../client_controller.dart';
import '../database.dart';
import '../irc.dart';
import '../models.dart';
@@ 101,11 102,7 @@ class ComposerState extends State<Composer> {
}
await db.storeMessages(entries);
- List<MessageModel> models = [];
- for (var entry in entries) {
- models.add(MessageModel(entry: entry));
- }
-
+ var models = await buildMessageModelList(db, entries);
if (buffer.messageHistoryLoaded) {
buffer.addMessages(models, append: true);
}