Add account settings page

Display name change working
This commit is contained in:
miruka 2019-07-13 20:15:20 -04:00
parent eeea0af4cd
commit 751a27157c
27 changed files with 435 additions and 162 deletions

View File

@ -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

51
src/icons/cancel.svg Normal file
View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
version="1.1"
id="svg14"
sodipodi:docname="invite_decline.svg"
inkscape:version="">
<metadata
id="metadata20">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs18" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="640"
inkscape:window-height="480"
id="namedview16"
showgrid="false"
inkscape:zoom="9.8333333"
inkscape:cx="6.9152542"
inkscape:cy="17.084746"
inkscape:current-layer="svg14" />
<path
d="M23 20.168l-8.185-8.187 8.185-8.174-2.832-2.807-8.182 8.179-8.176-8.179-2.81 2.81 8.186 8.196-8.186 8.184 2.81 2.81 8.203-8.192 8.18 8.192z"
id="path12"
style="fill:#ab0938;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

51
src/icons/save.svg Normal file
View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
version="1.1"
id="svg4"
sodipodi:docname="invite_accept.svg"
inkscape:version="">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="640"
inkscape:window-height="480"
id="namedview6"
showgrid="false"
inkscape:zoom="9.8333333"
inkscape:cx="-28.271186"
inkscape:cy="12"
inkscape:current-layer="svg4" />
<path
d="M9 21.035l-9-8.638 2.791-2.87 6.156 5.874 12.21-12.436 2.843 2.817z"
id="path2"
style="fill:#0d8967;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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()

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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))
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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

View File

@ -22,7 +22,6 @@ HRectangle {
HUserAvatar {
id: bannerAvatar
dimension: banner.Layout.preferredHeight
}
HIcon {

View File

@ -26,7 +26,6 @@ HRectangle {
HRoomAvatar {
id: avatar
roomId: chatPage.roomId
dimension: roomHeader.height
Layout.alignment: Qt.AlignTop
}

View File

@ -22,7 +22,6 @@ HRectangle {
HUserAvatar {
id: avatar
userId: chatPage.userId
dimension: sendBox.Layout.minimumHeight
}
HScrollableTextArea {

View File

@ -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,

View File

@ -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() }
}

View File

@ -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
}
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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 }
// }
}
}

View File

@ -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:<br>%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
}
}
}
}
}

View File

@ -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

View File

@ -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

View File

@ -47,7 +47,7 @@ function nameColor(name) {
function coloredNameHtml(name, alt_id) {
// substring: remove leading @
return "<font color='" + nameColor(name || alt_id.substring(1)) + "'>" +
return "<font color='" + nameColor(alt_id.substring(1)) + "'>" +
escapeHtml(name || alt_id) +
"</font>"
}
@ -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}
}