New backend work
Models, account connection, fetching user profiles, show connected accounts in sidebar
This commit is contained in:
parent
e5bdf6a497
commit
a1b4d8900f
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
__pycache__
|
||||
.mypy_cache
|
||||
build
|
||||
dist
|
||||
*.egg-info
|
||||
*.pyc
|
||||
*.qmlc
|
||||
*.jsc
|
||||
|
||||
.pylintrc
|
||||
|
||||
tmp-*
|
98
TODO.md
98
TODO.md
|
@ -0,0 +1,98 @@
|
|||
- license headers
|
||||
- replace "property var" by "property <object>" where applicable
|
||||
- [debug mode](https://docs.python.org/3/library/asyncio-dev.html)
|
||||
|
||||
OLD
|
||||
|
||||
- Refactoring
|
||||
- Migrate more JS functions to their own files / Implement in Python instead
|
||||
- Don't bake in size properties for components
|
||||
|
||||
- Bug fixes
|
||||
- dataclass-like `default_factory` for ListItem
|
||||
- Prevent briefly seeing login screen if there are accounts to
|
||||
resumeSession for but they take time to appear
|
||||
- 100% CPU usage when hitting top edge to trigger messages loading
|
||||
- Sending `![A picture](https://picsum.photos/256/256)` → not clickable?
|
||||
- Icons, images and HStyle singleton aren't reloaded
|
||||
- `MessageDelegate.qml:63: TypeError: 'reloadPreviousItem' not a function`
|
||||
- RoomEventsList scrolling when resizing the window
|
||||
|
||||
- UI
|
||||
- Invite to room
|
||||
- Accounts delegates background
|
||||
- SidePane delegates hover effect
|
||||
- Server selection
|
||||
- Register/Forgot? for SignIn dialog
|
||||
- Scaling
|
||||
- See [Text.fontSizeMode](https://doc.qt.io/qt-5/qml-qtquick-text.html#fontSizeMode-prop)
|
||||
- Add room
|
||||
- Leave room
|
||||
- Forget room warning popup
|
||||
- Prevent using the SendBox if no permission (power levels)
|
||||
- Spinner when loading past room events, images or clicking buttons
|
||||
- Better theming/styling system
|
||||
- See about <https://doc.qt.io/qt-5/qtquickcontrols2-configuration.html>
|
||||
- Settings page
|
||||
- Multiaccount aliases
|
||||
- Message/text selection
|
||||
|
||||
- Major features
|
||||
- E2E
|
||||
- Device verification
|
||||
- Edit/delete own devices
|
||||
- Request room keys from own other devices
|
||||
- Auto-trust accounts within the same client
|
||||
- Import/export keys
|
||||
- Uploads
|
||||
- QQuickImageProvider
|
||||
- Read receipts
|
||||
- Status message and presence
|
||||
- Links preview
|
||||
|
||||
- Client improvements
|
||||
- Filtering rooms: search more than display names?
|
||||
- nio.MatrixRoom has `typing_users`, no need to handle it on our own
|
||||
- Initial sync filter and lazy load, see weechat-matrix `_handle_login()`
|
||||
- See also `handle_response()`'s `keys_query` request
|
||||
- HTTP/2
|
||||
- `retry_after_ms` when rate-limited
|
||||
- Direct chats category
|
||||
- On sync, check messages API, if a limited sync timeline was received
|
||||
- Markdown: don't turn #things (no space) and `thing\n---` into title,
|
||||
disable `__` syntax for bold/italic
|
||||
- Push instead of replacing in stack view (remove getMemberFilter when done)
|
||||
- Make links in room subtitle clickable, formatting?
|
||||
- `<pre>` scrollbar on overflow
|
||||
- Handle cases where an avatar char is # or @ (#alias room, @user\_id)
|
||||
- When inviting someone to direct chat, room is "Empty room" until accepted,
|
||||
it should be the peer's display name instead.
|
||||
- Keep an accounts order
|
||||
- See `Qt.callLater()` potential usages
|
||||
- Banner name color instead of bold
|
||||
- Animate RoomEventDelegate DayBreak apparition
|
||||
|
||||
- Missing nio support
|
||||
- MatrixRoom invited members list
|
||||
- Invite events are missing their timestamps (needed for sorting)
|
||||
- Left room events after client reboot
|
||||
- `org.matrix.room.preview_urls` event
|
||||
- `m.room.aliases` event
|
||||
- Support "Empty room (was ...)" after peer left
|
||||
|
||||
- Waiting for approval/release
|
||||
- nio avatars
|
||||
- olm/olm-devel 0.3.1 in void repos
|
||||
|
||||
- Distribution
|
||||
- Review setup.py, add dependencies
|
||||
- README.md
|
||||
- Use PyInstaller or pyqtdeploy
|
||||
- Test command:
|
||||
```
|
||||
pyinstaller --onefile --windowed --name harmonyqml \
|
||||
--add-data 'harmonyqml/components:harmonyqml/components' \
|
||||
--additional-hooks-dir . \
|
||||
--upx-dir ~/opt/upx-3.95-amd64_linux \
|
||||
run.py
|
||||
```
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
35
src/app.py
35
src/app.py
|
@ -26,7 +26,7 @@ class App:
|
|||
debug = False
|
||||
|
||||
if "-d" in cli_flags or "--debug" in cli_flags:
|
||||
self._run_in_loop(self._exit_on_app_file_change())
|
||||
self.run_in_loop(self._exit_on_app_file_change())
|
||||
debug = True
|
||||
|
||||
from .backend import Backend
|
||||
|
@ -47,28 +47,43 @@ class App:
|
|||
self.loop.run_forever()
|
||||
|
||||
|
||||
def _run_in_loop(self, coro: Coroutine) -> Future:
|
||||
def run_in_loop(self, coro: Coroutine) -> Future:
|
||||
return asyncio.run_coroutine_threadsafe(coro, self.loop)
|
||||
|
||||
|
||||
def _call_coro(self, coro: Coroutine) -> str:
|
||||
uuid = str(uuid4())
|
||||
|
||||
self.run_in_loop(coro).add_done_callback(
|
||||
lambda future: CoroutineDone(uuid=uuid, result=future.result())
|
||||
)
|
||||
return uuid
|
||||
|
||||
|
||||
def call_backend_coro(self,
|
||||
name: str,
|
||||
args: Optional[List[str]] = None,
|
||||
kwargs: Optional[Dict[str, Any]] = None) -> str:
|
||||
# To be used from QML
|
||||
|
||||
coro = getattr(self.backend, name)(*args or [], **kwargs or {})
|
||||
uuid = str(uuid4())
|
||||
|
||||
self._run_in_loop(coro).add_done_callback(
|
||||
lambda future: CoroutineDone(uuid=uuid, result=future.result())
|
||||
return self._call_coro(
|
||||
getattr(self.backend, name)(*args or [], **kwargs or {})
|
||||
)
|
||||
|
||||
|
||||
def call_client_coro(self,
|
||||
account_id: str,
|
||||
name: str,
|
||||
args: Optional[List[str]] = None,
|
||||
kwargs: Optional[Dict[str, Any]] = None) -> str:
|
||||
client = self.backend.clients[account_id] # type: ignore
|
||||
return self._call_coro(
|
||||
getattr(client, name)(*args or [], **kwargs or {})
|
||||
)
|
||||
return uuid
|
||||
|
||||
|
||||
def pdb(self, additional_data: Sequence = ()) -> None:
|
||||
# pylint: disable=all
|
||||
ad = additional_data
|
||||
rl = self.run_in_loop
|
||||
ba = self.backend
|
||||
cl = self.backend.clients # type: ignore
|
||||
tcl = lambda user: cl[f"@test_{user}:matrix.org"]
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, Tuple
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
from atomicfile import AtomicFile
|
||||
|
||||
from .app import App
|
||||
from .events import users
|
||||
from .matrix_client import MatrixClient
|
||||
|
||||
SavedAccounts = Dict[str, Dict[str, str]]
|
||||
|
@ -34,6 +35,7 @@ class Backend:
|
|||
)
|
||||
await client.login(password)
|
||||
self.clients[client.user_id] = client
|
||||
users.AccountUpdated(client.user_id)
|
||||
|
||||
|
||||
async def resume_client(self,
|
||||
|
@ -46,12 +48,14 @@ class Backend:
|
|||
)
|
||||
await client.resume(user_id=user_id, token=token, device_id=device_id)
|
||||
self.clients[client.user_id] = client
|
||||
users.AccountUpdated(client.user_id)
|
||||
|
||||
|
||||
async def logout_client(self, user_id: str) -> None:
|
||||
client = self.clients.pop(user_id, None)
|
||||
if client:
|
||||
await client.close()
|
||||
await client.logout()
|
||||
users.AccountDeleted(user_id)
|
||||
|
||||
|
||||
async def logout_all_clients(self) -> None:
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -8,18 +8,22 @@ from .event import Event
|
|||
|
||||
@dataclass
|
||||
class RoomUpdated(Event):
|
||||
user_id: str = field()
|
||||
category: str = field()
|
||||
room_id: str = field()
|
||||
display_name: Optional[str] = None
|
||||
avatar_url: Optional[str] = None
|
||||
topic: Optional[str] = None
|
||||
last_event_date: Optional[datetime] = None
|
||||
|
||||
inviter: Optional[Dict[str, str]] = None
|
||||
inviter: Optional[str] = None
|
||||
left_event: Optional[Dict[str, str]] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoomDeleted(Event):
|
||||
user_id: str = field()
|
||||
category: str = field()
|
||||
room_id: str = field()
|
||||
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ class UserUpdated(Event):
|
|||
user_id: str = field()
|
||||
display_name: Optional[str] = None
|
||||
avatar_url: Optional[str] = None
|
||||
status_message: Optional[str] = None
|
||||
|
||||
|
||||
# Devices
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import asyncio
|
||||
import inspect
|
||||
import platform
|
||||
from contextlib import suppress
|
||||
from typing import Optional
|
||||
|
||||
import nio
|
||||
|
||||
from . import __about__
|
||||
from .events import rooms, users
|
||||
|
||||
|
||||
class MatrixClient(nio.AsyncClient):
|
||||
def __init__(self,
|
||||
|
@ -9,8 +16,12 @@ class MatrixClient(nio.AsyncClient):
|
|||
homeserver: str = "https://matrix.org",
|
||||
device_id: Optional[str] = None) -> None:
|
||||
|
||||
# TODO: ensure homeserver starts with a scheme://
|
||||
self.sync_task: Optional[asyncio.Task] = None
|
||||
super().__init__(homeserver=homeserver, user=user, device_id=device_id)
|
||||
|
||||
self.connect_callbacks()
|
||||
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "%s(user_id=%r, homeserver=%r, device_id=%r)" % (
|
||||
|
@ -18,5 +29,96 @@ class MatrixClient(nio.AsyncClient):
|
|||
)
|
||||
|
||||
|
||||
def connect_callbacks(self) -> None:
|
||||
for name in dir(nio.responses):
|
||||
if name.startswith("_"):
|
||||
continue
|
||||
|
||||
obj = getattr(nio.responses, name)
|
||||
if inspect.isclass(obj) and issubclass(obj, nio.Response):
|
||||
with suppress(AttributeError):
|
||||
self.add_response_callback(getattr(self, f"on{name}"), obj)
|
||||
|
||||
|
||||
async def start_syncing(self) -> None:
|
||||
self.sync_task = asyncio.ensure_future( # type: ignore
|
||||
self.sync_forever(timeout=10_000)
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def default_device_name(self) -> str:
|
||||
os_ = f" on {platform.system()}".rstrip()
|
||||
os_ = f"{os_} {platform.release()}".rstrip() if os_ != " on" else ""
|
||||
return f"{__about__.__pretty_name__}{os_}"
|
||||
|
||||
|
||||
async def login(self, password: str) -> None:
|
||||
response = await super().login(password, self.default_device_name)
|
||||
|
||||
if isinstance(response, nio.LoginError):
|
||||
print(response)
|
||||
else:
|
||||
await self.start_syncing()
|
||||
|
||||
|
||||
async def resume(self, user_id: str, token: str, device_id: str) -> None:
|
||||
self.receive_response(nio.LoginResponse(user_id, device_id, token))
|
||||
await self.start_syncing()
|
||||
|
||||
|
||||
async def logout(self) -> None:
|
||||
if self.sync_task:
|
||||
self.sync_task.cancel()
|
||||
with suppress(asyncio.CancelledError):
|
||||
await self.sync_task
|
||||
|
||||
await self.close()
|
||||
|
||||
|
||||
async def request_user_update_event(self, user_id: str) -> None:
|
||||
response = await self.get_profile(user_id)
|
||||
|
||||
users.UserUpdated(
|
||||
user_id = user_id,
|
||||
display_name = response.displayname,
|
||||
avatar_url = response.avatar_url,
|
||||
status_message = None, # TODO
|
||||
)
|
||||
|
||||
|
||||
# Callbacks for nio responses
|
||||
|
||||
async def onSyncResponse(self, response: nio.SyncResponse) -> None:
|
||||
for room_id in response.rooms.invite:
|
||||
room: nio.rooms.MatrixRoom = self.invited_rooms[room_id]
|
||||
|
||||
rooms.RoomUpdated(
|
||||
user_id = self.user_id,
|
||||
category = "Invites",
|
||||
room_id = room_id,
|
||||
display_name = room.display_name,
|
||||
avatar_url = room.gen_avatar_url,
|
||||
topic = room.topic,
|
||||
inviter = room.inviter,
|
||||
)
|
||||
|
||||
for room_id in response.rooms.join:
|
||||
room = self.rooms[room_id]
|
||||
|
||||
rooms.RoomUpdated(
|
||||
user_id = self.user_id,
|
||||
category = "Rooms",
|
||||
room_id = room_id,
|
||||
display_name = room.display_name,
|
||||
avatar_url = room.gen_avatar_url,
|
||||
topic = room.topic,
|
||||
)
|
||||
|
||||
for room_id in response.rooms.left:
|
||||
rooms.RoomUpdated(
|
||||
user_id = self.user_id,
|
||||
category = "Left",
|
||||
room_id = room_id,
|
||||
# left_event TODO
|
||||
)
|
||||
|
|
|
@ -37,16 +37,27 @@ ListModel {
|
|||
return results
|
||||
}
|
||||
|
||||
function upsert(where_role, is, new_item) {
|
||||
function forEachWhere(where_role, is, max, func) {
|
||||
var items = getWhere(where_role, is, max)
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
func(item)
|
||||
}
|
||||
}
|
||||
|
||||
function upsert(where_role, is, new_item, update_if_exist) {
|
||||
// new_item can contain only the keys we're interested in updating
|
||||
|
||||
var indices = getIndices(where_role, is, 1)
|
||||
|
||||
if (indices.length == 0) {
|
||||
listModel.append(new_item)
|
||||
} else {
|
||||
return listModel.get(listModel.count)
|
||||
}
|
||||
|
||||
if (update_if_exist != false) {
|
||||
listModel.set(indices[0], new_item)
|
||||
}
|
||||
return listModel.get(indices[0])
|
||||
}
|
||||
|
||||
function pop(index) {
|
||||
|
@ -54,4 +65,38 @@ ListModel {
|
|||
listModel.remove(index)
|
||||
return item
|
||||
}
|
||||
|
||||
function popWhere(where_role, is, max) {
|
||||
var indices = getIndices(where_role, is, max)
|
||||
var results = []
|
||||
|
||||
for (var i = 0; i < indices.length; i++) {
|
||||
results.push(listModel.get(indices[i]))
|
||||
listModel.remove(indices[i])
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
function toObject(item_list) {
|
||||
item_list = item_list || listModel
|
||||
var obj_list = []
|
||||
|
||||
for (var i = 0; i < item_list.count; i++) {
|
||||
var item = item_list.get(i)
|
||||
var obj = JSON.parse(JSON.stringify(item))
|
||||
|
||||
for (var role in obj) {
|
||||
if (obj[role]["objectName"] != undefined) {
|
||||
obj[role] = toObject(item[role])
|
||||
}
|
||||
}
|
||||
obj_list.push(obj)
|
||||
}
|
||||
return obj_list
|
||||
}
|
||||
|
||||
function toJson() {
|
||||
return JSON.stringify(toObject(), null, 4)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
// FIXME: Obsolete method, but need Qt 5.12+ for standard JS modules import
|
||||
Qt.include("app.js")
|
||||
Qt.include("users.js")
|
||||
Qt.include("rooms.js")
|
||||
Qt.include("rooms_timeline.js")
|
||||
|
|
60
src/qml/EventHandlers/rooms.js
Normal file
60
src/qml/EventHandlers/rooms.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
function clientId(user_id, category, room_id) {
|
||||
return user_id + " " + room_id + " " + category
|
||||
}
|
||||
|
||||
|
||||
function onRoomUpdated(user_id, category, room_id, display_name, avatar_url,
|
||||
topic, last_event_date, inviter, left_event) {
|
||||
|
||||
var client_id = clientId(user_id, category, room_id)
|
||||
var rooms = models.rooms
|
||||
|
||||
if (category == "Invites") {
|
||||
rooms.popWhere("clientId", clientId(user_id, "Rooms", room_id))
|
||||
rooms.popWhere("clientId", clientId(user_id, "Left", room_id))
|
||||
}
|
||||
else if (category == "Rooms") {
|
||||
rooms.popWhere("clientId", clientId(user_id, "Invites", room_id))
|
||||
rooms.popWhere("clientId", clientId(user_id, "Left", room_id))
|
||||
}
|
||||
else if (category == "Left") {
|
||||
var old_room =
|
||||
rooms.popWhere("clientId", clientId(user_id, "Rooms", room_id)) ||
|
||||
rooms.popWhere("clientId", clientId(user_id, "Invites", room_id))
|
||||
|
||||
if (old_room) {
|
||||
display_name = old_room.displayName
|
||||
avatar_url = old_room.avatarUrl
|
||||
topic = old_room.topic
|
||||
inviter = old_room.topic
|
||||
}
|
||||
}
|
||||
|
||||
rooms.upsert("clientId", client_id , {
|
||||
"clientId": client_id,
|
||||
"userId": user_id,
|
||||
"category": category,
|
||||
"roomId": room_id,
|
||||
"displayName": display_name,
|
||||
"avatarUrl": avatar_url,
|
||||
"topic": topic,
|
||||
"lastEventDate": last_event_date,
|
||||
"inviter": inviter,
|
||||
"leftEvent": left_event
|
||||
})
|
||||
//print("room up", rooms.toJson())
|
||||
}
|
||||
|
||||
|
||||
function onRoomDeleted(user_id, category, room_id) {
|
||||
var client_id = clientId(user_id, category, room_id)
|
||||
return models.rooms.popWhere("clientId", client_id, 1)
|
||||
}
|
||||
|
||||
|
||||
function onRoomMemberUpdated(room_id, user_id, typing) {
|
||||
}
|
||||
|
||||
|
||||
function onRoomMemberDeleted(room_id, user_id) {
|
||||
}
|
0
src/qml/EventHandlers/rooms_timeline.js
Normal file
0
src/qml/EventHandlers/rooms_timeline.js
Normal file
24
src/qml/EventHandlers/users.js
Normal file
24
src/qml/EventHandlers/users.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
function onAccountUpdated(user_id) {
|
||||
models.accounts.append({"userId": user_id})
|
||||
}
|
||||
|
||||
function AccountDeleted(user_id) {
|
||||
models.accounts.popWhere("userId", user_id, 1)
|
||||
}
|
||||
|
||||
function onUserUpdated(user_id, display_name, avatar_url, status_message) {
|
||||
models.users.upsert("userId", user_id, {
|
||||
"userId": user_id,
|
||||
"displayName": display_name,
|
||||
"avatarUrl": avatar_url,
|
||||
"statusMessage": status_message
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function onDeviceUpdated(user_id, device_id, ed25519_key, trust, display_name,
|
||||
last_seen_ip, last_seen_date) {
|
||||
}
|
||||
|
||||
function onDeviceDeleted(user_id, device_id) {
|
||||
}
|
34
src/qml/Models.qml
Normal file
34
src/qml/Models.qml
Normal file
|
@ -0,0 +1,34 @@
|
|||
import QtQuick 2.7
|
||||
import "Base"
|
||||
|
||||
QtObject {
|
||||
property HListModel accounts: HListModel {}
|
||||
|
||||
property HListModel users: HListModel {
|
||||
function getUser(as_account_id, wanted_user_id) {
|
||||
wanted_user_id = wanted_user_id || as_account_id
|
||||
|
||||
var found = users.getWhere("userId", wanted_user_id, 1)
|
||||
if (found.length > 0) { return found[0] }
|
||||
|
||||
users.append({
|
||||
"userId": wanted_user_id,
|
||||
"displayName": "",
|
||||
"avatarUrl": "",
|
||||
"statusMessage": ""
|
||||
})
|
||||
|
||||
py.callClientCoro(
|
||||
as_account_id, "request_user_update_event", [wanted_user_id]
|
||||
)
|
||||
|
||||
return users.getWhere("userId", wanted_user_id, 1)[0]
|
||||
}
|
||||
}
|
||||
|
||||
property HListModel devices: HListModel {}
|
||||
|
||||
property HListModel rooms: HListModel {}
|
||||
|
||||
property HListModel timelines: HListModel {}
|
||||
}
|
|
@ -6,16 +6,25 @@ import "EventHandlers/includes.js" as EventHandlers
|
|||
Python {
|
||||
id: py
|
||||
|
||||
signal ready(bool accountsToLoad)
|
||||
|
||||
property bool ready: false
|
||||
property var pendingCoroutines: ({})
|
||||
|
||||
property bool loadingAccounts: false
|
||||
|
||||
function callCoro(name, args, kwargs, callback) {
|
||||
call("APP.call_backend_coro", [name, args, kwargs], function(uuid){
|
||||
pendingCoroutines[uuid] = callback || function() {}
|
||||
})
|
||||
}
|
||||
|
||||
function callClientCoro(account_id, name, args, kwargs, callback) {
|
||||
var args = [account_id, name, args, kwargs]
|
||||
|
||||
call("APP.call_client_coro", args, function(uuid){
|
||||
pendingCoroutines[uuid] = callback || function() {}
|
||||
})
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
for (var func in EventHandlers) {
|
||||
if (EventHandlers.hasOwnProperty(func)) {
|
||||
|
@ -29,8 +38,14 @@ Python {
|
|||
window.debug = debug_on
|
||||
|
||||
callCoro("has_saved_accounts", [], {}, function(has) {
|
||||
print(has)
|
||||
py.ready(has)
|
||||
loadingAccounts = has
|
||||
py.ready = true
|
||||
|
||||
if (has) {
|
||||
py.callCoro("load_saved_accounts", [], {}, function() {
|
||||
loadingAccounts = false
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -6,7 +6,9 @@ Column {
|
|||
id: accountDelegate
|
||||
width: parent.width
|
||||
|
||||
property var user: Backend.users.get(userId)
|
||||
// Avoid binding loop by using Component.onCompleted
|
||||
property var user: null
|
||||
Component.onCompleted: user = models.users.getUser(userId)
|
||||
|
||||
property string roomCategoriesListUserId: userId
|
||||
property bool expanded: true
|
||||
|
@ -18,7 +20,7 @@ Column {
|
|||
|
||||
HAvatar {
|
||||
id: avatar
|
||||
name: user.displayName.value
|
||||
name: user.displayName
|
||||
}
|
||||
|
||||
HColumnLayout {
|
||||
|
@ -27,7 +29,7 @@ Column {
|
|||
|
||||
HLabel {
|
||||
id: accountLabel
|
||||
text: user.displayName.value
|
||||
text: user.displayName || user.userId
|
||||
elide: HLabel.ElideRight
|
||||
maximumLineCount: 1
|
||||
Layout.fillWidth: true
|
||||
|
|
|
@ -6,6 +6,6 @@ HListView {
|
|||
id: accountList
|
||||
clip: true
|
||||
|
||||
model: Backend.accounts
|
||||
model: models.accounts
|
||||
delegate: AccountDelegate {}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,10 @@ import "SidePane"
|
|||
Item {
|
||||
id: mainUI
|
||||
|
||||
property bool accountsLoggedIn: Backend.clients.count > 0
|
||||
property bool accountsPresent:
|
||||
models.accounts.count > 0 || py.loadingAccounts
|
||||
onAccountsPresentChanged:
|
||||
pageStack.showPage(accountsPresent ? "Default" : "SignIn")
|
||||
|
||||
HImage {
|
||||
id: mainUIBackground
|
||||
|
@ -26,7 +29,7 @@ Item {
|
|||
|
||||
SidePane {
|
||||
id: sidePane
|
||||
visible: accountsLoggedIn
|
||||
visible: accountsPresent
|
||||
collapsed: width < Layout.minimumWidth + normalSpacing
|
||||
|
||||
property int parentWidth: parent.width
|
||||
|
@ -68,17 +71,6 @@ Item {
|
|||
)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: py
|
||||
onReady: function(accountsToLoad) {
|
||||
pageStack.showPage(accountsToLoad ? "Default" : "SignIn")
|
||||
if (accountsToLoad) {
|
||||
py.callCoro("load_saved_accounts")
|
||||
// initialRoomTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
// TODO: remove this, debug
|
||||
id: initialRoomTimer
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.2
|
||||
import "Base"
|
||||
|
||||
ApplicationWindow {
|
||||
id: window
|
||||
|
@ -7,7 +8,7 @@ ApplicationWindow {
|
|||
height: 480
|
||||
visible: true
|
||||
color: "black"
|
||||
title: "Test"
|
||||
title: "Harmony QML"
|
||||
|
||||
property bool debug: false
|
||||
property bool ready: false
|
||||
|
@ -23,6 +24,10 @@ ApplicationWindow {
|
|||
id: py
|
||||
}
|
||||
|
||||
Models {
|
||||
id: models
|
||||
}
|
||||
|
||||
LoadingScreen {
|
||||
id: loadingScreen
|
||||
anchors.fill: parent
|
||||
|
@ -38,7 +43,7 @@ ApplicationWindow {
|
|||
source: uiLoader.ready ? "UI.qml" : ""
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: 100 }
|
||||
NumberAnimation { duration: HStyle.animationDuration }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user