Avatar change working

This commit is contained in:
miruka 2019-07-15 16:14:08 -04:00
parent 751a27157c
commit 62056b6124
11 changed files with 171 additions and 33 deletions

14
TODO.md
View File

@ -7,6 +7,18 @@
- normalSpacing in Theme
- Qt.AlignCenter instead of V | H
- banner button repair
- Wrong avatar for group rooms
- Can assign "" to an Image source
- Make sure to not cache user images and that sourceSize is set everywhere
- Reduce messages ListView cacheBuffer height once http thumbnails
downloading is implemented
- HTextField focus effect
- Button can get "hoverEnabled: false" to let HoverHandlers work
- Center account box buttons
- Handle TimeoutError for all kind of async requests (nio)
- Handle thumbnail response status 400
- "Loading..." if going to edit account page while it's loading
- Improve avatar tooltips position, add stuff to room tooltips (last msg?)
- Qt 5.12
- New input handlers
@ -50,6 +62,8 @@
- Multiaccount aliases
- Message/text selection
- Custom file picker for Linux...
- Major features
- E2E
- Device verification

View File

@ -1,5 +1,6 @@
TEMPLATE = app
QT = quick
# widgets: Make native file dialogs available to QML (must use QApplication)
QT = quick widgets
DEFINES += QT_DEPRECATED_WARNINGS
CONFIG += warn_off c++11 release
dev {

View File

@ -1,7 +1,7 @@
// Copyright 2019 miruka
// This file is part of harmonyqml, licensed under LGPLv3.
#include <QGuiApplication>
#include <QApplication>
#include <QQmlEngine>
#include <QQmlContext>
#include <QQmlComponent>
@ -10,7 +10,7 @@
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QApplication app(argc, argv);
QQmlEngine engine;
QQmlContext *objectContext = new QQmlContext(engine.rootContext());

View File

@ -9,10 +9,14 @@ import logging as log
import platform
from contextlib import suppress
from datetime import datetime
from enum import Enum
from pathlib import Path
from types import ModuleType
from typing import DefaultDict, Dict, Optional, Type
from typing import DefaultDict, Dict, Optional, Type, Union
from uuid import uuid4
import filetype
import nio
from nio.rooms import MatrixRoom
@ -22,6 +26,12 @@ from .events.rooms import TimelineEventReceived, TimelineMessageReceived
from .html_filter import HTML_FILTER
class UploadError(Enum):
forbidden = "M_FORBIDDEN"
too_large = "M_TOO_LARGE"
unknown = "UNKNOWN"
class MatrixClient(nio.AsyncClient):
def __init__(self,
backend,
@ -207,6 +217,38 @@ class MatrixClient(nio.AsyncClient):
rooms.RoomForgotten(user_id=self.user_id, room_id=room_id)
async def upload_file(self, path: Union[Path, str]) -> str:
path = Path(path)
with open(path, "rb") as file:
mime = filetype.guess_mime(file)
file.seek(0, 0)
resp = await self.upload(file, mime, path.name)
if not isinstance(resp, nio.ErrorResponse):
return resp.content_uri
if resp.status_code == 403:
return UploadError.forbidden.value
if resp.status_code == 413:
return UploadError.too_large.value
return UploadError.unknown.value
async def set_avatar_from_file(self, path: Union[Path, str]
) -> Union[bool, str]:
resp = await self.upload_file(path)
if resp in (i.value for i in UploadError):
return resp
await self.set_avatar(resp)
return True
# Callbacks for nio responses
async def onSyncResponse(self, resp: nio.SyncResponse) -> None:

View File

@ -54,15 +54,23 @@ HRectangle {
HToolTip {
id: avatarToolTip
visible: toolTipImageUrl && hoverHandler.hovered
width: 128
height: 128
width: 192 + background.border.width * 2
height: width
delay: 1000
background: HRectangle {
id: background
border.color: "black"
border.width: 2
}
HImage {
id: avatarToolTipImage
width: parent.width
height: parent.height
sourceSize.width: parent.width
sourceSize.height: parent.height
anchors.centerIn: parent
sourceSize.width: parent.width - background.border.width * 2
sourceSize.height: parent.height - background.border.width * 2
width: sourceSize.width
height: sourceSize.width
fillMode: Image.PreserveAspectCrop
}
}

View File

@ -0,0 +1,46 @@
// Copyright 2019 miruka
// This file is part of harmonyqml, licensed under LGPLv3.
import QtQuick 2.12
import Qt.labs.platform 1.1
Item {
anchors.fill: parent
property alias dialog: fileDialog
property var selectedFile: null
enum FileType { All, Images }
property int fileType: FileType.All
TapHandler { onTapped: fileDialog.open() }
FileDialog {
id: fileDialog
property var filters: ({
all: qsTr("All files") + " (*)",
images: qsTr("Image files") +
" (*.jpg *.jpeg *.png *.gif *.bmp *.webp)"
})
nameFilters:
fileType == HFileDialogOpener.FileType.Images ?
[filters.images, filters.all] :
[filters.all]
folder: StandardPaths.writableLocation(
fileType == HFileDialogOpener.FileType.Images ?
StandardPaths.PicturesLocation :
StandardPaths.HomeLocation
)
title: "Select file"
modality: Qt.WindowModal
onVisibleChanged: if (visible) {
selectedFile = Qt.binding(() => Qt.resolvedUrl(currentFile))
}
onRejected: selectedFile = null
}
}

View File

@ -6,7 +6,7 @@ ToolTip {
// going out of the window's boundaries
id: toolTip
delay: Qt.styleHints.mousePressAndHoldInterval
delay: 150
padding: 0
enter: Transition {

View File

@ -38,18 +38,12 @@ HBaseButton {
svgName: loading ? "hourglass" : iconName
dimension: iconDimension || contentLayout.height
transform: iconTransform
opacity: button.enabled ? 1 : 0.7
Layout.topMargin: verticalMargin
Layout.bottomMargin: verticalMargin
Layout.leftMargin: horizontalMargin
Layout.rightMargin: horizontalMargin
// Colorize {
// anchors.fill: parent
// source: parent
// visible: ! button.enabled
// saturation: 0
// }
}
HLabel {

View File

@ -7,15 +7,16 @@ HAvatar {
property string userId: ""
readonly property var userInfo: userId ? users.find(userId) : ({})
name:
userInfo.displayName || userId.substring(1) // no leading @
imageUrl:
readonly property var defaultImageUrl:
userInfo.avatarUrl ? ("image://python/" + userInfo.avatarUrl) : null
toolTipImageUrl:
readonly property var defaultToolTipImageUrl:
userInfo.avatarUrl ? ("image://python/" + userInfo.avatarUrl) : null
name: userInfo.displayName || userId.substring(1) // no leading @
imageUrl: defaultImageUrl
toolTipImageUrl:defaultToolTipImageUrl
//HImage {
//id: status
//anchors.right: parent.right

View File

@ -9,12 +9,27 @@ import "../../utils.js" as Utils
HGridLayout {
function applyChanges() {
saveButton.loading = true
if (nameField.changed) {
saveButton.nameChangeRunning = true
py.callClientCoro(
userId, "set_displayname", [nameField.field.text],
() => { saveButton.loading = false }
)
py.callClientCoro(
userId, "set_displayname", [nameField.field.text], () => {
saveButton.nameChangeRunning = false
}
)
}
if (avatar.changed) {
saveButton.avatarChangeRunning = true
var path = Qt.resolvedUrl(avatar.imageUrl).replace(/^file:/, "")
py.callClientCoro(
userId, "set_avatar_from_file", [path], response => {
saveButton.avatarChangeRunning = false
if (response != true) { print(response) }
}
)
}
}
columns: 2
@ -24,8 +39,11 @@ HGridLayout {
Component.onCompleted: nameField.field.forceActiveFocus()
HUserAvatar {
property bool changed: avatar.imageUrl != avatar.defaultImageUrl
id: avatar
userId: editAccount.userId
imageUrl: fileDialog.selectedFile || defaultImageUrl
toolTipImageUrl: null
Layout.alignment: Qt.AlignHCenter
@ -33,6 +51,13 @@ HGridLayout {
Layout.preferredWidth: thinMaxWidth
Layout.preferredHeight: Layout.preferredWidth
HFileDialogOpener {
id: fileDialog
fileType: HFileDialogOpener.FileType.Images
dialog.title: qsTr("Select profile picture for %1")
.arg(userInfo.displayName)
}
}
HColumnLayout {
@ -53,6 +78,8 @@ HGridLayout {
}
HLabeledTextField {
property bool changed: field.text != userInfo.displayName
id: nameField
label.text: qsTr("Display name:")
field.text: userInfo.displayName
@ -69,11 +96,15 @@ HGridLayout {
Layout.alignment: Qt.AlignBottom
HUIButton {
property bool nameChangeRunning: false
property bool avatarChangeRunning: false
id: saveButton
iconName: "save"
text: qsTr("Save")
centerText: false
enabled: nameField.field.text != userInfo.displayName
loading: nameChangeRunning || avatarChangeRunning
enabled: nameField.changed || avatar.changed
Layout.fillWidth: true
Layout.alignment: Qt.AlignBottom

View File

@ -18,10 +18,11 @@ ApplicationWindow {
property bool ready: false
Component.onCompleted: {
Qt.application.name = "harmonyqml"
Qt.application.displayName = "Harmony QML"
Qt.application.version = "0.1.0"
window.ready = true
Qt.application.organization = "harmonyqml"
Qt.application.name = "harmonyqml"
Qt.application.displayName = "Harmony QML"
Qt.application.version = "0.1.0"
window.ready = true
}
Theme { id: theme }