~emersion/goguma-dev

Support typing indicators v1 SUPERSEDED

delthas: 1
 Support typing indicators

 4 files changed, 88 insertions(+), 1 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~emersion/goguma-dev/patches/31708/mbox | git am -3
Learn more about email & git

[PATCH] Support typing indicators Export this patch

The feature is optional & disabled by default.

+typing=paused is not supported in this implementation.
---
 lib/client_controller.dart | 10 +++++++++-
 lib/models.dart            | 39 ++++++++++++++++++++++++++++++++++++++
 lib/page/buffer.dart       | 25 ++++++++++++++++++++++++
 lib/page/settings.dart     | 15 +++++++++++++++
 4 files changed, 88 insertions(+), 1 deletion(-)

diff --git a/lib/client_controller.dart b/lib/client_controller.dart
index 1497a60..05f1f77 100644
--- a/lib/client_controller.dart
+++ b/lib/client_controller.dart
@@ -450,7 +450,7 @@ class ClientController {
			}

			var buffer = _bufferList.get(msg.source.name, network);
			if(buffer != null) {
			if (buffer != null) {
				buffer.realname = realname;
				_db.storeBuffer(buffer.entry);
			}
@@ -496,6 +496,7 @@ class ClientController {
			break;
		case 'PRIVMSG':
		case 'NOTICE':
		case 'TAGMSG':
			var target = msg.params[0];
			if (msg.batchByType('chathistory') != null) {
				break;
@@ -505,6 +506,13 @@ class ClientController {
			if (!client.isChannel(target) && !client.isMyNick(msg.source.name)) {
				target = msg.source.name;
			}
			if (msg.cmd == 'TAGMSG') {
				var typing = msg.tags['+typing'];
				if (typing != null && !client.isMyNick(msg.source.name)) {
					_bufferList.get(target, network)?.setTyping(msg.source.name, typing == 'active');
				}
				break;
			}
			return _handleChatMessages(target, [msg]);
		case 'INVITE':
			var nickname = msg.params[0];
diff --git a/lib/models.dart b/lib/models.dart
index f2063ff..e7379cb 100644
--- a/lib/models.dart
+++ b/lib/models.dart
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:collection';
import 'package:flutter/material.dart';

@@ -360,6 +361,8 @@ class BufferModel extends ChangeNotifier {
	String? _lastDeliveredTime;
	bool _messageHistoryLoaded = false;
	List<MessageModel> _messages = [];
	Map<String, DateTime> _typing = {};
	DateTime? _ownTyping;

	// Kept in sync by BufferPageState
	bool focused = false;
@@ -481,6 +484,42 @@ class BufferModel extends ChangeNotifier {
		notifyListeners();
		return true;
	}

	void setTyping(String member, bool typing) {
		if (typing) {
			_typing[member] = DateTime.now();
		} else {
			_typing.remove(member);
		}
		notifyListeners();
		if (typing) {
			Timer(Duration(seconds: 6), () {
				notifyListeners();
			});
		}
	}

	List<String> getTyping() {
		var limit = DateTime.now().subtract(Duration(seconds: 6));
		var typing = _typing.entries.where((e) => e.value.isAfter(limit)).map((e) => e.key).toList();
		typing.sort();
		return typing;
	}

	bool setOwnTyping(bool active) {
		bool notify;
		var time = DateTime.now();
		if (!active) {
			notify = _ownTyping != null && _ownTyping!.add(Duration(seconds: 6)).isAfter(time);
			_ownTyping = null;
		} else {
			notify = _ownTyping == null || _ownTyping!.add(Duration(seconds: 3)).isBefore(time);
			if (notify) {
				_ownTyping = time;
			}
		}
		return notify;
	}
}

int _compareMessageModels(MessageModel a, MessageModel b) {
diff --git a/lib/page/buffer.dart b/lib/page/buffer.dart
index 0e47222..34e0909 100644
--- a/lib/page/buffer.dart
+++ b/lib/page/buffer.dart
@@ -111,6 +111,8 @@ class BufferPageState extends State<BufferPage> with WidgetsBindingObserver {
		var buffer = context.read<BufferModel>();
		var client = context.read<Client>();

		buffer.setOwnTyping(false);

		var msg = IrcMessage('PRIVMSG', [buffer.name, text]);
		client.send(msg);

@@ -125,6 +127,18 @@ class BufferPageState extends State<BufferPage> with WidgetsBindingObserver {
		}
	}

	void _updateComposer() {
		var buffer = context.read<BufferModel>();
		var client = context.read<Client>();

		var active = _composerController.text != '';
		var notify = buffer.setOwnTyping(active);
		if (notify) {
			var msg = IrcMessage('TAGMSG', [buffer.name], tags: {'+typing': active ? 'active' : 'done'});
			client.send(msg);
		}
	}

	void _submitComposer() {
		if (_composerController.text != '') {
			_send(_composerController.text);
@@ -302,6 +316,14 @@ class BufferPageState extends State<BufferPage> with WidgetsBindingObserver {
		}
		var messages = buffer.messages;
		var compact = context.read<SharedPreferences>().getBool('buffer_compact') ?? false;
		var showTyping = context.read<SharedPreferences>().getBool('typing_indicator') ?? false;

		if (canSendMessage && showTyping) {
			var typingNicks = buffer.getTyping();
			if (typingNicks.isNotEmpty) {
				subtitle = typingNicks.join(', ') + ' ${typingNicks.length > 1 ? 'are' : 'is'} typing...';
			}
		}

		Widget? joinBanner;
		if (isChannel && !buffer.joined && !buffer.joining) {
@@ -360,6 +382,9 @@ class BufferPageState extends State<BufferPage> with WidgetsBindingObserver {
								hintText: 'Write a message...',
								border: InputBorder.none,
							),
							onChanged: showTyping ? (value) {
								_updateComposer();
							} : null,
							onSubmitted: (value) {
								_submitComposer();
							},
diff --git a/lib/page/settings.dart b/lib/page/settings.dart
index cd90544..cd46f82 100644
--- a/lib/page/settings.dart
+++ b/lib/page/settings.dart
@@ -24,11 +24,13 @@ class SettingsPage extends StatefulWidget {

class _SettingsPageState extends State<SettingsPage> {
	late bool _compact;
	late bool _typing;

	@override
	void initState() {
		super.initState();
		_compact = context.read<SharedPreferences>().getBool('buffer_compact') ?? false;
		_typing = context.read<SharedPreferences>().getBool('typing_indicator') ?? false;
	}

	void _logout() {
@@ -120,6 +122,19 @@ class _SettingsPageState extends State<SettingsPage> {
						},
					),
				),
				ListTile(
					title: Text('Send & display typing indicators'),
					leading: Icon(Icons.border_color),
					trailing: Switch(
						value: _typing,
						onChanged: (bool c) {
							setState(() {
								_typing = c;
								context.read<SharedPreferences>().setBool('typing_indicator', c);
							});
						},
					),
				),
				Divider(),
				ListTile(
					title: Text('About'),

base-commit: fc4308af156f0642bc7ec7bac4910356e20bdd74
-- 
2.17.1