Reorganize sidePane, accounts and rooms

- Accordion design for accounts and rooms (not finished)
- Toolbar and account/room lists reduce correctly, buttons become
  hamburger menu if not enough width
- Can set status using the "Set status message" account fields
- Uniformized avatar sizes for sidePane, roomHeader and SendBox
This commit is contained in:
miruka 2019-03-26 03:19:55 -04:00
parent 16aa6142bb
commit cccc43a9ae
19 changed files with 219 additions and 129 deletions

View File

@ -2,53 +2,83 @@ import QtQuick 2.7
import QtQuick.Controls 2.0 import QtQuick.Controls 2.0
import QtQuick.Layouts 1.4 import QtQuick.Layouts 1.4
Row { ColumnLayout {
readonly property string displayName: id: "accountDelegate"
Backend.getUser(section).display_name spacing: 0
width: parent.width
id: row RowLayout {
width: roomListView.width id: "row"
height: Math.max(accountLabel.height + statusEdit.height, avatar.height) spacing: 0
Avatar { id: avatar; username: displayName; dimmension: 32 } Avatar { id: "avatar"; username: display_name; dimmension: 36 }
Rectangle {
color: "#111"
width: parent.width - avatar.width
height: parent.height
ColumnLayout { ColumnLayout {
anchors.fill: parent Layout.fillWidth: true
spacing: 1 Layout.fillHeight: true
spacing: 0
PlainLabel { PlainLabel {
id: accountLabel id: "accountLabel"
text: displayName text: display_name
color: "#CCC"
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1 maximumLineCount: 1
Layout.fillWidth: true Layout.fillWidth: true
leftPadding: 6
topPadding: -2 rightPadding: leftPadding
bottomPadding: -2
leftPadding: 5
rightPadding: 5
} }
TextField { TextField {
id: statusEdit id: "statusEdit"
text: status_message || ""
placeholderText: qsTr("Set status message") placeholderText: qsTr("Set status message")
background: Rectangle { color: "#333" } background: null
color: "#CCC" color: "black"
selectByMouse: true selectByMouse: true
font.family: "Roboto" font.family: "Roboto"
font.pixelSize: 12 font.pixelSize: 12
Layout.fillWidth: true Layout.fillWidth: true
padding: 0
leftPadding: accountLabel.leftPadding
rightPadding: leftPadding
topPadding: 0 onEditingFinished: {
bottomPadding: 0 Backend.setStatusMessage(user_id, text)
leftPadding: 5 pageStack.forceActiveFocus()
rightPadding: 5 }
}
}
HButton {
id: "toggleExpand"
iconName: roomList.visible ? "up" : "down"
Layout.maximumWidth: 28
Layout.maximumHeight: Layout.maximumWidth
onClicked: {
toggleExpand.ToolTip.hide()
roomList.visible = ! roomList.visible
} }
} }
} }
RoomList {
id: "roomList"
visible: true
user: Backend.getUser(user_id)
Layout.minimumHeight:
roomList.visible ?
roomList.contentHeight + roomList.anchors.margins * 2 :
0
Layout.maximumHeight: Layout.minimumHeight
Layout.minimumWidth: parent.width - Layout.leftMargin * 2
Layout.maximumWidth: Layout.minimumWidth
Layout.margins: accountList.spacing
Layout.leftMargin:
sidePane.width < 36 + Layout.margins ? 0 : Layout.margins
Layout.rightMargin: Layout.leftMargin
}
} }

View File

@ -0,0 +1,11 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
ListView {
id: "accountList"
spacing: 8
model: Backend.accountsModel
delegate: AccountDelegate {}
clip: true
}

View File

