Gather both Accounts and Rooms in all_rooms model

This commit is contained in:
miruka 2020-05-12 06:09:07 -04:00
parent 19243ec5a6
commit 4d3c26abd4
8 changed files with 145 additions and 59 deletions

View File

@ -1,13 +1,11 @@
# TODO # TODO
- add account number binds - add account number binds
- rename goto*account → scrollto*account
- fix opacity - remove `import QtQml.Models 2.12`s
- fix left rooms opacity - fix left rooms opacity
- fix escape keybinds (filter rooms, message selection) - fix escape keybinds (filter rooms, message selection)
- fix python getting stuck when loading large room - fix python getting stuck when loading large room
- fix room out of bounds (above section) when filtering and only one match
- account delegates refactor - account delegates refactor
- lag when switching accounts - lag when switching accounts

View File

@ -31,6 +31,8 @@ class ModelFilter(ModelProxy):
_changed_fields: Optional[Dict[str, Any]] = None, _changed_fields: Optional[Dict[str, Any]] = None,
) -> None: ) -> None:
if self.accept_source(source): if self.accept_source(source):
value = self.convert_item(value)
if self.accept_item(value): if self.accept_item(value):
self.__setitem__((source.sync_id, key), value, _changed_fields) self.__setitem__((source.sync_id, key), value, _changed_fields)
self.filtered_out.pop((source.sync_id, key), None) self.filtered_out.pop((source.sync_id, key), None)
@ -66,16 +68,25 @@ class ModelFilter(ModelProxy):
callback() callback()
def refilter(self) -> None: def refilter(
self,
only_if: Optional[Callable[["ModelItem"], bool]] = None,
) -> None:
with self._write_lock: with self._write_lock:
take_out = [] take_out = []
bring_back = [] bring_back = []
for key, item in sorted(self.items(), key=lambda kv: kv[1]): for key, item in sorted(self.items(), key=lambda kv: kv[1]):
if only_if and not only_if(item):
continue
if not self.accept_item(item): if not self.accept_item(item):
take_out.append(key) take_out.append(key)
for key, item in self.filtered_out.items(): for key, item in self.filtered_out.items():
if only_if and not only_if(item):
continue
if self.accept_item(item): if self.accept_item(item):
bring_back.append(key) bring_back.append(key)
@ -86,8 +97,9 @@ class ModelFilter(ModelProxy):
for key in bring_back: for key in bring_back:
self[key] = self.filtered_out.pop(key) self[key] = self.filtered_out.pop(key)
for callback in self.items_changed_callbacks: if take_out or bring_back:
callback() for callback in self.items_changed_callbacks:
callback()
class FieldSubstringFilter(ModelFilter): class FieldSubstringFilter(ModelFilter):

View File

@ -53,7 +53,7 @@ class Room(ModelItem):
"""A matrix room we are invited to, are or were member of.""" """A matrix room we are invited to, are or were member of."""
id: str = field() id: str = field()
for_account: str = field() for_account: str = ""
given_name: str = "" given_name: str = ""
display_name: str = "" display_name: str = ""
main_alias: str = "" main_alias: str = ""
@ -118,6 +118,33 @@ class Room(ModelItem):
) )
@dataclass
class AccountOrRoom(Account, Room):
type: Union[Type[Account], Type[Room]] = Account
def __lt__(self, other: "AccountOrRoom") -> bool: # type: ignore
return (
self.id if self.type is Account else self.for_account,
other.type is Account,
self.left,
other.inviter_id,
bool(other.mentions),
bool(other.unreads),
other.last_event_date,
(self.display_name or self.id).lower(),
) < (
other.id if other.type is Account else other.for_account,
self.type is Account,
other.left,
self.inviter_id,
bool(self.mentions),
bool(self.unreads),
self.last_event_date,
(other.display_name or other.id).lower(),
)
@dataclass @dataclass
class Member(ModelItem): class Member(ModelItem):
"""A member in a matrix room.""" """A member in a matrix room."""

View File

@ -25,6 +25,10 @@ class ModelProxy(Model):
return True return True
def convert_item(self, item: "ModelItem") -> "ModelItem":
return item
def source_item_set( def source_item_set(
self, self,
source: Model, source: Model,
@ -33,6 +37,7 @@ class ModelProxy(Model):
_changed_fields: Optional[Dict[str, Any]] = None, _changed_fields: Optional[Dict[str, Any]] = None,
) -> None: ) -> None:
if self.accept_source(source): if self.accept_source(source):
value = self.convert_item(value)
self.__setitem__((source.sync_id, key), value, _changed_fields) self.__setitem__((source.sync_id, key), value, _changed_fields)

View File

