From 246058e647b5de31e87e7946b777c0d54458c791 Mon Sep 17 00:00:00 2001 From: miruka Date: Sun, 21 Jul 2019 08:38:49 -0400 Subject: [PATCH] Make Chat show spinner until ready Like EditAccount, instead of crashing if the room isn't loaded yet. --- src/python/backend.py | 13 +++- src/qml/Chat/Chat.qml | 126 +++++---------------------------- src/qml/Chat/ChatSplitView.qml | 117 ++++++++++++++++++++++++++++++ src/qml/EventHandlers/rooms.js | 1 + src/qml/Models/Rooms.qml | 20 ++++++ src/qml/Models/Users.qml | 4 +- src/qml/Python.qml | 8 ++- src/qml/UI.qml | 4 +- 8 files changed, 178 insertions(+), 115 deletions(-) create mode 100644 src/qml/Chat/ChatSplitView.qml diff --git a/src/python/backend.py b/src/python/backend.py index bf6616aa..4bc2dc9c 100644 --- a/src/python/backend.py +++ b/src/python/backend.py @@ -91,6 +91,17 @@ class Backend: )) + async def wait_until_client_exists(self, user_id: str = "") -> None: + while True: + if user_id and user_id in self.clients: + return + + if not user_id and self.clients: + return + + await asyncio.sleep(0.1) + + # General functions async def load_settings(self) -> Tuple[Dict[str, Any], ...]: @@ -99,7 +110,7 @@ class Backend: async def request_user_update_event(self, user_id: str) -> None: if not self.clients: - return + await self.wait_until_client_exists() client = self.clients.get( user_id, diff --git a/src/qml/Chat/Chat.qml b/src/qml/Chat/Chat.qml index 9d7d486e..d92ffbdd 100644 --- a/src/qml/Chat/Chat.qml +++ b/src/qml/Chat/Chat.qml @@ -4,13 +4,11 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import "../Base" -import "Banners" -import "Timeline" -import "RoomSidePane" HPage { id: chatPage - onFocusChanged: sendBox.setFocus() + + property bool ready: roomInfo && ! roomInfo.loading property var roomInfo: null onRoomInfoChanged: if (! roomInfo) { pageStack.showPage("Default") } @@ -27,120 +25,34 @@ HPage { header: RoomHeader { id: roomHeader - width: parent.width displayName: roomInfo.displayName topic: roomInfo.topic + + clip: height < implicitHeight + width: parent.width + height: ready ? implicitHeight : 0 + Behavior on height { HNumberAnimation {} } } page.leftPadding: 0 page.rightPadding: 0 - HSplitView { - id: chatSplitView - Layout.fillWidth: true - Layout.fillHeight: true - HColumnLayout { - Layout.fillWidth: true - - EventList { - Layout.fillWidth: true - Layout.fillHeight: true - } - - TypingMembersBar { - Layout.fillWidth: true - } - - InviteBanner { - visible: category == "Invites" - inviterId: roomInfo.inviterId - - Layout.fillWidth: true - } - - //UnknownDevicesBanner { - //visible: category == "Rooms" && hasUnknownDevices - // - //Layout.fillWidth: true - //} - - SendBox { - id: sendBox - visible: category == "Rooms" && ! hasUnknownDevices - } - - LeftBanner { - visible: category == "Left" - userId: chatPage.userId - - Layout.fillWidth: true + Loader { + Timer { + interval: 200 + repeat: true + running: ! ready + onTriggered: { + let info = rooms.find(userId, category, roomId) + if (! info.loading) { roomInfo = Qt.binding(() => info) } } } - RoomSidePane { - id: roomSidePane + source: ready ? "ChatSplitView.qml" : "../Base/HBusyIndicator.qml" - activeView: roomHeader.activeButton - property int oldWidth: width - onActiveViewChanged: - activeView ? restoreAnimation.start() : hideAnimation.start() - - HNumberAnimation { - id: hideAnimation - target: roomSidePane - properties: "width" - from: target.width - to: 0 - - onStarted: { - target.oldWidth = target.width - target.Layout.minimumWidth = 0 - } - } - - HNumberAnimation { - id: restoreAnimation - target: roomSidePane - properties: "width" - from: 0 - to: target.oldWidth - - onStopped: target.Layout.minimumWidth = Qt.binding( - () => theme.avatar.size - ) - } - - collapsed: width < theme.avatar.size + theme.spacing - - property bool wasSnapped: false - property int referenceWidth: roomHeader.buttonsWidth - onReferenceWidthChanged: { - if (! chatSplitView.manuallyResized || wasSnapped) { - if (wasSnapped) { chatSplitView.manuallyResized = false } - width = referenceWidth - } - } - - property int currentWidth: width - onCurrentWidthChanged: { - if (referenceWidth != width && - referenceWidth - 15 < width && - width < referenceWidth + 15) - { - currentWidth = referenceWidth - width = referenceWidth - wasSnapped = true - currentWidth = Qt.binding(() => roomSidePane.width) - } else { - wasSnapped = false - } - } - - width: referenceWidth // Initial width - Layout.minimumWidth: theme.avatar.size - Layout.maximumWidth: - parent.width - theme.minimumSupportedWidthPlusSpacing - } + Layout.fillWidth: ready + Layout.fillHeight: ready + Layout.alignment: Qt.AlignCenter } } diff --git a/src/qml/Chat/ChatSplitView.qml b/src/qml/Chat/ChatSplitView.qml new file mode 100644 index 00000000..abf63f7e --- /dev/null +++ b/src/qml/Chat/ChatSplitView.qml @@ -0,0 +1,117 @@ +// Copyright 2019 miruka +// This file is part of harmonyqml, licensed under LGPLv3. + +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import "../Base" +import "Banners" +import "Timeline" +import "RoomSidePane" + +HSplitView { + id: chatSplitView + Component.onCompleted: sendBox.setFocus() + + HColumnLayout { + Layout.fillWidth: true + + EventList { + Layout.fillWidth: true + Layout.fillHeight: true + } + + TypingMembersBar { + Layout.fillWidth: true + } + + InviteBanner { + visible: category == "Invites" + inviterId: roomInfo.inviterId + + Layout.fillWidth: true + } + + //UnknownDevicesBanner { + //visible: category == "Rooms" && hasUnknownDevices + // + //Layout.fillWidth: true + //} + + SendBox { + id: sendBox + visible: category == "Rooms" && ! hasUnknownDevices + } + + LeftBanner { + visible: category == "Left" + userId: chatPage.userId + + Layout.fillWidth: true + } + } + + RoomSidePane { + id: roomSidePane + + activeView: roomHeader.activeButton + property int oldWidth: width + onActiveViewChanged: + activeView ? restoreAnimation.start() : hideAnimation.start() + + HNumberAnimation { + id: hideAnimation + target: roomSidePane + properties: "width" + from: target.width + to: 0 + + onStarted: { + target.oldWidth = target.width + target.Layout.minimumWidth = 0 + } + } + + HNumberAnimation { + id: restoreAnimation + target: roomSidePane + properties: "width" + from: 0 + to: target.oldWidth + + onStopped: target.Layout.minimumWidth = Qt.binding( + () => theme.avatar.size + ) + } + + collapsed: width < theme.avatar.size + theme.spacing + + property bool wasSnapped: false + property int referenceWidth: roomHeader.buttonsWidth + onReferenceWidthChanged: { + if (! chatSplitView.manuallyResized || wasSnapped) { + if (wasSnapped) { chatSplitView.manuallyResized = false } + width = referenceWidth + } + } + + property int currentWidth: width + onCurrentWidthChanged: { + if (referenceWidth != width && + referenceWidth - 15 < width && + width < referenceWidth + 15) + { + currentWidth = referenceWidth + width = referenceWidth + wasSnapped = true + currentWidth = Qt.binding(() => roomSidePane.width) + } else { + wasSnapped = false + } + } + + width: referenceWidth // Initial width + Layout.minimumWidth: theme.avatar.size + Layout.maximumWidth: + parent.width - theme.minimumSupportedWidthPlusSpacing + } +} diff --git a/src/qml/EventHandlers/rooms.js b/src/qml/EventHandlers/rooms.js index b4729ccc..0facac36 100644 --- a/src/qml/EventHandlers/rooms.js +++ b/src/qml/EventHandlers/rooms.js @@ -56,6 +56,7 @@ function onRoomUpdated( else if (category == "Left") { replace = find("Invites") || find("Rooms")} let item = { + loading: false, typingText: typingTextFor(typingMembers, userId), userId, category, roomId, displayName, avatarUrl, topic, members, diff --git a/src/qml/Models/Rooms.qml b/src/qml/Models/Rooms.qml index 43154893..0813fa88 100644 --- a/src/qml/Models/Rooms.qml +++ b/src/qml/Models/Rooms.qml @@ -9,4 +9,24 @@ HListModel { sorters: StringSorter { roleName: "displayName" } + + readonly property ListModel _emptyModel: ListModel {} + + function find(userId, category, roomId) { + if (! userId) { return } + + let found = rooms.getWhere({userId, roomId, category}, 1)[0] + if (found) { return found } + + return { + userId, category, roomId, + displayName: "", + avatarUrl: "", + topic: "", + members: _emptyModel, + typingText: "", + inviterId: "", + loading: true, + } + } } diff --git a/src/qml/Models/Users.qml b/src/qml/Models/Users.qml index b931a9ad..ed07be7a 100644 --- a/src/qml/Models/Users.qml +++ b/src/qml/Models/Users.qml @@ -11,8 +11,8 @@ HListModel { // the expression with invalid data to establish property bindings if (! userId) { return } - let found = getWhere({userId}, 1) - if (found.length > 0) { return found[0] } + let found = getWhere({userId}, 1)[0] + if (found) { return found } py.callCoro("request_user_update_event", [userId]) diff --git a/src/qml/Python.qml b/src/qml/Python.qml index e5f4de37..2d76bd7d 100644 --- a/src/qml/Python.qml +++ b/src/qml/Python.qml @@ -27,10 +27,12 @@ Python { } function callClientCoro(accountId, name, args=[], callback=null) { - let uuid = Math.random() + "." + name + callCoro("wait_until_client_exists", [accountId], () => { + let uuid = Math.random() + "." + name - pendingCoroutines[uuid] = callback || function() {} - call("APP.call_client_coro", [accountId, name, uuid, args]) + pendingCoroutines[uuid] = callback || function() {} + call("APP.call_client_coro", [accountId, name, uuid, args]) + }) } function saveConfig(backend_attribute, data, callback=null) { diff --git a/src/qml/UI.qml b/src/qml/UI.qml index ff80e7e1..f4177599 100644 --- a/src/qml/UI.qml +++ b/src/qml/UI.qml @@ -69,8 +69,8 @@ Item { } function showRoom(userId, category, roomId) { - let info = rooms.getWhere({userId, roomId, category}, 1)[0] - show("Chat/Chat.qml", {"roomInfo": info}) + let roomInfo = rooms.find(userId, category, roomId) + show("Chat/Chat.qml", {roomInfo}) } onCurrentItemChanged: if (currentItem) {