~emersion/goguma-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
1

[PATCH v2] Support typing indicators

Details
Message ID
<20220502172749.1691-1-delthas@dille.cc>
DKIM signature
pass
Download raw message
Patch: +87 -1
The feature is optional & disabled by default.

+typing=paused is not supported in this implementation.
---
Updated based on your comments.
The typing indicator in the subtitle mirrors Whatsapp's behavior and is 
easier to implement than some text at the bottom of the screen.

 lib/client_controller.dart | 10 ++++++++-
 lib/models.dart            | 21 +++++++++++++++++++
 lib/page/buffer.dart       | 42 ++++++++++++++++++++++++++++++++++++++
 lib/page/settings.dart     | 15 ++++++++++++++
 4 files changed, 87 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..7808622 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,7 @@ class BufferModel extends ChangeNotifier {
	String? _lastDeliveredTime;
	bool _messageHistoryLoaded = false;
	List<MessageModel> _messages = [];
	Map<String, Timer> _typing = {};

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

	List<String> get typing {
		var typing = _typing.keys.toList();
		typing.sort();
		return typing;
	}

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

int _compareMessageModels(MessageModel a, MessageModel b) {
diff --git a/lib/page/buffer.dart b/lib/page/buffer.dart
index 0e47222..8b620a0 100644
--- a/lib/page/buffer.dart
+++ b/lib/page/buffer.dart
@@ -78,6 +78,8 @@ class BufferPageState extends State<BufferPage> with WidgetsBindingObserver {

	bool _showJumpToBottom = false;

	DateTime? _ownTyping;

	@override
	void initState() {
		super.initState();
@@ -111,6 +113,8 @@ class BufferPageState extends State<BufferPage> with WidgetsBindingObserver {
		var buffer = context.read<BufferModel>();
		var client = context.read<Client>();

		_setOwnTyping(false);

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

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

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

		var active = _composerController.text != '';
		var notify = _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);
@@ -178,6 +194,21 @@ class BufferPageState extends State<BufferPage> with WidgetsBindingObserver {
		}
	}

	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;
	}

	@override
	void dispose() {
		_composerFocusNode.dispose();
@@ -302,6 +333,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.typing;
			if (typingNicks.isNotEmpty) {
				subtitle = typingNicks.join(', ') + ' ${typingNicks.length > 1 ? 'are' : 'is'} typing...';
			}
		}

		Widget? joinBanner;
		if (isChannel && !buffer.joined && !buffer.joining) {
@@ -360,6 +399,9 @@ class BufferPageState extends State<BufferPage> with WidgetsBindingObserver {
								hintText: 'Write a message...',
								border: InputBorder.none,
							),
							onChanged: showTyping ? (value) {
								_sendTypingStatus();
							} : 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
Details
Message ID
<p1UrMpzAPIW0oGeQUDWiVFpOdf6prNuzjqMudtL3g5EdgmiKrcE_BilRQuyw_iNb3V_IQ2ruFoUmP6BY08SvwT-bZ6kxjJJLRaByMc7ZzgI=@emersion.fr>
In-Reply-To
<20220502172749.1691-1-delthas@dille.cc> (view parent)
DKIM signature
pass
Download raw message
Pushed with a minor edit to disable the feature if the server doesn't support
message-tags, thanks!
Reply to thread Export thread (mbox)