@ -1,6 +1,9 @@
# SPDX-License-Identifier: LGPL-3.0-or-later # SPDX-License-Identifier: LGPL-3.0-or-later
from dataclasses import asdict
from .filters import FieldSubstringFilter, ModelFilter from .filters import FieldSubstringFilter, ModelFilter
from .items import Account, AccountOrRoom
from .model import Model from .model import Model
from .model_item import ModelItem from .model_item import ModelItem
@ -8,16 +11,38 @@ from .model_item import ModelItem
class AllRooms(FieldSubstringFilter): class AllRooms(FieldSubstringFilter):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__(sync_id="all_rooms", fields=("display_name",)) super().__init__(sync_id="all_rooms", fields=("display_name",))
self.items_changed_callbacks.append(self.refilter_accounts)
def accept_source(self, source: Model) -> bool: def accept_source(self, source: Model) -> bool:
return ( return source.sync_id == "accounts" or (
isinstance(source.sync_id, tuple) and isinstance(source.sync_id, tuple) and
len(source.sync_id) == 2 and len(source.sync_id) == 2 and
source.sync_id[1] == "rooms" # type: ignore source.sync_id[1] == "rooms" # type: ignore
) )
def convert_item(self, item: ModelItem) -> AccountOrRoom:
return AccountOrRoom(**asdict(item), type=type(item)) # type: ignore
def accept_item(self, item: ModelItem) -> bool:
matches_filter = super().accept_item(item)
if item.type is not Account or not self.filter: # type: ignore
return matches_filter
return next(
(i for i in self.values() if i.for_account == item.id), False,
)
def refilter_accounts(self) -> None:
self.refilter(
lambda i: isinstance(i, AccountOrRoom) and i.type is Account,
)
class MatchingAccounts(ModelFilter): class MatchingAccounts(ModelFilter):
def __init__(self, all_rooms: AllRooms) -> None: def __init__(self, all_rooms: AllRooms) -> None:
self.all_rooms = all_rooms self.all_rooms = all_rooms
@ -35,7 +60,7 @@ class MatchingAccounts(ModelFilter):
return True return True
return next( return next(
(r for r in self.all_rooms.values() if r.for_account == item.id), (i for i in self.all_rooms.values() if i.id == item.id),
False, False,
) )

View File

