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
- 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

View File

@ -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")
)
)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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

View File

@ -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()

View File

@ -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:

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}
}

View File

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

View File

@ -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 }
}
}
}

View File

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

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.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 }
}
}
}

View File

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

View File

@ -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 }
}

View File

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

View File

@ -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"

View File

@ -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 }
)
}