From 43df8fd60b5933421a36dd05fb3a2abde2dfa9b1 Mon Sep 17 00:00:00 2001 From: vslg Date: Thu, 9 Jul 2020 20:53:25 -0300 Subject: [PATCH] Bug fix and minor improvements Improvements: - Add instant feedback upon setting a different presence for account (local echo) - Sort room members by power level and then presence - Periodically update members' `last_acitve_at` field on the room pane - Move status message field up on account context menu, and put invisible before offline again Bug fix: - Do not try to override presence set from another client, accept it --- src/backend/matrix_client.py | 11 ++-- src/backend/models/items.py | 28 ++++---- src/backend/nio_callbacks.py | 19 ++++-- src/gui/Base/HUserAvatar.qml | 8 ++- src/gui/MainPane/AccountContextMenu.qml | 64 +++++++++---------- .../RoomPane/MemberView/MemberDelegate.qml | 24 +++++-- 6 files changed, 94 insertions(+), 60 deletions(-) diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index 8e5756d3..d12ddfc8 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -400,7 +400,7 @@ class MatrixClient(nio.AsyncClient): presence = self.backend.presences.get(self.user_id, None) if presence: - presence.members.pop(("account", self.user_id), None) + presence.account = None async def update_own_profile(self) -> None: @@ -1315,10 +1315,11 @@ class MatrixClient(nio.AsyncClient): account.connecting = True self.start_task = asyncio.ensure_future(self._start()) - # Assign invisible on model in here, because server will tell us we are - # offline - if presence == "invisible": - account.presence = Presence.State.invisible + if ( + presence != "offline" and + Presence.State(presence) != account.presence + ): + account.presence = Presence.State("echo_" + presence) if not account.presence_support: account.presence = Presence.State(presence) diff --git a/src/backend/models/items.py b/src/backend/models/items.py index 320440ef..9e288a8a 100644 --- a/src/backend/models/items.py +++ b/src/backend/models/items.py @@ -10,7 +10,6 @@ from typing import Any, Dict, List, Optional, Tuple, Type, Union from uuid import UUID import lxml # nosec - import nio from ..utils import AutoStrEnum, auto @@ -20,7 +19,6 @@ ZeroDate = datetime.fromtimestamp(0) OptionalExceptionType = Union[Type[None], Type[Exception]] - class TypeSpecifier(AutoStrEnum): """Enum providing clarification of purpose for some matrix events.""" @@ -37,6 +35,10 @@ class Presence(): online = auto() invisible = auto() + echo_unavailable = auto() + echo_online = auto() + echo_invisible = auto() + def __lt__(self, other: "Presence.State") -> bool: order = [ self.online, @@ -53,7 +55,7 @@ class Presence(): presence: State = State.offline currently_active: bool = False - last_active_ago: int = -1 + last_active_at: datetime = ZeroDate status_msg: str = "" members: Dict[Tuple[str, str], "Member"] = field(default_factory=dict) @@ -63,7 +65,7 @@ class Presence(): for member in self.members.values(): member.presence = self.presence member.status_msg = self.status_msg - member.last_active_ago = self.last_active_ago + member.last_active_at = self.last_active_at member.currently_active = self.currently_active def update_account(self) -> None: @@ -73,14 +75,16 @@ class Presence(): # account presence to offline. if ( not self.account or - self.account.presence == self.State.invisible and - self.presence == self.State.offline + self.presence == self.State.offline and + self.account.presence != self.State.echo_invisible ): return - self.account.presence = self.presence + self.account.presence = self.presence if ( + self.account.presence != self.State.echo_invisible + ) else self.State.invisible self.account.status_msg = self.status_msg - self.account.last_active_ago = self.last_active_ago + self.account.last_active_at = self.last_active_at self.account.currently_active = self.currently_active @@ -105,7 +109,7 @@ class Account(ModelItem): presence_support: bool = False presence: Presence.State = Presence.State.offline currently_active: bool = False - last_active_ago: int = -1 + last_active_at: datetime = ZeroDate status_msg: str = "" def __lt__(self, other: "Account") -> bool: @@ -242,7 +246,7 @@ class Member(ModelItem): presence: Presence.State = Presence.State.offline currently_active: bool = False - last_active_ago: int = -1 + last_active_at: datetime = ZeroDate status_msg: str = "" def __lt__(self, other: "Member") -> bool: @@ -252,14 +256,14 @@ class Member(ModelItem): other_name = other.display_name or other.id[1:] return ( - self.presence, self.invited, other.power_level, + self.presence, name.lower(), ) < ( - other.presence, other.invited, self.power_level, + other.presence, other_name.lower(), ) diff --git a/src/backend/nio_callbacks.py b/src/backend/nio_callbacks.py index 3ae46eff..57b14907 100644 --- a/src/backend/nio_callbacks.py +++ b/src/backend/nio_callbacks.py @@ -3,7 +3,7 @@ import json import logging as log from dataclasses import dataclass, field -from datetime import datetime +from datetime import datetime, timedelta from html import escape from typing import TYPE_CHECKING, Optional, Tuple from urllib.parse import quote @@ -596,7 +596,9 @@ class NioCallbacks: presence.status_msg = ev.status_msg or "" presence.presence = Presence.State(ev.presence) if ev.presence\ else Presence.State.offline - presence.last_active_ago = ev.last_active_ago or -1 + presence.last_active_at = ( + datetime.now() - timedelta(milliseconds=ev.last_active_ago) + ) if ev.last_active_ago else datetime.fromtimestamp(0) presence.currently_active = ev.currently_active or False # Add all existing members related to this presence @@ -614,17 +616,26 @@ class NioCallbacks: # Check if presence event is ours if ( ev.user_id in self.models["accounts"] and - presence.presence != Presence.State.offline + not ( + presence.presence == Presence.State.offline and + self.models["accounts"][ev.user_id].presence != + Presence.State.echo_invisible + ) ): account = self.models["accounts"][ev.user_id] + # Do not fight back presence + self.client.backend.clients[ev.user_id]._presence = ev.presence + # Servers that send presence events support presence account.presence_support = True # Save the presence for the next resume await self.client.backend.saved_accounts.update( user_id = ev.user_id, - presence = presence.presence.value, + presence = presence.presence.value if ( + account.presence != Presence.State.echo_invisible + ) else "invisible", ) presence.update_account() diff --git a/src/gui/Base/HUserAvatar.qml b/src/gui/Base/HUserAvatar.qml index 811bdb10..31467352 100644 --- a/src/gui/Base/HUserAvatar.qml +++ b/src/gui/Base/HUserAvatar.qml @@ -71,12 +71,13 @@ HAvatar { anchors.bottom: parent.bottom anchors.right: parent.right radius: diameter / 2 + opacity: presence.includes("echo") ? 0.4 : 1 color: - presence === "online" ? + presence.includes("online") ? theme.controls.presence.online : - presence === "unavailable" ? + presence.includes("unavailable") ? theme.controls.presence.unavailable : theme.controls.presence.offline @@ -84,6 +85,9 @@ HAvatar { border.color: theme.controls.presence.border border.width: diameter / 10 + Behavior on color { HColorAnimation {} } + Behavior on opacity { HNumberAnimation {} } + HoverHandler { id: presenceHover } HToolTip { diff --git a/src/gui/MainPane/AccountContextMenu.qml b/src/gui/MainPane/AccountContextMenu.qml index 5e68b00f..29b97c7a 100644 --- a/src/gui/MainPane/AccountContextMenu.qml +++ b/src/gui/MainPane/AccountContextMenu.qml @@ -21,38 +21,6 @@ HMenu { } - HMenuItem { - icon.name: "presence" - icon.color: theme.controls.presence.online - text: qsTr("Online") - onTriggered: setPresence("online") - } - - HMenuItem { - enabled: presence - icon.name: "presence-busy" - icon.color: theme.controls.presence.unavailable - text: qsTr("Unavailable") - onTriggered: setPresence("unavailable") - } - - HMenuItem { - icon.name: "presence-offline" - icon.color: theme.controls.presence.offline - text: qsTr("Offline") - onTriggered: setPresence("offline") - } - - HMenuItem { - enabled: presence - icon.name: "presence-invisible" - icon.color: theme.controls.presence.offline - text: qsTr("Invisible") - onTriggered: setPresence("invisible") - } - - HMenuSeparator { } - HLabeledItem { id: statusMsgLabel enabled: presence && presence !== "offline" @@ -95,6 +63,38 @@ HMenu { } } + HMenuSeparator { } + + HMenuItem { + icon.name: "presence" + icon.color: theme.controls.presence.online + text: qsTr("Online") + onTriggered: setPresence("online") + } + + HMenuItem { + enabled: presence + icon.name: "presence-busy" + icon.color: theme.controls.presence.unavailable + text: qsTr("Unavailable") + onTriggered: setPresence("unavailable") + } + + HMenuItem { + enabled: presence + icon.name: "presence-invisible" + icon.color: theme.controls.presence.offline + text: qsTr("Invisible") + onTriggered: setPresence("invisible") + } + + HMenuItem { + icon.name: "presence-offline" + icon.color: theme.controls.presence.offline + text: qsTr("Offline") + onTriggered: setPresence("offline") + } + HMenuSeparator { visible: statusMsgLabel.visible height: visible ? implicitHeight : 0 diff --git a/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml b/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml index 53b1b82e..98391ec9 100644 --- a/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml +++ b/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml @@ -44,11 +44,25 @@ HTile { TitleRightInfoLabel { tile: member - text: - model.presence !== "online" && - model.last_active_ago !== -1 ? - utils.formatRelativeTime(model.last_active_ago) : - "" + visible: presenceTimer.running + + Timer { + id: presenceTimer + running: + ! model.currently_active && + model.last_active_at > new Date(1) + repeat: true + interval: + new Date() - model.last_active_at < 60000 ? + 10000 : + 60000 + triggeredOnStart: true + onTriggered: parent.text = Qt.binding(() => + utils.formatRelativeTime( + new Date() - model.last_active_at + ) + ) + } } }