@ -16,19 +16,19 @@ HTile {
HUserAvatar { HUserAvatar {
id: avatar id: avatar
userId: accountModel.id userId: model.id
displayName: accountModel.display_name displayName: model.display_name
mxc: accountModel.avatar_url mxc: model.avatar_url
radius: 0 radius: 0
compact: account.compact compact: account.compact
} }
TitleLabel { TitleLabel {
text: accountModel.display_name || accountModel.id text: model.display_name || model.id
color: color:
hovered ? hovered ?
utils.nameColor( utils.nameColor(
accountModel.display_name || accountModel.id.substring(1), model.display_name || model.id.substring(1),
) : ) :
theme.accountView.account.name theme.accountView.account.name
@ -42,7 +42,7 @@ HTile {
backgroundColor: "transparent" backgroundColor: "transparent"
toolTip.text: qsTr("Add new chat") toolTip.text: qsTr("Add new chat")
onClicked: pageLoader.showPage( onClicked: pageLoader.showPage(
"AddChat/AddChat", {userId: accountModel.id}, "AddChat/AddChat", {userId: model.id},
) )
Layout.fillHeight: true Layout.fillHeight: true
@ -57,16 +57,15 @@ HTile {
} }
} }
contextMenu: AccountContextMenu { userId: accountModel.id } contextMenu: AccountContextMenu { userId: model.id }
onLeftClicked: { onLeftClicked: {
pageLoader.showPage( pageLoader.showPage(
"AccountSettings/AccountSettings", { "userId": accountModel.id } "AccountSettings/AccountSettings", { "userId": model.id }
) )
} }
property var accountModel
property bool isCurrent: false property bool isCurrent: false

View File

@ -33,7 +33,9 @@ HColumnLayout {
roomList.count === 0 || roomList.currentIndex === -1 ? roomList.count === 0 || roomList.currentIndex === -1 ?
-1 : -1 :
model.findIndex( model.findIndex(
roomList.model.get(roomList.currentIndex).for_account, -1, roomList.model.get(roomList.currentIndex).for_account ||
roomList.model.get(roomList.currentIndex).id,
-1,
) )
model: ModelStore.get("matching_accounts") model: ModelStore.get("matching_accounts")
@ -77,6 +79,11 @@ HColumnLayout {
HLoader { HLoader {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin:
accountList.highlightItem ?
accountList.highlightItem.border.width :
0
opacity: model.first_sync_done ? 0 : 1 opacity: model.first_sync_done ? 0 : 1
active: opacity > 0 active: opacity > 0
@ -96,14 +103,12 @@ HColumnLayout {
contextMenu: AccountContextMenu { userId: model.id } contextMenu: AccountContextMenu { userId: model.id }
onLeftClicked: { onLeftClicked: roomList.goToAccount(model.id)
model.id in roomList.sectionIndice ?
roomList.goToAccount(model.id) :
pageLoader.showPage("AddChat/AddChat", {userId: model.id})
}
} }
highlight: Item { highlight: Item {
readonly property alias border: border
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: theme.accountsBar.accountList.account.selectedBackground color: theme.accountsBar.accountList.account.selectedBackground
@ -112,7 +117,7 @@ HColumnLayout {
} }
Rectangle { Rectangle {
z: 100 id: border
width: theme.accountsBar.accountList.account.selectedBorderSize width: theme.accountsBar.accountList.account.selectedBorderSize
height: parent.height height: parent.height
color: theme.accountsBar.accountList.account.selectedBorder color: theme.accountsBar.accountList.account.selectedBorder

View File

@ -3,6 +3,7 @@
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import QtQml.Models 2.12 import QtQml.Models 2.12
import Qt.labs.qmlmodels 1.0
import ".." import ".."
import "../Base" import "../Base"
@ -10,82 +11,96 @@ HListView {
id: roomList id: roomList
model: ModelStore.get("all_rooms") model: ModelStore.get("all_rooms")
delegate: Room { delegate: DelegateChooser {
id: room role: "type"
width: roomList.width
onActivated: showRoomAtIndex(model.index)
}
section.property: "for_account" DelegateChoice {
section.labelPositioning: roleValue: "Account"
ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart Account { width: roomList.width }
}
section.delegate: Account { DelegateChoice {
width: roomList.width roleValue: "Room"
accountModel: ModelStore.get("accounts").find(section) Room {
width: roomList.width
onActivated: showItemAtIndex(model.index)
}
}
} }
onFilterChanged: py.callCoro("set_substring_filter", ["all_rooms", filter]) onFilterChanged: py.callCoro("set_substring_filter", ["all_rooms", filter])
property string filter: "" property string filter: ""
readonly property var sectionIndice: { readonly property var accountIndice: {
const sections = {} const accounts = {}
let currentUserId = null
for (let i = 0; i < model.count; i++) { for (let i = 0; i < model.count; i++) {
const userId = model.get(i).for_account if (model.get(i).type === "Account")
accounts[model.get(i).id] = i
if (userId !== currentUserId) {
sections[userId] = i
currentUserId = userId
}
} }
return sections return accounts
} }
function goToAccount(userId) { function goToAccount(userId) {
currentIndex = sectionIndice[userId] model.get(accountIndice[userId] + 1).type === "Room" ?
currentIndex = accountIndice[userId] + 1 :
currentIndex = accountIndice[userId]
showItemLimiter.restart()
} }
function goToAccountNumber(num) { function goToAccountNumber(num) {
currentIndex = Object.values(sectionIndice).sort()[num] const index = Object.values(accountIndice).sort()[num]
model.get(index + 1).type === "Room" ?
currentIndex = index + 1 :
currentIndex = index
showItemLimiter.restart()
} }
function showRoomAtIndex(index=currentIndex) { function showItemAtIndex(index=currentIndex) {
if (index === -1) index = 0 if (index === -1) index = 0
index = Math.min(index, model.count - 1) index = Math.min(index, model.count - 1)
const room = model.get(index) const item = model.get(index)
pageLoader.showRoom(room.for_account, room.id)
item.type === "Account" ?
pageLoader.showPage(
"AccountSettings/AccountSettings", { "userId": item.id }
) :
pageLoader.showRoom(item.for_account, item.id)
currentIndex = index currentIndex = index
} }
function showAccountRoomAtIndex(index) { function showAccountRoomAtIndex(index) {
const currentUserId = model.get( const item = model.get(currentIndex === -1 ? 0 : currentIndex)
currentIndex === -1 ? 0 : currentIndex
).for_account
showRoomAtIndex(sectionIndice[currentUserId] + index) const currentUserId =
item.type === "Account" ? item.id : item.for_account
showItemAtIndex(accountIndice[currentUserId] + 1 + index)
} }
Timer { Timer {
id: showRoomLimiter id: showItemLimiter
interval: 200 interval: 200
onTriggered: showRoomAtIndex() onTriggered: showItemAtIndex()
} }
HShortcut { HShortcut {
sequences: window.settings.keys.goToPreviousRoom sequences: window.settings.keys.goToPreviousRoom
onActivated: { decrementCurrentIndex(); showRoomLimiter.restart() } onActivated: { decrementCurrentIndex(); showItemLimiter.restart() }
} }
HShortcut { HShortcut {
sequences: window.settings.keys.goToNextRoom sequences: window.settings.keys.goToNextRoom
onActivated: { incrementCurrentIndex(); showRoomLimiter.restart() } onActivated: { incrementCurrentIndex(); showItemLimiter.restart() }
} }
Repeater { Repeater {