@ -2,41 +2,15 @@ import QtQuick 2.7
import QtQuick.Controls 2.2 import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4 import QtQuick.Layouts 1.4
ToolButton { HButton {
property string tooltip: ""
property string iconName: ""
property string targetPage: ""
function toolBarIsBig() { function toolBarIsBig() {
return roomPane.width > return sidePane.width >
Layout.minimumWidth * (toolBar.children.length - 2) Layout.minimumWidth * (toolBar.children.length - 2)
} }
id: "button" id: "button"
display: ToolButton.IconOnly
icon.source: "icons/" + iconName + ".svg"
background: Rectangle { color: "transparent" }
visible: toolBarIsBig() visible: toolBarIsBig()
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: height Layout.minimumWidth: height
onClicked: { toolTip.hide(); pageStack.show_page(targetPage) }
ToolTip {
id: "toolTip"
text: tooltip
delay: Qt.styleHints.mousePressAndHoldInterval
visible: text ? toolTipZone.containsMouse : false
}
MouseArea {
id: toolTipZone
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton // Make button receive clicks normally
onEntered: button.background.color = "#656565"
onExited: button.background.color = "transparent"
}
} }

View File

@ -17,13 +17,11 @@ RowLayout {
ActionButton { ActionButton {
iconName: "settings" iconName: "settings"
tooltip: "Settings" tooltip: "Settings"
targetPage: "SettingsPage"
} }
ActionButton { ActionButton {
iconName: "add_account" iconName: "add_account"
tooltip: "Add new account" tooltip: "Add new account"
targetPage: "AddAccountPage"
} }
ActionButton { ActionButton {
@ -33,7 +31,7 @@ RowLayout {
ActionButton { ActionButton {
iconName: "search" iconName: "search"
tooltip: "Filter rooms and people" tooltip: "Filter rooms"
} }

View File

@ -3,6 +3,7 @@ import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4 import QtQuick.Layouts 1.4
ColumnLayout { ColumnLayout {
property var user: null
property var room: null property var room: null
id: chatPage id: chatPage

31
harmonyqml/HButton.qml Normal file
View File

@ -0,0 +1,31 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
ToolButton {
property string tooltip: ""
property string iconName: ""
id: "button"
display: ToolButton.IconOnly
icon.source: "icons/" + iconName + ".svg"
background: Rectangle { color: "transparent" }
onClicked: toolTip.hide()
ToolTip {
id: "toolTip"
text: tooltip
delay: Qt.styleHints.mousePressAndHoldInterval
visible: text ? toolTipZone.containsMouse : false
}
MouseArea {
id: "toolTipZone"
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton // Make button receive clicks normally
onEntered: button.background.color = "#656565"
onExited: button.background.color = "transparent"
}
}

View File

@ -1,5 +0,0 @@
import QtQuick 2.7
MouseArea {
cursorShape: Qt.PointingHandCursor
}

View File

@ -13,7 +13,7 @@ Column {
Backend.getUser(sender_id).display_name Backend.getUser(sender_id).display_name
readonly property bool isOwn: readonly property bool isOwn:
chatPage.room.account_id === sender_id chatPage.user.user_id === sender_id
readonly property var previousData: readonly property var previousData:
index > 0 ? messageListView.model.get(index - 1) : null index > 0 ? messageListView.model.get(index - 1) : null

View File

@ -2,17 +2,22 @@ import QtQuick 2.7
import QtQuick.Controls 2.0 import QtQuick.Controls 2.0
import QtQuick.Layouts 1.4 import QtQuick.Layouts 1.4
Item { MouseArea {
id: "root" id: "root"
width: roomListView.width width: roomList.width
height: Math.max(roomLabel.height + subtitleLabel.height, avatar.height) height: Math.max(roomLabel.height + subtitleLabel.height, avatar.height)
onClicked: pageStack.show_room(
roomList.user,
roomList.model.get(index)
)
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
id: row id: row
spacing: 1 spacing: 1
Avatar { id: avatar; username: display_name; dimmension: 32 } Avatar { id: avatar; username: display_name; dimmension: 36 }
ColumnLayout { ColumnLayout {
spacing: 0 spacing: 0
@ -48,9 +53,4 @@ Item {
Item { Layout.fillWidth: true } Item { Layout.fillWidth: true }
} }
HMouseArea {
anchors.fill: parent
onClicked: pageStack.show_room(roomListView.model.get(index))
}
} }

View File

@ -5,7 +5,7 @@ import QtQuick.Layouts 1.4
Rectangle { Rectangle {
id: root id: root
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: 32 Layout.minimumHeight: 36
Layout.maximumHeight: Layout.minimumHeight Layout.maximumHeight: Layout.minimumHeight
color: "#BBB" color: "#BBB"

25
harmonyqml/RoomList.qml Normal file
View File

@ -0,0 +1,25 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
ListView {
property var user: null
property int contentHeight: 0
onCountChanged: {
var children = roomList.children
var childrenHeight = 0
for (var i = 0; i < children.length; i++) {
childrenHeight += children[i].height
}
contentHeight = childrenHeight + spacing * (children.length - 1)
}
id: "roomList"
spacing: 8
model: Backend.roomsModel[user.user_id]
delegate: RoomDelegate {}
}

View File

@ -1,30 +0,0 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
Rectangle {
id: roomPane
color: "gray"
clip: true // Avoid artifacts when resizing pane width to minimum
ColumnLayout {
anchors.fill: parent
spacing: 0
TopBar {}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
id: roomListView
spacing: 0
model: Backend.roomsModel
delegate: RoomDelegate {}
//highlight: Rectangle {color: "lightsteelblue"; radius: 5}
section.property: "account_id"
section.delegate: AccountDelegate {}
}
}
}

View File

@ -19,9 +19,10 @@ Rectangle {
Avatar { Avatar {
id: "avatar" id: "avatar"
username: Backend.getUser(chatPage.room.account_id).display_name username: chatPage.user.display_name
dimmension: root.Layout.minimumHeight dimmension: root.Layout.minimumHeight
visible: textArea.text === "" //visible: textArea.text === ""
visible: textArea.height <= root.Layout.minimumHeight
} }
ScrollView { ScrollView {
@ -49,7 +50,7 @@ Rectangle {
return return
} }
Backend.sendMessage(chatPage.room.account_id, Backend.sendMessage(chatPage.user.user_id,
chatPage.room.room_id, chatPage.room.room_id,
textArea.text) textArea.text)
textArea.clear() textArea.clear()

21
harmonyqml/SidePane.qml Normal file
View File

@ -0,0 +1,21 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.4
Rectangle {
id: sidePane
color: "gray"
clip: true // Avoid artifacts when resizing pane width to minimum
ColumnLayout {
anchors.fill: parent
spacing: 0
AccountList {
Layout.fillWidth: true
Layout.fillHeight: true
}
ButtonsBar {}
}
}

View File

@ -6,21 +6,26 @@ import QtQuick.Layouts 1.4
Controls1.SplitView { Controls1.SplitView {
anchors.fill: parent anchors.fill: parent
RoomPane { SidePane {
Layout.minimumWidth: 32 Layout.minimumWidth: 36
width: 180 width: 200
} }
StackView { StackView {
function show_page(componentName) { function show_page(componentName) {
pageStack.replace(componentName + ".qml") pageStack.replace(componentName + ".qml")
} }
function show_room(room_obj) { function show_room(user_obj, room_obj) {
pageStack.replace("ChatPage.qml", { room: room_obj }) pageStack.replace(
"ChatPage.qml", { user: user_obj, room: room_obj }
)
} }
id: "pageStack" id: "pageStack"
initialItem: ChatPage { room: Backend.roomsModel.get(0) } initialItem: ChatPage {
user: Backend.accountsModel.get(0)
room: Backend.roomsModel[Backend.accountsModel.get(0).user_id].get(0)
}
onCurrentItemChanged: currentItem.forceActiveFocus() onCurrentItemChanged: currentItem.forceActiveFocus()

View File

@ -11,13 +11,13 @@ from .list_model import ListModel, _QtListModel
class User(NamedTuple): class User(NamedTuple):
user_id: str user_id: str
display_name: str display_name: str
avatar_url: Optional[str] = None avatar_url: Optional[str] = None
status_message: Optional[str] = None
class Room(NamedTuple): class Room(NamedTuple):
account_id: str
room_id: str room_id: str
display_name: str display_name: str
subtitle: str = "" subtitle: str = ""
@ -41,13 +41,19 @@ class Backend(QObject):
super().__init__() super().__init__()
self._known_users: Dict[str, User] = {} self._known_users: Dict[str, User] = {}
self.rooms: ListModel = ListModel() self.accounts: ListModel = ListModel()
self.rooms: DefaultDict[str, ListModel] = DefaultDict(ListModel)
self.messages: DefaultDict[str, ListModel] = DefaultDict(ListModel) self.messages: DefaultDict[str, ListModel] = DefaultDict(ListModel)
@pyqtProperty(_QtListModel, constant=True) @pyqtProperty(_QtListModel, constant=True)
def roomsModel(self) -> _QtListModel: def accountsModel(self) -> _QtListModel:
return self.rooms.qt_model return self.accounts.qt_model
@pyqtProperty("QVariantMap", constant=True)
def roomsModel(self) -> Dict[str, _QtListModel]:
return {account_id: l.qt_model for account_id, l in self.rooms.items()}
@pyqtProperty("QVariantMap", constant=True) @pyqtProperty("QVariantMap", constant=True)
@ -73,6 +79,10 @@ class Backend(QObject):
@pyqtSlot(str, result="QVariantMap") @pyqtSlot(str, result="QVariantMap")
def getUser(self, user_id: str) -> Dict[str, Any]: def getUser(self, user_id: str) -> Dict[str, Any]:
for user in self.accounts:
if user.user_id == user_id:
return user._asdict()
try: try:
return self._known_users[user_id]._asdict() return self._known_users[user_id]._asdict()
except KeyError: except KeyError:
@ -87,3 +97,13 @@ class Backend(QObject):
# pylint: disable=no-self-use # pylint: disable=no-self-use
md5 = hashlib.md5(bytes(string, "utf-8")).hexdigest() md5 = hashlib.md5(bytes(string, "utf-8")).hexdigest()
return float("0.%s" % int(md5[-10:], 16)) return float("0.%s" % int(md5[-10:], 16))
@pyqtSlot(str, str)
def setStatusMessage(self, user_id: str, to: str) -> None:
for user in self.accounts:
if user.user_id == user_id:
user.status_message = to
break
else:
raise ValueError(f"{user_id} not found in Backend.accounts")

View File

@ -3,7 +3,7 @@
from PyQt5.QtCore import QDateTime, Qt from PyQt5.QtCore import QDateTime, Qt
from .base import Backend, Message, Room from .base import Backend, Message, Room, User
class DummyBackend(Backend): class DummyBackend(Backend):
@ -15,16 +15,22 @@ class DummyBackend(Backend):
db = lambda t: QDateTime.fromString(f"2019-03-20T{t}.456", db = lambda t: QDateTime.fromString(f"2019-03-20T{t}.456",
Qt.ISODateWithMs) Qt.ISODateWithMs)
self.rooms.extend([ self.accounts.extend([
Room("@renko:matrix.org", "!test:matrix.org", "Test", "Test room"), User("@renko:matrix.org", "Renko", None, "Sleeping, zzz..."),
Room("@renko:matrix.org", "!mary:matrix.org", "Mary", User("@mary:matrix.org", "Mary"),
])
self.rooms["@renko:matrix.org"].extend([
Room("!test:matrix.org", "Test", "Test room"),
Room("!mary:matrix.org", "Mary",
"Lorem ipsum sit dolor amet this is a long text to test " "Lorem ipsum sit dolor amet this is a long text to test "
"wrapping of room subtitle etc 1234 example foo bar abc", 2), "wrapping of room subtitle etc 1234 example foo bar abc", 2),
Room("@renko:matrix.org", "!foo:matrix.org", "Another room"), Room("!foo:matrix.org", "Another room"),
])
Room("@mary:matrix.org", "!test:matrix.org", "Test", "Test room"), self.rooms["@mary:matrix.org"].extend([
Room("@mary:matrix.org", "!mary:matrix.org", "Renko", Room("!test:matrix.org", "Test", "Test room"),
"Lorem ipsum sit dolor amet"), Room("!mary:matrix.org", "Renko", "Lorem ipsum sit dolor amet"),
]) ])
self.messages["!test:matrix.org"].extend([ self.messages["!test:matrix.org"].extend([

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 7.33l2.829-2.83 9.175 9.339 9.167-9.339 2.829 2.83-11.996 12.17z"/></svg>

After

Width:  |  Height:  |  Size: 168 B

1
harmonyqml/icons/up.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 16.67l2.829 2.83 9.175-9.339 9.167 9.339 2.829-2.83-11.996-12.17z"/></svg>

After

Width:  |  Height:  |  Size: 169 B