From 83f35c034e8bee0e79aa289fe68042a8b0c90a6a Mon Sep 17 00:00:00 2001 From: miruka Date: Mon, 24 Aug 2020 05:44:25 -0400 Subject: [PATCH] Refactor MessageArea composer alias handling Simplify the code, make it more declarative, and improve the reliability of sending typing notifications (notably when changing the used alias in the middle of a message). --- TODO.md | 2 +- src/backend/matrix_client.py | 2 +- src/gui/Pages/Chat/Composer/Composer.qml | 21 ++-- src/gui/Pages/Chat/Composer/MessageArea.qml | 127 +++++++++----------- 4 files changed, 68 insertions(+), 84 deletions(-) diff --git a/TODO.md b/TODO.md index 7c36909f..756bf5bf 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,6 @@ # TODO -- model not in list error in the onri test room +- session list: prevent tab-focusing the delegates - refresh server list button - global presence control diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index 16b81a8b..b58f254e 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -1239,8 +1239,8 @@ class MatrixClient(nio.AsyncClient): ): """Set typing notice to the server.""" - # Do not send typing notice if the user is invisible presence = self.models["accounts"][self.user_id].presence + if presence not in [Presence.State.invisible, Presence.State.offline]: await super().room_typing(room_id, typing_state, timeout) diff --git a/src/gui/Pages/Chat/Composer/Composer.qml b/src/gui/Pages/Chat/Composer/Composer.qml index cde75bf4..98b44710 100644 --- a/src/gui/Pages/Chat/Composer/Composer.qml +++ b/src/gui/Pages/Chat/Composer/Composer.qml @@ -2,6 +2,7 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 +import "../../.." import "../../../Base" import "../AutoCompletion" @@ -24,19 +25,15 @@ Rectangle { HUserAvatar { id: avatar + + readonly property QtObject writerInfo: + ModelStore.get("accounts").find(clientUserId) + + clientUserId: messageArea.writerId + userId: clientUserId + mxc: writerInfo ? writerInfo.avatar_url : "" + displayName: writerInfo ? writerInfo.display_name : "" radius: 0 - clientUserId: messageArea.writingUserId - userId: messageArea.writingUserId - - mxc: - messageArea.writingUserInfo ? - messageArea.writingUserInfo.avatar_url : - "" - - displayName: - messageArea.writingUserInfo ? - messageArea.writingUserInfo.display_name : - "" } HScrollView { diff --git a/src/gui/Pages/Chat/Composer/MessageArea.qml b/src/gui/Pages/Chat/Composer/MessageArea.qml index 56a2bac8..ba834c18 100644 --- a/src/gui/Pages/Chat/Composer/MessageArea.qml +++ b/src/gui/Pages/Chat/Composer/MessageArea.qml @@ -15,12 +15,47 @@ HTextArea { property string indent: " " - property string toSend: "" + property string userSetAsTyping: "" property bool textChangedSinceLostFocus: false - property string writingUserId: chat.userId - readonly property QtObject writingUserInfo: - ModelStore.get("accounts").find(writingUserId) + readonly property var usableAliases: { + const obj = {} + + // Get accounts that are members of this room with permission to talk + for (const [id, alia] of Object.entries(window.settings.writeAliases)){ + const room = ModelStore.get(id, "rooms").find(chat.roomId) + if (room && + ! room.inviter_id && ! room.left && room.can_send_messages) + obj[id] = alia + } + + return obj + } + + readonly property var candidateAliases: { + if (! text) return [] + + const candidates = [] + const words = text.split(" ") + + for (const [userId, alias] of Object.entries(usableAliases)) + if ((words.length === 1 && alias.startsWith(words[0])) || + (words.length > 1 && words[0] == alias)) + + candidates.push({id: userId, text: alias}) + + return candidates + } + + readonly property var usingAlias: + candidateAliases.length === 1 && text.includes(" ") ? + candidateAliases[0] : + null + + readonly property string writerId: usingAlias ? usingAlias.id : chat.userId + + readonly property string toSend: + usingAlias ? text.replace(usingAlias.text + " ", "") : text readonly property int cursorY: text.substring(0, cursorPosition).split("\n").length - 1 @@ -39,20 +74,6 @@ HTextArea { // lineTextUntilCursor.match(/ {1,4}/g).slice(-1)[0].length : // 1 - readonly property var usableAliases: { - const obj = {} - - // Get accounts that are members of this room with permission to talk - for (const [id, alia] of Object.entries(window.settings.writeAliases)){ - const room = ModelStore.get(id, "rooms").find(chat.roomId) - if (room && - ! room.inviter_id && ! room.left && room.can_send_messages) - obj[id] = alia - } - - return obj - } - signal autoCompletePrevious() signal autoCompleteNext() signal acceptAutoCompletion() @@ -61,9 +82,18 @@ HTextArea { function setTyping(typing) { if (! area.enabled) return - py.callClientCoro( - writingUserId, "room_typing", [chat.roomId, typing, 5000], - ) + if (typing && userSetAsTyping && userSetAsTyping !== writerId) + py.callClientCoro( + userSetAsTyping, "room_typing", [chat.roomId, false], + ) + + const userId = typing ? writerId : userSetAsTyping + + userSetAsTyping = typing ? writerId : "" + + if (! userId) return // ! typing && ! userSetAsTyping + + py.callClientCoro(userId, "room_typing", [chat.roomId, typing]) } function clearReplyTo() { @@ -94,7 +124,7 @@ HTextArea { // clear it before it reaches Python. const mentions = Object.assign({}, usersCompleted) const args = [chat.roomId, toSend, mentions, chat.replyToEventId] - py.callClientCoro(writingUserId, "send_text", args) + py.callClientCoro(writerId, "send_text", args) area.clear() clearReplyTo() @@ -116,56 +146,13 @@ HTextArea { tabStopDistance: 4 * 4 // 4 spaces focus: true - // TODO: make this more declarative - onTextChanged: { - if (! text || utils.isEmptyObject(usableAliases)) { - writingUserId = Qt.binding(() => chat.userId) - toSend = text - setTyping(Boolean(text)) - textChangedSinceLostFocus = true - return - } + onTextChanged: if (! text) setTyping(false) - let foundAlias = null + onToSendChanged: { + textChangedSinceLostFocus = true - for (const [user, writing_alias] of Object.entries(usableAliases)) { - if (text.startsWith(writing_alias + " ")) { - writingUserId = user - foundAlias = new RegExp("^" + writing_alias + " ") - break - } - } - - if (foundAlias) { - toSend = text.replace(foundAlias, "") - setTyping(Boolean(text)) - textChangedSinceLostFocus = true - return - } - - writingUserId = Qt.binding(() => chat.userId) - toSend = text - - const vals = Object.values(usableAliases) - - const longestAlias = - vals.reduce((a, b) => a.length > b.length ? a: b) - - const textNotStartsWithAnyAlias = - ! vals.some(a => a.startsWith(text)) - - const textContainsCharNotInAnyAlias = - vals.every(a => text.split("").some(c => ! a.includes(c))) - - // Only set typing when it's sure that the user will not use - // an alias and has written something - if (toSend && - (text.length > longestAlias.length || - textNotStartsWithAnyAlias || - textContainsCharNotInAnyAlias)) - { - setTyping(Boolean(text)) - textChangedSinceLostFocus = true + if (toSend && (usingAlias || ! candidateAliases.length)) { + setTyping(true) } }