Rework models hierarchy, room categories models

This commit is contained in:
miruka 2019-05-02 14:20:21 -04:00
parent ada44cf6f7
commit 047225fded
23 changed files with 325 additions and 293 deletions

View File

@ -3,16 +3,18 @@
- Don't bake in size properties for components - Don't bake in size properties for components
- Bug fixes - Bug fixes
- Sendbox
- 100% CPU usage when hitting top edge to trigger messages loading - 100% CPU usage when hitting top edge to trigger messages loading
- Sending `![A picture](https://picsum.photos/256/256)` → not clickable? - Sending `![A picture](https://picsum.photos/256/256)` → not clickable?
- Icons and images aren't reloaded - Icons and images aren't reloaded
- HStyle singleton isn't reloaded - HStyle singleton isn't reloaded
- `MessageDelegate.qml:63: TypeError: 'reloadPreviousItem' not a function` - `MessageDelegate.qml:63: TypeError: 'reloadPreviousItem' not a function`
- Bug when resizing window being tiled (i3), can't figure it out - Bug when resizing window being tiled (i3), can't figure it out
- Can scroll the SidePane rooms too far
- UI - UI
- Use nested listview for categories instead of section property
- Improve SidePane appearance when at min width - Improve SidePane appearance when at min width
- Accounts delegates background
- Server selection - Server selection
- Register/Forgot? for SignIn dialog - Register/Forgot? for SignIn dialog
- Scaling - Scaling

View File

@ -110,22 +110,3 @@ class Backend(QObject):
from PyQt5.QtCore import pyqtRemoveInputHook from PyQt5.QtCore import pyqtRemoveInputHook
pyqtRemoveInputHook() pyqtRemoveInputHook()
pdb.set_trace() 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")
)
)

View File

@ -89,7 +89,6 @@ class ClientManager(QObject):
def deleteAll(self) -> None: def deleteAll(self) -> None:
for user_id in self.clients.copy(): for user_id in self.clients.copy():
self.delete(user_id) self.delete(user_id)
print("deleted", user_id, self.clients)
@pyqtProperty(str, constant=True) @pyqtProperty(str, constant=True)

View File

@ -3,29 +3,7 @@ from typing import Any, Dict, List, Optional
from PyQt5.QtCore import QDateTime from PyQt5.QtCore import QDateTime
from .list_item import ListItem from .list_item import ListItem
from .list_model import ListModel
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
class RoomEvent(ListItem): class RoomEvent(ListItem):
@ -36,3 +14,38 @@ class RoomEvent(ListItem):
dict: Dict[str, Any] = {} dict: Dict[str, Any] = {}
dateTime: QDateTime = QDateTime.currentDateTime() dateTime: QDateTime = QDateTime.currentDateTime()
isLocalEcho: bool = False 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

View File

