Make offline presence to stop sync
Setting the presence of an account to offline will make the client to end sync task and will prevent messages from being sent. Setting it to online again or any other presence will start sync task again. Left: - Local echo to presence change - UI Control to affect all members presence - Block more requests to be sent (e.g. member actions)
This commit is contained in:
parent
3fa35b88c9
commit
a3c9ac20c6
|
@ -132,7 +132,7 @@ class Backend:
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await client.login(password)
|
await client.login(password, order=order)
|
||||||
except MatrixError:
|
except MatrixError:
|
||||||
await client.close()
|
await client.close()
|
||||||
raise
|
raise
|
||||||
|
@ -142,22 +142,7 @@ class Backend:
|
||||||
await client.logout()
|
await client.logout()
|
||||||
return client.user_id
|
return client.user_id
|
||||||
|
|
||||||
if order is None and not self.models["accounts"]:
|
|
||||||
order = 0
|
|
||||||
elif order is None:
|
|
||||||
order = max(
|
|
||||||
account.order
|
|
||||||
for i, account in enumerate(self.models["accounts"].values())
|
|
||||||
) + 1
|
|
||||||
|
|
||||||
account = Account(client.user_id, order)
|
|
||||||
|
|
||||||
self.clients[client.user_id] = client
|
self.clients[client.user_id] = client
|
||||||
self.models["accounts"][client.user_id] = account
|
|
||||||
|
|
||||||
# Get or create presence for account
|
|
||||||
presence = self.presences.setdefault(client.user_id, Presence())
|
|
||||||
presence.members["account", client.user_id] = account
|
|
||||||
|
|
||||||
return client.user_id
|
return client.user_id
|
||||||
|
|
||||||
|
@ -168,7 +153,6 @@ class Backend:
|
||||||
token: str,
|
token: str,
|
||||||
device_id: str,
|
device_id: str,
|
||||||
homeserver: str = "https://matrix.org",
|
homeserver: str = "https://matrix.org",
|
||||||
order: int = -1,
|
|
||||||
state: str = "online",
|
state: str = "online",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create and register a `MatrixClient` with known account details."""
|
"""Create and register a `MatrixClient` with known account details."""
|
||||||
|
@ -178,14 +162,7 @@ class Backend:
|
||||||
user=user_id, homeserver=homeserver, device_id=device_id,
|
user=user_id, homeserver=homeserver, device_id=device_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
account = Account(user_id, order)
|
|
||||||
|
|
||||||
self.clients[user_id] = client
|
self.clients[user_id] = client
|
||||||
self.models["accounts"][user_id] = account
|
|
||||||
|
|
||||||
# Get or create presence for account
|
|
||||||
presence = self.presences.setdefault(user_id, Presence())
|
|
||||||
presence.members["account", user_id] = account
|
|
||||||
|
|
||||||
await client.resume(user_id, token, device_id, state)
|
await client.resume(user_id, token, device_id, state)
|
||||||
|
|
||||||
|
@ -194,14 +171,19 @@ class Backend:
|
||||||
"""Call `resume_client` for all saved accounts in user config."""
|
"""Call `resume_client` for all saved accounts in user config."""
|
||||||
|
|
||||||
async def resume(user_id: str, info: Dict[str, Any]) -> str:
|
async def resume(user_id: str, info: Dict[str, Any]) -> str:
|
||||||
|
# Get or create account model
|
||||||
|
self.models["accounts"].setdefault(
|
||||||
|
user_id, Account(user_id, info.get("order", -1)),
|
||||||
|
)
|
||||||
|
|
||||||
await self.resume_client(
|
await self.resume_client(
|
||||||
user_id = user_id,
|
user_id = user_id,
|
||||||
token = info["token"],
|
token = info["token"],
|
||||||
device_id = info["device_id"],
|
device_id = info["device_id"],
|
||||||
homeserver = info["homeserver"],
|
homeserver = info["homeserver"],
|
||||||
order = info.get("order", -1),
|
|
||||||
state = info.get("presence", "online"),
|
state = info.get("presence", "online"),
|
||||||
)
|
)
|
||||||
|
|
||||||
return user_id
|
return user_id
|
||||||
|
|
||||||
return await asyncio.gather(*(
|
return await asyncio.gather(*(
|
||||||
|
|
|
@ -40,7 +40,7 @@ from .errors import (
|
||||||
from .html_markdown import HTML_PROCESSOR as HTML
|
from .html_markdown import HTML_PROCESSOR as HTML
|
||||||
from .media_cache import Media, Thumbnail
|
from .media_cache import Media, Thumbnail
|
||||||
from .models.items import (
|
from .models.items import (
|
||||||
Event, Member, Presence, Room, Upload, UploadStatus, ZeroDate,
|
Account, Event, Member, Presence, Room, Upload, UploadStatus, ZeroDate,
|
||||||
)
|
)
|
||||||
from .models.model_store import ModelStore
|
from .models.model_store import ModelStore
|
||||||
from .nio_callbacks import NioCallbacks
|
from .nio_callbacks import NioCallbacks
|
||||||
|
@ -231,13 +231,35 @@ class MatrixClient(nio.AsyncClient):
|
||||||
return f"{__display_name__} on {os_name} {os_ver}".rstrip()
|
return f"{__display_name__} on {os_name} {os_ver}".rstrip()
|
||||||
|
|
||||||
|
|
||||||
async def login(self, password: str, device_name: str = "") -> None:
|
async def login(
|
||||||
|
self, password: str,
|
||||||
|
device_name: str = "",
|
||||||
|
order: Optional[int] = None,
|
||||||
|
) -> None:
|
||||||
"""Login to the server using the account's password."""
|
"""Login to the server using the account's password."""
|
||||||
|
|
||||||
await super().login(
|
await super().login(
|
||||||
password, device_name or self.default_device_name(),
|
password, device_name or self.default_device_name(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if order is None and not self.models["accounts"]:
|
||||||
|
order = 0
|
||||||
|
elif order is None:
|
||||||
|
order = max(
|
||||||
|
account.order
|
||||||
|
for i, account in enumerate(self.models["accounts"].values())
|
||||||
|
) + 1
|
||||||
|
|
||||||
|
# Get or create account model
|
||||||
|
# We need to create account model in here, because _start() needs it
|
||||||
|
account = self.models["accounts"].setdefault(
|
||||||
|
self.user_id, Account(self.user_id, order),
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: set presence on login
|
||||||
|
self._presence = "online"
|
||||||
|
account.presence = Presence.State.online
|
||||||
|
account.connecting = True
|
||||||
self.start_task = asyncio.ensure_future(self._start())
|
self.start_task = asyncio.ensure_future(self._start())
|
||||||
|
|
||||||
|
|
||||||
|
@ -251,32 +273,21 @@ class MatrixClient(nio.AsyncClient):
|
||||||
"""Login to the server using an existing access token."""
|
"""Login to the server using an existing access token."""
|
||||||
|
|
||||||
response = nio.LoginResponse(user_id, device_id, token)
|
response = nio.LoginResponse(user_id, device_id, token)
|
||||||
|
account = self.models["accounts"][user_id]
|
||||||
await self.receive_response(response)
|
await self.receive_response(response)
|
||||||
|
|
||||||
self._presence = "offline" if state == "invisible" else state
|
self._presence = "offline" if state == "invisible" else state
|
||||||
self.start_task = asyncio.ensure_future(self._start())
|
account.presence = Presence.State(state)
|
||||||
|
|
||||||
if state == "invisible":
|
if state != "offline":
|
||||||
self.models["accounts"][self.user_id].presence = \
|
account.connecting = True
|
||||||
Presence.State.invisible
|
self.start_task = asyncio.ensure_future(self._start())
|
||||||
|
|
||||||
|
|
||||||
async def logout(self) -> None:
|
async def logout(self) -> None:
|
||||||
"""Logout from the server. This will delete the device."""
|
"""Logout from the server. This will delete the device."""
|
||||||
|
|
||||||
tasks = (
|
await self._stop()
|
||||||
self.profile_task,
|
|
||||||
self.sync_task,
|
|
||||||
self.server_config_task,
|
|
||||||
self.start_task,
|
|
||||||
)
|
|
||||||
|
|
||||||
for task in tasks:
|
|
||||||
if task:
|
|
||||||
task.cancel()
|
|
||||||
with suppress(asyncio.CancelledError):
|
|
||||||
await task
|
|
||||||
|
|
||||||
await super().logout()
|
await super().logout()
|
||||||
await self.close()
|
await self.close()
|
||||||
|
|
||||||
|
@ -300,8 +311,6 @@ class MatrixClient(nio.AsyncClient):
|
||||||
if future.cancelled(): # Account logged out
|
if future.cancelled(): # Account logged out
|
||||||
return
|
return
|
||||||
|
|
||||||
account = self.models["accounts"][self.user_id]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
account.max_upload_size = future.result()
|
account.max_upload_size = future.result()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -316,6 +325,13 @@ class MatrixClient(nio.AsyncClient):
|
||||||
on_server_config_response,
|
on_server_config_response,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
account = self.models["accounts"][self.user_id]
|
||||||
|
|
||||||
|
# Get or create presence for account
|
||||||
|
presence = self.backend.presences.setdefault(self.user_id, Presence())
|
||||||
|
presence.account = account
|
||||||
|
presence.presence = Presence.State(self._presence)
|
||||||
|
|
||||||
self.profile_task = asyncio.ensure_future(self.update_own_profile())
|
self.profile_task = asyncio.ensure_future(self.update_own_profile())
|
||||||
|
|
||||||
self.server_config_task = asyncio.ensure_future(
|
self.server_config_task = asyncio.ensure_future(
|
||||||
|
@ -362,6 +378,31 @@ class MatrixClient(nio.AsyncClient):
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
async def _stop(self) -> None:
|
||||||
|
"""Stop client tasks. Will prevent client to receive further events."""
|
||||||
|
|
||||||
|
tasks = (
|
||||||
|
self.profile_task,
|
||||||
|
self.sync_task,
|
||||||
|
self.server_config_task,
|
||||||
|
self.start_task,
|
||||||
|
)
|
||||||
|
|
||||||
|
for task in tasks:
|
||||||
|
if task:
|
||||||
|
task.cancel()
|
||||||
|
with suppress(asyncio.CancelledError):
|
||||||
|
await task
|
||||||
|
|
||||||
|
self.first_sync_done.clear()
|
||||||
|
|
||||||
|
# Remove account model from presence update
|
||||||
|
presence = self.backend.presences.get(self.user_id, None)
|
||||||
|
|
||||||
|
if presence:
|
||||||
|
presence.members.pop(("account", self.user_id), None)
|
||||||
|
|
||||||
|
|
||||||
async def update_own_profile(self) -> None:
|
async def update_own_profile(self) -> None:
|
||||||
"""Fetch our profile from server and Update our model `Account`."""
|
"""Fetch our profile from server and Update our model `Account`."""
|
||||||
|
|
||||||
|
@ -478,6 +519,12 @@ class MatrixClient(nio.AsyncClient):
|
||||||
mentions = mentions,
|
mentions = mentions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
while (
|
||||||
|
self.models["accounts"][self.user_id].presence ==
|
||||||
|
Presence.State.offline
|
||||||
|
):
|
||||||
|
await asyncio.sleep(0.2)
|
||||||
|
|
||||||
await self._send_message(room_id, content, tx_id)
|
await self._send_message(room_id, content, tx_id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -503,6 +550,12 @@ class MatrixClient(nio.AsyncClient):
|
||||||
async def send_file(self, room_id: str, path: Union[Path, str]) -> None:
|
async def send_file(self, room_id: str, path: Union[Path, str]) -> None:
|
||||||
"""Send a `m.file`, `m.image`, `m.audio` or `m.video` message."""
|
"""Send a `m.file`, `m.image`, `m.audio` or `m.video` message."""
|
||||||
|
|
||||||
|
while (
|
||||||
|
self.models["accounts"][self.user_id].presence ==
|
||||||
|
Presence.State.offline
|
||||||
|
):
|
||||||
|
await asyncio.sleep(0.2)
|
||||||
|
|
||||||
item_uuid = uuid4()
|
item_uuid = uuid4()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -1070,8 +1123,10 @@ 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
|
# Do not send typing notice if the user is invisible
|
||||||
if self.models["accounts"][self.user_id].presence != \
|
if (
|
||||||
Presence.State.invisible:
|
self.models["accounts"][self.user_id].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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1237,21 +1292,46 @@ class MatrixClient(nio.AsyncClient):
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set presence state for this account."""
|
"""Set presence state for this account."""
|
||||||
|
|
||||||
|
account = self.models["accounts"][self.user_id]
|
||||||
status_msg = status_msg if status_msg is not None else (
|
status_msg = status_msg if status_msg is not None else (
|
||||||
self.models["accounts"][self.user_id].status_msg
|
self.models["accounts"][self.user_id].status_msg
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if presence == "offline":
|
||||||
|
# Do not do anything if account is offline and setting to offline
|
||||||
|
if account.presence == Presence.State.offline:
|
||||||
|
return
|
||||||
|
|
||||||
|
await self._stop()
|
||||||
|
|
||||||
|
# Uppdate manually since we may not receive the presence event back
|
||||||
|
# in time
|
||||||
|
account.presence = Presence.State.offline
|
||||||
|
account.currently_active = False
|
||||||
|
elif (
|
||||||
|
presence != "offline" and
|
||||||
|
account.presence == Presence.State.offline
|
||||||
|
):
|
||||||
|
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 not account.presence_support:
|
||||||
|
account.presence = Presence.State(presence)
|
||||||
|
|
||||||
|
await self.backend.saved_accounts.update(
|
||||||
|
self.user_id, presence=presence,
|
||||||
|
)
|
||||||
|
|
||||||
await super().set_presence(
|
await super().set_presence(
|
||||||
"offline" if presence == "invisible" else presence,
|
"offline" if presence == "invisible" else presence,
|
||||||
status_msg,
|
status_msg,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Assign invisible on model in here, because server will tell us we are
|
|
||||||
# offline
|
|
||||||
if presence == "invisible":
|
|
||||||
self.models["accounts"][self.user_id].presence = \
|
|
||||||
Presence.State.invisible
|
|
||||||
|
|
||||||
|
|
||||||
async def import_keys(self, infile: str, passphrase: str) -> None:
|
async def import_keys(self, infile: str, passphrase: str) -> None:
|
||||||
"""Import decryption keys from a file, then retry decrypting events."""
|
"""Import decryption keys from a file, then retry decrypting events."""
|
||||||
|
|
|
@ -56,27 +56,33 @@ class Presence():
|
||||||
last_active_ago: int = -1
|
last_active_ago: int = -1
|
||||||
status_msg: str = ""
|
status_msg: str = ""
|
||||||
|
|
||||||
members: Dict[Tuple[str, str], Union["Member", "Account"]] = \
|
members: Dict[Tuple[str, str], "Member"] = field(default_factory=dict)
|
||||||
field(default_factory=dict)
|
account: Optional["Account"] = None
|
||||||
|
|
||||||
def update_members(self):
|
def update_members(self) -> None:
|
||||||
for member in self.members.values():
|
for member in self.members.values():
|
||||||
# Do not update if member is changing to invisible
|
|
||||||
# Because when setting invisible presence will give us presence
|
|
||||||
# event telling us we are offline, we do not want to set member
|
|
||||||
# presence to offline.
|
|
||||||
if (
|
|
||||||
member.presence == self.State.invisible
|
|
||||||
) and (
|
|
||||||
self.presence == self.State.offline
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
|
|
||||||
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_ago = self.last_active_ago
|
||||||
member.currently_active = self.currently_active
|
member.currently_active = self.currently_active
|
||||||
|
|
||||||
|
def update_account(self) -> None:
|
||||||
|
# Do not update if account is changing to invisible.
|
||||||
|
# When setting presence to invisible, the server will give us a
|
||||||
|
# presence event telling us we are offline, but we do not want to set
|
||||||
|
# account presence to offline.
|
||||||
|
if (
|
||||||
|
not self.account or
|
||||||
|
self.account.presence == self.State.invisible and
|
||||||
|
self.presence == self.State.offline
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.account.presence = self.presence
|
||||||
|
self.account.status_msg = self.status_msg
|
||||||
|
self.account.last_active_ago = self.last_active_ago
|
||||||
|
self.account.currently_active = self.currently_active
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Account(ModelItem):
|
class Account(ModelItem):
|
||||||
|
@ -88,7 +94,7 @@ class Account(ModelItem):
|
||||||
avatar_url: str = ""
|
avatar_url: str = ""
|
||||||
max_upload_size: int = 0
|
max_upload_size: int = 0
|
||||||
profile_updated: datetime = ZeroDate
|
profile_updated: datetime = ZeroDate
|
||||||
first_sync_done: bool = False
|
connecting: bool = False
|
||||||
total_unread: int = 0
|
total_unread: int = 0
|
||||||
total_highlights: int = 0
|
total_highlights: int = 0
|
||||||
local_unreads: bool = False
|
local_unreads: bool = False
|
||||||
|
@ -220,7 +226,7 @@ class AccountOrRoom(Account, Room):
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Member(Presence, ModelItem):
|
class Member(ModelItem):
|
||||||
"""A member in a matrix room."""
|
"""A member in a matrix room."""
|
||||||
|
|
||||||
id: str = field()
|
id: str = field()
|
||||||
|
@ -234,6 +240,11 @@ class Member(Presence, ModelItem):
|
||||||
last_read_event: str = ""
|
last_read_event: str = ""
|
||||||
last_read_at: datetime = ZeroDate
|
last_read_at: datetime = ZeroDate
|
||||||
|
|
||||||
|
presence: Presence.State = Presence.State.offline
|
||||||
|
currently_active: bool = False
|
||||||
|
last_active_ago: int = -1
|
||||||
|
status_msg: str = ""
|
||||||
|
|
||||||
def __lt__(self, other: "Member") -> bool:
|
def __lt__(self, other: "Member") -> bool:
|
||||||
"""Sort by presence, power level, then by display name/user ID."""
|
"""Sort by presence, power level, then by display name/user ID."""
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ class NioCallbacks:
|
||||||
self.client.first_sync_date = datetime.now()
|
self.client.first_sync_date = datetime.now()
|
||||||
|
|
||||||
account = self.models["accounts"][self.user_id]
|
account = self.models["accounts"][self.user_id]
|
||||||
account.first_sync_done = True
|
account.connecting = False
|
||||||
|
|
||||||
|
|
||||||
async def onKeysQueryResponse(self, resp: nio.KeysQueryResponse) -> None:
|
async def onKeysQueryResponse(self, resp: nio.KeysQueryResponse) -> None:
|
||||||
|
@ -612,11 +612,21 @@ class NioCallbacks:
|
||||||
presence.update_members()
|
presence.update_members()
|
||||||
|
|
||||||
# Check if presence event is ours
|
# Check if presence event is ours
|
||||||
if ev.user_id in self.models["accounts"]:
|
if (
|
||||||
|
ev.user_id in self.models["accounts"] and
|
||||||
|
presence.presence != Presence.State.offline
|
||||||
|
):
|
||||||
|
account = self.models["accounts"][ev.user_id]
|
||||||
|
|
||||||
# Servers that send presence events support presence
|
# Servers that send presence events support presence
|
||||||
self.models["accounts"][ev.user_id].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.add(ev.user_id)
|
await self.client.backend.saved_accounts.update(
|
||||||
|
user_id = ev.user_id,
|
||||||
|
presence = presence.presence.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
presence.update_account()
|
||||||
|
|
||||||
self.client.backend.presences[ev.user_id] = presence
|
self.client.backend.presences[ev.user_id] = presence
|
||||||
|
|
|
@ -184,7 +184,7 @@ class Accounts(JSONDataFile):
|
||||||
|
|
||||||
client = self.backend.clients[user_id]
|
client = self.backend.clients[user_id]
|
||||||
saved = await self.read()
|
saved = await self.read()
|
||||||
presence = self.backend.models["accounts"][user_id].presence.value
|
account = self.backend.models["accounts"][user_id]
|
||||||
|
|
||||||
await self.write({
|
await self.write({
|
||||||
**saved,
|
**saved,
|
||||||
|
@ -193,8 +193,10 @@ class Accounts(JSONDataFile):
|
||||||
"token": client.access_token,
|
"token": client.access_token,
|
||||||
"device_id": client.device_id,
|
"device_id": client.device_id,
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"presence": presence or "online",
|
"presence": account.presence.value,
|
||||||
"order": max([
|
|
||||||
|
# Can account.order converge with any other saved value?
|
||||||
|
"order": account.order if account.order >= 0 else max([
|
||||||
account.get("order", i)
|
account.get("order", i)
|
||||||
for i, account in enumerate(saved.values())
|
for i, account in enumerate(saved.values())
|
||||||
] or [-1]) + 1,
|
] or [-1]) + 1,
|
||||||
|
@ -202,6 +204,24 @@ class Accounts(JSONDataFile):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
async def update(
|
||||||
|
self,
|
||||||
|
user_id: str,
|
||||||
|
enabled: Optional[str] = None,
|
||||||
|
presence: Optional[str] = None,
|
||||||
|
order: Optional[int] = None,
|
||||||
|
) -> None:
|
||||||
|
"""Update existing account in the config and write to disk."""
|
||||||
|
|
||||||
|
saved = await self.read()
|
||||||
|
|
||||||
|
saved[user_id]["enabled"] = enabled or saved[user_id]["enabled"]
|
||||||
|
saved[user_id]["presence"] = presence or saved[user_id]["presence"]
|
||||||
|
saved[user_id]["order"] = order or saved[user_id]["order"]
|
||||||
|
|
||||||
|
await self.write({**saved})
|
||||||
|
|
||||||
|
|
||||||
async def delete(self, user_id: str) -> None:
|
async def delete(self, user_id: str) -> None:
|
||||||
"""Delete an account from the config and write it on disk."""
|
"""Delete an account from the config and write it on disk."""
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import QtQuick.Shapes 1.15
|
import QtQuick.Shapes 1.12
|
||||||
|
|
||||||
HAvatar {
|
HAvatar {
|
||||||
name: displayName || userId.substring(1) // no leading @
|
name: displayName || userId.substring(1) // no leading @
|
||||||
|
|
|
@ -12,18 +12,16 @@ HMenu {
|
||||||
property string userId
|
property string userId
|
||||||
property string presence
|
property string presence
|
||||||
property string statusMsg
|
property string statusMsg
|
||||||
property bool firstSyncDone
|
|
||||||
|
|
||||||
onOpened: statusText.forceActiveFocus()
|
onOpened: statusText.forceActiveFocus()
|
||||||
|
|
||||||
|
|
||||||
function setPresence(presence, statusMsg = null) {
|
function setPresence(presence, statusMsg = undefined) {
|
||||||
py.callClientCoro(userId, "set_presence", [presence, statusMsg])
|
py.callClientCoro(userId, "set_presence", [presence, statusMsg])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
HMenuItem {
|
HMenuItem {
|
||||||
enabled: firstSyncDone
|
|
||||||
icon.name: "presence"
|
icon.name: "presence"
|
||||||
icon.color: theme.controls.presence.online
|
icon.color: theme.controls.presence.online
|
||||||
text: qsTr("Online")
|
text: qsTr("Online")
|
||||||
|
@ -31,8 +29,7 @@ HMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
HMenuItem {
|
HMenuItem {
|
||||||
visible: presence
|
enabled: presence
|
||||||
enabled: firstSyncDone
|
|
||||||
icon.name: "presence-busy"
|
icon.name: "presence-busy"
|
||||||
icon.color: theme.controls.presence.unavailable
|
icon.color: theme.controls.presence.unavailable
|
||||||
text: qsTr("Unavailable")
|
text: qsTr("Unavailable")
|
||||||
|
@ -40,7 +37,6 @@ HMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
HMenuItem {
|
HMenuItem {
|
||||||
enabled: firstSyncDone
|
|
||||||
icon.name: "presence-offline"
|
icon.name: "presence-offline"
|
||||||
icon.color: theme.controls.presence.offline
|
icon.color: theme.controls.presence.offline
|
||||||
text: qsTr("Offline")
|
text: qsTr("Offline")
|
||||||
|
@ -48,8 +44,7 @@ HMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
HMenuItem {
|
HMenuItem {
|
||||||
visible: presence
|
enabled: presence
|
||||||
enabled: firstSyncDone
|
|
||||||
icon.name: "presence-invisible"
|
icon.name: "presence-invisible"
|
||||||
icon.color: theme.controls.presence.offline
|
icon.color: theme.controls.presence.offline
|
||||||
text: qsTr("Invisible")
|
text: qsTr("Invisible")
|
||||||
|
@ -60,8 +55,7 @@ HMenu {
|
||||||
|
|
||||||
HLabeledItem {
|
HLabeledItem {
|
||||||
id: statusMsgLabel
|
id: statusMsgLabel
|
||||||
visible: presence
|
enabled: presence && presence !== "offline"
|
||||||
enabled: firstSyncDone
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: visible ? implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
label.text: qsTr("Status message:")
|
label.text: qsTr("Status message:")
|
||||||
|
@ -80,12 +74,14 @@ HMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultText: statusMsg
|
defaultText: statusMsg
|
||||||
|
placeholderText: ! presence ? "Unsupported server" : ""
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
HButton {
|
HButton {
|
||||||
id: button
|
id: button
|
||||||
|
visible: presence
|
||||||
|
|
||||||
icon.name: "apply"
|
icon.name: "apply"
|
||||||
icon.color: theme.colors.positiveBackground
|
icon.color: theme.colors.positiveBackground
|
||||||
|
|
|
@ -13,7 +13,12 @@ HTile {
|
||||||
tile: account
|
tile: account
|
||||||
spacing: 0
|
spacing: 0
|
||||||
opacity:
|
opacity:
|
||||||
collapsed ? theme.mainPane.listView.account.collapsedOpacity : 1
|
collapsed ?
|
||||||
|
theme.mainPane.listView.account.collapsedOpacity :
|
||||||
|
|
||||||
|
model.presence == "offline" ?
|
||||||
|
theme.mainPane.listView.offlineOpacity :
|
||||||
|
1
|
||||||
|
|
||||||
Behavior on opacity { HNumberAnimation {} }
|
Behavior on opacity { HNumberAnimation {} }
|
||||||
|
|
||||||
|
@ -25,14 +30,14 @@ HTile {
|
||||||
radius: theme.mainPane.listView.account.avatarRadius
|
radius: theme.mainPane.listView.account.avatarRadius
|
||||||
compact: account.compact
|
compact: account.compact
|
||||||
|
|
||||||
presence: model.presence
|
presence: model.presence_support ? model.presence : ""
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignCenter
|
Layout.alignment: Qt.AlignCenter
|
||||||
|
|
||||||
HLoader {
|
HLoader {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
z: 9998
|
z: 9998
|
||||||
opacity: model.first_sync_done ? 0 : 1
|
opacity: model.connecting ? 1 : 0
|
||||||
active: opacity > 0
|
active: opacity > 0
|
||||||
|
|
||||||
sourceComponent: Rectangle {
|
sourceComponent: Rectangle {
|
||||||
|
@ -158,11 +163,11 @@ HTile {
|
||||||
|
|
||||||
contextMenu: AccountContextMenu {
|
contextMenu: AccountContextMenu {
|
||||||
userId: model.id
|
userId: model.id
|
||||||
presence: model.presence_support ? model.presence : null
|
presence:
|
||||||
|
model.presence_support || model.presence === "offline" ?
|
||||||
|
model.presence :
|
||||||
|
null
|
||||||
statusMsg: model.status_msg
|
statusMsg: model.status_msg
|
||||||
|
|
||||||
// Gray out buttons before first sync
|
|
||||||
firstSyncDone: model.first_sync_done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,13 @@ HTile {
|
||||||
|
|
||||||
contentItem: ContentRow {
|
contentItem: ContentRow {
|
||||||
tile: room
|
tile: room
|
||||||
opacity: model.left ? theme.mainPane.listView.room.leftRoomOpacity : 1
|
opacity:
|
||||||
|
accountModel.presence === "offline" ?
|
||||||
|
theme.mainPane.listView.offlineOpacity :
|
||||||
|
|
||||||
|
model.left ?
|
||||||
|
theme.mainPane.listView.room.leftRoomOpacity :
|
||||||
|
1
|
||||||
|
|
||||||
Behavior on opacity { HNumberAnimation {} }
|
Behavior on opacity { HNumberAnimation {} }
|
||||||
|
|
||||||
|
@ -179,6 +185,10 @@ HTile {
|
||||||
readonly property ListModel eventModel:
|
readonly property ListModel eventModel:
|
||||||
ModelStore.get(model.for_account, model.id, "events")
|
ModelStore.get(model.for_account, model.id, "events")
|
||||||
|
|
||||||
|
// TODO: binding loop
|
||||||
|
readonly property QtObject accountModel:
|
||||||
|
ModelStore.get("accounts").find(model.for_account)
|
||||||
|
|
||||||
readonly property QtObject lastEvent:
|
readonly property QtObject lastEvent:
|
||||||
eventModel.count > 0 ? eventModel.get(0) : null
|
eventModel.count > 0 ? eventModel.get(0) : null
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,10 @@ MultiviewPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
readonly property QtObject accountModel:
|
||||||
|
ModelStore.get("accounts").find(chat.roomInfo.for_account)
|
||||||
|
|
||||||
|
|
||||||
function toggleFocus() {
|
function toggleFocus() {
|
||||||
if (roomPane.activeFocus) {
|
if (roomPane.activeFocus) {
|
||||||
if (roomPane.collapse) roomPane.close()
|
if (roomPane.collapse) roomPane.close()
|
||||||
|
@ -78,7 +82,9 @@ MultiviewPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberView {}
|
MemberView {}
|
||||||
SettingsView {}
|
SettingsView {
|
||||||
|
enabled: accountModel.presence !== "offline"
|
||||||
|
}
|
||||||
|
|
||||||
HShortcut {
|
HShortcut {
|
||||||
sequences: window.settings.keys.toggleFocusRoomPane
|
sequences: window.settings.keys.toggleFocusRoomPane
|
||||||
|
|
|
@ -317,6 +317,7 @@ mainPane:
|
||||||
|
|
||||||
listView:
|
listView:
|
||||||
color background: colors.mediumBackground
|
color background: colors.mediumBackground
|
||||||
|
real offlineOpacity: 0.5
|
||||||
|
|
||||||
account:
|
account:
|
||||||
real collapsedOpacity: 0.3
|
real collapsedOpacity: 0.3
|
||||||
|
|
|
@ -326,6 +326,7 @@ mainPane:
|
||||||
|
|
||||||
listView:
|
listView:
|
||||||
color background: colors.mediumBackground
|
color background: colors.mediumBackground
|
||||||
|
real offlineOpacity: 0.5
|
||||||
|
|
||||||
account:
|
account:
|
||||||
real collapsedOpacity: 0.3
|
real collapsedOpacity: 0.3
|
||||||
|
|
Loading…
Reference in New Issue
Block a user