Add account settings page
Display name change working
This commit is contained in:
parent
eeea0af4cd
commit
751a27157c
9
TODO.md
9
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
|
||||
|
|
51
src/icons/cancel.svg
Normal file
51
src/icons/cancel.svg
Normal 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
51
src/icons/save.svg
Normal 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 |
|
@ -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:
|
||||
# pylint: disable=no-member
|
||||
provider: "ImageProvider" = field()
|
||||
id: str = 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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
14
src/qml/Base/HGridLayout.qml
Normal file
14
src/qml/Base/HGridLayout.qml
Normal 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))
|
||||
}
|
23
src/qml/Base/HLabeledTextField.qml
Normal file
23
src/qml/Base/HLabeledTextField.qml
Normal 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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -22,7 +22,6 @@ HRectangle {
|
|||
|
||||
HUserAvatar {
|
||||
id: bannerAvatar
|
||||
dimension: banner.Layout.preferredHeight
|
||||
}
|
||||
|
||||
HIcon {
|
||||
|
|
|
@ -26,7 +26,6 @@ HRectangle {
|
|||
HRoomAvatar {
|
||||
id: avatar
|
||||
roomId: chatPage.roomId
|
||||
dimension: roomHeader.height
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ HRectangle {
|
|||
HUserAvatar {
|
||||
id: avatar
|
||||
userId: chatPage.userId
|
||||
dimension: sendBox.Layout.minimumHeight
|
||||
}
|
||||
|
||||
HScrollableTextArea {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,6 @@ import QtQuick.Layouts 1.12
|
|||
import "../../Base"
|
||||
import "../../utils.js" as Utils
|
||||
|
||||
HRectangle {
|
||||
HLabel {
|
||||
anchors.centerIn: parent
|
||||
text: "Client - TODO"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,6 @@ import QtQuick.Layouts 1.12
|
|||
import "../../Base"
|
||||
import "../../utils.js" as Utils
|
||||
|
||||
HRectangle {
|
||||
HLabel {
|
||||
anchors.centerIn: parent
|
||||
text: "Devices - TODO"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
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 }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,93 @@ import QtQuick.Layouts 1.12
|
|||
import "../../Base"
|
||||
import "../../utils.js" as Utils
|
||||
|
||||
HRectangle {
|
||||
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 {
|
||||
anchors.centerIn: parent
|
||||
text: "profile"
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user