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
- model not in list error in the onri test room
- session list: prevent tab-focusing the delegates
- refresh server list button
- global presence control

View File

@ -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)

View File

@ -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 {

View File

@ -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
if (typing && userSetAsTyping && userSetAsTyping !== writerId)
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() {
@ -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))
onTextChanged: if (! text) setTyping(false)
onToSendChanged: {
textChangedSinceLostFocus = true
return
}
let foundAlias = null
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)
}
}