Make EditAccount show a spinner until ready

Instead of crashing if userInfo is not yet available.

statusMessage is removed for now from UserUpdated events,
and the users model items will have a "loading" property.
This commit is contained in:
miruka 2019-07-21 07:14:16 -04:00
parent 71f78feec6
commit 853bb350b4
13 changed files with 53 additions and 62 deletions

View File

@ -3,7 +3,7 @@
import asyncio import asyncio
import random import random
from typing import Dict, Optional, Set, Tuple from typing import Any, Dict, Optional, Set, Tuple
from .app import App from .app import App
from .events import users from .events import users
@ -18,6 +18,7 @@ class Backend:
from . import config_files from . import config_files
self.saved_accounts = config_files.Accounts(self) self.saved_accounts = config_files.Accounts(self)
self.ui_settings = config_files.UISettings(self) self.ui_settings = config_files.UISettings(self)
self.ui_state = config_files.UIState(self)
self.clients: Dict[str, MatrixClient] = {} self.clients: Dict[str, MatrixClient] = {}
@ -92,7 +93,14 @@ class Backend:
# General functions # General functions
async def load_settings(self) -> Tuple[Dict[str, Any], ...]:
return (await self.ui_settings.read(), await self.ui_state.read())
async def request_user_update_event(self, user_id: str) -> None: async def request_user_update_event(self, user_id: str) -> None:
if not self.clients:
return
client = self.clients.get( client = self.clients.get(
user_id, user_id,
random.choice(tuple(self.clients.values())) random.choice(tuple(self.clients.values()))

View File

@ -18,7 +18,7 @@ WRITE_LOCK = asyncio.Lock()
@dataclass @dataclass
class ConfigFile: class ConfigFile:
backend: Backend = field() backend: Backend = field(repr=False)
filename: str = field() filename: str = field()
use_data_dir: bool = False use_data_dir: bool = False

View File

@ -29,8 +29,6 @@ class UserUpdated(Event):
user_id: str = field() user_id: str = field()
display_name: str = "" display_name: str = ""
avatar_url: str = "" avatar_url: str = ""
status_message: str = ""
@classmethod @classmethod
def from_nio(cls, user: MatrixUser) -> "UserUpdated": def from_nio(cls, user: MatrixUser) -> "UserUpdated":

View File

@ -131,7 +131,6 @@ class MatrixClient(nio.AsyncClient):
async def request_user_update_event(self, user_id: str) -> None: async def request_user_update_event(self, user_id: str) -> None:
if user_id in self.backend.pending_profile_requests: if user_id in self.backend.pending_profile_requests:
return return
print("Requesting profile for", user_id)
self.backend.pending_profile_requests.add(user_id) self.backend.pending_profile_requests.add(user_id)
response = await self.get_profile(user_id) response = await self.get_profile(user_id)
@ -143,7 +142,6 @@ class MatrixClient(nio.AsyncClient):
user_id = user_id, user_id = user_id,
display_name = getattr(response, "displayname", "") or "", display_name = getattr(response, "displayname", "") or "",
avatar_url = getattr(response, "avatar_url", "") or "", avatar_url = getattr(response, "avatar_url", "") or "",
status_message = "", # TODO
) )
self.backend.pending_profile_requests.discard(user_id) self.backend.pending_profile_requests.discard(user_id)

View File

@ -0,0 +1,4 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
BusyIndicator {}

View File

@ -11,8 +11,8 @@ function onAccountDeleted(userId) {
accounts.popWhere({userId}, 1) accounts.popWhere({userId}, 1)
} }
function onUserUpdated(userId, displayName, avatarUrl, statusMessage) { function onUserUpdated(userId, displayName, avatarUrl) {
users.upsert({userId}, {userId, displayName, avatarUrl, statusMessage}) users.upsert({userId}, {userId, displayName, avatarUrl, loading: false})
} }
function onDeviceUpdated(userId, deviceId, ed25519Key, trust, displayName, function onDeviceUpdated(userId, deviceId, ed25519Key, trust, displayName,

View File

@ -2,12 +2,12 @@
// This file is part of harmonyqml, licensed under LGPLv3. // This file is part of harmonyqml, licensed under LGPLv3.
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.12 import "Base"
Rectangle { Rectangle {
color: "lightgray" color: "lightgray"
BusyIndicator { HBusyIndicator {
anchors.centerIn: parent anchors.centerIn: parent
} }
} }

View File

@ -20,7 +20,7 @@ HListModel {
userId, userId,
displayName: "", displayName: "",
avatarUrl: "", avatarUrl: "",
statusMessage: "", loading: true,
} }
} }
} }

View File

