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
This commit is contained in:
vslg 2020-07-09 20:53:25 -03:00 committed by miruka
parent a3c9ac20c6
commit 43df8fd60b
6 changed files with 94 additions and 60 deletions

View File

@ -400,7 +400,7 @@ class MatrixClient(nio.AsyncClient):
presence = self.backend.presences.get(self.user_id, None) presence = self.backend.presences.get(self.user_id, None)
if presence: if presence:
presence.members.pop(("account", self.user_id), None) presence.account = None
async def update_own_profile(self) -> None: async def update_own_profile(self) -> None:
@ -1315,10 +1315,11 @@ class MatrixClient(nio.AsyncClient):
account.connecting = True account.connecting = True
self.start_task = asyncio.ensure_future(self._start()) self.start_task = asyncio.ensure_future(self._start())
# Assign invisible on model in here, because server will tell us we are if (
# offline presence != "offline" and
if presence == "invisible": Presence.State(presence) != account.presence
account.presence = Presence.State.invisible ):
account.presence = Presence.State("echo_" + presence)
if not account.presence_support: if not account.presence_support:
account.presence = Presence.State(presence) account.presence = Presence.State(presence)

View File

@ -10,7 +10,6 @@ from typing import Any, Dict, List, Optional, Tuple, Type, Union
from uuid import UUID from uuid import UUID
import lxml # nosec import lxml # nosec
import nio import nio
from ..utils import AutoStrEnum, auto from ..utils import AutoStrEnum, auto
@ -20,7 +19,6 @@ ZeroDate = datetime.fromtimestamp(0)
OptionalExceptionType = Union[Type[None], Type[Exception]] OptionalExceptionType = Union[Type[None], Type[Exception]]
class TypeSpecifier(AutoStrEnum): class TypeSpecifier(AutoStrEnum):
"""Enum providing clarification of purpose for some matrix events.""" """Enum providing clarification of purpose for some matrix events."""
@ -37,6 +35,10 @@ class Presence():
online = auto() online = auto()
invisible = auto() invisible = auto()
echo_unavailable = auto()
echo_online = auto()
echo_invisible = auto()
def __lt__(self, other: "Presence.State") -> bool: def __lt__(self, other: "Presence.State") -> bool:
order = [ order = [
self.online, self.online,
@ -53,7 +55,7 @@ class Presence():
presence: State = State.offline presence: State = State.offline
currently_active: bool = False currently_active: bool = False
last_active_ago: int = -1 last_active_at: datetime = ZeroDate
status_msg: str = "" status_msg: str = ""
members: Dict[Tuple[str, str], "Member"] = field(default_factory=dict) members: Dict[Tuple[str, str], "Member"] = field(default_factory=dict)
@ -63,7 +65,7 @@ class Presence():
for member in self.members.values(): for member in self.members.values():
member.presence = self.presence member.presence = self.presence
member.status_msg = self.status_msg 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 member.currently_active = self.currently_active
def update_account(self) -> None: def update_account(self) -> None:
@ -73,14 +75,16 @@ class Presence():
# account presence to offline. # account presence to offline.
if ( if (
not self.account or not self.account or
self.account.presence == self.State.invisible and self.presence == self.State.offline and
self.presence == self.State.offline self.account.presence != self.State.echo_invisible
): ):
return 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.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 self.account.currently_active = self.currently_active
@ -105,7 +109,7 @@ class Account(ModelItem):
presence_support: bool = False presence_support: bool = False
presence: Presence.State = Presence.State.offline presence: Presence.State = Presence.State.offline
currently_active: bool = False currently_active: bool = False
last_active_ago: int = -1 last_active_at: datetime = ZeroDate
status_msg: str = "" status_msg: str = ""
def __lt__(self, other: "Account") -> bool: def __lt__(self, other: "Account") -> bool:
@ -242,7 +246,7 @@ class Member(ModelItem):
presence: Presence.State = Presence.State.offline presence: Presence.State = Presence.State.offline
currently_active: bool = False currently_active: bool = False
last_active_ago: int = -1 last_active_at: datetime = ZeroDate
status_msg: str = "" status_msg: str = ""
def __lt__(self, other: "Member") -> bool: def __lt__(self, other: "Member") -> bool:
@ -252,14 +256,14 @@ class Member(ModelItem):
other_name = other.display_name or other.id[1:] other_name = other.display_name or other.id[1:]
return ( return (
self.presence,
self.invited, self.invited,
other.power_level, other.power_level,
self.presence,
name.lower(), name.lower(),
) < ( ) < (
other.presence,
other.invited, other.invited,
self.power_level, self.power_level,
other.presence,
other_name.lower(), other_name.lower(),
) )

View File

@ -3,7 +3,7 @@
import json import json
import logging as log import logging as log
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime, timedelta
from html import escape from html import escape
from typing import TYPE_CHECKING, Optional, Tuple from typing import TYPE_CHECKING, Optional, Tuple
from urllib.parse import quote from urllib.parse import quote
@ -596,7 +596,9 @@ class NioCallbacks:
presence.status_msg = ev.status_msg or "" presence.status_msg = ev.status_msg or ""
presence.presence = Presence.State(ev.presence) if ev.presence\ presence.presence = Presence.State(ev.presence) if ev.presence\
else Presence.State.offline 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 presence.currently_active = ev.currently_active or False
# Add all existing members related to this presence # Add all existing members related to this presence
@ -614,17 +616,26 @@ class NioCallbacks:
# Check if presence event is ours # Check if presence event is ours
if ( if (
ev.user_id in self.models["accounts"] and 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] 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 # Servers that send presence events support presence
account.presence_support = True account.presence_support = True
# Save the presence for the next resume # Save the presence for the next resume
await self.client.backend.saved_accounts.update( await self.client.backend.saved_accounts.update(
user_id = ev.user_id, 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() presence.update_account()

View File

@ -71,12 +71,13 @@ HAvatar {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
radius: diameter / 2 radius: diameter / 2
opacity: presence.includes("echo") ? 0.4 : 1
color: color:
presence === "online" ? presence.includes("online") ?
theme.controls.presence.online : theme.controls.presence.online :
presence === "unavailable" ? presence.includes("unavailable") ?
theme.controls.presence.unavailable : theme.controls.presence.unavailable :
theme.controls.presence.offline theme.controls.presence.offline
@ -84,6 +85,9 @@ HAvatar {
border.color: theme.controls.presence.border border.color: theme.controls.presence.border
border.width: diameter / 10 border.width: diameter / 10
Behavior on color { HColorAnimation {} }
Behavior on opacity { HNumberAnimation {} }
HoverHandler { id: presenceHover } HoverHandler { id: presenceHover }
HToolTip { HToolTip {

View File

@ -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 { HLabeledItem {
id: statusMsgLabel id: statusMsgLabel
enabled: presence && presence !== "offline" 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 { HMenuSeparator {
visible: statusMsgLabel.visible visible: statusMsgLabel.visible
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0

View File

@ -44,11 +44,25 @@ HTile {
TitleRightInfoLabel { TitleRightInfoLabel {
tile: member tile: member
text: visible: presenceTimer.running
model.presence !== "online" &&
model.last_active_ago !== -1 ? Timer {
utils.formatRelativeTime(model.last_active_ago) : 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
)
)
}
} }
} }