diff --git a/TODO.md b/TODO.md
index 5b7e510a..88e7aee9 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,4 +1,12 @@
+- ElidedLabel component
+- Can set `Layout.fillWidth: true` to elide/wrap
+- Use childrenRect stuff
- Rename theme.bottomElementsHeight
+- Account delegate name color
+- If avatar is set, name color from average color?
+- normalSpacing in Theme
+- Qt.AlignCenter instead of V | H
+- banner button repair
- Qt 5.12
- New input handlers
@@ -57,7 +65,6 @@
- Client improvements
- [debug mode](https://docs.python.org/3/library/asyncio-dev.html)
- - More intelligent thumbnails downloading for different sizes
- Filtering rooms: search more than display names?
- Initial sync filter and lazy load, see weechat-matrix `_handle_login()`
- See also `handle_response()`'s `keys_query` request
diff --git a/src/icons/cancel.svg b/src/icons/cancel.svg
new file mode 100644
index 00000000..584a4121
--- /dev/null
+++ b/src/icons/cancel.svg
@@ -0,0 +1,51 @@
+
+
diff --git a/src/icons/save.svg b/src/icons/save.svg
new file mode 100644
index 00000000..c2bef64e
--- /dev/null
+++ b/src/icons/save.svg
@@ -0,0 +1,51 @@
+
+
diff --git a/src/python/image_provider.py b/src/python/image_provider.py
index ebc54935..5e1afb51 100644
--- a/src/python/image_provider.py
+++ b/src/python/image_provider.py
@@ -17,35 +17,48 @@ import nio
import pyotherside
from nio.api import ResizingMethod
-from .app import App
-
Size = Tuple[int, int]
ImageData = Tuple[bytearray, Size, int] # last int: pyotherside format enum
@dataclass
class Thumbnail:
- provider: "ImageProvider" = field()
- id: str = field()
- width: int = field()
- height: int = field()
+ # pylint: disable=no-member
+ provider: "ImageProvider" = field()
+ mxc: str = field()
+ width: int = field()
+ height: int = field()
def __post_init__(self) -> None:
- self.id = re.sub(r"#auto$", "", self.id)
+ self.mxc = re.sub(r"#auto$", "", self.mxc)
- if not re.match(r"^(crop|scale)/mxc://.+/.+", self.id):
- raise ValueError(f"Invalid image ID: {self.id}")
+ if not re.match(r"^mxc://.+/.+", self.mxc):
+ raise ValueError(f"Invalid mxc URI: {self.mxc}")
+
+
+ @property
+ def server_size(self) -> Tuple[int, int]:
+ # https://matrix.org/docs/spec/client_server/latest#thumbnails
+
+ if self.width > 640 or self.height > 480:
+ return (800, 600)
+
+ if self.width > 320 or self.height > 240:
+ return (640, 480)
+
+ if self.width > 96 or self.height > 96:
+ return (320, 240)
+
+ if self.width > 32 or self.height > 32:
+ return (96, 96)
+
+ return (32, 32)
@property
def resize_method(self) -> ResizingMethod:
- return ResizingMethod.crop \
- if self.id.startswith("crop/") else ResizingMethod.scale
-
-
- @property
- def mxc(self) -> str:
- return re.sub(r"^(crop|scale)/", "", self.id)
+ return ResizingMethod.scale \
+ if self.width > 96 or self.height > 96 else ResizingMethod.crop
@property
@@ -55,11 +68,12 @@ class Thumbnail:
@property
def local_path(self) -> Path:
+ # pylint: disable=bad-string-format-type
parsed = urlparse(self.mxc)
- name = "%s.%d.%d.%s" % (
+ name = "%s.%03d.%03d.%s" % (
parsed.path.lstrip("/"),
- self.width,
- self.height,
+ self.server_size[0],
+ self.server_size[1],
self.resize_method.value,
)
return self.provider.cache / parsed.netloc / name
@@ -74,16 +88,16 @@ class Thumbnail:
response = await client.thumbnail(
server_name = parsed.netloc,
media_id = parsed.path.lstrip("/"),
- width = self.width,
- height = self.height,
+ width = self.server_size[0],
+ height = self.server_size[1],
method = self.resize_method,
)
body = response.body
if response.content_type not in ("image/jpeg", "image/png"):
- with BytesIO(body) as in_, BytesIO() as out:
- PILImage.open(in_).save(out, "PNG")
- body = out.getvalue()
+ with BytesIO(body) as img_in, BytesIO() as img_out:
+ PILImage.open(img_in).save(img_out, "PNG")
+ body = img_out.getvalue()
self.local_path.parent.mkdir(parents=True, exist_ok=True)
@@ -99,8 +113,10 @@ class Thumbnail:
except FileNotFoundError:
body = await self.download()
- size = (self.width, self.height)
- return (bytearray(body), size , pyotherside.format_data)
+ with BytesIO(body) as img_in:
+ real_size = PILImage.open(img_in).size
+
+ return (bytearray(body), real_size, pyotherside.format_data)
class ImageProvider:
@@ -112,10 +128,10 @@ class ImageProvider:
def get(self, image_id: str, requested_size: Size) -> ImageData:
- width = 128 if requested_size[0] < 1 else requested_size[0]
- height = width if requested_size[1] < 1 else requested_size[1]
- thumb = Thumbnail(self, image_id, width, height)
+ if requested_size[0] < 1 or requested_size[1] < 1:
+ raise ValueError(f"width or height < 1: {requested_size!r}")
return asyncio.run_coroutine_threadsafe(
- thumb.get_data(), self.app.loop
+ Thumbnail(self, image_id, *requested_size).get_data(),
+ self.app.loop
).result()
diff --git a/src/qml/Base/HAvatar.qml b/src/qml/Base/HAvatar.qml
index 27555e94..1a59bc83 100644
--- a/src/qml/Base/HAvatar.qml
+++ b/src/qml/Base/HAvatar.qml
@@ -7,11 +7,14 @@ import "../Base"
import "../utils.js" as Utils
HRectangle {
+ id: avatar
+ implicitWidth: theme.avatar.size
+ implicitHeight: theme.avatar.size
+
property string name: ""
property var imageUrl: null
property var toolTipImageUrl: imageUrl
- property int dimension: theme.avatar.size
- property bool hidden: false
+ property alias fillMode: avatarImage.fillMode
onImageUrlChanged: if (imageUrl) { avatarImage.source = imageUrl }
@@ -19,19 +22,16 @@ HRectangle {
avatarToolTipImage.source = toolTipImageUrl
}
- width: dimension
- height: hidden ? 1 : dimension
- implicitWidth: dimension
- implicitHeight: hidden ? 1 : dimension
+ readonly property var params: Utils.thumbnailParametersFor(width, height)
- opacity: hidden ? 0 : 1
-
- color: name ? Utils.avatarColor(name) : theme.avatar.background.unknown
+ color: imageUrl ? "transparent" :
+ name ? Utils.avatarColor(name) :
+ theme.avatar.background.unknown
HLabel {
z: 1
anchors.centerIn: parent
- visible: ! hidden && ! imageUrl
+ visible: ! imageUrl
text: name ? name.charAt(0) : "?"
color: theme.avatar.letter
@@ -39,14 +39,13 @@ HRectangle {
}
HImage {
- z: 2
id: avatarImage
anchors.fill: parent
- visible: ! hidden && imageUrl
- fillMode: Image.PreserveAspectCrop
-
- sourceSize.width: dimension
- sourceSize.height: dimension
+ visible: imageUrl
+ z: 2
+ sourceSize.width: params.width
+ sourceSize.height: params.height
+ fillMode: params.fillMode
HoverHandler {
id: hoverHandler
@@ -54,16 +53,17 @@ HRectangle {
HToolTip {
id: avatarToolTip
- visible: hoverHandler.hovered
+ visible: toolTipImageUrl && hoverHandler.hovered
width: 128
height: 128
HImage {
id: avatarToolTipImage
- sourceSize.width: avatarToolTip.width
- sourceSize.height: avatarToolTip.height
- width: sourceSize.width
- height: sourceSize.height
+ width: parent.width
+ height: parent.height
+ sourceSize.width: parent.width
+ sourceSize.height: parent.height
+ fillMode: Image.PreserveAspectCrop
}
}
}
diff --git a/src/qml/Base/HBaseButton.qml b/src/qml/Base/HBaseButton.qml
index 202927e7..ee98adb8 100644
--- a/src/qml/Base/HBaseButton.qml
+++ b/src/qml/Base/HBaseButton.qml
@@ -26,7 +26,10 @@ Button {
background: Rectangle {
id: buttonBackground
color: Qt.lighter(
- backgroundColor, checked ? (checkedLightens ? 1.3 : 0.7) : 1.0
+ backgroundColor,
+ ! enabled ? 0.7 :
+ checked ? (checkedLightens ? 1.3 : 0.7) :
+ 1.0
)
radius: circle ? height : 0
diff --git a/src/qml/Base/HColumnLayout.qml b/src/qml/Base/HColumnLayout.qml
index 66ca242e..4c50edc4 100644
--- a/src/qml/Base/HColumnLayout.qml
+++ b/src/qml/Base/HColumnLayout.qml
@@ -2,7 +2,6 @@
// This file is part of harmonyqml, licensed under LGPLv3.
import QtQuick 2.12
-import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
ColumnLayout {
diff --git a/src/qml/Base/HGridLayout.qml b/src/qml/Base/HGridLayout.qml
new file mode 100644
index 00000000..126c767f
--- /dev/null
+++ b/src/qml/Base/HGridLayout.qml
@@ -0,0 +1,14 @@
+// Copyright 2019 miruka
+// This file is part of harmonyqml, licensed under LGPLv3.
+
+import QtQuick 2.12
+import QtQuick.Layouts 1.12
+
+GridLayout {
+ id: gridLayout
+ rowSpacing: 0
+ columnSpacing: 0
+
+ property int totalSpacing:
+ spacing * Math.max(0, (gridLayout.visibleChildren.length - 1))
+}
diff --git a/src/qml/Base/HLabeledTextField.qml b/src/qml/Base/HLabeledTextField.qml
new file mode 100644
index 00000000..b11e0027
--- /dev/null
+++ b/src/qml/Base/HLabeledTextField.qml
@@ -0,0 +1,23 @@
+// Copyright 2019 miruka
+// This file is part of harmonyqml, licensed under LGPLv3.
+
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+
+Column {
+ spacing: 4
+
+ property alias label: fieldLabel
+ property alias field: textField
+
+ HLabel {
+ id: fieldLabel
+ }
+
+ HTextField {
+ id: textField
+ bordered: true
+ radius: 2
+ width: parent.width
+ }
+}
diff --git a/src/qml/Base/HRichLabel.qml b/src/qml/Base/HRichLabel.qml
index b89b8757..c8923f82 100644
--- a/src/qml/Base/HRichLabel.qml
+++ b/src/qml/Base/HRichLabel.qml
@@ -4,25 +4,14 @@
import QtQuick 2.12
HLabel {
+ // https://blog.shantanu.io/2015/02/15/creating-working-hyperlinks-in-qtquick-text/
id: label
textFormat: Text.RichText
+ onLinkActivated: Qt.openUrlExternally(link)
MouseArea {
- id: mouseArea
anchors.fill: parent
- hoverEnabled: true
- propagateComposedEvents: true
-
- onPositionChanged: function (mouse) {
- mouse.accepted = false
- cursorShape = label.linkAt(mouse.x, mouse.y) ?
- Qt.PointingHandCursor : Qt.ArrowCursor
- }
-
- onClicked: function(mouse) {
- var link = label.linkAt(mouse.x, mouse.y)
- mouse.accepted = Boolean(link)
- if (link) { Qt.openUrlExternally(link) }
- }
+ acceptedButtons: Qt.NoButton
+ cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
diff --git a/src/qml/Base/HRoomAvatar.qml b/src/qml/Base/HRoomAvatar.qml
index cab58529..6968c84c 100644
--- a/src/qml/Base/HRoomAvatar.qml
+++ b/src/qml/Base/HRoomAvatar.qml
@@ -14,10 +14,8 @@ HAvatar {
dname[0] == "#" && dname.length > 1 ? dname.substring(1) : dname
imageUrl:
- roomInfo.avatarUrl ?
- ("image://python/crop/" + roomInfo.avatarUrl) : null
+ roomInfo.avatarUrl ? ("image://python/" + roomInfo.avatarUrl) : null
toolTipImageUrl:
- roomInfo.avatarUrl ?
- ("image://python/scale/" + roomInfo.avatarUrl) : null
+ roomInfo.avatarUrl ? ("image://python/" + roomInfo.avatarUrl) : null
}
diff --git a/src/qml/Base/HTextField.qml b/src/qml/Base/HTextField.qml
index 160e822d..b42db833 100644
--- a/src/qml/Base/HTextField.qml
+++ b/src/qml/Base/HTextField.qml
@@ -5,7 +5,9 @@ import QtQuick 2.12
import QtQuick.Controls 2.12
TextField {
+ property bool bordered: false
property alias backgroundColor: textFieldBackground.color
+ property alias radius: textFieldBackground.radius
font.family: theme.fontFamily.sans
font.pixelSize: theme.fontSize.normal
@@ -14,6 +16,8 @@ TextField {
background: Rectangle {
id: textFieldBackground
color: theme.controls.textField.background
+ border.color: theme.controls.textField.borderColor
+ border.width: bordered ? theme.controls.textField.borderWidth : 0
}
selectByMouse: true
diff --git a/src/qml/Base/HUIButton.qml b/src/qml/Base/HUIButton.qml
index e6c9cbe6..387eee4a 100644
--- a/src/qml/Base/HUIButton.qml
+++ b/src/qml/Base/HUIButton.qml
@@ -4,6 +4,7 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
+import QtGraphicalEffects 1.12
HBaseButton {
property int horizontalMargin: 0
@@ -14,6 +15,7 @@ HBaseButton {
property var iconTransform: null
property int fontSize: theme.fontSize.normal
+ property bool centerText: Boolean(iconName)
property bool loading: false
@@ -29,7 +31,7 @@ HBaseButton {
HRowLayout {
id: contentLayout
- spacing: button.text && iconName ? 5 : 0
+ spacing: button.text && iconName ? 8 : 0
Component.onCompleted: contentWidth = implicitWidth
HIcon {
@@ -41,15 +43,25 @@ HBaseButton {
Layout.bottomMargin: verticalMargin
Layout.leftMargin: horizontalMargin
Layout.rightMargin: horizontalMargin
+
+ // Colorize {
+ // anchors.fill: parent
+ // source: parent
+ // visible: ! button.enabled
+ // saturation: 0
+ // }
}
HLabel {
text: button.text
font.pixelSize: fontSize
- horizontalAlignment: Text.AlignHCenter
+ horizontalAlignment: button.centerText ?
+ Text.AlignHCenter : Text.AlignLeft
verticalAlignment: Text.AlignVCenter
+ color: enabled ?
+ theme.colors.foreground : theme.colors.foregroundDim2
- Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+ Layout.fillWidth: true
}
}
}
diff --git a/src/qml/Base/HUserAvatar.qml b/src/qml/Base/HUserAvatar.qml
index 768c4a47..716d10b0 100644
--- a/src/qml/Base/HUserAvatar.qml
+++ b/src/qml/Base/HUserAvatar.qml
@@ -5,19 +5,16 @@ import QtQuick 2.12
HAvatar {
property string userId: ""
-
readonly property var userInfo: userId ? users.find(userId) : ({})
name:
userInfo.displayName || userId.substring(1) // no leading @
imageUrl:
- userInfo.avatarUrl ?
- ("image://python/crop/" + userInfo.avatarUrl) : null
+ userInfo.avatarUrl ? ("image://python/" + userInfo.avatarUrl) : null
toolTipImageUrl:
- userInfo.avatarUrl ?
- ("image://python/scale/" + userInfo.avatarUrl) : null
+ userInfo.avatarUrl ? ("image://python/" + userInfo.avatarUrl) : null
//HImage {
//id: status
diff --git a/src/qml/Chat/Banners/Banner.qml b/src/qml/Chat/Banners/Banner.qml
index 0e14e4f2..5d134f38 100644
--- a/src/qml/Chat/Banners/Banner.qml
+++ b/src/qml/Chat/Banners/Banner.qml
@@ -22,7 +22,6 @@ HRectangle {
HUserAvatar {
id: bannerAvatar
- dimension: banner.Layout.preferredHeight
}
HIcon {
diff --git a/src/qml/Chat/RoomHeader.qml b/src/qml/Chat/RoomHeader.qml
index 14efa1d3..a3ed2b0a 100644
--- a/src/qml/Chat/RoomHeader.qml
+++ b/src/qml/Chat/RoomHeader.qml
@@ -26,7 +26,6 @@ HRectangle {
HRoomAvatar {
id: avatar
roomId: chatPage.roomId
- dimension: roomHeader.height
Layout.alignment: Qt.AlignTop
}
diff --git a/src/qml/Chat/SendBox.qml b/src/qml/Chat/SendBox.qml
index 38be711c..fbaead21 100644
--- a/src/qml/Chat/SendBox.qml
+++ b/src/qml/Chat/SendBox.qml
@@ -22,7 +22,6 @@ HRectangle {
HUserAvatar {
id: avatar
userId: chatPage.userId
- dimension: sendBox.Layout.minimumHeight
}
HScrollableTextArea {
diff --git a/src/qml/Chat/Timeline/EventContent.qml b/src/qml/Chat/Timeline/EventContent.qml
index 5af4cbe5..87143e45 100644
--- a/src/qml/Chat/Timeline/EventContent.qml
+++ b/src/qml/Chat/Timeline/EventContent.qml
@@ -13,9 +13,10 @@ Row {
HUserAvatar {
id: avatar
- hidden: combine
userId: model.senderId
- dimension: model.showNameLine ? 48 : 28
+ width: model.showNameLine ? 48 : 28
+ height: combine ? 1 : model.showNameLine ? 48 : 28
+ opacity: combine ? 0 : 1
visible: ! isOwn
}
@@ -26,7 +27,7 @@ Row {
//width: nameLabel.implicitWidth
width: Math.min(
- roomEventListView.width - avatar.width - messageContent.spacing,
+ eventList.width - avatar.width - messageContent.spacing,
theme.fontSize.normal * 0.5 * 75, // 600 with 16px font
Math.max(
nameLabel.visible ? nameLabel.implicitWidth : 0,
diff --git a/src/qml/Chat/Timeline/EventDelegate.qml b/src/qml/Chat/Timeline/EventDelegate.qml
index 26be9440..0e61bd64 100644
--- a/src/qml/Chat/Timeline/EventDelegate.qml
+++ b/src/qml/Chat/Timeline/EventDelegate.qml
@@ -16,8 +16,8 @@ Column {
function getPreviousItem(nth) {
// Remember, index 0 = newest bottomest message
nth = nth || 1
- return roomEventListView.model.count - 1 > model.index + nth ?
- roomEventListView.model.get(model.index + nth) : null
+ return eventList.model.count - 1 > model.index + nth ?
+ eventList.model.get(model.index + nth) : null
}
property var previousItem: getPreviousItem()
@@ -60,7 +60,7 @@ Column {
property int verticalPadding: 4
ListView.onAdd: {
- var nextDelegate = roomEventListView.contentItem.children[index]
+ var nextDelegate = eventList.contentItem.children[index]
if (nextDelegate) { nextDelegate.reloadPreviousItem() }
}
diff --git a/src/qml/Chat/Timeline/EventList.qml b/src/qml/Chat/Timeline/EventList.qml
index 74bb3973..67268059 100644
--- a/src/qml/Chat/Timeline/EventList.qml
+++ b/src/qml/Chat/Timeline/EventList.qml
@@ -6,14 +6,14 @@ import SortFilterProxyModel 0.2
import "../../Base"
HRectangle {
- property alias listView: roomEventListView
+ property alias listView: eventList
property int space: 8
- color: theme.chat.roomEventList.background
+ color: theme.chat.eventList.background
HListView {
- id: roomEventListView
+ id: eventList
clip: true
model: HListModel {
@@ -48,12 +48,12 @@ HRectangle {
if (chatPage.category != "Invites" && canLoad && yPos <= 0.1) {
zz += 1
print(canLoad, zz)
- canLoad = false
+ eventList.canLoad = false
py.callClientCoro(
chatPage.userId,
"load_past_events",
[chatPage.roomId],
- function(more_to_load) { canLoad = more_to_load }
+ function(more_to_load) { eventList.canLoad = more_to_load }
)
}
}
@@ -62,7 +62,7 @@ HRectangle {
HNoticePage {
text: qsTr("Nothing to show here yet...")
- visible: roomEventListView.model.count < 1
+ visible: eventList.model.count < 1
anchors.fill: parent
}
}
diff --git a/src/qml/Pages/EditAccount/ClientSettings.qml b/src/qml/Pages/EditAccount/ClientSettings.qml
index 656d82bb..742ae0ab 100644
--- a/src/qml/Pages/EditAccount/ClientSettings.qml
+++ b/src/qml/Pages/EditAccount/ClientSettings.qml
@@ -7,9 +7,6 @@ import QtQuick.Layouts 1.12
import "../../Base"
import "../../utils.js" as Utils
-HRectangle {
- HLabel {
- anchors.centerIn: parent
- text: "Client - TODO"
- }
+HLabel {
+ text: "Client - TODO"
}
diff --git a/src/qml/Pages/EditAccount/Devices.qml b/src/qml/Pages/EditAccount/Devices.qml
index 4ac80c26..1e8cb0a6 100644
--- a/src/qml/Pages/EditAccount/Devices.qml
+++ b/src/qml/Pages/EditAccount/Devices.qml
@@ -7,9 +7,6 @@ import QtQuick.Layouts 1.12
import "../../Base"
import "../../utils.js" as Utils
-HRectangle {
- HLabel {
- anchors.centerIn: parent
- text: "Devices - TODO"
- }
+HLabel {
+ text: "Devices - TODO"
}
diff --git a/src/qml/Pages/EditAccount/EditAccount.qml b/src/qml/Pages/EditAccount/EditAccount.qml
index 39059258..2c81e45b 100644
--- a/src/qml/Pages/EditAccount/EditAccount.qml
+++ b/src/qml/Pages/EditAccount/EditAccount.qml
@@ -7,66 +7,75 @@ import QtQuick.Layouts 1.12
import "../../Base"
import "../../utils.js" as Utils
-HRectangle {
+Page {
+ id: editAccount
+ padding: currentSpacing < 8 ? 0 : currentSpacing
+ Behavior on padding { HNumberAnimation {} }
+
+ property bool wide: width > 414 + padding * 2
+ property int thinMaxWidth: 240
+ property int normalSpacing: 8
+ property int currentSpacing:
+ Math.min(normalSpacing * width / 400, normalSpacing * 2)
+
property string userId: ""
readonly property var userInfo: users.find(userId)
- HColumnLayout {
- anchors.fill: parent
+ header: HRectangle {
+ width: parent.width
+ height: theme.bottomElementsHeight
+ color: theme.pageHeadersBackground
HRowLayout {
- Layout.preferredHeight: theme.bottomElementsHeight
+ width: parent.width
HLabel {
- text: qsTr("Edit %1").arg(
+ text: qsTr("Account settings for %1").arg(
Utils.coloredNameHtml(userInfo.displayName, userId)
)
textFormat: Text.StyledText
font.pixelSize: theme.fontSize.big
elide: Text.ElideRight
maximumLineCount: 1
- // visible: width > 50
- Layout.fillWidth: true
- Layout.maximumWidth: parent.width - tabBar.width
- Layout.leftMargin: 8
+ Layout.leftMargin: currentSpacing
Layout.rightMargin: Layout.leftMargin
+ Layout.fillWidth: true
}
-
- TabBar {
- id: tabBar
- currentIndex: swipeView.currentIndex
- spacing: 0
- contentHeight: parent.height
-
- TabButton {
- text: qsTr("Profile")
- width: implicitWidth * 1.25
- }
-
- TabButton {
- text: qsTr("Devices")
- width: implicitWidth * 1.25
- }
-
- TabButton {
- text: qsTr("Harmony")
- width: implicitWidth * 1.25
- }
- }
- }
-
- SwipeView {
- id: swipeView
- clip: true
- currentIndex: tabBar.currentIndex
-
- Layout.fillHeight: true
- Layout.fillWidth: true
-
- Profile {}
- Devices {}
- ClientSettings {}
}
}
+
+ background: null
+
+ HColumnLayout {
+ anchors.fill: parent
+ spacing: 16
+
+ HRectangle {
+ color: theme.box.background
+ // radius: theme.box.radius
+
+ Layout.alignment: Qt.AlignCenter
+
+ Layout.preferredWidth: wide ? parent.width : thinMaxWidth
+ Layout.maximumWidth: Math.min(parent.width, 640)
+
+ Layout.preferredHeight: childrenRect.height
+ Layout.maximumHeight: parent.height
+
+ Profile { width: parent.width }
+ }
+
+ // 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 }
+ // }
+ }
}
diff --git a/src/qml/Pages/EditAccount/Profile.qml b/src/qml/Pages/EditAccount/Profile.qml
index 45369bc8..1eaf9748 100644
--- a/src/qml/Pages/EditAccount/Profile.qml
+++ b/src/qml/Pages/EditAccount/Profile.qml
@@ -7,9 +7,93 @@ import QtQuick.Layouts 1.12
import "../../Base"
import "../../utils.js" as Utils
-HRectangle {
- HLabel {
- anchors.centerIn: parent
- text: "profile"
+HGridLayout {
+ function applyChanges() {
+ saveButton.loading = true
+
+ py.callClientCoro(
+ userId, "set_displayname", [nameField.field.text],
+ () => { saveButton.loading = false }
+ )
+ }
+
+ columns: 2
+ flow: wide ? GridLayout.LeftToRight : GridLayout.TopToBottom
+ rowSpacing: currentSpacing
+
+ Component.onCompleted: nameField.field.forceActiveFocus()
+
+ HUserAvatar {
+ id: avatar
+ userId: editAccount.userId
+ toolTipImageUrl: null
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: wide ? 0 : currentSpacing
+
+ Layout.preferredWidth: thinMaxWidth
+ Layout.preferredHeight: Layout.preferredWidth
+ }
+
+ HColumnLayout {
+ id: profileInfo
+ spacing: normalSpacing
+
+ HColumnLayout {
+ spacing: normalSpacing
+ Layout.margins: currentSpacing
+
+ HLabel {
+ text: qsTr("User ID:
%1")
+ .arg(Utils.coloredNameHtml(userId, userId))
+ textFormat: Text.StyledText
+ wrapMode: Text.Wrap
+
+ Layout.fillWidth: true
+ }
+
+ HLabeledTextField {
+ id: nameField
+ label.text: qsTr("Display name:")
+ field.text: userInfo.displayName
+ field.onAccepted: applyChanges()
+
+ Layout.fillWidth: true
+ Layout.maximumWidth: 480
+ }
+ }
+
+ HSpacer {}
+
+ HRowLayout {
+ Layout.alignment: Qt.AlignBottom
+
+ HUIButton {
+ id: saveButton
+ iconName: "save"
+ text: qsTr("Save")
+ centerText: false
+ enabled: nameField.field.text != userInfo.displayName
+
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignBottom
+
+ onClicked: applyChanges()
+ }
+
+ HUIButton {
+ iconName: "cancel"
+ text: qsTr("Cancel")
+ centerText: false
+
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignBottom
+ enabled: saveButton.enabled && ! saveButton.loading
+
+ onClicked: {
+ nameField.field.text = userInfo.displayName
+ }
+ }
+ }
}
}
diff --git a/src/qml/SidePane/AccountDelegate.qml b/src/qml/SidePane/AccountDelegate.qml
index e830d13e..6691202f 100644
--- a/src/qml/SidePane/AccountDelegate.qml
+++ b/src/qml/SidePane/AccountDelegate.qml
@@ -12,18 +12,18 @@ Column {
property var userInfo: users.find(model.userId)
property bool expanded: true
- TapHandler {
- onTapped: pageStack.showPage(
- "EditAccount/EditAccount", { "userId": model.userId }
- )
- }
-
HHighlightRectangle {
width: parent.width
height: childrenRect.height
normalColor: theme.sidePane.account.background
+ TapHandler {
+ onTapped: pageStack.showPage(
+ "EditAccount/EditAccount", { "userId": model.userId }
+ )
+ }
+
HRowLayout {
id: row
width: parent.width
diff --git a/src/qml/Theme.qml b/src/qml/Theme.qml
index 579af502..d88b5414 100644
--- a/src/qml/Theme.qml
+++ b/src/qml/Theme.qml
@@ -32,6 +32,7 @@ QtObject {
property color background2: Qt.hsla(0, 0, 0.9, 0.7)
property color foreground: "black"
property color foregroundDim: Qt.hsla(0, 0, 0.2, 1)
+ property color foregroundDim2: Qt.hsla(0, 0, 0.3, 1)
property color foregroundError: Qt.hsla(0.95, 0.64, 0.32, 1)
property color textBorder: Qt.hsla(0, 0, 0, 0.07)
}
@@ -50,6 +51,8 @@ QtObject {
property QtObject textField: QtObject {
property color background: colors.background2
+ property color borderColor: "black"
+ property int borderWidth: 1
}
property QtObject textArea: QtObject {
@@ -82,7 +85,7 @@ QtObject {
property color background: colors.background2
}
- property QtObject roomEventList: QtObject {
+ property QtObject eventList: QtObject {
property color background: "transparent"
}
@@ -120,6 +123,8 @@ QtObject {
}
}
+ property color pageHeadersBackground: colors.background2
+
property QtObject box: QtObject {
property color background: colors.background0
property int radius: theme.radius
diff --git a/src/qml/utils.js b/src/qml/utils.js
index df4c6a4d..5e3de386 100644
--- a/src/qml/utils.js
+++ b/src/qml/utils.js
@@ -47,7 +47,7 @@ function nameColor(name) {
function coloredNameHtml(name, alt_id) {
// substring: remove leading @
- return "" +
+ return "" +
escapeHtml(name || alt_id) +
""
}
@@ -106,3 +106,22 @@ function filterMatches(filter, text) {
}
return true
}
+
+
+function thumbnailParametersFor(width, height) {
+ // https://matrix.org/docs/spec/client_server/latest#thumbnails
+
+ if (width > 640 || height > 480)
+ return {width: 800, height: 600, fillMode: Image.PreserveAspectFit}
+
+ if (width > 320 || height > 240)
+ return {width: 640, height: 480, fillMode: Image.PreserveAspectFit}
+
+ if (width > 96 || height > 96)
+ return {width: 320, height: 240, fillMode: Image.PreserveAspectFit}
+
+ if (width > 32 || height > 32)
+ return {width: 96, height: 96, fillMode: Image.PreserveAspectCrop}
+
+ return {width: 32, height: 32, fillMode: Image.PreserveAspectCrop}
+}