@ -15,15 +15,23 @@ Index = Union[int, str]
NewItem = Union[ListItem, Mapping[str, Any], Sequence] NewItem = Union[ListItem, Mapping[str, Any], Sequence]
class _GetFail:
pass
class _PopFail:
pass
class ListModel(QAbstractListModel): class ListModel(QAbstractListModel):
rolesSet = pyqtSignal() rolesSet = pyqtSignal()
changed = pyqtSignal() changed = pyqtSignal()
countChanged = pyqtSignal(int) countChanged = pyqtSignal(int)
def __init__(self, def __init__(self,
parent: QObject,
initial_data: Optional[List[NewItem]] = None, initial_data: Optional[List[NewItem]] = None,
container: Callable[..., MutableSequence] = list) -> None: container: Callable[..., MutableSequence] = list,
parent: QObject = None) -> None:
super().__init__(parent) super().__init__(parent)
self._data: MutableSequence[ListItem] = container() self._data: MutableSequence[ListItem] = container()
@ -135,11 +143,19 @@ class ListModel(QAbstractListModel):
@pyqtSlot(int, result="QVariant") @pyqtSlot(int, result="QVariant")
@pyqtSlot(str, result="QVariant") @pyqtSlot(str, result="QVariant")
def get(self, index: Index) -> ListItem: @pyqtSlot(int, "QVariant", result="QVariant")
if isinstance(index, str): @pyqtSlot(str, "QVariant", result="QVariant")
index = self.indexWhere(self.mainKey, index) 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") @pyqtSlot(int, "QVariantMap")
@ -170,20 +186,20 @@ class ListModel(QAbstractListModel):
self.append(val) self.append(val)
@pyqtSlot(int, "QVariantMap") @pyqtSlot(int, "QVariantMap", result=int)
@pyqtSlot(int, "QVariantMap", "QStringList") @pyqtSlot(int, "QVariantMap", "QStringList", result=int)
@pyqtSlot(str, "QVariantMap") @pyqtSlot(str, "QVariantMap", result=int)
@pyqtSlot(str, "QVariantMap", "QStringList") @pyqtSlot(str, "QVariantMap", "QStringList", result=int)
def update(self, def update(self,
index: Index, index: Index,
value: NewItem, value: NewItem,
ignore_roles: Sequence[str] = ()) -> None: ignore_roles: Sequence[str] = ()) -> int:
value = self._convert_new_value(value) value = self._convert_new_value(value)
if isinstance(index, str): i_index: int = self.indexWhere(self.mainKey, index) \
index = self.indexWhere(self.mainKey or value.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: for role in self.roles:
if role not in ignore_roles: if role not in ignore_roles:
@ -192,35 +208,40 @@ class ListModel(QAbstractListModel):
except AttributeError: # constant/not settable except AttributeError: # constant/not settable
pass pass
qidx = QAbstractListModel.index(self, index, 0) qidx = QAbstractListModel.index(self, i_index, 0)
self.dataChanged.emit(qidx, qidx, self.roleNames()) self.dataChanged.emit(qidx, qidx, self.roleNames())
self.changed.emit() self.changed.emit()
return i_index
@pyqtSlot(str, "QVariantMap") @pyqtSlot(str, "QVariantMap")
@pyqtSlot(str, "QVariantMap", int) @pyqtSlot(str, "QVariantMap", int)
@pyqtSlot(str, "QVariantMap", int, "QStringList") @pyqtSlot(str, "QVariantMap", int, int)
@pyqtSlot(str, "QVariantMap", int, int, "QStringList")
def upsert(self, def upsert(self,
where_main_key_is_value: Any, where_main_key_is: Any,
update_with: NewItem, update_with: NewItem,
index_if_insert: Optional[int] = None, new_index_if_insert: Optional[int] = None,
ignore_roles: Sequence[str] = ()) -> None: new_index_if_update: Optional[int] = None,
ignore_roles: Sequence[str] = ()) -> None:
try: 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): 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(int, list)
@pyqtSlot(str, list) @pyqtSlot(str, list)
def set(self, index: Index, value: NewItem) -> None: def set(self, index: Index, value: NewItem) -> None:
if isinstance(index, str): i_index: int = self.indexWhere(self.mainKey, index) \
index = self.indexWhere(self.mainKey, index) if isinstance(index, str) else index
qidx = QAbstractListModel.index(self, index, 0) qidx = QAbstractListModel.index(self, i_index, 0)
value = self._convert_new_value(value) value = self._convert_new_value(value)
self._data[index] = value # type: ignore self._data[i_index] = value
self.dataChanged.emit(qidx, qidx, self.roleNames()) self.dataChanged.emit(qidx, qidx, self.roleNames())
self.changed.emit() self.changed.emit()
@ -228,11 +249,11 @@ 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:
if isinstance(index, str): i_index: int = self.indexWhere(self.mainKey, index) \
index = self.indexWhere(self.mainKey, index) if isinstance(index, str) else index
setattr(self._data[index], prop, value) # type: ignore setattr(self[i_index], prop, value)
qidx = QAbstractListModel.index(self, index, 0) qidx = QAbstractListModel.index(self, i_index, 0)
self.dataChanged.emit(qidx, qidx, self.roleNames()) self.dataChanged.emit(qidx, qidx, self.roleNames())
self.changed.emit() self.changed.emit()
@ -269,17 +290,39 @@ class ListModel(QAbstractListModel):
@pyqtSlot(int) @pyqtSlot(int)
@pyqtSlot(str) @pyqtSlot(str)
def remove(self, index: Index) -> None: def remove(self, index: Index) -> None:
if isinstance(index, str): i_index: int = self.indexWhere(self.mainKey, index) \
index = self.indexWhere(self.mainKey, index) if isinstance(index, str) else index
self.beginRemoveRows(QModelIndex(), index, index) self.beginRemoveRows(QModelIndex(), i_index, i_index)
del self._data[index] # type: ignore del self._data[i_index]
self.endRemoveRows() self.endRemoveRows()
self.countChanged.emit(len(self)) self.countChanged.emit(len(self))
self.changed.emit() 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() @pyqtSlot()
def clear(self) -> None: def clear(self) -> None:
# Reimplemented for performance reasons (begin/endRemoveRows) # Reimplemented for performance reasons (begin/endRemoveRows)