@ -15,13 +15,17 @@ HPage {
property string userId: "" property string userId: ""
readonly property var userInfo: users.find(userId) readonly property var userInfo: users.find(userId)
readonly property bool ready: userInfo && ! userInfo.loading
hideHeaderUnderHeight: avatarPreferredSize hideHeaderUnderHeight: avatarPreferredSize
headerLabel.text: qsTr("Account settings for %1") headerLabel.text:
.arg(Utils.coloredNameHtml(userInfo.displayName, userId)) qsTr("Account settings for %1").arg(
Utils.coloredNameHtml(userInfo ? userInfo.displayName : "", userId)
)
HRectangle { HRectangle {
color: theme.box.background color: ready ? theme.box.background : "transparent"
Behavior on color { HColorAnimation {} }
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
@ -31,18 +35,9 @@ HPage {
Layout.preferredHeight: childrenRect.height Layout.preferredHeight: childrenRect.height
Profile { width: parent.width } Loader {
width: parent.width
source: ready ? "Profile.qml" : "../../Base/HBusyIndicator.qml"
}
} }
// HRectangle {
// color: theme.box.background
// radius: theme.box.radius
// ClientSettings { width: parent.width }
// }
// HRectangle {
// color: theme.box.background
// radius: theme.box.radius
// Devices { width: parent.width }
// }
} }

View File

@ -33,9 +33,9 @@ Python {
call("APP.call_client_coro", [accountId, name, uuid, args]) call("APP.call_client_coro", [accountId, name, uuid, args])
} }
function saveSettings(callback=null) { function saveConfig(backend_attribute, data, callback=null) {
if (! py.ready) { return } // config not loaded yet if (! py.ready) { return } // config not loaded yet
callCoro("ui_settings.write", [window.settings], callback) callCoro(backend_attribute + ".write", [data], callback)
} }
Component.onCompleted: { Component.onCompleted: {
@ -51,8 +51,9 @@ Python {
call("APP.is_debug_on", [Qt.application.arguments], on => { call("APP.is_debug_on", [Qt.application.arguments], on => {
window.debug = on window.debug = on
callCoro("ui_settings.read", [], settings => { callCoro("load_settings", [], ([settings, uiState]) => {
window.settings = settings window.settings = settings
window.uiState = uiState
callCoro("saved_accounts.any_saved", [], any => { callCoro("saved_accounts.any_saved", [], any => {
py.ready = true py.ready = true

View File

@ -53,7 +53,7 @@ Column {
visible: false // TODO visible: false // TODO
id: statusEdit id: statusEdit
text: userInfo.statusMessage // text: userInfo.statusMessage
placeholderText: qsTr("Set status message") placeholderText: qsTr("Set status message")
font.pixelSize: theme.fontSize.small font.pixelSize: theme.fontSize.small
background: null background: null

View File

@ -14,8 +14,8 @@ Item {
Connections { Connections {
target: py target: py
onWillLoadAccounts: will => { onWillLoadAccounts: will => {
pageStack.showPage(will ? "Default": "SignIn") if (! will) { pageStack.showPage("SignIn") }
// if (will) { initialRoomTimer.start() } pageStack.show(window.uiState.page)
} }
} }
@ -60,33 +60,17 @@ Item {
id: pageStack id: pageStack
property bool isWide: width > theme.contentIsWideAbove property bool isWide: width > theme.contentIsWideAbove
function show(componentUrl, properties={}) {
pageStack.replace(componentUrl, properties)
}
function showPage(name, properties={}) { function showPage(name, properties={}) {
pageStack.replace("Pages/" + name + ".qml", properties) show("Pages/" + name + ".qml", properties)
} }
function showRoom(userId, category, roomId) { function showRoom(userId, category, roomId) {
let info = rooms.getWhere({userId, roomId, category}, 1)[0] let info = rooms.getWhere({userId, roomId, category}, 1)[0]
pageStack.replace("Chat/Chat.qml", {"roomInfo": info}) show("Chat/Chat.qml", {"roomInfo": info})
}
Timer {
// TODO: remove this, debug
id: initialRoomTimer
interval: 4000
repeat: false
// onTriggered: pageStack.showRoom(
// "@test_mary:matrix.org",
// "Rooms",
// "!TSXGsbBbdwsdylIOJZ:matrix.org" // st
// "!VDSsFIzQnXARSCVNxS:matrix.org" // hs
// "!XhxUcnVhVhUHkBZEIL:matrix.org" // nc
// "Invites",
// "!xjqvLOGhMVutPXpAqi:matrix.org"
// )
onTriggered: pageStack.showPage(
"EditAccount/EditAccount",
{"userId": "@test_mary:matrix.org"}
)
} }
onCurrentItemChanged: if (currentItem) { onCurrentItemChanged: if (currentItem) {

View File

@ -27,9 +27,12 @@ ApplicationWindow {
property bool debug: false property bool debug: false
property bool ready: false property bool ready: false
// Note: window.settingsChanged() must be called manually // Note: settingsChanged(), uiStateChanged(), etc must be called manually
property var settings: ({}) property var settings: ({})
onSettingsChanged: py.saveSettings() onSettingsChanged: py.saveConfig("ui_settings", settings)
property var uiState: ({})
onUiStateChanged: py.saveConfig("ui_state", uiState)
Theme { id: theme } Theme { id: theme }
Shortcuts { id: shortcuts} Shortcuts { id: shortcuts}