Avatar change working
This commit is contained in:
parent
751a27157c
commit
62056b6124
14
TODO.md
14
TODO.md
|
@ -7,6 +7,18 @@
|
||||||
- normalSpacing in Theme
|
- normalSpacing in Theme
|
||||||
- Qt.AlignCenter instead of V | H
|
- Qt.AlignCenter instead of V | H
|
||||||
- banner button repair
|
- 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
|
- Qt 5.12
|
||||||
- New input handlers
|
- New input handlers
|
||||||
|
@ -50,6 +62,8 @@
|
||||||
- Multiaccount aliases
|
- Multiaccount aliases
|
||||||
- Message/text selection
|
- Message/text selection
|
||||||
|
|
||||||
|
- Custom file picker for Linux...
|
||||||
|
|
||||||
- Major features
|
- Major features
|
||||||
- E2E
|
- E2E
|
||||||
- Device verification
|
- Device verification
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
TEMPLATE = app
|
TEMPLATE = app
|
||||||
QT = quick
|
# widgets: Make native file dialogs available to QML (must use QApplication)
|
||||||
|
QT = quick widgets
|
||||||
DEFINES += QT_DEPRECATED_WARNINGS
|
DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
CONFIG += warn_off c++11 release
|
CONFIG += warn_off c++11 release
|
||||||
dev {
|
dev {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2019 miruka
|
// Copyright 2019 miruka
|
||||||
// This file is part of harmonyqml, licensed under LGPLv3.
|
// This file is part of harmonyqml, licensed under LGPLv3.
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <QApplication>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QQmlComponent>
|
#include <QQmlComponent>
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
QGuiApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
QQmlEngine engine;
|
QQmlEngine engine;
|
||||||
QQmlContext *objectContext = new QQmlContext(engine.rootContext());
|
QQmlContext *objectContext = new QQmlContext(engine.rootContext());
|
||||||
|
|
|
@ -9,10 +9,14 @@ import logging as log
|
||||||
import platform
|
import platform
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import DefaultDict, Dict, Optional, Type
|
from typing import DefaultDict, Dict, Optional, Type, Union
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import filetype
|
||||||
|
|
||||||
import nio
|
import nio
|
||||||
from nio.rooms import MatrixRoom
|
from nio.rooms import MatrixRoom
|
||||||
|
|
||||||
|
@ -22,6 +26,12 @@ from .events.rooms import TimelineEventReceived, TimelineMessageReceived
|
||||||
from .html_filter import HTML_FILTER
|
from .html_filter import HTML_FILTER
|
||||||
|
|
||||||
|
|
||||||
|
class UploadError(Enum):
|
||||||
|
forbidden = "M_FORBIDDEN"
|
||||||
|
too_large = "M_TOO_LARGE"
|
||||||
|
unknown = "UNKNOWN"
|
||||||
|
|
||||||
|
|
||||||
class MatrixClient(nio.AsyncClient):
|
class MatrixClient(nio.AsyncClient):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
backend,
|
backend,
|
||||||
|
@ -207,6 +217,38 @@ class MatrixClient(nio.AsyncClient):
|
||||||
rooms.RoomForgotten(user_id=self.user_id, room_id=room_id)
|
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
|
# Callbacks for nio responses
|
||||||
|
|
||||||
async def onSyncResponse(self, resp: nio.SyncResponse) -> None:
|
async def onSyncResponse(self, resp: nio.SyncResponse) -> None:
|
||||||
|
|
|
@ -54,15 +54,23 @@ HRectangle {
|
||||||
HToolTip {
|
HToolTip {
|
||||||
id: avatarToolTip
|
id: avatarToolTip
|
||||||
visible: toolTipImageUrl && hoverHandler.hovered
|
visible: toolTipImageUrl && hoverHandler.hovered
|
||||||
width: 128
|
width: 192 + background.border.width * 2
|
||||||
height: 128
|
height: width
|
||||||
|
delay: 1000
|
||||||
|
|
||||||
|
background: HRectangle {
|
||||||
|
id: background
|
||||||
|
border.color: "black"
|
||||||
|
border.width: 2
|
||||||
|
}
|
||||||
|
|
||||||
HImage {
|
HImage {
|
||||||
id: avatarToolTipImage
|
id: avatarToolTipImage
|
||||||
width: parent.width
|
anchors.centerIn: parent
|
||||||
height: parent.height
|
sourceSize.width: parent.width - background.border.width * 2
|
||||||
sourceSize.width: parent.width
|
sourceSize.height: parent.height - background.border.width * 2
|
||||||
sourceSize.height: parent.height
|
width: sourceSize.width
|
||||||
|
height: sourceSize.width
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
46
src/qml/Base/HFileDialogOpener.qml
Normal file
46
src/qml/Base/HFileDialogOpener.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ ToolTip {
|
||||||
// going out of the window's boundaries
|
// going out of the window's boundaries
|
||||||
|
|
||||||
id: toolTip
|
id: toolTip
|
||||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
delay: 150
|
||||||
padding: 0
|
padding: 0
|
||||||
|
|
||||||
enter: Transition {
|
enter: Transition {
|
||||||
|
|
|
@ -38,18 +38,12 @@ HBaseButton {
|
||||||
svgName: loading ? "hourglass" : iconName
|
svgName: loading ? "hourglass" : iconName
|
||||||
dimension: iconDimension || contentLayout.height
|
dimension: iconDimension || contentLayout.height
|
||||||
transform: iconTransform
|
transform: iconTransform
|
||||||
|
opacity: button.enabled ? 1 : 0.7
|
||||||
|
|
||||||
Layout.topMargin: verticalMargin
|
Layout.topMargin: verticalMargin
|
||||||
Layout.bottomMargin: verticalMargin
|
Layout.bottomMargin: verticalMargin
|
||||||
Layout.leftMargin: horizontalMargin
|
Layout.leftMargin: horizontalMargin
|
||||||
Layout.rightMargin: horizontalMargin
|
Layout.rightMargin: horizontalMargin
|
||||||
|
|
||||||
// Colorize {
|
|
||||||
// anchors.fill: parent
|
|
||||||
// source: parent
|
|
||||||
// visible: ! button.enabled
|
|
||||||
// saturation: 0
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HLabel {
|
HLabel {
|
||||||
|
|
|
@ -7,15 +7,16 @@ HAvatar {
|
||||||
property string userId: ""
|
property string userId: ""
|
||||||
readonly property var userInfo: userId ? users.find(userId) : ({})
|
readonly property var userInfo: userId ? users.find(userId) : ({})
|
||||||
|
|
||||||
name:
|
readonly property var defaultImageUrl:
|
||||||
userInfo.displayName || userId.substring(1) // no leading @
|
|
||||||
|
|
||||||
imageUrl:
|
|
||||||
userInfo.avatarUrl ? ("image://python/" + userInfo.avatarUrl) : null
|
userInfo.avatarUrl ? ("image://python/" + userInfo.avatarUrl) : null
|
||||||
|
|
||||||
toolTipImageUrl:
|
readonly property var defaultToolTipImageUrl:
|
||||||
userInfo.avatarUrl ? ("image://python/" + userInfo.avatarUrl) : null
|
userInfo.avatarUrl ? ("image://python/" + userInfo.avatarUrl) : null
|
||||||
|
|
||||||
|
name: userInfo.displayName || userId.substring(1) // no leading @
|
||||||
|
imageUrl: defaultImageUrl
|
||||||
|
toolTipImageUrl:defaultToolTipImageUrl
|
||||||
|
|
||||||
//HImage {
|
//HImage {
|
||||||
//id: status
|
//id: status
|
||||||
//anchors.right: parent.right
|
//anchors.right: parent.right
|
||||||
|
|
|
@ -9,12 +9,27 @@ import "../../utils.js" as Utils
|
||||||
|
|
||||||
HGridLayout {
|
HGridLayout {
|
||||||
function applyChanges() {
|
function applyChanges() {
|
||||||
saveButton.loading = true
|
if (nameField.changed) {
|
||||||
|
saveButton.nameChangeRunning = true
|
||||||
|
|
||||||
py.callClientCoro(
|
py.callClientCoro(
|
||||||
userId, "set_displayname", [nameField.field.text],
|
userId, "set_displayname", [nameField.field.text], () => {
|
||||||
() => { saveButton.loading = false }
|
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
|
columns: 2
|
||||||
|
@ -24,8 +39,11 @@ HGridLayout {
|
||||||
Component.onCompleted: nameField.field.forceActiveFocus()
|
Component.onCompleted: nameField.field.forceActiveFocus()
|
||||||
|
|
||||||
HUserAvatar {
|
HUserAvatar {
|
||||||
|
property bool changed: avatar.imageUrl != avatar.defaultImageUrl
|
||||||
|
|
||||||
id: avatar
|
id: avatar
|
||||||
userId: editAccount.userId
|
userId: editAccount.userId
|
||||||
|
imageUrl: fileDialog.selectedFile || defaultImageUrl
|
||||||
toolTipImageUrl: null
|
toolTipImageUrl: null
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
@ -33,6 +51,13 @@ HGridLayout {
|
||||||
|
|
||||||
Layout.preferredWidth: thinMaxWidth
|
Layout.preferredWidth: thinMaxWidth
|
||||||
Layout.preferredHeight: Layout.preferredWidth
|
Layout.preferredHeight: Layout.preferredWidth
|
||||||
|
|
||||||
|
HFileDialogOpener {
|
||||||
|
id: fileDialog
|
||||||
|
fileType: HFileDialogOpener.FileType.Images
|
||||||
|
dialog.title: qsTr("Select profile picture for %1")
|
||||||
|
.arg(userInfo.displayName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HColumnLayout {
|
HColumnLayout {
|
||||||
|
@ -53,6 +78,8 @@ HGridLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
HLabeledTextField {
|
HLabeledTextField {
|
||||||
|
property bool changed: field.text != userInfo.displayName
|
||||||
|
|
||||||
id: nameField
|
id: nameField
|
||||||
label.text: qsTr("Display name:")
|
label.text: qsTr("Display name:")
|
||||||
field.text: userInfo.displayName
|
field.text: userInfo.displayName
|
||||||
|
@ -69,11 +96,15 @@ HGridLayout {
|
||||||
Layout.alignment: Qt.AlignBottom
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
|
||||||
HUIButton {
|
HUIButton {
|
||||||
|
property bool nameChangeRunning: false
|
||||||
|
property bool avatarChangeRunning: false
|
||||||
|
|
||||||
id: saveButton
|
id: saveButton
|
||||||
iconName: "save"
|
iconName: "save"
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
centerText: false
|
centerText: false
|
||||||
enabled: nameField.field.text != userInfo.displayName
|
loading: nameChangeRunning || avatarChangeRunning
|
||||||
|
enabled: nameField.changed || avatar.changed
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignBottom
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
|
|
@ -18,10 +18,11 @@ ApplicationWindow {
|
||||||
property bool ready: false
|
property bool ready: false
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
Qt.application.name = "harmonyqml"
|
Qt.application.organization = "harmonyqml"
|
||||||
Qt.application.displayName = "Harmony QML"
|
Qt.application.name = "harmonyqml"
|
||||||
Qt.application.version = "0.1.0"
|
Qt.application.displayName = "Harmony QML"
|
||||||
window.ready = true
|
Qt.application.version = "0.1.0"
|
||||||
|
window.ready = true
|
||||||
}
|
}
|
||||||
|
|
||||||
Theme { id: theme }
|
Theme { id: theme }
|
||||||
|
|
Loading…
Reference in New Issue
Block a user