Rework Backend, models and items organization
This commit is contained in:
parent
6051ba187a
commit
bbc4c15ad3
2
TODO.md
2
TODO.md
|
@ -4,6 +4,7 @@
|
||||||
- Cleanup unused icons
|
- Cleanup unused icons
|
||||||
|
|
||||||
- Bug fixes
|
- Bug fixes
|
||||||
|
- dataclass-like `default_factory` for ListItem
|
||||||
- Local echo messages all have the same time
|
- Local echo messages all have the same time
|
||||||
- Prevent briefly seeing login screen if there are accounts to
|
- Prevent briefly seeing login screen if there are accounts to
|
||||||
resumeSession for but they take time to appear
|
resumeSession for but they take time to appear
|
||||||
|
@ -47,6 +48,7 @@
|
||||||
- Links preview
|
- Links preview
|
||||||
|
|
||||||
- Client improvements
|
- Client improvements
|
||||||
|
- nio.MatrixRoom has `typing_users`, no need to handle it on our own
|
||||||
- Don't send setTypingState False when focus lost if nothing in sendbox
|
- Don't send setTypingState False when focus lost if nothing in sendbox
|
||||||
- Initial sync filter and lazy load, see weechat-matrix `_handle_login()`
|
- Initial sync filter and lazy load, see weechat-matrix `_handle_login()`
|
||||||
- See also `handle_response()`'s `keys_query` request
|
- See also `handle_response()`'s `keys_query` request
|
||||||
|
|
|
@ -2,14 +2,17 @@
|
||||||
# This file is part of harmonyqml, licensed under GPLv3.
|
# This file is part of harmonyqml, licensed under GPLv3.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from typing import Deque, Dict, Optional, Sequence, Set
|
from typing import Deque, Dict, Optional, Sequence, Set, Tuple
|
||||||
|
|
||||||
from atomicfile import AtomicFile
|
from atomicfile import AtomicFile
|
||||||
from PyQt5.QtCore import QObject, QStandardPaths, pyqtProperty, pyqtSlot
|
from PyQt5.QtCore import QObject, QStandardPaths, pyqtProperty, pyqtSlot
|
||||||
|
|
||||||
from .html_filter import HtmlFilter
|
from .html_filter import HtmlFilter
|
||||||
from .model import ListModel, ListModelMap
|
from .model import ListModel, ListModelMap
|
||||||
|
from .model.items import User
|
||||||
|
from .network_manager import NioErrorResponse
|
||||||
from .pyqt_future import futurize
|
from .pyqt_future import futurize
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,8 +21,6 @@ class Backend(QObject):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.pool: ThreadPoolExecutor = ThreadPoolExecutor(max_workers=6)
|
self.pool: ThreadPoolExecutor = ThreadPoolExecutor(max_workers=6)
|
||||||
|
|
||||||
self._queried_displaynames: Dict[str, str] = {}
|
|
||||||
|
|
||||||
self.past_tokens: Dict[str, str] = {}
|
self.past_tokens: Dict[str, str] = {}
|
||||||
self.fully_loaded_rooms: Set[str] = set()
|
self.fully_loaded_rooms: Set[str] = set()
|
||||||
|
|
||||||
|
@ -28,9 +29,17 @@ class Backend(QObject):
|
||||||
from .client_manager import ClientManager
|
from .client_manager import ClientManager
|
||||||
self._client_manager: ClientManager = ClientManager(self)
|
self._client_manager: ClientManager = ClientManager(self)
|
||||||
|
|
||||||
self._accounts: ListModel = ListModel(parent=parent)
|
self._accounts: ListModel = ListModel(parent=parent)
|
||||||
self._room_events: ListModelMap = ListModelMap(Deque, parent)
|
|
||||||
self._devices: ListModelMap = ListModelMap(parent=parent)
|
self._room_events: ListModelMap = ListModelMap(
|
||||||
|
container = Deque,
|
||||||
|
parent = self
|
||||||
|
)
|
||||||
|
|
||||||
|
self._users: ListModel = ListModel(
|
||||||
|
default_factory = self._query_user,
|
||||||
|
parent = self
|
||||||
|
)
|
||||||
|
|
||||||
from .signal_manager import SignalManager
|
from .signal_manager import SignalManager
|
||||||
self._signal_manager: SignalManager = SignalManager(self)
|
self._signal_manager: SignalManager = SignalManager(self)
|
||||||
|
@ -55,38 +64,31 @@ class Backend(QObject):
|
||||||
return self._room_events
|
return self._room_events
|
||||||
|
|
||||||
@pyqtProperty("QVariant", constant=True)
|
@pyqtProperty("QVariant", constant=True)
|
||||||
def devices(self):
|
def users(self):
|
||||||
return self._devices
|
return self._users
|
||||||
|
|
||||||
@pyqtProperty("QVariant", constant=True)
|
@pyqtProperty("QVariant", constant=True)
|
||||||
def signals(self):
|
def signals(self):
|
||||||
return self._signal_manager
|
return self._signal_manager
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot(str, result="QVariant")
|
def _query_user(self, user_id: str) -> User:
|
||||||
@pyqtSlot(str, bool, result="QVariant")
|
client = random.choice(tuple(self.clients.values())) # nosec
|
||||||
@futurize(max_running=1, consider_args=True)
|
|
||||||
def getUserDisplayName(self, user_id: str, can_block: bool = True) -> str:
|
|
||||||
if user_id in self._queried_displaynames:
|
|
||||||
return self._queried_displaynames[user_id]
|
|
||||||
|
|
||||||
for client in self.clients.values():
|
@futurize(running_value=user_id)
|
||||||
for room in client.nio.rooms.values():
|
def get_displayname(self) -> str:
|
||||||
displayname = room.user_name(user_id)
|
print("querying", user_id)
|
||||||
|
try:
|
||||||
|
response = client.net.talk(client.nio.get_displayname, user_id)
|
||||||
|
return response.displayname or user_id
|
||||||
|
except NioErrorResponse:
|
||||||
|
return user_id
|
||||||
|
|
||||||
if displayname:
|
return User(
|
||||||
return displayname
|
userId = user_id,
|
||||||
|
displayName = get_displayname(self),
|
||||||
return self._query_user_displayname(user_id) if can_block else user_id
|
devices = ListModel(),
|
||||||
|
)
|
||||||
|
|
||||||
def _query_user_displayname(self, user_id: str) -> str:
|
|
||||||
client = next(iter(self.clients.values()))
|
|
||||||
response = client.net.talk(client.nio.get_displayname, user_id)
|
|
||||||
displayname = getattr(response, "displayname", "") or user_id
|
|
||||||
|
|
||||||
self._queried_displaynames[user_id] = displayname
|
|
||||||
return displayname
|
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot(str, result=float)
|
@pyqtSlot(str, result=float)
|
||||||
|
@ -146,7 +148,7 @@ class Backend(QObject):
|
||||||
cl = self.clients
|
cl = self.clients
|
||||||
ac = self.accounts
|
ac = self.accounts
|
||||||
re = self.roomEvents
|
re = self.roomEvents
|
||||||
de = self.devices
|
us = self.users
|
||||||
|
|
||||||
tcl = lambda user: cl[f"@test_{user}:matrix.org"]
|
tcl = lambda user: cl[f"@test_{user}:matrix.org"]
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Client(QObject):
|
||||||
roomSyncPrevBatchTokenReceived = pyqtSignal(str, str)
|
roomSyncPrevBatchTokenReceived = pyqtSignal(str, str)
|
||||||
roomPastPrevBatchTokenReceived = pyqtSignal(str, str)
|
roomPastPrevBatchTokenReceived = pyqtSignal(str, str)
|
||||||
roomEventReceived = pyqtSignal(str, str, dict)
|
roomEventReceived = pyqtSignal(str, str, dict)
|
||||||
roomTypingUsersUpdated = pyqtSignal(str, list)
|
roomTypingMembersUpdated = pyqtSignal(str, list)
|
||||||
|
|
||||||
messageAboutToBeSent = pyqtSignal(str, dict)
|
messageAboutToBeSent = pyqtSignal(str, dict)
|
||||||
|
|
||||||
|
@ -208,7 +208,7 @@ class Client(QObject):
|
||||||
|
|
||||||
for ev in room_info.ephemeral:
|
for ev in room_info.ephemeral:
|
||||||
if isinstance(ev, nio.TypingNoticeEvent):
|
if isinstance(ev, nio.TypingNoticeEvent):
|
||||||
self.roomTypingUsersUpdated.emit(room_id, ev.users)
|
self.roomTypingMembersUpdated.emit(room_id, ev.users)
|
||||||
else:
|
else:
|
||||||
print("ephemeral event: ", ev)
|
print("ephemeral event: ", ev)
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,10 @@
|
||||||
# This file is part of harmonyqt, licensed under GPLv3.
|
# This file is part of harmonyqt, licensed under GPLv3.
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import platform
|
import platform
|
||||||
import threading
|
import threading
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from typing import Dict, Iterable, Optional
|
from typing import Dict
|
||||||
|
|
||||||
from atomicfile import AtomicFile
|
from atomicfile import AtomicFile
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
|
|
|
@ -3,6 +3,7 @@ from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import QDateTime, QSortFilterProxyModel
|
from PyQt5.QtCore import QDateTime, QSortFilterProxyModel
|
||||||
|
|
||||||
|
from ..pyqt_future import PyQtFuture
|
||||||
from .list_item import ListItem
|
from .list_item import ListItem
|
||||||
from .list_model import ListModel
|
from .list_model import ListModel
|
||||||
|
|
||||||
|
@ -11,21 +12,15 @@ class Account(ListItem):
|
||||||
_required_init_values = {"userId", "roomCategories"}
|
_required_init_values = {"userId", "roomCategories"}
|
||||||
_constant = {"userId", "roomCategories"}
|
_constant = {"userId", "roomCategories"}
|
||||||
|
|
||||||
userId: str = ""
|
userId: str = ""
|
||||||
roomCategories: ListModel = ListModel()
|
roomCategories: ListModel = ListModel()
|
||||||
displayName: Optional[str] = None
|
|
||||||
avatarUrl: Optional[str] = None
|
|
||||||
statusMessage: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class RoomCategory(ListItem):
|
class RoomCategory(ListItem):
|
||||||
_required_init_values = {"name", "rooms", "sortedRooms"}
|
_required_init_values = {"name", "rooms", "sortedRooms"}
|
||||||
_constant = {"rooms", "sortedRooms"}
|
_constant = {"name", "rooms", "sortedRooms"}
|
||||||
|
|
||||||
name: str = ""
|
name: str = ""
|
||||||
|
|
||||||
# Must be provided at init, else it will be the same object
|
|
||||||
# for every RoomCategory
|
|
||||||
rooms: ListModel = ListModel()
|
rooms: ListModel = ListModel()
|
||||||
sortedRooms: QSortFilterProxyModel = QSortFilterProxyModel()
|
sortedRooms: QSortFilterProxyModel = QSortFilterProxyModel()
|
||||||
|
|
||||||
|
@ -37,13 +32,16 @@ class Room(ListItem):
|
||||||
roomId: str = ""
|
roomId: str = ""
|
||||||
displayName: str = ""
|
displayName: str = ""
|
||||||
topic: Optional[str] = None
|
topic: Optional[str] = None
|
||||||
typingUsers: List[str] = []
|
|
||||||
lastEventDateTime: Optional[QDateTime] = None
|
lastEventDateTime: Optional[QDateTime] = None
|
||||||
|
typingMembers: List[str] = []
|
||||||
|
members: List[str] = []
|
||||||
|
|
||||||
inviter: Optional[Dict[str, str]] = None
|
inviter: Optional[Dict[str, str]] = None
|
||||||
leftEvent: Optional[Dict[str, str]] = None
|
leftEvent: Optional[Dict[str, str]] = None
|
||||||
|
|
||||||
|
|
||||||
|
# ----------
|
||||||
|
|
||||||
class RoomEvent(ListItem):
|
class RoomEvent(ListItem):
|
||||||
_required_init_values = {"type", "dict"}
|
_required_init_values = {"type", "dict"}
|
||||||
_constant = {"type"}
|
_constant = {"type"}
|
||||||
|
@ -56,6 +54,20 @@ class RoomEvent(ListItem):
|
||||||
|
|
||||||
# ----------
|
# ----------
|
||||||
|
|
||||||
|
class User(ListItem):
|
||||||
|
_required_init_values = {"userId", "devices"}
|
||||||
|
_constant = {"userId", "devices"}
|
||||||
|
|
||||||
|
# Use PyQtFutures because the info might or might not need a request
|
||||||
|
# to be fetched, and we don't want to block the UI in any case.
|
||||||
|
# QML's property binding ability is used on the PyQtFuture.value
|
||||||
|
userId: str = ""
|
||||||
|
displayName: Optional[PyQtFuture] = None
|
||||||
|
avatarUrl: Optional[PyQtFuture] = None
|
||||||
|
statusMessage: Optional[PyQtFuture] = None
|
||||||
|
devices: ListModel = ListModel()
|
||||||
|
|
||||||
|
|
||||||
class Trust(Enum):
|
class Trust(Enum):
|
||||||
blacklisted = -1
|
blacklisted = -1
|
||||||
undecided = 0
|
undecided = 0
|
||||||
|
|
|
@ -154,27 +154,18 @@ class ListItem(QObject, metaclass=_ListItemMeta):
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
from .list_model import ListModel
|
|
||||||
multiline = any((
|
|
||||||
isinstance(v, ListModel) for _, v in self._props.values()
|
|
||||||
))
|
|
||||||
|
|
||||||
prop_strings = (
|
prop_strings = (
|
||||||
"\033[{0}m{1}{2}={2}{3}\033[0m".format(
|
"\033[{0};34m{1}\033[0,{0}m = \033[{0};32m{2}\033[0m".format(
|
||||||
1 if p == self.mainKey else 0, # 1 = term bold
|
1 if p == self.mainKey else 0, # 1 = term bold
|
||||||
p,
|
p,
|
||||||
" " if multiline else "",
|
repr(getattr(self, p))
|
||||||
getattr(self, p)
|
|
||||||
) for p in list(self._props.keys()) + self._direct_props
|
) for p in list(self._props.keys()) + self._direct_props
|
||||||
)
|
)
|
||||||
|
|
||||||
if any((isinstance(v, ListModel) for _, v in self._props.values())):
|
return "\033[35m%s\033[0m(\n%s\n)" % (
|
||||||
return "%s(\n%s\n)" % (
|
type(self).__name__,
|
||||||
type(self).__name__,
|
textwrap.indent(",\n".join(prop_strings), prefix=" " * 4)
|
||||||
textwrap.indent(",\n".join(prop_strings), prefix=" " * 4)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
return "%s(%s)" % (type(self).__name__, ", ".join(prop_strings))
|
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot(result=str)
|
@pyqtSlot(result=str)
|
||||||
|
|
|
@ -30,21 +30,24 @@ class ListModel(QAbstractListModel):
|
||||||
countChanged = pyqtSignal(int)
|
countChanged = pyqtSignal(int)
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
initial_data: Optional[List[NewItem]] = None,
|
initial_data: Optional[List[NewItem]] = None,
|
||||||
container: Callable[..., MutableSequence] = list,
|
container: Callable[..., MutableSequence] = list,
|
||||||
parent: QObject = None) -> None:
|
default_factory: Optional[Callable[[str], ListItem]] = None,
|
||||||
|
parent: QObject = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._data: MutableSequence[ListItem] = container()
|
self._data: MutableSequence[ListItem] = container()
|
||||||
|
|
||||||
|
self.default_factory = default_factory
|
||||||
|
|
||||||
if initial_data:
|
if initial_data:
|
||||||
self.extend(initial_data)
|
self.extend(initial_data)
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
if not self._data:
|
if not self._data:
|
||||||
return "%s()" % type(self).__name__
|
return "\033[35m%s\033[0m()" % type(self).__name__
|
||||||
|
|
||||||
return "%s(\n%s\n)" % (
|
return "\033[35m%s\033[0m(\n%s\n)" % (
|
||||||
type(self).__name__,
|
type(self).__name__,
|
||||||
textwrap.indent(
|
textwrap.indent(
|
||||||
",\n".join((repr(item) for item in self._data)),
|
",\n".join((repr(item) for item in self._data)),
|
||||||
|
@ -56,7 +59,7 @@ class ListModel(QAbstractListModel):
|
||||||
def __contains__(self, index: Index) -> bool:
|
def __contains__(self, index: Index) -> bool:
|
||||||
if isinstance(index, str):
|
if isinstance(index, str):
|
||||||
try:
|
try:
|
||||||
self.indexWhere(self.mainKey, index)
|
self.indexWhere(index)
|
||||||
return True
|
return True
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
@ -84,6 +87,10 @@ class ListModel(QAbstractListModel):
|
||||||
return iter(self._data)
|
return iter(self._data)
|
||||||
|
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
return bool(self._data)
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot(result=str)
|
@pyqtSlot(result=str)
|
||||||
def repr(self) -> str:
|
def repr(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
|
@ -157,14 +164,22 @@ class ListModel(QAbstractListModel):
|
||||||
return len(self)
|
return len(self)
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot(str, "QVariant", result=int)
|
@pyqtSlot("QVariant", result=int)
|
||||||
def indexWhere(self, prop: str, is_value: Any) -> int:
|
def indexWhere(self,
|
||||||
|
main_key_is_value: Any,
|
||||||
|
_can_use_default_factory: bool = True) -> int:
|
||||||
|
|
||||||
for i, item in enumerate(self._data):
|
for i, item in enumerate(self._data):
|
||||||
if getattr(item, prop) == is_value:
|
if getattr(item, self.mainKey) == main_key_is_value:
|
||||||
return i
|
return i
|
||||||
|
|
||||||
raise ValueError(f"No item in model data with "
|
if _can_use_default_factory and self.default_factory:
|
||||||
f"property {prop!r} set to {is_value!r}.")
|
return self.append(self.default_factory(main_key_is_value))
|
||||||
|
|
||||||
|
raise ValueError(
|
||||||
|
f"No item in model data with "
|
||||||
|
f"property {self.mainKey} is set to {main_key_is_value!r}."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot(int, result="QVariant")
|
@pyqtSlot(int, result="QVariant")
|
||||||
|
@ -173,19 +188,25 @@ class ListModel(QAbstractListModel):
|
||||||
@pyqtSlot(str, "QVariant", result="QVariant")
|
@pyqtSlot(str, "QVariant", result="QVariant")
|
||||||
def get(self, index: Index, default: Any = _GetFail()) -> ListItem:
|
def get(self, index: Index, default: Any = _GetFail()) -> ListItem:
|
||||||
try:
|
try:
|
||||||
i_index: int = self.indexWhere(self.mainKey, index) \
|
i_index: int = \
|
||||||
if isinstance(index, str) else index
|
self.indexWhere(index, _can_use_default_factory=False) \
|
||||||
|
if isinstance(index, str) else index
|
||||||
|
|
||||||
return self._data[i_index]
|
return self._data[i_index]
|
||||||
|
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
if isinstance(default, _GetFail):
|
if isinstance(default, _GetFail):
|
||||||
|
if self.default_factory and isinstance(index, str):
|
||||||
|
item = self.default_factory(index)
|
||||||
|
self.append(item)
|
||||||
|
return item
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot(int, "QVariantMap")
|
@pyqtSlot(int, "QVariantMap", result=int)
|
||||||
def insert(self, index: int, value: NewItem) -> None:
|
def insert(self, index: int, value: NewItem) -> int:
|
||||||
value = self._convert_new_value(value)
|
value = self._convert_new_value(value)
|
||||||
|
|
||||||
self.beginInsertRows(QModelIndex(), index, index)
|
self.beginInsertRows(QModelIndex(), index, index)
|
||||||
|
@ -199,11 +220,12 @@ class ListModel(QAbstractListModel):
|
||||||
|
|
||||||
self.countChanged.emit(len(self))
|
self.countChanged.emit(len(self))
|
||||||
self.changed.emit()
|
self.changed.emit()
|
||||||
|
return index
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot("QVariantMap")
|
@pyqtSlot("QVariantMap", result=int)
|
||||||
def append(self, value: NewItem) -> None:
|
def append(self, value: NewItem) -> int:
|
||||||
self.insert(len(self), value)
|
return self.insert(len(self), value)
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot(list)
|
@pyqtSlot(list)
|
||||||
|
@ -222,7 +244,7 @@ class ListModel(QAbstractListModel):
|
||||||
ignore_roles: Sequence[str] = ()) -> int:
|
ignore_roles: Sequence[str] = ()) -> int:
|
||||||
value = self._convert_new_value(value)
|
value = self._convert_new_value(value)
|
||||||
|
|
||||||
i_index: int = self.indexWhere(self.mainKey, index) \
|
i_index: int = self.indexWhere(index, _can_use_default_factory=False) \
|
||||||
if isinstance(index, str) else index
|
if isinstance(index, str) else index
|
||||||
|
|
||||||
to_update = self[i_index]
|
to_update = self[i_index]
|
||||||
|
@ -272,7 +294,7 @@ class ListModel(QAbstractListModel):
|
||||||
@pyqtSlot(int, list)
|
@pyqtSlot(int, list)
|
||||||
@pyqtSlot(str, list)
|
@pyqtSlot(str, list)
|
||||||
def set(self, index: Index, value: NewItem) -> None:
|
def set(self, index: Index, value: NewItem) -> None:
|
||||||
i_index: int = self.indexWhere(self.mainKey, index) \
|
i_index: int = self.indexWhere(index) \
|
||||||
if isinstance(index, str) else index
|
if isinstance(index, str) else index
|
||||||
|
|
||||||
qidx = QAbstractListModel.index(self, i_index, 0)
|
qidx = QAbstractListModel.index(self, i_index, 0)
|
||||||
|
@ -285,7 +307,7 @@ class ListModel(QAbstractListModel):
|
||||||
@pyqtSlot(int, str, "QVariant")
|
@pyqtSlot(int, str, "QVariant")
|
||||||
@pyqtSlot(str, str, "QVariant")
|
@pyqtSlot(str, str, "QVariant")
|
||||||
def setProperty(self, index: Index, prop: str, value: Any) -> None:
|
def setProperty(self, index: Index, prop: str, value: Any) -> None:
|
||||||
i_index: int = self.indexWhere(self.mainKey, index) \
|
i_index: int = self.indexWhere(index) \
|
||||||
if isinstance(index, str) else index
|
if isinstance(index, str) else index
|
||||||
|
|
||||||
if getattr(self[i_index], prop) != value:
|
if getattr(self[i_index], prop) != value:
|
||||||
|
@ -301,7 +323,7 @@ class ListModel(QAbstractListModel):
|
||||||
@pyqtSlot(str, int, int)
|
@pyqtSlot(str, int, int)
|
||||||
def move(self, from_: Index, to: int, n: int = 1) -> None:
|
def move(self, from_: Index, to: int, n: int = 1) -> None:
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
i_from: int = self.indexWhere(self.mainKey, from_) \
|
i_from: int = self.indexWhere(from_) \
|
||||||
if isinstance(from_, str) else from_
|
if isinstance(from_, str) else from_
|
||||||
|
|
||||||
qlast = i_from + n - 1
|
qlast = i_from + n - 1
|
||||||
|
@ -332,7 +354,7 @@ class ListModel(QAbstractListModel):
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def remove(self, index: Index) -> None:
|
def remove(self, index: Index) -> None:
|
||||||
i_index: int = self.indexWhere(self.mainKey, index) \
|
i_index: int = self.indexWhere(index) \
|
||||||
if isinstance(index, str) else index
|
if isinstance(index, str) else index
|
||||||
|
|
||||||
self.beginRemoveRows(QModelIndex(), i_index, i_index)
|
self.beginRemoveRows(QModelIndex(), i_index, i_index)
|
||||||
|
@ -347,7 +369,7 @@ class ListModel(QAbstractListModel):
|
||||||
@pyqtSlot(str, result="QVariant")
|
@pyqtSlot(str, result="QVariant")
|
||||||
def pop(self, index: Index, default: Any = _PopFail()) -> ListItem:
|
def pop(self, index: Index, default: Any = _PopFail()) -> ListItem:
|
||||||
try:
|
try:
|
||||||
i_index: int = self.indexWhere(self.mainKey, index) \
|
i_index: int = self.indexWhere(index) \
|
||||||
if isinstance(index, str) else index
|
if isinstance(index, str) else index
|
||||||
item = self[i_index]
|
item = self[i_index]
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any, Callable, DefaultDict, MutableSequence
|
from typing import Any, DefaultDict
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSlot
|
from PyQt5.QtCore import QObject, pyqtSlot
|
||||||
|
|
||||||
|
@ -6,15 +6,15 @@ from .list_model import ListModel
|
||||||
|
|
||||||
|
|
||||||
class ListModelMap(QObject):
|
class ListModelMap(QObject):
|
||||||
def __init__(self,
|
def __init__(self, *models_args, parent: QObject = None, **models_kwargs
|
||||||
models_container: Callable[..., MutableSequence] = list,
|
) -> None:
|
||||||
parent: QObject = None) -> None:
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
models_kwargs["parent"] = self
|
||||||
|
|
||||||
# Set the parent to prevent item garbage-collection on the C++ side
|
# Set the parent to prevent item garbage-collection on the C++ side
|
||||||
self.dict: DefaultDict[Any, ListModel] = \
|
self.dict: DefaultDict[Any, ListModel] = \
|
||||||
DefaultDict(
|
DefaultDict(
|
||||||
lambda: ListModel(container=models_container, parent=self)
|
lambda: ListModel(*models_args, **models_kwargs)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import sys
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from concurrent.futures import Executor, Future
|
from concurrent.futures import Executor, Future
|
||||||
from typing import Callable, Deque, Optional, Tuple, Union
|
from typing import Any, Callable, Deque, Optional, Tuple, Union
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
|
@ -15,10 +15,14 @@ from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||||
class PyQtFuture(QObject):
|
class PyQtFuture(QObject):
|
||||||
gotResult = pyqtSignal("QVariant")
|
gotResult = pyqtSignal("QVariant")
|
||||||
|
|
||||||
def __init__(self, future: Future, parent: QObject) -> None:
|
def __init__(self,
|
||||||
|
future: Future,
|
||||||
|
running_value: Any = None,
|
||||||
|
parent: Optional[QObject] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.future = future
|
self.future = future
|
||||||
self._result = None
|
self.running_value = running_value
|
||||||
|
self._result = None
|
||||||
|
|
||||||
self.future.add_done_callback(
|
self.future.add_done_callback(
|
||||||
lambda future: self.gotResult.emit(future.result())
|
lambda future: self.gotResult.emit(future.result())
|
||||||
|
@ -64,7 +68,7 @@ class PyQtFuture(QObject):
|
||||||
|
|
||||||
@pyqtProperty("QVariant", notify=gotResult)
|
@pyqtProperty("QVariant", notify=gotResult)
|
||||||
def value(self):
|
def value(self):
|
||||||
return self.future.result() if self.done else None
|
return self.future.result() if self.done else self.running_value
|
||||||
|
|
||||||
|
|
||||||
def add_done_callback(self, fn: Callable[[Future], None]) -> None:
|
def add_done_callback(self, fn: Callable[[Future], None]) -> None:
|
||||||
|
@ -79,7 +83,8 @@ _PENDING: Deque[_Task] = Deque()
|
||||||
def futurize(max_running: Optional[int] = None,
|
def futurize(max_running: Optional[int] = None,
|
||||||
consider_args: bool = False,
|
consider_args: bool = False,
|
||||||
discard_if_max_running: bool = False,
|
discard_if_max_running: bool = False,
|
||||||
pyqt: bool = True) -> Callable:
|
pyqt: bool = True,
|
||||||
|
running_value: Any = None) -> Callable:
|
||||||
|
|
||||||
def decorator(func: Callable) -> Callable:
|
def decorator(func: Callable) -> Callable:
|
||||||
|
|
||||||
|
@ -145,7 +150,9 @@ def futurize(max_running: Optional[int] = None,
|
||||||
del _RUNNING[_RUNNING.index(task)]
|
del _RUNNING[_RUNNING.index(task)]
|
||||||
|
|
||||||
future = self.pool.submit(run_and_catch_errs)
|
future = self.pool.submit(run_and_catch_errs)
|
||||||
return PyQtFuture(future, self) if pyqt else future
|
return PyQtFuture(
|
||||||
|
future=future, running_value=running_value, parent=self
|
||||||
|
) if pyqt else future
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Copyright 2019 miruka
|
# Copyright 2019 miruka
|
||||||
# This file is part of harmonyqml, licensed under GPLv3.
|
# This file is part of harmonyqml, licensed under GPLv3.
|
||||||
|
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Any, Deque, Dict, List, Optional
|
from typing import Any, Deque, Dict, List, Optional
|
||||||
|
|
||||||
|
@ -12,9 +13,10 @@ from nio.rooms import MatrixRoom
|
||||||
from .backend import Backend
|
from .backend import Backend
|
||||||
from .client import Client
|
from .client import Client
|
||||||
from .model.items import (
|
from .model.items import (
|
||||||
Account, Device, ListModel, Room, RoomCategory, RoomEvent
|
Account, Device, ListModel, Room, RoomCategory, RoomEvent, User
|
||||||
)
|
)
|
||||||
from .model.sort_filter_proxy import SortFilterProxy
|
from .model.sort_filter_proxy import SortFilterProxy
|
||||||
|
from .pyqt_future import futurize
|
||||||
|
|
||||||
Inviter = Optional[Dict[str, str]]
|
Inviter = Optional[Dict[str, str]]
|
||||||
LeftEvent = Optional[Dict[str, str]]
|
LeftEvent = Optional[Dict[str, str]]
|
||||||
|
@ -27,6 +29,8 @@ class SignalManager(QObject):
|
||||||
|
|
||||||
def __init__(self, backend: Backend) -> None:
|
def __init__(self, backend: Backend) -> None:
|
||||||
super().__init__(parent=backend)
|
super().__init__(parent=backend)
|
||||||
|
self.pool: ThreadPoolExecutor = ThreadPoolExecutor(max_workers=6)
|
||||||
|
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
|
|
||||||
self.last_room_events: Deque[str] = Deque(maxlen=1000)
|
self.last_room_events: Deque[str] = Deque(maxlen=1000)
|
||||||
|
@ -37,7 +41,23 @@ class SignalManager(QObject):
|
||||||
|
|
||||||
|
|
||||||
def onClientAdded(self, client: Client) -> None:
|
def onClientAdded(self, client: Client) -> None:
|
||||||
# Build an Account item for the Backend.accounts model
|
if client.userId in self.backend.accounts:
|
||||||
|
return
|
||||||
|
|
||||||
|
# An user might already exist in the model, e.g. if another account
|
||||||
|
# was in a room with the account that we just connected to
|
||||||
|
self.backend.users.upsert(
|
||||||
|
where_main_key_is = client.userId,
|
||||||
|
update_with = User(
|
||||||
|
userId = client.userId,
|
||||||
|
displayName = self.backend.users[client.userId].displayName,
|
||||||
|
# Devices are added later, we might need to upload keys before
|
||||||
|
# but we want to show the accounts ASAP in the client side pane
|
||||||
|
devices = ListModel(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Backend.accounts
|
||||||
room_categories_kwargs: List[Dict[str, Any]] = [
|
room_categories_kwargs: List[Dict[str, Any]] = [
|
||||||
{"name": "Invites", "rooms": ListModel()},
|
{"name": "Invites", "rooms": ListModel()},
|
||||||
{"name": "Rooms", "rooms": ListModel()},
|
{"name": "Rooms", "rooms": ListModel()},
|
||||||
|
@ -57,25 +77,31 @@ class SignalManager(QObject):
|
||||||
roomCategories = ListModel([
|
roomCategories = ListModel([
|
||||||
RoomCategory(**kws) for kws in room_categories_kwargs
|
RoomCategory(**kws) for kws in room_categories_kwargs
|
||||||
]),
|
]),
|
||||||
displayName = self.backend.getUserDisplayName(client.userId),
|
|
||||||
))
|
))
|
||||||
|
|
||||||
# Upload our E2E keys to the matrix server if needed
|
# Upload our E2E keys to the matrix server if needed
|
||||||
if not client.nio.olm_account_shared:
|
if not client.nio.olm_account_shared:
|
||||||
client.uploadE2EKeys()
|
client.uploadE2EKeys()
|
||||||
|
|
||||||
# Add our devices to the Backend.devices model
|
# Add all devices nio knows for this account
|
||||||
store = client.nio.device_store
|
store = client.nio.device_store
|
||||||
|
|
||||||
for user_id in store.users:
|
for user_id in store.users:
|
||||||
self.backend.devices[user_id].clear()
|
user = self.backend.users.get(user_id, None)
|
||||||
self.backend.devices[user_id].extend([
|
if not user:
|
||||||
Device(
|
self.backend.users.append(
|
||||||
deviceId = dev.id,
|
User(userId=user_id, devices=ListModel())
|
||||||
ed25519Key = dev.ed25519,
|
)
|
||||||
trust = client.getDeviceTrust(dev),
|
|
||||||
) for dev in store.active_user_devices(user_id)
|
for device in store.active_user_devices(user_id):
|
||||||
])
|
self.backend.users[client.userId].devices.upsert(
|
||||||
|
where_main_key_is = device.id,
|
||||||
|
update_with = Device(
|
||||||
|
deviceId = device.id,
|
||||||
|
ed25519Key = device.ed25519,
|
||||||
|
trust = client.getDeviceTrust(device),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Finally, connect all client signals
|
# Finally, connect all client signals
|
||||||
self.connectClient(client)
|
self.connectClient(client)
|
||||||
|
@ -107,12 +133,32 @@ class SignalManager(QObject):
|
||||||
return None if name == "Empty room?" else name
|
return None if name == "Empty room?" else name
|
||||||
|
|
||||||
|
|
||||||
|
def _add_users_from_nio_room(self, room: nio.rooms.MatrixRoom) -> None:
|
||||||
|
for user in room.users.values():
|
||||||
|
@futurize(running_value=user.display_name)
|
||||||
|
def get_displayname(self, user) -> str:
|
||||||
|
# pylint:disable=unused-argument
|
||||||
|
return user.display_name
|
||||||
|
|
||||||
|
self.backend.users.upsert(
|
||||||
|
where_main_key_is = user.user_id,
|
||||||
|
update_with = User(
|
||||||
|
userId = user.user_id,
|
||||||
|
displayName = get_displayname(self, user),
|
||||||
|
devices = ListModel()
|
||||||
|
),
|
||||||
|
ignore_roles = ("devices",),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def onRoomInvited(self,
|
def onRoomInvited(self,
|
||||||
client: Client,
|
client: Client,
|
||||||
room_id: str,
|
room_id: str,
|
||||||
inviter: Inviter = None) -> None:
|
inviter: Inviter = None) -> None:
|
||||||
|
|
||||||
nio_room = client.nio.invited_rooms[room_id]
|
nio_room = client.nio.invited_rooms[room_id]
|
||||||
|
self._add_users_from_nio_room(nio_room)
|
||||||
|
|
||||||
categories = self.backend.accounts[client.userId].roomCategories
|
categories = self.backend.accounts[client.userId].roomCategories
|
||||||
|
|
||||||
previous_room = categories["Rooms"].rooms.pop(room_id, None)
|
previous_room = categories["Rooms"].rooms.pop(room_id, None)
|
||||||
|
@ -126,9 +172,9 @@ class SignalManager(QObject):
|
||||||
topic = nio_room.topic,
|
topic = nio_room.topic,
|
||||||
inviter = inviter,
|
inviter = inviter,
|
||||||
lastEventDateTime = QDateTime.currentDateTime(), # FIXME
|
lastEventDateTime = QDateTime.currentDateTime(), # FIXME
|
||||||
|
members = list(nio_room.users.keys()),
|
||||||
),
|
),
|
||||||
new_index_if_insert = 0,
|
ignore_roles = ("typingMembers"),
|
||||||
ignore_roles = ("typingUsers"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
signal = self.roomCategoryChanged
|
signal = self.roomCategoryChanged
|
||||||
|
@ -139,7 +185,9 @@ class SignalManager(QObject):
|
||||||
|
|
||||||
|
|
||||||
def onRoomJoined(self, client: Client, room_id: str) -> None:
|
def onRoomJoined(self, client: Client, room_id: str) -> None:
|
||||||
nio_room = client.nio.rooms[room_id]
|
nio_room = client.nio.rooms[room_id]
|
||||||
|
self._add_users_from_nio_room(nio_room)
|
||||||
|
|
||||||
categories = self.backend.accounts[client.userId].roomCategories
|
categories = self.backend.accounts[client.userId].roomCategories
|
||||||
|
|
||||||
previous_invite = categories["Invites"].rooms.pop(room_id, None)
|
previous_invite = categories["Invites"].rooms.pop(room_id, None)
|
||||||
|
@ -151,9 +199,9 @@ class SignalManager(QObject):
|
||||||
roomId = room_id,
|
roomId = room_id,
|
||||||
displayName = self._get_room_displayname(nio_room),
|
displayName = self._get_room_displayname(nio_room),
|
||||||
topic = nio_room.topic,
|
topic = nio_room.topic,
|
||||||
|
members = list(nio_room.users.keys()),
|
||||||
),
|
),
|
||||||
new_index_if_insert = 0,
|
ignore_roles = ("typingMembers", "lastEventDateTime"),
|
||||||
ignore_roles = ("typingUsers", "lastEventDateTime"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
signal = self.roomCategoryChanged
|
signal = self.roomCategoryChanged
|
||||||
|
@ -188,8 +236,7 @@ class SignalManager(QObject):
|
||||||
if left_time else QDateTime.currentDateTime()
|
if left_time else QDateTime.currentDateTime()
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
new_index_if_insert = 0,
|
ignore_roles = ("members", "lastEventDateTime"),
|
||||||
ignore_roles = ("typingUsers", "lastEventDateTime"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
signal = self.roomCategoryChanged
|
signal = self.roomCategoryChanged
|
||||||
|
@ -304,14 +351,14 @@ class SignalManager(QObject):
|
||||||
self._set_room_last_event(client.userId, room_id, new_event)
|
self._set_room_last_event(client.userId, room_id, new_event)
|
||||||
|
|
||||||
|
|
||||||
def onRoomTypingUsersUpdated(self,
|
def onRoomTypingMembersUpdated(self,
|
||||||
client: Client,
|
client: Client,
|
||||||
room_id: str,
|
room_id: str,
|
||||||
users: List[str]) -> None:
|
users: List[str]) -> None:
|
||||||
categories = self.backend.accounts[client.userId].roomCategories
|
categories = self.backend.accounts[client.userId].roomCategories
|
||||||
for categ in categories:
|
for categ in categories:
|
||||||
try:
|
try:
|
||||||
categ.rooms.setProperty(room_id, "typingUsers", users)
|
categ.rooms.setProperty(room_id, "typingMembers", users)
|
||||||
break
|
break
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
@ -355,9 +402,16 @@ class SignalManager(QObject):
|
||||||
user_id: str,
|
user_id: str,
|
||||||
device_id: str,
|
device_id: str,
|
||||||
ed25519_key: str) -> None:
|
ed25519_key: str) -> None:
|
||||||
|
|
||||||
nio_device = client.nio.device_store[user_id][device_id]
|
nio_device = client.nio.device_store[user_id][device_id]
|
||||||
|
|
||||||
self.backend.devices[user_id].upsert(
|
user = self.backend.users.get(user_id, None)
|
||||||
|
if not user:
|
||||||
|
self.backend.users.append(
|
||||||
|
User(userId=user_id, devices=ListModel())
|
||||||
|
)
|
||||||
|
|
||||||
|
self.backend.users[user_id].devices.upsert(
|
||||||
where_main_key_is = device_id,
|
where_main_key_is = device_id,
|
||||||
update_with = Device(
|
update_with = Device(
|
||||||
deviceId = device_id,
|
deviceId = device_id,
|
||||||
|
@ -369,4 +423,7 @@ class SignalManager(QObject):
|
||||||
|
|
||||||
def onDeviceIsDeleted(self, _: Client, user_id: str, device_id: str
|
def onDeviceIsDeleted(self, _: Client, user_id: str, device_id: str
|
||||||
) -> None:
|
) -> None:
|
||||||
self.backend.devices[user_id].pop(device_id, None)
|
try:
|
||||||
|
del self.backend.users[user_id].devices[device_id]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
|
@ -2,16 +2,10 @@ import QtQuick 2.7
|
||||||
import "../Base"
|
import "../Base"
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
property bool hidden: false
|
property var name: null
|
||||||
property var name: null // null, string or PyQtFuture
|
property var imageUrl: null
|
||||||
property var imageSource: null
|
|
||||||
property int dimension: 36
|
property int dimension: 36
|
||||||
|
property bool hidden: false
|
||||||
|
|
||||||
readonly property string resolvedName:
|
|
||||||
! name ? "?" :
|
|
||||||
typeof(name) == "string" ? name :
|
|
||||||
(name.value ? name.value : "?")
|
|
||||||
|
|
||||||
width: dimension
|
width: dimension
|
||||||
height: hidden ? 1 : dimension
|
height: hidden ? 1 : dimension
|
||||||
|
@ -20,21 +14,21 @@ Rectangle {
|
||||||
|
|
||||||
opacity: hidden ? 0 : 1
|
opacity: hidden ? 0 : 1
|
||||||
|
|
||||||
color: resolvedName === "?" ?
|
color: name ?
|
||||||
HStyle.avatar.background.unknown :
|
|
||||||
Qt.hsla(
|
Qt.hsla(
|
||||||
Backend.hueFromString(resolvedName),
|
Backend.hueFromString(name),
|
||||||
HStyle.avatar.background.saturation,
|
HStyle.avatar.background.saturation,
|
||||||
HStyle.avatar.background.lightness,
|
HStyle.avatar.background.lightness,
|
||||||
HStyle.avatar.background.alpha
|
HStyle.avatar.background.alpha
|
||||||
)
|
) :
|
||||||
|
HStyle.avatar.background.unknown
|
||||||
|
|
||||||
HLabel {
|
HLabel {
|
||||||
z: 1
|
z: 1
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: ! hidden
|
visible: ! hidden
|
||||||
|
|
||||||
text: resolvedName.charAt(0)
|
text: name ? name.charAt(0) : "?"
|
||||||
color: HStyle.avatar.letter
|
color: HStyle.avatar.letter
|
||||||
font.pixelSize: parent.height / 1.4
|
font.pixelSize: parent.height / 1.4
|
||||||
}
|
}
|
||||||
|
@ -42,9 +36,9 @@ Rectangle {
|
||||||
HImage {
|
HImage {
|
||||||
z: 2
|
z: 2
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: ! hidden && imageSource !== null
|
visible: ! hidden && imageUrl
|
||||||
|
|
||||||
Component.onCompleted: if (imageSource) {source = imageSource}
|
Component.onCompleted: if (imageUrl) { source = imageUrl }
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
sourceSize.width: dimension
|
sourceSize.width: dimension
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ QtObject {
|
||||||
property color background: colors.background1
|
property color background: colors.background1
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property QtObject typingUsers: QtObject {
|
readonly property QtObject typingMembers: QtObject {
|
||||||
property color background: colors.background0
|
property color background: colors.background0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ Banner {
|
||||||
color: HStyle.chat.inviteBanner.background
|
color: HStyle.chat.inviteBanner.background
|
||||||
|
|
||||||
avatar.name: inviter ? inviter.displayname : ""
|
avatar.name: inviter ? inviter.displayname : ""
|
||||||
//avatar.imageSource: inviter ? inviter.avatar_url : ""
|
//avatar.imageUrl: inviter ? inviter.avatar_url : ""
|
||||||
|
|
||||||
labelText:
|
labelText:
|
||||||
(inviter ?
|
(inviter ?
|
||||||
|
|
|
@ -14,8 +14,11 @@ HColumnLayout {
|
||||||
.roomCategories.get(category)
|
.roomCategories.get(category)
|
||||||
.rooms.get(roomId)
|
.rooms.get(roomId)
|
||||||
|
|
||||||
|
readonly property var sender: Backend.users.get(userId)
|
||||||
|
|
||||||
readonly property bool hasUnknownDevices:
|
readonly property bool hasUnknownDevices:
|
||||||
Backend.clients.get(userId).roomHasUnknownDevices(roomId)
|
category == "Rooms" ?
|
||||||
|
Backend.clients.get(userId).roomHasUnknownDevices(roomId) : false
|
||||||
|
|
||||||
id: chatPage
|
id: chatPage
|
||||||
onFocusChanged: sendBox.setFocus()
|
onFocusChanged: sendBox.setFocus()
|
||||||
|
@ -38,7 +41,7 @@ HColumnLayout {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
}
|
}
|
||||||
|
|
||||||
TypingUsersBar {}
|
TypingMembersBar {}
|
||||||
|
|
||||||
InviteBanner {
|
InviteBanner {
|
||||||
visible: category === "Invites"
|
visible: category === "Invites"
|
||||||
|
@ -46,12 +49,12 @@ HColumnLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
UnknownDevicesBanner {
|
UnknownDevicesBanner {
|
||||||
visible: category === "Rooms" && hasUnknownDevices
|
visible: category == "Rooms" && hasUnknownDevices
|
||||||
}
|
}
|
||||||
|
|
||||||
SendBox {
|
SendBox {
|
||||||
id: sendBox
|
id: sendBox
|
||||||
visible: category === "Rooms" && ! hasUnknownDevices
|
visible: category == "Rooms" && ! hasUnknownDevices
|
||||||
}
|
}
|
||||||
|
|
||||||
LeftBanner {
|
LeftBanner {
|
||||||
|
|
|
@ -16,7 +16,7 @@ Row {
|
||||||
|
|
||||||
HAvatar {
|
HAvatar {
|
||||||
id: avatar
|
id: avatar
|
||||||
name: displayName
|
name: sender.displayName.value
|
||||||
hidden: combine
|
hidden: combine
|
||||||
dimension: 28
|
dimension: 28
|
||||||
}
|
}
|
||||||
|
@ -26,12 +26,12 @@ Row {
|
||||||
|
|
||||||
id: contentLabel
|
id: contentLabel
|
||||||
text: "<font color='" +
|
text: "<font color='" +
|
||||||
Qt.hsla(Backend.hueFromString(displayName.value || dict.sender),
|
Qt.hsla(Backend.hueFromString(sender.displayName.value),
|
||||||
HStyle.chat.event.saturation,
|
HStyle.chat.event.saturation,
|
||||||
HStyle.chat.event.lightness,
|
HStyle.chat.event.lightness,
|
||||||
1) +
|
1) +
|
||||||
"'>" +
|
"'>" +
|
||||||
(displayName.value || dict.sender) + " " +
|
sender.displayName.value + " " +
|
||||||
ChatJS.getEventText(type, dict) +
|
ChatJS.getEventText(type, dict) +
|
||||||
|
|
||||||
" " +
|
" " +
|
||||||
|
|
|
@ -10,7 +10,7 @@ Row {
|
||||||
HAvatar {
|
HAvatar {
|
||||||
id: avatar
|
id: avatar
|
||||||
hidden: combine
|
hidden: combine
|
||||||
name: displayName
|
name: sender.displayName.value
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -37,7 +37,7 @@ Row {
|
||||||
visible: height > 0
|
visible: height > 0
|
||||||
|
|
||||||
id: nameLabel
|
id: nameLabel
|
||||||
text: displayName.value || dict.sender
|
text: sender.displayName.value
|
||||||
color: Qt.hsla(Backend.hueFromString(text),
|
color: Qt.hsla(Backend.hueFromString(text),
|
||||||
HStyle.displayName.saturation,
|
HStyle.displayName.saturation,
|
||||||
HStyle.displayName.lightness,
|
HStyle.displayName.lightness,
|
||||||
|
|
|
@ -26,8 +26,7 @@ Column {
|
||||||
readonly property bool isUndecryptableEvent:
|
readonly property bool isUndecryptableEvent:
|
||||||
type === "OlmEvent" || type === "MegolmEvent"
|
type === "OlmEvent" || type === "MegolmEvent"
|
||||||
|
|
||||||
readonly property var displayName:
|
readonly property var sender: Backend.users.get(dict.sender)
|
||||||
Backend.getUserDisplayName(dict.sender)
|
|
||||||
|
|
||||||
readonly property bool isOwn:
|
readonly property bool isOwn:
|
||||||
chatPage.userId === dict.sender
|
chatPage.userId === dict.sender
|
||||||
|
|
|
@ -18,7 +18,7 @@ HGlassRectangle {
|
||||||
|
|
||||||
HAvatar {
|
HAvatar {
|
||||||
id: avatar
|
id: avatar
|
||||||
name: Backend.getUserDisplayName(chatPage.userId)
|
name: chatPage.sender.displayName.value
|
||||||
dimension: root.Layout.minimumHeight
|
dimension: root.Layout.minimumHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ import "../Base"
|
||||||
import "utils.js" as ChatJS
|
import "utils.js" as ChatJS
|
||||||
|
|
||||||
HGlassRectangle {
|
HGlassRectangle {
|
||||||
property var typingUsers: chatPage.roomInfo.typingUsers
|
property var typingMembers: chatPage.roomInfo.typingMembers
|
||||||
|
|
||||||
color: HStyle.chat.typingUsers.background
|
color: HStyle.chat.typingMembers.background
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.minimumHeight: usersLabel.text ? usersLabel.implicitHeight : 0
|
Layout.minimumHeight: usersLabel.text ? usersLabel.implicitHeight : 0
|
||||||
|
@ -16,7 +16,7 @@ HGlassRectangle {
|
||||||
id: usersLabel
|
id: usersLabel
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
text: ChatJS.getTypingUsersText(typingUsers, chatPage.userId)
|
text: ChatJS.getTypingMembersText(typingMembers, chatPage.userId)
|
||||||
elide: Text.ElideMiddle
|
elide: Text.ElideMiddle
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
}
|
}
|
|
@ -84,9 +84,7 @@ function getHistoryVisibilityEventText(dict) {
|
||||||
function getStateDisplayName(dict) {
|
function getStateDisplayName(dict) {
|
||||||
// The dict.content.displayname may be outdated, prefer
|
// The dict.content.displayname may be outdated, prefer
|
||||||
// retrieving it fresh
|
// retrieving it fresh
|
||||||
var name = Backend.getUserDisplayName(dict.state_key, false)
|
return Backend.users.get(dict.state_key).displayName.value
|
||||||
return name === dict.state_key ?
|
|
||||||
dict.content.displayname : name.result()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -168,7 +166,7 @@ function getLeftBannerText(leftEvent) {
|
||||||
|
|
||||||
if (info.membership)
|
if (info.membership)
|
||||||
|
|
||||||
var name = Backend.getUserDisplayName(leftEvent.sender, false).result()
|
var name = Backend.users.get(leftEvent.sender).displayName.value
|
||||||
|
|
||||||
return "<b>" + name + "</b> " +
|
return "<b>" + name + "</b> " +
|
||||||
(info.membership == "ban" ?
|
(info.membership == "ban" ?
|
||||||
|
@ -187,19 +185,19 @@ function getLeftBannerText(leftEvent) {
|
||||||
|
|
||||||
function getLeftBannerAvatarName(leftEvent, accountId) {
|
function getLeftBannerAvatarName(leftEvent, accountId) {
|
||||||
if (! leftEvent || leftEvent.state_key == leftEvent.sender) {
|
if (! leftEvent || leftEvent.state_key == leftEvent.sender) {
|
||||||
return Backend.getUserDisplayName(accountId, false).result()
|
return Backend.users.get(accountId).displayName.value
|
||||||
}
|
}
|
||||||
|
|
||||||
return Backend.getUserDisplayName(leftEvent.sender, false).result()
|
return Backend.users.get(leftEvent.sender).displayName.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getTypingUsersText(users, ourAccountId) {
|
function getTypingMembersText(users, ourAccountId) {
|
||||||
var names = []
|
var names = []
|
||||||
|
|
||||||
for (var i = 0; i < users.length; i++) {
|
for (var i = 0; i < users.length; i++) {
|
||||||
if (users[i] !== ourAccountId) {
|
if (users[i] !== ourAccountId) {
|
||||||
names.push(Backend.getUserDisplayName(users[i], false).result())
|
names.push(Backend.users.get(users[i]).displayName.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ Column {
|
||||||
id: accountDelegate
|
id: accountDelegate
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
||||||
|
property var user: Backend.users.get(userId)
|
||||||
|
|
||||||
property string roomCategoriesListUserId: userId
|
property string roomCategoriesListUserId: userId
|
||||||
property bool expanded: true
|
property bool expanded: true
|
||||||
|
|
||||||
|
@ -16,7 +18,7 @@ Column {
|
||||||
|
|
||||||
HAvatar {
|
HAvatar {
|
||||||
id: avatar
|
id: avatar
|
||||||
name: displayName
|
name: user.displayName.value
|
||||||
}
|
}
|
||||||
|
|
||||||
HColumnLayout {
|
HColumnLayout {
|
||||||
|
@ -25,7 +27,7 @@ Column {
|
||||||
|
|
||||||
HLabel {
|
HLabel {
|
||||||
id: accountLabel
|
id: accountLabel
|
||||||
text: displayName.value || userId
|
text: user.displayName.value
|
||||||
elide: HLabel.ElideRight
|
elide: HLabel.ElideRight
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
@ -35,7 +37,7 @@ Column {
|
||||||
|
|
||||||
HTextField {
|
HTextField {
|
||||||
id: statusEdit
|
id: statusEdit
|
||||||
text: statusMessage || ""
|
text: user.statusMessage || ""
|
||||||
placeholderText: qsTr("Set status message")
|
placeholderText: qsTr("Set status message")
|
||||||
font.pixelSize: HStyle.fontSize.small
|
font.pixelSize: HStyle.fontSize.small
|
||||||
background: null
|
background: null
|
||||||
|
|
|
@ -6,7 +6,8 @@ function getLastRoomEventText(roomId, accountId) {
|
||||||
if (eventsModel.count < 1) { return "" }
|
if (eventsModel.count < 1) { return "" }
|
||||||
var ev = eventsModel.get(0)
|
var ev = eventsModel.get(0)
|
||||||
|
|
||||||
var name = Backend.getUserDisplayName(ev.dict.sender, false).result()
|
var name = Backend.users.get(ev.dict.sender).displayName.value
|
||||||
|
|
||||||
var undecryptable = ev.type === "OlmEvent" || ev.type === "MegolmEvent"
|
var undecryptable = ev.type === "OlmEvent" || ev.type === "MegolmEvent"
|
||||||
|
|
||||||
if (undecryptable || ev.type.startsWith("RoomMessage")) {
|
if (undecryptable || ev.type.startsWith("RoomMessage")) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user