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
|
||||
- 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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
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
|
||||
|
||||
id: toolTip
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
delay: 150
|
||||
padding: 0
|
||||
|
||||
enter: Transition {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in New Issue
Block a user