View File

@ -7,15 +7,15 @@ from .list_model import ListModel
class ListModelMap(QObject): class ListModelMap(QObject):
def __init__(self, def __init__(self,
parent: QObject, models_container: Callable[..., MutableSequence] = list,
models_container: Callable[..., MutableSequence] = list parent: QObject = None) -> None:
) -> None:
super().__init__(parent) super().__init__(parent)
# 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(lambda: ListModel(parent = self, DefaultDict(
container = models_container)) lambda: ListModel(container=models_container, parent=self)
)
@pyqtSlot(str, result="QVariant") @pyqtSlot(str, result="QVariant")

View File

@ -12,10 +12,8 @@ from .list_model_map import ListModelMap
class QMLModels(QObject): class QMLModels(QObject):
def __init__(self, parent: QObject) -> None: def __init__(self, parent: QObject) -> None:
super().__init__(parent) super().__init__(parent)
self._accounts: ListModel = ListModel(parent) self._accounts: ListModel = ListModel(parent=parent)
self._rooms: ListModelMap = ListModelMap(parent) self._room_events: ListModelMap = ListModelMap(Deque, parent)
self._room_events: ListModelMap = ListModelMap(parent,
models_container=Deque)
@pyqtProperty(ListModel, constant=True) @pyqtProperty(ListModel, constant=True)
@ -23,11 +21,6 @@ class QMLModels(QObject):
return self._accounts return self._accounts
@pyqtProperty("QVariant", constant=True)
def rooms(self):
return self._rooms
@pyqtProperty("QVariant", constant=True) @pyqtProperty("QVariant", constant=True)
def roomEvents(self): def roomEvents(self):
return self._room_events return self._room_events

View File

