Rework models hierarchy, room categories models
This commit is contained in:
parent
ada44cf6f7
commit
047225fded
4
TODO.md
4
TODO.md
@ -3,16 +3,18 @@
|
||||
- Don't bake in size properties for components
|
||||
|
||||
- Bug fixes
|
||||
- Sendbox
|
||||
- 100% CPU usage when hitting top edge to trigger messages loading
|
||||
- Sending `![A picture](https://picsum.photos/256/256)` → not clickable?
|
||||
- Icons and images aren't reloaded
|
||||
- HStyle singleton isn't reloaded
|
||||
- `MessageDelegate.qml:63: TypeError: 'reloadPreviousItem' not a function`
|
||||
- Bug when resizing window being tiled (i3), can't figure it out
|
||||
- Can scroll the SidePane rooms too far
|
||||
|
||||
- UI
|
||||
- Use nested listview for categories instead of section property
|
||||
- Improve SidePane appearance when at min width
|
||||
- Accounts delegates background
|
||||
- Server selection
|
||||
- Register/Forgot? for SignIn dialog
|
||||
- Scaling
|
||||
|
@ -110,22 +110,3 @@ class Backend(QObject):
|
||||
from PyQt5.QtCore import pyqtRemoveInputHook
|
||||
pyqtRemoveInputHook()
|
||||
pdb.set_trace()
|
||||
|
||||
|
||||
@pyqtSlot("QVariant", str, result=bool)
|
||||
def EventIsOurProfileChanged(self, event: RoomEvent, account_id) -> bool:
|
||||
# pylint: disable=unused-self
|
||||
info = event.dict.get("content")
|
||||
previous = event.dict.get("prev_content")
|
||||
|
||||
return (
|
||||
event.type == "RoomMemberEvent" and
|
||||
event.dict["sender"] == account_id and
|
||||
bool(info) and
|
||||
bool(previous) and
|
||||
info["membership"] == previous["membership"] and
|
||||
(
|
||||
info.get("displayname") != previous.get("displayname") or
|
||||
info.get("avatar_url") != previous.get("avatar_url")
|
||||
)
|
||||
)
|
||||
|
@ -89,7 +89,6 @@ class ClientManager(QObject):
|
||||
def deleteAll(self) -> None:
|
||||
for user_id in self.clients.copy():
|
||||
self.delete(user_id)
|
||||
print("deleted", user_id, self.clients)
|
||||
|
||||
|
||||
@pyqtProperty(str, constant=True)
|
||||
|
@ -3,29 +3,7 @@ from typing import Any, Dict, List, Optional
|
||||
from PyQt5.QtCore import QDateTime
|
||||
|
||||
from .list_item import ListItem
|
||||
|
||||
|
||||
class User(ListItem):
|
||||
_required_init_values = {"userId"}
|
||||
_constant = {"userId"}
|
||||
|
||||
userId: str = ""
|
||||
displayName: Optional[str] = None
|
||||
avatarUrl: Optional[str] = None
|
||||
statusMessage: Optional[str] = None
|
||||
|
||||
|
||||
class Room(ListItem):
|
||||
_required_init_values = {"roomId", "displayName"}
|
||||
_constant = {"roomId"}
|
||||
|
||||
roomId: str = ""
|
||||
displayName: str = ""
|
||||
category: str = "Rooms"
|
||||
topic: Optional[str] = None
|
||||
typingUsers: List[str] = []
|
||||
inviter: Optional[Dict[str, str]] = None
|
||||
leftEvent: Optional[Dict[str, str]] = None
|
||||
from .list_model import ListModel
|
||||
|
||||
|
||||
class RoomEvent(ListItem):
|
||||
@ -36,3 +14,38 @@ class RoomEvent(ListItem):
|
||||
dict: Dict[str, Any] = {}
|
||||
dateTime: QDateTime = QDateTime.currentDateTime()
|
||||
isLocalEcho: bool = False
|
||||
|
||||
|
||||
class Room(ListItem):
|
||||
_required_init_values = {"roomId", "displayName"}
|
||||
_constant = {"roomId"}
|
||||
|
||||
roomId: str = ""
|
||||
displayName: str = ""
|
||||
topic: Optional[str] = None
|
||||
typingUsers: List[str] = []
|
||||
|
||||
inviter: Optional[Dict[str, str]] = None
|
||||
leftEvent: Optional[Dict[str, str]] = None
|
||||
|
||||
|
||||
class RoomCategory(ListItem):
|
||||
_required_init_values = {"name", "rooms"}
|
||||
_constant = {"rooms"}
|
||||
|
||||
name: str = ""
|
||||
|
||||
# Must be provided at init, else it will be the same object
|
||||
# for every RoomCategory
|
||||
rooms: ListModel = ListModel()
|
||||
|
||||
|
||||
class Account(ListItem):
|
||||
_required_init_values = {"userId", "roomCategories"}
|
||||
_constant = {"userId", "roomCategories"}
|
||||
|
||||
userId: str = ""
|
||||
roomCategories: ListModel = ListModel() # same as RoomCategory.rooms
|
||||
displayName: Optional[str] = None
|
||||
avatarUrl: Optional[str] = None
|
||||
statusMessage: Optional[str] = None
|
||||
|
@ -15,15 +15,23 @@ Index = Union[int, str]
|
||||
NewItem = Union[ListItem, Mapping[str, Any], Sequence]
|
||||
|
||||
|
||||
class _GetFail:
|
||||
pass
|
||||
|
||||
|
||||
class _PopFail:
|
||||
pass
|
||||
|
||||
|
||||
class ListModel(QAbstractListModel):
|
||||
rolesSet = pyqtSignal()
|
||||
changed = pyqtSignal()
|
||||
countChanged = pyqtSignal(int)
|
||||
|
||||
def __init__(self,
|
||||
parent: QObject,
|
||||
initial_data: Optional[List[NewItem]] = None,
|
||||
container: Callable[..., MutableSequence] = list) -> None:
|
||||
container: Callable[..., MutableSequence] = list,
|
||||
parent: QObject = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._data: MutableSequence[ListItem] = container()
|
||||
|
||||
@ -135,11 +143,19 @@ class ListModel(QAbstractListModel):
|
||||
|
||||
@pyqtSlot(int, result="QVariant")
|
||||
@pyqtSlot(str, result="QVariant")
|
||||
def get(self, index: Index) -> ListItem:
|
||||
if isinstance(index, str):
|
||||
index = self.indexWhere(self.mainKey, index)
|
||||
@pyqtSlot(int, "QVariant", result="QVariant")
|
||||
@pyqtSlot(str, "QVariant", result="QVariant")
|
||||
def get(self, index: Index, default: Any = _GetFail()) -> ListItem:
|
||||
try:
|
||||
i_index: int = self.indexWhere(self.mainKey, index) \
|
||||
if isinstance(index, str) else index
|
||||
|
||||
return self._data[index] # type: ignore
|
||||
return self._data[i_index]
|
||||
|
||||
except (ValueError, IndexError):
|
||||
if isinstance(default, _GetFail):
|
||||
raise
|
||||
return default
|
||||
|
||||
|
||||
@pyqtSlot(int, "QVariantMap")
|
||||
@ -170,20 +186,20 @@ class ListModel(QAbstractListModel):
|
||||
self.append(val)
|
||||
|
||||
|
||||
@pyqtSlot(int, "QVariantMap")
|
||||
@pyqtSlot(int, "QVariantMap", "QStringList")
|
||||
@pyqtSlot(str, "QVariantMap")
|
||||
@pyqtSlot(str, "QVariantMap", "QStringList")
|
||||
@pyqtSlot(int, "QVariantMap", result=int)
|
||||
@pyqtSlot(int, "QVariantMap", "QStringList", result=int)
|
||||
@pyqtSlot(str, "QVariantMap", result=int)
|
||||
@pyqtSlot(str, "QVariantMap", "QStringList", result=int)
|
||||
def update(self,
|
||||
index: Index,
|
||||
value: NewItem,
|
||||
ignore_roles: Sequence[str] = ()) -> None:
|
||||
ignore_roles: Sequence[str] = ()) -> int:
|
||||
value = self._convert_new_value(value)
|
||||
|
||||
if isinstance(index, str):
|
||||
index = self.indexWhere(self.mainKey or value.mainKey, index)
|
||||
i_index: int = self.indexWhere(self.mainKey, index) \
|
||||
if isinstance(index, str) else index
|
||||
|
||||
to_update = self._data[index] # type: ignore
|
||||
to_update = self[i_index]
|
||||
|
||||
for role in self.roles:
|
||||
if role not in ignore_roles:
|
||||
@ -192,35 +208,40 @@ class ListModel(QAbstractListModel):
|
||||
except AttributeError: # constant/not settable
|
||||
pass
|
||||
|
||||
qidx = QAbstractListModel.index(self, index, 0)
|
||||
qidx = QAbstractListModel.index(self, i_index, 0)
|
||||
self.dataChanged.emit(qidx, qidx, self.roleNames())
|
||||
self.changed.emit()
|
||||
return i_index
|
||||
|
||||
|
||||
@pyqtSlot(str, "QVariantMap")
|
||||
@pyqtSlot(str, "QVariantMap", int)
|
||||
@pyqtSlot(str, "QVariantMap", int, "QStringList")
|
||||
@pyqtSlot(str, "QVariantMap", int, int)
|
||||
@pyqtSlot(str, "QVariantMap", int, int, "QStringList")
|
||||
def upsert(self,
|
||||
where_main_key_is_value: Any,
|
||||
update_with: NewItem,
|
||||
index_if_insert: Optional[int] = None,
|
||||
ignore_roles: Sequence[str] = ()) -> None:
|
||||
where_main_key_is: Any,
|
||||
update_with: NewItem,
|
||||
new_index_if_insert: Optional[int] = None,
|
||||
new_index_if_update: Optional[int] = None,
|
||||
ignore_roles: Sequence[str] = ()) -> None:
|
||||
try:
|
||||
self.update(where_main_key_is_value, update_with, ignore_roles)
|
||||
index = self.update(where_main_key_is, update_with, ignore_roles)
|
||||
except (IndexError, ValueError):
|
||||
self.insert(index_if_insert or len(self), update_with)
|
||||
|
||||
self.insert(new_index_if_insert or len(self), update_with)
|
||||
else:
|
||||
if new_index_if_update:
|
||||
self.move(index, new_index_if_update)
|
||||
|
||||
|
||||
@pyqtSlot(int, list)
|
||||
@pyqtSlot(str, list)
|
||||
def set(self, index: Index, value: NewItem) -> None:
|
||||
if isinstance(index, str):
|
||||
index = self.indexWhere(self.mainKey, index)
|
||||
i_index: int = self.indexWhere(self.mainKey, index) \
|
||||
if isinstance(index, str) else index
|
||||
|
||||
qidx = QAbstractListModel.index(self, index, 0)
|
||||
value = self._convert_new_value(value)
|
||||
self._data[index] = value # type: ignore
|
||||
qidx = QAbstractListModel.index(self, i_index, 0)
|
||||
value = self._convert_new_value(value)
|
||||
self._data[i_index] = value
|
||||
self.dataChanged.emit(qidx, qidx, self.roleNames())
|
||||
self.changed.emit()
|
||||
|
||||
@ -228,11 +249,11 @@ class ListModel(QAbstractListModel):
|
||||
@pyqtSlot(int, str, "QVariant")
|
||||
@pyqtSlot(str, str, "QVariant")
|
||||
def setProperty(self, index: Index, prop: str, value: Any) -> None:
|
||||
if isinstance(index, str):
|
||||
index = self.indexWhere(self.mainKey, index)
|
||||
i_index: int = self.indexWhere(self.mainKey, index) \
|
||||
if isinstance(index, str) else index
|
||||
|
||||
setattr(self._data[index], prop, value) # type: ignore
|
||||
qidx = QAbstractListModel.index(self, index, 0)
|
||||
setattr(self[i_index], prop, value)
|
||||
qidx = QAbstractListModel.index(self, i_index, 0)
|
||||
self.dataChanged.emit(qidx, qidx, self.roleNames())
|
||||
self.changed.emit()
|
||||
|
||||
@ -269,17 +290,39 @@ class ListModel(QAbstractListModel):
|
||||
@pyqtSlot(int)
|
||||
@pyqtSlot(str)
|
||||
def remove(self, index: Index) -> None:
|
||||
if isinstance(index, str):
|
||||
index = self.indexWhere(self.mainKey, index)
|
||||
i_index: int = self.indexWhere(self.mainKey, index) \
|
||||
if isinstance(index, str) else index
|
||||
|
||||
self.beginRemoveRows(QModelIndex(), index, index)
|
||||
del self._data[index] # type: ignore
|
||||
self.beginRemoveRows(QModelIndex(), i_index, i_index)
|
||||
del self._data[i_index]
|
||||
self.endRemoveRows()
|
||||
|
||||
self.countChanged.emit(len(self))
|
||||
self.changed.emit()
|
||||
|
||||
|
||||
@pyqtSlot(int, result="QVariant")
|
||||
@pyqtSlot(str, result="QVariant")
|
||||
def pop(self, index: Index, default: Any = _PopFail()) -> ListItem:
|
||||
try:
|
||||
i_index: int = self.indexWhere(self.mainKey, index) \
|
||||
if isinstance(index, str) else index
|
||||
item = self[i_index]
|
||||
|
||||
except (ValueError, IndexError):
|
||||
if isinstance(default, _PopFail):
|
||||
raise
|
||||
return default
|
||||
|
||||
self.beginRemoveRows(QModelIndex(), i_index, i_index)
|
||||
del self._data[i_index]
|
||||
self.endRemoveRows()
|
||||
|
||||
self.countChanged.emit(len(self))
|
||||
self.changed.emit()
|
||||
return item
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def clear(self) -> None:
|
||||
# Reimplemented for performance reasons (begin/endRemoveRows)
|
||||
|
@ -7,15 +7,15 @@ from .list_model import ListModel
|
||||
|
||||
class ListModelMap(QObject):
|
||||
def __init__(self,
|
||||
parent: QObject,
|
||||
models_container: Callable[..., MutableSequence] = list
|
||||
) -> None:
|
||||
models_container: Callable[..., MutableSequence] = list,
|
||||
parent: QObject = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
# Set the parent to prevent item garbage-collection on the C++ side
|
||||
self.dict: DefaultDict[Any, ListModel] = \
|
||||
DefaultDict(lambda: ListModel(parent = self,
|
||||
container = models_container))
|
||||
DefaultDict(
|
||||
lambda: ListModel(container=models_container, parent=self)
|
||||
)
|
||||
|
||||
|
||||
@pyqtSlot(str, result="QVariant")
|
||||
|
@ -12,10 +12,8 @@ from .list_model_map import ListModelMap
|
||||
class QMLModels(QObject):
|
||||
def __init__(self, parent: QObject) -> None:
|
||||
super().__init__(parent)
|
||||
self._accounts: ListModel = ListModel(parent)
|
||||
self._rooms: ListModelMap = ListModelMap(parent)
|
||||
self._room_events: ListModelMap = ListModelMap(parent,
|
||||
models_container=Deque)
|
||||
self._accounts: ListModel = ListModel(parent=parent)
|
||||
self._room_events: ListModelMap = ListModelMap(Deque, parent)
|
||||
|
||||
|
||||
@pyqtProperty(ListModel, constant=True)
|
||||
@ -23,11 +21,6 @@ class QMLModels(QObject):
|
||||
return self._accounts
|
||||
|
||||
|
||||
@pyqtProperty("QVariant", constant=True)
|
||||
def rooms(self):
|
||||
return self._rooms
|
||||
|
||||
|
||||
@pyqtProperty("QVariant", constant=True)
|
||||
def roomEvents(self):
|
||||
return self._room_events
|
||||
|
@ -2,7 +2,7 @@
|
||||
# This file is part of harmonyqml, licensed under GPLv3.
|
||||
|
||||
from threading import Lock
|
||||
from typing import Any, Deque, Dict, List, Optional, Sequence
|
||||
from typing import Any, Deque, Dict, List, Optional
|
||||
|
||||
from PyQt5.QtCore import QDateTime, QObject, pyqtBoundSignal
|
||||
|
||||
@ -11,7 +11,8 @@ from nio.rooms import MatrixRoom
|
||||
|
||||
from .backend import Backend
|
||||
from .client import Client
|
||||
from .model.items import Room, RoomEvent, User
|
||||
from .model.items import Account, Room, RoomCategory, RoomEvent
|
||||
from .model.list_model import ListModel
|
||||
|
||||
Inviter = Optional[Dict[str, str]]
|
||||
LeftEvent = Optional[Dict[str, str]]
|
||||
@ -34,8 +35,14 @@ class SignalManager(QObject):
|
||||
|
||||
def onClientAdded(self, client: Client) -> None:
|
||||
self.connectClient(client)
|
||||
self.backend.models.accounts.append(User(
|
||||
userId = client.userId,
|
||||
|
||||
self.backend.models.accounts.append(Account(
|
||||
userId = client.userId,
|
||||
roomCategories = ListModel([
|
||||
RoomCategory("Invites", ListModel()),
|
||||
RoomCategory("Rooms", ListModel()),
|
||||
RoomCategory("Left", ListModel()),
|
||||
]),
|
||||
displayName = self.backend.getUserDisplayName(client.userId),
|
||||
))
|
||||
|
||||
@ -56,104 +63,65 @@ class SignalManager(QObject):
|
||||
attr.connect(onSignal)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _get_room_displayname(nio_room: MatrixRoom) -> Optional[str]:
|
||||
name = nio_room.name or nio_room.canonical_alias
|
||||
if name:
|
||||
return name
|
||||
|
||||
name = nio_room.group_name()
|
||||
return None if name == "Empty room?" else name
|
||||
|
||||
|
||||
def onRoomInvited(self,
|
||||
client: Client,
|
||||
room_id: str,
|
||||
inviter: Inviter = None) -> None:
|
||||
|
||||
self._add_room(client, room_id, client.nio.invited_rooms[room_id],
|
||||
"Invites", inviter=inviter)
|
||||
nio_room = client.nio.invited_rooms[room_id]
|
||||
categories = self.backend.models.accounts[client.userId].roomCategories
|
||||
|
||||
categories["Rooms"].rooms.pop(room_id, None)
|
||||
categories["Left"].rooms.pop(room_id, None)
|
||||
|
||||
categories["Invites"].rooms.upsert(room_id, Room(
|
||||
roomId = room_id,
|
||||
displayName = self._get_room_displayname(nio_room),
|
||||
topic = nio_room.topic,
|
||||
inviter = inviter,
|
||||
), 0, 0)
|
||||
|
||||
|
||||
def onRoomJoined(self, client: Client, room_id: str) -> None:
|
||||
self._add_room(client, room_id, client.nio.rooms[room_id], "Rooms")
|
||||
nio_room = client.nio.rooms[room_id]
|
||||
categories = self.backend.models.accounts[client.userId].roomCategories
|
||||
|
||||
categories["Invites"].rooms.pop(room_id, None)
|
||||
categories["Left"].rooms.pop(room_id, None)
|
||||
|
||||
categories["Rooms"].rooms.upsert(room_id, Room(
|
||||
roomId = room_id,
|
||||
displayName = self._get_room_displayname(nio_room),
|
||||
topic = nio_room.topic,
|
||||
), 0, 0)
|
||||
|
||||
|
||||
def onRoomLeft(self,
|
||||
client: Client,
|
||||
room_id: str,
|
||||
left_event: LeftEvent = None) -> None:
|
||||
categories = self.backend.models.accounts[client.userId].roomCategories
|
||||
|
||||
self._add_room(client, room_id, client.nio.rooms.get(room_id), "Left",
|
||||
left_event=left_event)
|
||||
previous = categories["Rooms"].rooms.pop(room_id, None)
|
||||
previous = previous or categories["Invites"].rooms.pop(room_id, None)
|
||||
previous = previous or categories["Left"].rooms.get(room_id, None)
|
||||
|
||||
|
||||
def _move_room(self, account_id: str, room_id: str) -> None:
|
||||
def get_newest_event_date_time(room_id: str) -> QDateTime:
|
||||
for ev in self.backend.models.roomEvents[room_id]:
|
||||
if not self.backend.EventIsOurProfileChanged(ev, account_id):
|
||||
return ev.dateTime
|
||||
|
||||
return QDateTime.fromMSecsSinceEpoch(0)
|
||||
|
||||
rooms_model = self.backend.models.rooms[account_id]
|
||||
room_index = rooms_model.indexWhere("roomId", room_id)
|
||||
category = rooms_model[room_index].category
|
||||
timestamp = get_newest_event_date_time(room_id)
|
||||
|
||||
def get_index(put_before_categories: Sequence[str],
|
||||
put_after_categories: Sequence[str]) -> int:
|
||||
for i, room in enumerate(rooms_model):
|
||||
if room.category not in put_after_categories and \
|
||||
(room.category in put_before_categories or
|
||||
timestamp >= get_newest_event_date_time(room.roomId)):
|
||||
return i
|
||||
|
||||
return len(rooms_model) - 1
|
||||
|
||||
to = 0
|
||||
|
||||
if category == "Invites":
|
||||
to = get_index(["Rooms", "Left"], [])
|
||||
|
||||
if category == "Rooms":
|
||||
to = get_index(["Left"], ["Invites"])
|
||||
|
||||
elif category == "Left":
|
||||
to = get_index([], ["Invites", "Rooms", "Left"])
|
||||
|
||||
rooms_model.move(room_index, to)
|
||||
|
||||
|
||||
def _add_room(self,
|
||||
client: Client,
|
||||
room_id: str,
|
||||
room: MatrixRoom,
|
||||
category: str = "Rooms",
|
||||
inviter: Inviter = None,
|
||||
left_event: LeftEvent = None) -> None:
|
||||
|
||||
if (inviter and left_event):
|
||||
raise ValueError()
|
||||
|
||||
model = self.backend.models.rooms[client.userId]
|
||||
no_update = []
|
||||
|
||||
def get_displayname() -> Optional[str]:
|
||||
if not room:
|
||||
no_update.append("displayName")
|
||||
return room_id
|
||||
|
||||
name = room.name or room.canonical_alias
|
||||
if name:
|
||||
return name
|
||||
|
||||
name = room.group_name()
|
||||
return None if name == "Empty room?" else name
|
||||
|
||||
item = Room(
|
||||
categories["Left"].rooms.upsert(0, Room(
|
||||
roomId = room_id,
|
||||
displayName = get_displayname(),
|
||||
category = category,
|
||||
topic = room.topic if room else None,
|
||||
inviter = inviter,
|
||||
displayName = previous.displayName if previous else None,
|
||||
topic = previous.topic if previous else None,
|
||||
leftEvent = left_event,
|
||||
)
|
||||
|
||||
model.upsert(room_id, item, ignore_roles=no_update)
|
||||
with self._lock:
|
||||
self._move_room(client.userId, room_id)
|
||||
|
||||
), 0, 0)
|
||||
|
||||
def onRoomSyncPrevBatchTokenReceived(self,
|
||||
_: Client,
|
||||
@ -176,7 +144,7 @@ class SignalManager(QObject):
|
||||
|
||||
|
||||
def onRoomEventReceived(self,
|
||||
client: Client,
|
||||
_: Client,
|
||||
room_id: str,
|
||||
etype: str,
|
||||
edict: Dict[str, Any]) -> None:
|
||||
@ -192,6 +160,16 @@ class SignalManager(QObject):
|
||||
.fromMSecsSinceEpoch(edict["server_timestamp"])
|
||||
new_event = RoomEvent(type=etype, dateTime=date_time, dict=edict)
|
||||
|
||||
event_is_our_profile_changed = (
|
||||
etype == "RoomMemberEvent" and
|
||||
edict.get("sender") in self.backend.clientManager.clients and
|
||||
((edict.get("content") or {}).get("membership") ==
|
||||
(edict.get("prev_content") or {}).get("membership"))
|
||||
)
|
||||
|
||||
if event_is_our_profile_changed:
|
||||
return
|
||||
|
||||
if etype == "RoomCreateEvent":
|
||||
self.backend.fully_loaded_rooms.add(room_id)
|
||||
|
||||
@ -233,14 +211,20 @@ class SignalManager(QObject):
|
||||
|
||||
with self._lock:
|
||||
process()
|
||||
self._move_room(client.userId, room_id)
|
||||
# self._move_room(client.userId, room_id)
|
||||
|
||||
|
||||
def onRoomTypingUsersUpdated(self,
|
||||
client: Client,
|
||||
room_id: str,
|
||||
users: List[str]) -> None:
|
||||
self.backend.models.rooms[client.userId][room_id].typingUsers = users
|
||||
categories = self.backend.models.accounts[client.userId].roomCategories
|
||||
for categ in categories:
|
||||
try:
|
||||
categ.rooms[room_id].typingUsers = users
|
||||
break
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def onMessageAboutToBeSent(self,
|
||||
@ -264,10 +248,13 @@ class SignalManager(QObject):
|
||||
model.insert(0, event)
|
||||
self._events_in_transfer += 1
|
||||
|
||||
self._move_room(client.userId, room_id)
|
||||
# self._move_room(client.userId, room_id)
|
||||
|
||||
|
||||
def onRoomAboutToBeForgotten(self, client: Client, room_id: str) -> None:
|
||||
with self._lock:
|
||||
del self.backend.models.rooms[client.userId][room_id]
|
||||
self.backend.models.roomEvents[room_id].clear()
|
||||
categories = self.backend.models.accounts[client.userId].roomCategories
|
||||
|
||||
for categ in categories:
|
||||
categ.rooms.pop(room_id, None)
|
||||
|
||||
self.backend.models.roomEvents[room_id].clear()
|
||||
|
@ -5,7 +5,7 @@ Rectangle {
|
||||
property bool hidden: false
|
||||
property var name: null // null, string or PyQtFuture
|
||||
property var imageSource: null
|
||||
property int dimension: 48
|
||||
property int dimension: 36
|
||||
|
||||
|
||||
readonly property string resolvedName:
|
||||
|
@ -3,6 +3,7 @@ import QtQuick.Layouts 1.3
|
||||
import "../Base"
|
||||
|
||||
HRowLayout {
|
||||
property alias label: noticeLabel
|
||||
property alias text: noticeLabel.text
|
||||
property alias color: noticeLabel.color
|
||||
property alias font: noticeLabel.font
|
||||
@ -19,7 +20,10 @@ HRowLayout {
|
||||
|
||||
Layout.margins: 10
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.maximumWidth: parent.width - Layout.margins * 2
|
||||
Layout.maximumWidth:
|
||||
parent.width - Layout.leftMargin - Layout.rightMargin
|
||||
|
||||
opacity: width > Layout.leftMargin + Layout.rightMargin ? 1 : 0
|
||||
|
||||
background: Rectangle {
|
||||
id: noticeLabelBackground
|
@ -1,14 +1,17 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import "../Base"
|
||||
import "Banners"
|
||||
import "RoomEventList"
|
||||
|
||||
HColumnLayout {
|
||||
property string userId: ""
|
||||
property string category: ""
|
||||
property string roomId: ""
|
||||
|
||||
readonly property var roomInfo:
|
||||
Backend.models.rooms.get(userId).get(roomId)
|
||||
Backend.models.accounts.get(userId)
|
||||
.roomCategories.get(category).rooms.get(roomId)
|
||||
|
||||
property bool canLoadPastEvents: true
|
||||
|
||||
@ -20,22 +23,25 @@ HColumnLayout {
|
||||
topic: roomInfo.topic || ""
|
||||
}
|
||||
|
||||
RoomEventList {}
|
||||
RoomEventList {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
TypingUsersBar {}
|
||||
|
||||
InviteBanner {
|
||||
visible: roomInfo.category === "Invites"
|
||||
visible: category === "Invites"
|
||||
inviter: roomInfo.inviter
|
||||
}
|
||||
|
||||
SendBox {
|
||||
id: sendBox
|
||||
visible: roomInfo.category === "Rooms"
|
||||
visible: category === "Rooms"
|
||||
}
|
||||
|
||||
LeftBanner {
|
||||
visible: roomInfo.category === "Left"
|
||||
visible: category === "Left"
|
||||
leftEvent: roomInfo.leftEvent
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import QtQuick 2.7
|
||||
import "../../Base"
|
||||
|
||||
HNoticeLabel {
|
||||
HNoticePage {
|
||||
text: dateTime.toLocaleDateString()
|
||||
color: HStyle.chat.daybreak.foreground
|
||||
backgroundColor: HStyle.chat.daybreak.background
|
||||
|
@ -1,5 +1,4 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import "../../Base"
|
||||
|
||||
HGlassRectangle {
|
||||
@ -8,9 +7,6 @@ HGlassRectangle {
|
||||
|
||||
color: HStyle.chat.roomEventList.background
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
ListView {
|
||||
id: roomEventListView
|
||||
delegate: RoomEventDelegate {}
|
||||
@ -20,7 +16,6 @@ HGlassRectangle {
|
||||
anchors.leftMargin: space
|
||||
anchors.rightMargin: space
|
||||
|
||||
clip: true
|
||||
topMargin: space
|
||||
bottomMargin: space
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
@ -39,10 +34,10 @@ HGlassRectangle {
|
||||
}
|
||||
}
|
||||
|
||||
HNoticeLabel {
|
||||
HNoticePage {
|
||||
text: qsTr("Nothing to show here yet...")
|
||||
|
||||
visible: roomEventListView.model.count < 1
|
||||
anchors.centerIn: parent
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import "../Base"
|
||||
|
||||
HNoticeLabel {
|
||||
HNoticePage {
|
||||
text: "Select or add a room to start."
|
||||
}
|
||||
|
@ -1,16 +1,23 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import "../Base"
|
||||
|
||||
HColumnLayout {
|
||||
Column {
|
||||
id: accountDelegate
|
||||
width: parent.width
|
||||
|
||||
property string roomListUserId: userId
|
||||
property string roomCategoriesListUserId: userId
|
||||
property bool expanded: true
|
||||
|
||||
HRowLayout {
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
id: row
|
||||
|
||||
HAvatar { id: avatar; name: displayName; dimension: 36 }
|
||||
HAvatar {
|
||||
id: avatar
|
||||
name: displayName
|
||||
}
|
||||
|
||||
HColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
@ -47,30 +54,27 @@ HColumnLayout {
|
||||
|
||||
HButton {
|
||||
id: toggleExpand
|
||||
iconName: roomList.visible ? "up" : "down"
|
||||
iconName: roomCategoriesList.visible ? "up" : "down"
|
||||
iconDimension: 16
|
||||
backgroundColor: "transparent"
|
||||
onClicked: roomList.visible = ! roomList.visible
|
||||
onClicked: accountDelegate.expanded = ! accountDelegate.expanded
|
||||
|
||||
Layout.preferredHeight: row.height
|
||||
}
|
||||
}
|
||||
|
||||
RoomList {
|
||||
id: roomList
|
||||
visible: true
|
||||
RoomCategoriesList {
|
||||
id: roomCategoriesList
|
||||
interactive: false // no scrolling
|
||||
userId: roomListUserId
|
||||
visible: height > 0
|
||||
width: parent.width
|
||||
height: childrenRect.height * (accountDelegate.expanded ? 1 : 0)
|
||||
clip: heightAnimation.running
|
||||
|
||||
Layout.preferredHeight: roomList.visible ? roomList.contentHeight : 0
|
||||
userId: roomCategoriesListUserId
|
||||
|
||||
Layout.preferredWidth:
|
||||
parent.width - Layout.leftMargin - Layout.rightMargin
|
||||
|
||||
Layout.margins: accountList.spacing
|
||||
Layout.rightMargin: 0
|
||||
Layout.leftMargin:
|
||||
sidePane.width <= (sidePane.Layout.minimumWidth + Layout.margins) ?
|
||||
0 : Layout.margins
|
||||
Behavior on height {
|
||||
NumberAnimation { id: heightAnimation; duration: 100 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,6 @@ ListView {
|
||||
spacing: 8
|
||||
topMargin: spacing
|
||||
bottomMargin: topMargin
|
||||
Layout.leftMargin:
|
||||
sidePane.width <= (sidePane.Layout.minimumWidth + spacing) ?
|
||||
0 : spacing
|
||||
|
||||
model: Backend.models.accounts
|
||||
delegate: AccountDelegate {}
|
||||
|
11
harmonyqml/components/SidePane/RoomCategoriesList.qml
Normal file
11
harmonyqml/components/SidePane/RoomCategoriesList.qml
Normal file
@ -0,0 +1,11 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import "../Base"
|
||||
|
||||
ListView {
|
||||
property string userId: ""
|
||||
|
||||
id: roomCategoriesList
|
||||
model: Backend.models.accounts.get(userId).roomCategories
|
||||
delegate: RoomCategoryDelegate {}
|
||||
}
|
@ -1,15 +1,52 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import "../Base"
|
||||
|
||||
HLabel {
|
||||
width: roomList.width
|
||||
Column {
|
||||
id: roomCategoryDelegate
|
||||
width: roomCategoriesList.width
|
||||
height: childrenRect.height
|
||||
visible: roomList.contentHeight > 0
|
||||
|
||||
// topPadding is provided by the roomList spacing
|
||||
bottomPadding: roomList.spacing
|
||||
property string roomListUserId: userId
|
||||
property bool expanded: true
|
||||
|
||||
text: section
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
HRowLayout {
|
||||
width: parent.width
|
||||
|
||||
font.weight: Font.DemiBold
|
||||
HLabel {
|
||||
id: roomCategoryLabel
|
||||
text: name
|
||||
font.weight: Font.DemiBold
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
HButton {
|
||||
id: roomCategoryToggleExpand
|
||||
iconName: roomList.visible ? "up" : "down"
|
||||
iconDimension: 16
|
||||
backgroundColor: "transparent"
|
||||
onClicked:
|
||||
roomCategoryDelegate.expanded = !roomCategoryDelegate.expanded
|
||||
}
|
||||
}
|
||||
|
||||
RoomList {
|
||||
id: roomList
|
||||
interactive: false // no scrolling
|
||||
visible: height > 0
|
||||
width: roomCategoriesList.width - accountList.Layout.leftMargin
|
||||
height: childrenRect.height * (roomCategoryDelegate.expanded ? 1 : 0)
|
||||
clip: heightAnimation.running
|
||||
|
||||
userId: roomListUserId
|
||||
category: name
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation { id: heightAnimation; duration: 100 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,36 +6,33 @@ import "utils.js" as SidePaneJS
|
||||
MouseArea {
|
||||
id: roomDelegate
|
||||
width: roomList.width
|
||||
height: roomList.childrenHeight
|
||||
height: childrenRect.height
|
||||
|
||||
onClicked: pageStack.showRoom(roomList.userId, roomId)
|
||||
onClicked: pageStack.showRoom(roomList.userId, roomList.category, roomId)
|
||||
|
||||
HRowLayout {
|
||||
anchors.fill: parent
|
||||
id: row
|
||||
spacing: 1
|
||||
width: parent.width
|
||||
spacing: roomList.spacing
|
||||
|
||||
HAvatar {
|
||||
id: roomAvatar
|
||||
name: displayName
|
||||
dimension: roomDelegate.height
|
||||
}
|
||||
|
||||
HColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth:
|
||||
parent.width - parent.totalSpacing - roomAvatar.width
|
||||
|
||||
HLabel {
|
||||
id: roomLabel
|
||||
text: displayName ? displayName : "<i>Empty room</i>"
|
||||
textFormat: Text.StyledText
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
Layout.maximumWidth:
|
||||
row.width - row.totalSpacing - roomAvatar.width
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
|
||||
topPadding: -2
|
||||
bottomPadding: subtitleLabel.visible ? 0 : topPadding
|
||||
leftPadding: 5
|
||||
rightPadding: leftPadding
|
||||
Layout.maximumWidth: parent.width
|
||||
}
|
||||
|
||||
HLabel {
|
||||
@ -58,15 +55,9 @@ MouseArea {
|
||||
font.pixelSize: HStyle.fontSize.small
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
Layout.maximumWidth: roomLabel.Layout.maximumWidth
|
||||
|
||||
topPadding: -2
|
||||
bottomPadding: topPadding
|
||||
leftPadding: 5
|
||||
rightPadding: leftPadding
|
||||
Layout.maximumWidth: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
HSpacer {}
|
||||
}
|
||||
}
|
||||
|
@ -3,36 +3,11 @@ import QtQuick.Layouts 1.3
|
||||
import "../Base"
|
||||
|
||||
ListView {
|
||||
property var userId: null
|
||||
|
||||
property int childrenHeight: 36
|
||||
property int sectionHeight: 16 + spacing
|
||||
property int contentHeight: getContentHeight()
|
||||
|
||||
function getContentHeight() {
|
||||
var sections = []
|
||||
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
var categ = model.get(i).category
|
||||
if (sections.indexOf(categ) == -1) { sections.push(categ) }
|
||||
}
|
||||
|
||||
contentHeight =
|
||||
childrenHeight * model.count +
|
||||
spacing * Math.max(0, (model.count - 1)) +
|
||||
sectionHeight * sections.length
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: model
|
||||
onChanged: getContentHeight()
|
||||
}
|
||||
property string userId: ""
|
||||
property string category: ""
|
||||
|
||||
id: roomList
|
||||
spacing: 8
|
||||
model: Backend.models.rooms.get(userId)
|
||||
spacing: accountList.spacing
|
||||
model: Backend.models.accounts.get(userId).roomCategories.get(category).rooms
|
||||
delegate: RoomDelegate {}
|
||||
|
||||
section.property: "category"
|
||||
section.delegate: RoomCategoryDelegate { height: sectionHeight }
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ import "../Base"
|
||||
|
||||
HGlassRectangle {
|
||||
id: sidePane
|
||||
clip: true // Avoid artifacts when resizing pane width to minimum
|
||||
|
||||
isPageStackDescendant: false
|
||||
|
||||
HColumnLayout {
|
||||
@ -14,6 +12,10 @@ HGlassRectangle {
|
||||
AccountList {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
Layout.leftMargin:
|
||||
sidePane.width <= (sidePane.Layout.minimumWidth + spacing) ?
|
||||
0 : spacing
|
||||
}
|
||||
|
||||
PaneToolBar {}
|
||||
|
@ -3,17 +3,8 @@
|
||||
|
||||
function getLastRoomEventText(roomId, accountId) {
|
||||
var eventsModel = Backend.models.roomEvents.get(roomId)
|
||||
|
||||
for (var i = 0; i < eventsModel.count; i++) {
|
||||
var ev = eventsModel.get(i)
|
||||
|
||||
if (! Backend.EventIsOurProfileChanged(ev, accountId)) {
|
||||
var found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (! found) { return "" }
|
||||
if (eventsModel.count < 1) { return "" }
|
||||
var ev = eventsModel.get(0)
|
||||
|
||||
var name = Backend.getUserDisplayName(ev.dict.sender, false).result()
|
||||
var undecryptable = ev.type === "OlmEvent" || ev.type === "MegolmEvent"
|
||||
|
@ -40,9 +40,10 @@ Item {
|
||||
pageStack.replace("Pages/" + name + ".qml", properties || {})
|
||||
}
|
||||
|
||||
function showRoom(userId, roomId) {
|
||||
function showRoom(userId, category, roomId) {
|
||||
pageStack.replace(
|
||||
"Chat/Chat.qml", { userId: userId, roomId: roomId }
|
||||
"Chat/Chat.qml",
|
||||
{ userId: userId, category: category, roomId: roomId }
|
||||
)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user