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).
This commit is contained in:
miruka 2020-08-24 05:44:25 -04:00
parent aba47ef26b
commit 83f35c034e
4 changed files with 68 additions and 84 deletions

View File

@ -1,6 +1,6 @@
# TODO # TODO
- model not in list error in the onri test room - session list: prevent tab-focusing the delegates
- refresh server list button - refresh server list button
- global presence control - global presence control

View File

@ -1239,8 +1239,8 @@ class MatrixClient(nio.AsyncClient):
): ):
"""Set typing notice to the server.""" """Set typing notice to the server."""
# Do not send typing notice if the user is invisible
presence = self.models["accounts"][self.user_id].presence presence = self.models["accounts"][self.user_id].presence
if presence not in [Presence.State.invisible, Presence.State.offline]: if presence not in [Presence.State.invisible, Presence.State.offline]:
await super().room_typing(room_id, typing_state, timeout) await super().room_typing(room_id, typing_state, timeout)

View File

@ -2,6 +2,7 @@
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import "../../.."
import "../../../Base" import "../../../Base"
import "../AutoCompletion" import "../AutoCompletion"
@ -24,19 +25,15 @@ Rectangle {
HUserAvatar { HUserAvatar {
id: avatar 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 radius: 0
clientUserId: messageArea.writingUserId
userId: messageArea.writingUserId
mxc:
messageArea.writingUserInfo ?
messageArea.writingUserInfo.avatar_url :
""
displayName:
messageArea.writingUserInfo ?
messageArea.writingUserInfo.display_name :
""
} }
HScrollView { HScrollView {

View File

@ -15,12 +15,47 @@ HTextArea {
property string indent: " " property string indent: " "
property string toSend: "" property string userSetAsTyping: ""
property bool textChangedSinceLostFocus: false property bool textChangedSinceLostFocus: false
property string writingUserId: chat.userId
readonly property QtObject writingUserInfo: readonly property var usableAliases: {
ModelStore.get("accounts").find(writingUserId) 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: readonly property int cursorY:
text.substring(0, cursorPosition).split("\n").length - 1 text.substring(0, cursorPosition).split("\n").length - 1
@ -39,20 +74,6 @@ HTextArea {
// lineTextUntilCursor.match(/ {1,4}/g).slice(-1)[0].length : // lineTextUntilCursor.match(/ {1,4}/g).slice(-1)[0].length :
// 1 // 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 autoCompletePrevious()
signal autoCompleteNext() signal autoCompleteNext()
signal acceptAutoCompletion() signal acceptAutoCompletion()
@ -61,9 +82,18 @@ HTextArea {
function setTyping(typing) { function setTyping(typing) {
if (! area.enabled) return if (! area.enabled) return
if (typing && userSetAsTyping && userSetAsTyping !== writerId)
py.callClientCoro( py.callClientCoro(
writingUserId, "room_typing", [chat.roomId, typing, 5000], 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() { function clearReplyTo() {
@ -94,7 +124,7 @@ HTextArea {
// clear it before it reaches Python. // clear it before it reaches Python.
const mentions = Object.assign({}, usersCompleted) const mentions = Object.assign({}, usersCompleted)
const args = [chat.roomId, toSend, mentions, chat.replyToEventId] const args = [chat.roomId, toSend, mentions, chat.replyToEventId]
py.callClientCoro(writingUserId, "send_text", args) py.callClientCoro(writerId, "send_text", args)
area.clear() area.clear()
clearReplyTo() clearReplyTo()
@ -116,56 +146,13 @@ HTextArea {
tabStopDistance: 4 * 4 // 4 spaces tabStopDistance: 4 * 4 // 4 spaces
focus: true focus: true
// TODO: make this more declarative onTextChanged: if (! text) setTyping(false)
onTextChanged: {
if (! text || utils.isEmptyObject(usableAliases)) { onToSendChanged: {
writingUserId = Qt.binding(() => chat.userId)
toSend = text
setTyping(Boolean(text))
textChangedSinceLostFocus = true textChangedSinceLostFocus = true
return
}
let foundAlias = null if (toSend && (usingAlias || ! candidateAliases.length)) {
setTyping(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
} }
} }