@ -2,7 +2,7 @@
# This file is part of harmonyqml, licensed under GPLv3. # This file is part of harmonyqml, licensed under GPLv3.
from threading import Lock 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 from PyQt5.QtCore import QDateTime, QObject, pyqtBoundSignal
@ -11,7 +11,8 @@ 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 Room, RoomEvent, User from .model.items import Account, Room, RoomCategory, RoomEvent
from .model.list_model import ListModel
Inviter = Optional[Dict[str, str]] Inviter = Optional[Dict[str, str]]
LeftEvent = Optional[Dict[str, str]] LeftEvent = Optional[Dict[str, str]]
@ -34,8 +35,14 @@ class SignalManager(QObject):
def onClientAdded(self, client: Client) -> None: def onClientAdded(self, client: Client) -> None:
self.connectClient(client) 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), displayName = self.backend.getUserDisplayName(client.userId),
)) ))
@ -56,104 +63,65 @@ class SignalManager(QObject):
attr.connect(onSignal) 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, def onRoomInvited(self,
client: Client, client: Client,
room_id: str, room_id: str,
inviter: Inviter = None) -> None: inviter: Inviter = None) -> None:
self._add_room(client, room_id, client.nio.invited_rooms[room_id], nio_room = client.nio.invited_rooms[room_id]
"Invites", inviter=inviter) 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: 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, def onRoomLeft(self,
client: Client, client: Client,
room_id: str, room_id: str,
left_event: LeftEvent = None) -> None: 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", previous = categories["Rooms"].rooms.pop(room_id, None)
left_event=left_event) previous = previous or categories["Invites"].rooms.pop(room_id, None)
previous = previous or categories["Left"].rooms.get(room_id, None)
categories["Left"].rooms.upsert(0, Room(
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(
roomId = room_id, roomId = room_id,
displayName = get_displayname(), displayName = previous.displayName if previous else None,
category = category, topic = previous.topic if previous else None,
topic = room.topic if room else None,
inviter = inviter,
leftEvent = left_event, leftEvent = left_event,
) ), 0, 0)
model.upsert(room_id, item, ignore_roles=no_update)
with self._lock:
self._move_room(client.userId, room_id)
def onRoomSyncPrevBatchTokenReceived(self, def onRoomSyncPrevBatchTokenReceived(self,
_: Client, _: Client,
@ -176,7 +144,7 @@ class SignalManager(QObject):
def onRoomEventReceived(self, def onRoomEventReceived(self,
client: Client, _: Client,
room_id: str, room_id: str,
etype: str, etype: str,
edict: Dict[str, Any]) -> None: edict: Dict[str, Any]) -> None:
@ -192,6 +160,16 @@ class SignalManager(QObject):
.fromMSecsSinceEpoch(edict["server_timestamp"]) .fromMSecsSinceEpoch(edict["server_timestamp"])
new_event = RoomEvent(type=etype, dateTime=date_time, dict=edict) 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": if etype == "RoomCreateEvent":
self.backend.fully_loaded_rooms.add(room_id) self.backend.fully_loaded_rooms.add(room_id)
@ -233,14 +211,20 @@ class SignalManager(QObject):
with self._lock: with self._lock:
process() process()
self._move_room(client.userId, room_id) # self._move_room(client.userId, room_id)
def onRoomTypingUsersUpdated(self, def onRoomTypingUsersUpdated(self,
client: Client, client: Client,
room_id: str, room_id: str,
users: List[str]) -> None: 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, def onMessageAboutToBeSent(self,
@ -264,10 +248,13 @@ class SignalManager(QObject):
model.insert(0, event) model.insert(0, event)
self._events_in_transfer += 1 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: def onRoomAboutToBeForgotten(self, client: Client, room_id: str) -> None:
with self._lock: categories = self.backend.models.accounts[client.userId].roomCategories
del self.backend.models.rooms[client.userId][room_id]
self.backend.models.roomEvents[room_id].clear() for categ in categories:
categ.rooms.pop(room_id, None)
self.backend.models.roomEvents[room_id].clear()

View File

@ -5,7 +5,7 @@ Rectangle {
property bool hidden: false property bool hidden: false
property var name: null // null, string or PyQtFuture property var name: null // null, string or PyQtFuture
property var imageSource: null property var imageSource: null
property int dimension: 48 property int dimension: 36
readonly property string resolvedName: readonly property string resolvedName:

View File

@ -3,6 +3,7 @@ import QtQuick.Layouts 1.3
import "../Base" import "../Base"
HRowLayout { HRowLayout {
property alias label: noticeLabel
property alias text: noticeLabel.text property alias text: noticeLabel.text
property alias color: noticeLabel.color property alias color: noticeLabel.color
property alias font: noticeLabel.font property alias font: noticeLabel.font
@ -19,7 +20,10 @@ HRowLayout {
Layout.margins: 10 Layout.margins: 10
Layout.alignment: Qt.AlignCenter 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 { background: Rectangle {
id: noticeLabelBackground id: noticeLabelBackground

View File

@ -1,14 +1,17 @@
import QtQuick 2.7 import QtQuick 2.7
import QtQuick.Layouts 1.3
import "../Base" import "../Base"
import "Banners" import "Banners"
import "RoomEventList" import "RoomEventList"
HColumnLayout { HColumnLayout {
property string userId: "" property string userId: ""
property string category: ""
property string roomId: "" property string roomId: ""
readonly property var roomInfo: 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 property bool canLoadPastEvents: true
@ -20,22 +23,25 @@ HColumnLayout {
topic: roomInfo.topic || "" topic: roomInfo.topic || ""
} }
RoomEventList {} RoomEventList {
Layout.fillWidth: true
Layout.fillHeight: true
}
TypingUsersBar {} TypingUsersBar {}
InviteBanner { InviteBanner {
visible: roomInfo.category === "Invites" visible: category === "Invites"
inviter: roomInfo.inviter inviter: roomInfo.inviter
} }
SendBox { SendBox {
id: sendBox id: sendBox
visible: roomInfo.category === "Rooms" visible: category === "Rooms"
} }
LeftBanner { LeftBanner {
visible: roomInfo.category === "Left" visible: category === "Left"
leftEvent: roomInfo.leftEvent leftEvent: roomInfo.leftEvent
} }
} }

View File

@ -1,7 +1,7 @@
import QtQuick 2.7 import QtQuick 2.7
import "../../Base" import "../../Base"
HNoticeLabel { HNoticePage {
text: dateTime.toLocaleDateString() text: dateTime.toLocaleDateString()
color: HStyle.chat.daybreak.foreground color: HStyle.chat.daybreak.foreground
backgroundColor: HStyle.chat.daybreak.background backgroundColor: HStyle.chat.daybreak.background

View File

@ -1,5 +1,4 @@
import QtQuick 2.7 import QtQuick 2.7
import QtQuick.Layouts 1.3
import "../../Base" import "../../Base"
HGlassRectangle { HGlassRectangle {
@ -8,9 +7,6 @@ HGlassRectangle {
color: HStyle.chat.roomEventList.background color: HStyle.chat.roomEventList.background
Layout.fillWidth: true
Layout.fillHeight: true
ListView { ListView {
id: roomEventListView id: roomEventListView
delegate: RoomEventDelegate {} delegate: RoomEventDelegate {}
@ -20,7 +16,6 @@ HGlassRectangle {
anchors.leftMargin: space anchors.leftMargin: space
anchors.rightMargin: space anchors.rightMargin: space
clip: true
topMargin: space topMargin: space
bottomMargin: space bottomMargin: space
verticalLayoutDirection: ListView.BottomToTop verticalLayoutDirection: ListView.BottomToTop
@ -39,10 +34,10 @@ HGlassRectangle {
} }
} }
HNoticeLabel { HNoticePage {
text: qsTr("Nothing to show here yet...") text: qsTr("Nothing to show here yet...")
visible: roomEventListView.model.count < 1 visible: roomEventListView.model.count < 1
anchors.centerIn: parent anchors.fill: parent
} }
} }

View File

@ -1,5 +1,5 @@
import "../Base" import "../Base"
HNoticeLabel { HNoticePage {
text: "Select or add a room to start." text: "Select or add a room to start."
} }

View File

@ -1,16 +1,23 @@
import QtQuick 2.7
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import "../Base" import "../Base"
HColumnLayout { Column {
id: accountDelegate id: accountDelegate
width: parent.width width: parent.width
property string roomListUserId: userId property string roomCategoriesListUserId: userId
property bool expanded: true
HRowLayout { HRowLayout {
width: parent.width
height: childrenRect.height
id: row id: row
HAvatar { id: avatar; name: displayName; dimension: 36 } HAvatar {
id: avatar
name: displayName
}
HColumnLayout { HColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
@ -47,30 +54,27 @@ HColumnLayout {
HButton { HButton {
id: toggleExpand id: toggleExpand
iconName: roomList.visible ? "up" : "down" iconName: roomCategoriesList.visible ? "up" : "down"
iconDimension: 16 iconDimension: 16
backgroundColor: "transparent" backgroundColor: "transparent"
onClicked: roomList.visible = ! roomList.visible onClicked: accountDelegate.expanded = ! accountDelegate.expanded
Layout.preferredHeight: row.height Layout.preferredHeight: row.height
} }
} }
RoomList { RoomCategoriesList {
id: roomList id: roomCategoriesList
visible: true
interactive: false // no scrolling 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: Behavior on height {
parent.width - Layout.leftMargin - Layout.rightMargin NumberAnimation { id: heightAnimation; duration: 100 }
}
Layout.margins: accountList.spacing
Layout.rightMargin: 0
Layout.leftMargin:
sidePane.width <= (sidePane.Layout.minimumWidth + Layout.margins) ?
0 : Layout.margins
} }
} }

View File

@ -8,9 +8,6 @@ ListView {
spacing: 8 spacing: 8
topMargin: spacing topMargin: spacing
bottomMargin: topMargin bottomMargin: topMargin
Layout.leftMargin:
sidePane.width <= (sidePane.Layout.minimumWidth + spacing) ?
0 : spacing
model: Backend.models.accounts model: Backend.models.accounts
delegate: AccountDelegate {} delegate: AccountDelegate {}

View 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 {}
}

View File

@ -1,15 +1,52 @@
import QtQuick 2.7 import QtQuick 2.7
import QtQuick.Layouts 1.3
import "../Base" import "../Base"
HLabel { Column {
width: roomList.width id: roomCategoryDelegate
width: roomCategoriesList.width
height: childrenRect.height
visible: roomList.contentHeight > 0
// topPadding is provided by the roomList spacing property string roomListUserId: userId
bottomPadding: roomList.spacing property bool expanded: true
text: section HRowLayout {
elide: Text.ElideRight width: parent.width
maximumLineCount: 1
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 }
}
}
} }

View File

@ -6,36 +6,33 @@ import "utils.js" as SidePaneJS
MouseArea { MouseArea {
id: roomDelegate id: roomDelegate
width: roomList.width width: roomList.width
height: roomList.childrenHeight height: childrenRect.height
onClicked: pageStack.showRoom(roomList.userId, roomId) onClicked: pageStack.showRoom(roomList.userId, roomList.category, roomId)
HRowLayout { HRowLayout {
anchors.fill: parent width: parent.width
id: row spacing: roomList.spacing
spacing: 1
HAvatar { HAvatar {
id: roomAvatar id: roomAvatar
name: displayName name: displayName
dimension: roomDelegate.height
} }
HColumnLayout { HColumnLayout {
Layout.fillWidth: true
Layout.maximumWidth:
parent.width - parent.totalSpacing - roomAvatar.width
HLabel { HLabel {
id: roomLabel id: roomLabel
text: displayName ? displayName : "<i>Empty room</i>" text: displayName ? displayName : "<i>Empty room</i>"
textFormat: Text.StyledText textFormat: Text.StyledText
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1 maximumLineCount: 1
Layout.maximumWidth:
row.width - row.totalSpacing - roomAvatar.width
verticalAlignment: Qt.AlignVCenter verticalAlignment: Qt.AlignVCenter
topPadding: -2 Layout.maximumWidth: parent.width
bottomPadding: subtitleLabel.visible ? 0 : topPadding
leftPadding: 5
rightPadding: leftPadding
} }
HLabel { HLabel {
@ -58,15 +55,9 @@ MouseArea {
font.pixelSize: HStyle.fontSize.small font.pixelSize: HStyle.fontSize.small
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1 maximumLineCount: 1
Layout.maximumWidth: roomLabel.Layout.maximumWidth
topPadding: -2 Layout.maximumWidth: parent.width
bottomPadding: topPadding
leftPadding: 5
rightPadding: leftPadding
} }
} }
HSpacer {}
} }
} }

View File

@ -3,36 +3,11 @@ import QtQuick.Layouts 1.3
import "../Base" import "../Base"
ListView { ListView {
property var userId: null property string userId: ""
property string category: ""
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()
}
id: roomList id: roomList
spacing: 8 spacing: accountList.spacing
model: Backend.models.rooms.get(userId) model: Backend.models.accounts.get(userId).roomCategories.get(category).rooms
delegate: RoomDelegate {} delegate: RoomDelegate {}
section.property: "category"
section.delegate: RoomCategoryDelegate { height: sectionHeight }
} }

View File

@ -4,8 +4,6 @@ import "../Base"
HGlassRectangle { HGlassRectangle {
id: sidePane id: sidePane
clip: true // Avoid artifacts when resizing pane width to minimum
isPageStackDescendant: false isPageStackDescendant: false
HColumnLayout { HColumnLayout {
@ -14,6 +12,10 @@ HGlassRectangle {
AccountList { AccountList {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.leftMargin:
sidePane.width <= (sidePane.Layout.minimumWidth + spacing) ?
0 : spacing
} }
PaneToolBar {} PaneToolBar {}

View File

@ -3,17 +3,8 @@
function getLastRoomEventText(roomId, accountId) { function getLastRoomEventText(roomId, accountId) {
var eventsModel = Backend.models.roomEvents.get(roomId) var eventsModel = Backend.models.roomEvents.get(roomId)
if (eventsModel.count < 1) { return "" }
for (var i = 0; i < eventsModel.count; i++) { var ev = eventsModel.get(0)
var ev = eventsModel.get(i)
if (! Backend.EventIsOurProfileChanged(ev, accountId)) {
var found = true
break
}
}
if (! found) { return "" }
var name = Backend.getUserDisplayName(ev.dict.sender, false).result() var name = Backend.getUserDisplayName(ev.dict.sender, false).result()
var undecryptable = ev.type === "OlmEvent" || ev.type === "MegolmEvent" var undecryptable = ev.type === "OlmEvent" || ev.type === "MegolmEvent"

View File

@ -40,9 +40,10 @@ Item {
pageStack.replace("Pages/" + name + ".qml", properties || {}) pageStack.replace("Pages/" + name + ".qml", properties || {})
} }
function showRoom(userId, roomId) { function showRoom(userId, category, roomId) {
pageStack.replace( pageStack.replace(
"Chat/Chat.qml", { userId: userId, roomId: roomId } "Chat/Chat.qml",
{ userId: userId, category: category, roomId: roomId }
) )
} }