~emersion/goguma

9dcf1f63aff5d7e673bc02d86c4732180a2aefcb — Simon Ser 13 days ago c569b5e
Show message reply indicator
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);
			}