diff --git a/.ci/licenses.sh b/.ci/licenses.sh index cdcd305f..b54f8c39 100755 --- a/.ci/licenses.sh +++ b/.ci/licenses.sh @@ -7,7 +7,7 @@ set -eu -FILES=$(find src resources/qml -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.qml" \)) +FILES=$(find src qml -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.qml" \)) reuse addheader --copyright="Nheko Contributors" --license="GPL-3.0-or-later" $FILES diff --git a/.qmlformat.ini b/.qmlformat.ini new file mode 100644 index 00000000..c103c200 --- /dev/null +++ b/.qmlformat.ini @@ -0,0 +1,5 @@ +[General] +IndentWidth=4 +NewlineType=native +NormalizeOrder=true +UseTabs=false diff --git a/.qmllint.ini b/.qmllint.ini new file mode 100644 index 00000000..ccf13177 --- /dev/null +++ b/.qmllint.ini @@ -0,0 +1,23 @@ +[General] +AdditionalQmlImportPaths= +DisableDefaultImports=false +OverwriteImportTypes= +ResourcePath= + +[Warnings] +AttachedPropertyReuse=disable +BadSignalHandler=warning +CompilerWarnings=disable +ControlsSanity=disable +DeferredPropertyId=warning +Deprecated=warning +ImportFailure=warning +InheritanceCycle=warning +MultilineStrings=info +PropertyAlias=warning +RequiredProperty=warning +TypeError=warning +UnknownProperty=warning +UnqualifiedAccess=disable +UnusedImports=info +WithStatement=warning diff --git a/qml/Avatar.qml b/qml/Avatar.qml index 72ebdc14..e944d921 100644 --- a/qml/Avatar.qml +++ b/qml/Avatar.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./ui" +import "ui" import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Window 2.15 @@ -12,70 +10,54 @@ import im.nheko AbstractButton { id: avatar + property alias color: bg.color + property bool crop: true + property string displayName + property string roomid + property alias textColor: label.color property string url property string userid - property string roomid - property string displayName - property alias textColor: label.color - property bool crop: true - property alias color: bg.color - width: 48 height: 48 + width: 48 + background: Rectangle { id: bg - radius: Settings.avatarCircles ? height / 2 : height / 8 color: timelineRoot.palette.alternateBase + radius: Settings.avatarCircles ? height / 2 : height / 8 } Label { id: label - - enabled: false - anchors.fill: parent + color: timelineRoot.palette.text + enabled: false + font.pixelSize: avatar.height / 2 + horizontalAlignment: Text.AlignHCenter text: TimelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "") textFormat: Text.RichText - font.pixelSize: avatar.height / 2 verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter visible: img.status != Image.Ready && !Settings.useIdenticon - color: timelineRoot.palette.text } - Image { id: identicon - anchors.fill: parent - visible: Settings.useIdenticon && img.status != Image.Ready source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : "" + visible: Settings.useIdenticon && img.status != Image.Ready } - Image { id: img - anchors.fill: parent asynchronous: true fillMode: avatar.crop ? Image.PreserveAspectCrop : Image.PreserveAspectFit mipmap: true smooth: true - sourceSize.width: avatar.width * Screen.devicePixelRatio - sourceSize.height: avatar.height * Screen.devicePixelRatio source: avatar.url ? (avatar.url + "?radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale")) : "" - + sourceSize.height: avatar.height * Screen.devicePixelRatio + sourceSize.width: avatar.width * Screen.devicePixelRatio } - Rectangle { id: onlineIndicator - - anchors.bottom: avatar.bottom - anchors.right: avatar.right - visible: !!userid - height: avatar.height / 6 - width: height - radius: Settings.avatarCircles ? height / 2 : height / 8 - color: updatePresence() - function updatePresence() { switch (Presence.userPresence(userid)) { case "online": @@ -89,22 +71,28 @@ AbstractButton { } } - Connections { - target: Presence + anchors.bottom: avatar.bottom + anchors.right: avatar.right + color: updatePresence() + height: avatar.height / 6 + radius: Settings.avatarCircles ? height / 2 : height / 8 + visible: !!userid + width: height + Connections { function onPresenceChanged(id) { - if (id == userid) onlineIndicator.color = onlineIndicator.updatePresence(); + if (id == userid) + onlineIndicator.color = onlineIndicator.updatePresence(); } + + target: Presence } } - NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } - Ripple { color: Qt.rgba(timelineRoot.palette.alternateBase.r, timelineRoot.palette.alternateBase.g, timelineRoot.palette.alternateBase.b, 0.5) } - } diff --git a/qml/ChatPage.qml b/qml/ChatPage.qml index d8ae7feb..7b0da916 100644 --- a/qml/ChatPage.qml +++ b/qml/ChatPage.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.3 @@ -14,41 +12,28 @@ import QtQml 2.15 Rectangle { id: chatPage - color: timelineRoot.palette.window ColumnLayout { - spacing: 0 anchors.fill: parent + spacing: 0 Rectangle { id: offlineIndicator - + Layout.fillWidth: true + Layout.preferredHeight: offlineLabel.height + Nheko.paddingMedium color: Nheko.theme.error visible: !TimelineManager.isConnected - Layout.preferredHeight: offlineLabel.height + Nheko.paddingMedium - Layout.fillWidth: true z: 1 Label { id: offlineLabel - anchors.centerIn: parent text: qsTr("No network connection") } } - AdaptiveLayout { id: adaptiveView - - Layout.fillWidth: true - Layout.fillHeight: true - singlePageMode: communityListC.preferredWidth + roomListC.preferredWidth + timlineViewC.minimumWidth > width - pageIndex: 1 - - Component.onCompleted: initializePageIndex() - onSinglePageModeChanged: initializePageIndex() - function initializePageIndex() { if (!singlePageMode) adaptiveView.pageIndex = 0; @@ -58,91 +43,80 @@ Rectangle { adaptiveView.pageIndex = 1; } + Layout.fillHeight: true + Layout.fillWidth: true + pageIndex: 1 + singlePageMode: communityListC.preferredWidth + roomListC.preferredWidth + timlineViewC.minimumWidth > width + + Component.onCompleted: initializePageIndex() + onSinglePageModeChanged: initializePageIndex() + Connections { - target: Rooms function onCurrentRoomChanged() { adaptiveView.initializePageIndex(); } - } + target: Rooms + } AdaptiveLayoutElement { id: communityListC - - visible: Settings.groupView - minimumWidth: communitiesList.avatarSize * 4 + Nheko.paddingMedium * 2 collapsedWidth: communitiesList.avatarSize + 2 * Nheko.paddingMedium - preferredWidth: Settings.communityListWidth >= minimumWidth ? Settings.communityListWidth : collapsedWidth maximumWidth: communitiesList.avatarSize * 10 + 2 * Nheko.paddingMedium + minimumWidth: communitiesList.avatarSize * 4 + Nheko.paddingMedium * 2 + preferredWidth: Settings.communityListWidth >= minimumWidth ? Settings.communityListWidth : collapsedWidth + visible: Settings.groupView CommunitiesList { id: communitiesList - collapsed: parent.collapsed } - Binding { - target: Settings + delayed: true property: 'communityListWidth' + restoreMode: Binding.RestoreBindingOrValue + target: Settings value: communityListC.preferredWidth when: !adaptiveView.singlePageMode - delayed: true - restoreMode: Binding.RestoreBindingOrValue } - } - AdaptiveLayoutElement { id: roomListC - - minimumWidth: roomlist.avatarSize * 4 + Nheko.paddingSmall * 2 - preferredWidth: (Settings.roomListWidth == - 1) - ? (roomlist.avatarSize * 5 + Nheko.paddingSmall * 2) - : (Settings.roomListWidth >= minimumWidth ? Settings.roomListWidth : collapsedWidth) - maximumWidth: roomlist.avatarSize * 10 + Nheko.paddingSmall * 2 collapsedWidth: roomlist.avatarSize + 2 * Nheko.paddingMedium + maximumWidth: roomlist.avatarSize * 10 + Nheko.paddingSmall * 2 + minimumWidth: roomlist.avatarSize * 4 + Nheko.paddingSmall * 2 + preferredWidth: (Settings.roomListWidth == -1) ? (roomlist.avatarSize * 5 + Nheko.paddingSmall * 2) : (Settings.roomListWidth >= minimumWidth ? Settings.roomListWidth : collapsedWidth) RoomList { id: roomlist - - height: adaptiveView.height collapsed: parent.collapsed + height: adaptiveView.height } - Binding { - target: Settings + delayed: true property: 'roomListWidth' + restoreMode: Binding.RestoreBindingOrValue + target: Settings value: roomListC.preferredWidth when: !adaptiveView.singlePageMode - delayed: true - restoreMode: Binding.RestoreBindingOrValue } - } - AdaptiveLayoutElement { id: timlineViewC - minimumWidth: fontMetrics.averageCharacterWidth * 40 + Nheko.avatarSize + 2 * Nheko.paddingMedium TimelineView { id: timeline - - showBackButton: adaptiveView.singlePageMode room: Rooms.currentRoom roomPreview: Rooms.currentRoomPreview.roomid ? Rooms.currentRoomPreview : null + showBackButton: adaptiveView.singlePageMode } - } - } - } - PrivacyScreen { anchors.fill: parent - visible: Settings.privacyScreen screenTimeout: Settings.privacyScreenTimeout timelineRoot: adaptiveView + visible: Settings.privacyScreen } - } diff --git a/qml/CommunitiesList.qml b/qml/CommunitiesList.qml index ea4c8006..62f34c0b 100644 --- a/qml/CommunitiesList.qml +++ b/qml/CommunitiesList.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./dialogs" +import "dialogs" import Qt.labs.platform 1.1 as Platform import QtQml 2.12 import QtQuick 2.12 @@ -13,19 +11,142 @@ import im.nheko Page { id: communitySidebar + //leftPadding: Nheko.paddingSmall //rightPadding: Nheko.paddingSmall property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6) property bool collapsed: false + background: Rectangle { + color: Nheko.theme.sidebarBackground + } + ListView { id: communitiesList - anchors.left: parent.left anchors.right: parent.right height: parent.height model: Communities.filtered() + delegate: ItemDelegate { + id: communityItem + + property color backgroundColor: timelineRoot.palette.window + property color bubbleBackground: timelineRoot.palette.highlight + property color bubbleText: timelineRoot.palette.highlightedText + property color importantText: timelineRoot.palette.text + property color unimportantText: timelineRoot.palette.placeholderText + + ToolTip.delay: Nheko.tooltipDelay + ToolTip.text: model.tooltip + ToolTip.visible: hovered && collapsed + height: avatarSize + 2 * Nheko.paddingMedium + state: "normal" + width: ListView.view.width + + background: Rectangle { + color: backgroundColor + } + states: [ + State { + name: "highlight" + when: (communityItem.hovered || model.hidden) && !(Communities.currentTagId == model.id) + + PropertyChanges { + backgroundColor: timelineRoot.palette.dark + bubbleBackground: timelineRoot.palette.highlight + bubbleText: timelineRoot.palette.highlightedText + importantText: timelineRoot.palette.brightText + target: communityItem + unimportantText: timelineRoot.palette.brightText + } + }, + State { + name: "selected" + when: Communities.currentTagId == model.id + + PropertyChanges { + backgroundColor: timelineRoot.palette.highlight + bubbleBackground: timelineRoot.palette.highlightedText + bubbleText: timelineRoot.palette.highlight + importantText: timelineRoot.palette.highlightedText + target: communityItem + unimportantText: timelineRoot.palette.highlightedText + } + } + ] + + onClicked: Communities.setCurrentTagId(model.id) + onPressAndHold: communityContextMenu.show(model.id) + + Item { + anchors.fill: parent + + TapHandler { + acceptedButtons: Qt.RightButton + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad + gesturePolicy: TapHandler.ReleaseWithinBounds + + onSingleTapped: communityContextMenu.show(model.id) + } + } + RowLayout { + id: r + anchors.fill: parent + anchors.leftMargin: Nheko.paddingMedium + (communitySidebar.collapsed ? 0 : (fontMetrics.lineSpacing * model.depth)) + anchors.margins: Nheko.paddingMedium + spacing: Nheko.paddingMedium + + ImageButton { + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: fontMetrics.lineSpacing + Layout.preferredWidth: fontMetrics.lineSpacing + ToolTip.delay: Nheko.tooltipDelay + ToolTip.text: model.collapsed ? qsTr("Expand") : qsTr("Collapse") + ToolTip.visible: hovered + height: fontMetrics.lineSpacing + hoverEnabled: true + image: model.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg" + visible: !communitySidebar.collapsed && model.collapsible + width: fontMetrics.lineSpacing + + onClicked: model.collapsed = !model.collapsed + } + Item { + Layout.preferredWidth: fontMetrics.lineSpacing + visible: !communitySidebar.collapsed && !model.collapsible && Communities.containsSubspaces + } + Avatar { + id: avatar + Layout.alignment: Qt.AlignVCenter + color: communityItem.backgroundColor + displayName: model.displayName + enabled: false + height: avatarSize + roomid: model.id + url: { + if (model.avatarUrl.startsWith("mxc://")) + return model.avatarUrl.replace("mxc://", "image://MxcImage/"); + else + return "image://colorimage/" + model.avatarUrl + "?" + communityItem.unimportantText; + } + width: avatarSize + } + ElidedLabel { + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + color: communityItem.importantText + elideWidth: width + fullText: model.displayName + textFormat: Text.PlainText + visible: !communitySidebar.collapsed + } + Item { + Layout.fillWidth: true + } + } + } + Platform.Menu { id: communityContextMenu @@ -38,143 +159,9 @@ Page { Platform.MenuItem { text: qsTr("Hide rooms with this tag or from this space by default.") + onTriggered: Communities.toggleTagId(communityContextMenu.tagId) } - } - - delegate: ItemDelegate { - id: communityItem - - property color backgroundColor: timelineRoot.palette.window - property color importantText: timelineRoot.palette.text - property color unimportantText: timelineRoot.palette.placeholderText - property color bubbleBackground: timelineRoot.palette.highlight - property color bubbleText: timelineRoot.palette.highlightedText - - height: avatarSize + 2 * Nheko.paddingMedium - width: ListView.view.width - state: "normal" - ToolTip.visible: hovered && collapsed - ToolTip.text: model.tooltip - ToolTip.delay: Nheko.tooltipDelay - onClicked: Communities.setCurrentTagId(model.id) - onPressAndHold: communityContextMenu.show(model.id) - states: [ - State { - name: "highlight" - when: (communityItem.hovered || model.hidden) && !(Communities.currentTagId == model.id) - - PropertyChanges { - target: communityItem - backgroundColor: timelineRoot.palette.dark - importantText: timelineRoot.palette.brightText - unimportantText: timelineRoot.palette.brightText - bubbleBackground: timelineRoot.palette.highlight - bubbleText: timelineRoot.palette.highlightedText - } - - }, - State { - name: "selected" - when: Communities.currentTagId == model.id - - PropertyChanges { - target: communityItem - backgroundColor: timelineRoot.palette.highlight - importantText: timelineRoot.palette.highlightedText - unimportantText: timelineRoot.palette.highlightedText - bubbleBackground: timelineRoot.palette.highlightedText - bubbleText: timelineRoot.palette.highlight - } - - } - ] - - Item { - anchors.fill: parent - - TapHandler { - acceptedButtons: Qt.RightButton - onSingleTapped: communityContextMenu.show(model.id) - gesturePolicy: TapHandler.ReleaseWithinBounds - acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad - } - - } - - RowLayout { - id: r - spacing: Nheko.paddingMedium - anchors.fill: parent - anchors.margins: Nheko.paddingMedium - anchors.leftMargin: Nheko.paddingMedium + (communitySidebar.collapsed ? 0 : (fontMetrics.lineSpacing * model.depth)) - - ImageButton { - visible: !communitySidebar.collapsed && model.collapsible - Layout.preferredHeight: fontMetrics.lineSpacing - Layout.preferredWidth: fontMetrics.lineSpacing - Layout.alignment: Qt.AlignVCenter - height: fontMetrics.lineSpacing - width: fontMetrics.lineSpacing - image: model.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg" - ToolTip.visible: hovered - ToolTip.delay: Nheko.tooltipDelay - ToolTip.text: model.collapsed ? qsTr("Expand") : qsTr("Collapse") - hoverEnabled: true - - onClicked: model.collapsed = !model.collapsed - } - - Item { - Layout.preferredWidth: fontMetrics.lineSpacing - visible: !communitySidebar.collapsed && !model.collapsible && Communities.containsSubspaces - } - - Avatar { - id: avatar - - enabled: false - Layout.alignment: Qt.AlignVCenter - height: avatarSize - width: avatarSize - url: { - if (model.avatarUrl.startsWith("mxc://")) - return model.avatarUrl.replace("mxc://", "image://MxcImage/"); - else - return "image://colorimage/" + model.avatarUrl + "?" + communityItem.unimportantText; - } - roomid: model.id - displayName: model.displayName - color: communityItem.backgroundColor - } - - ElidedLabel { - visible: !communitySidebar.collapsed - Layout.alignment: Qt.AlignVCenter - color: communityItem.importantText - Layout.fillWidth: true - elideWidth: width - fullText: model.displayName - textFormat: Text.PlainText - } - - Item { - Layout.fillWidth: true - } - - } - - background: Rectangle { - color: backgroundColor - } - - } - } - - background: Rectangle { - color: Nheko.theme.sidebarBackground - } - } diff --git a/qml/Completer.qml b/qml/Completer.qml index c8fc62bf..ad93b55e 100644 --- a/qml/Completer.qml +++ b/qml/Completer.qml @@ -1,115 +1,90 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick import QtQuick.Controls import QtQuick.Layouts import im.nheko import im.nheko - -import "./ui/" +import "ui" Control { id: popup - property alias currentIndex: listView.currentIndex - property string completerName - property var completer - property bool bottomToTop: true - property bool fullWidth: false - property bool centerRowContent: true property int avatarHeight: 24 property int avatarWidth: 24 + property bool bottomToTop: true + property bool centerRowContent: true + property var completer + property string completerName + property alias count: listView.count + property alias currentIndex: listView.currentIndex + property bool fullWidth: false property int rowMargin: 0 property int rowSpacing: 5 - property alias count: listView.count signal completionClicked(string completion) signal completionSelected(string id) - function up() { - if (bottomToTop) - down_(); - else - up_(); - } - - function down() { - if (bottomToTop) - up_(); - else - down_(); - } - - function up_() { - currentIndex = currentIndex - 1; - if (currentIndex == -2) - currentIndex = listView.count - 1; - - } - - function down_() { - currentIndex = currentIndex + 1; - if (currentIndex >= listView.count) - currentIndex = -1; - - } - function currentCompletion() { if (currentIndex > -1 && currentIndex < listView.count) return completer.completionAt(currentIndex); else return null; } - + function down() { + if (bottomToTop) + up_(); + else + down_(); + } + function down_() { + currentIndex = currentIndex + 1; + if (currentIndex >= listView.count) + currentIndex = -1; + } function finishCompletion() { if (popup.completerName == "room") popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid); - } - - onCompleterNameChanged: { - if (completerName) { - completer = TimelineManager.completerFor(completerName, completerName == "room" ? "" : room.roomId); - completer.setSearchString(""); - } else { - completer = undefined; - } - currentIndex = -1 + function up() { + if (bottomToTop) + down_(); + else + up_(); + } + function up_() { + currentIndex = currentIndex - 1; + if (currentIndex == -2) + currentIndex = listView.count - 1; } bottomPadding: 1 leftPadding: 1 - topPadding: 1 rightPadding: 1 + topPadding: 1 + background: Rectangle { + border.color: timelineRoot.palette.mid + color: timelineRoot.palette.base + } contentItem: ListView { id: listView + clip: true + highlightFollowsCurrentItem: true - // If we have fewer than 7 items, just use the list view's content height. + // If we have fewer than 7 items, just use the list view's content height. // Otherwise, we want to show 7 items. Each item consists of row spacing between rows, row margins // on each side of a row, 1px of padding above the first item and below the last item, and nominally // some kind of content height. avatarHeight is used for just about every delegate, so we're using // that until we find something better. Put is all together and you have the formula below! - implicitHeight: Math.min(contentHeight, 6*rowSpacing + 7*(popup.avatarHeight + 2*rowMargin)) - clip: true - - Timer { - id: deadTimer - interval: 50 - } - - onContentYChanged: deadTimer.restart() - - reuseItems: true + implicitHeight: Math.min(contentHeight, 6 * rowSpacing + 7 * (popup.avatarHeight + 2 * rowMargin)) implicitWidth: listView.contentItem.childrenRect.width model: completer - verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom - spacing: rowSpacing pixelAligned: true - highlightFollowsCurrentItem: true + reuseItems: true + spacing: rowSpacing + verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom delegate: Rectangle { property variant modelData: model @@ -120,193 +95,176 @@ Control { MouseArea { id: mouseArea - anchors.fill: parent hoverEnabled: true - onPositionChanged: if (!listView.moving && !deadTimer.running) popup.currentIndex = model.index + onClicked: { - popup.completionClicked(completer.completionAt(model.index)); - if (popup.completerName == "room") - popup.completionSelected(model.roomid); + popup.completionClicked(completer.completionAt(model.index)); + if (popup.completerName == "room") + popup.completionSelected(model.roomid); } + onPositionChanged: if (!listView.moving && !deadTimer.running) + popup.currentIndex = model.index } Ripple { color: Qt.rgba(timelineRoot.palette.base.r, timelineRoot.palette.base.g, timelineRoot.palette.base.b, 0.5) } - DelegateChooser { id: chooser - - roleValue: popup.completerName anchors.fill: parent anchors.margins: popup.rowMargin enabled: false + roleValue: popup.completerName DelegateChoice { roleValue: "user" RowLayout { id: del - anchors.centerIn: parent spacing: rowSpacing Avatar { - height: popup.avatarHeight - width: popup.avatarWidth displayName: model.displayName - userid: model.userid + height: popup.avatarHeight url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + userid: model.userid + width: popup.avatarWidth + onClicked: popup.completionClicked(completer.completionAt(model.index)) } - Label { - text: model.displayName color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text + text: model.displayName } - Label { - text: "(" + model.userid + ")" color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.placeholderText + text: "(" + model.userid + ")" } - } - } - DelegateChoice { roleValue: "emoji" RowLayout { id: del - anchors.centerIn: parent spacing: rowSpacing Label { - text: model.unicode color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text font: Settings.emojiFont + text: model.unicode } - Label { - text: model.shortName color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text + text: model.shortName } - } - } - DelegateChoice { roleValue: "customEmoji" RowLayout { id: del - anchors.centerIn: parent spacing: rowSpacing Avatar { - height: popup.avatarHeight - width: popup.avatarWidth + crop: false displayName: model.shortcode + height: popup.avatarHeight //userid: model.shortcode url: model.url.replace("mxc://", "image://MxcImage/") + width: popup.avatarWidth + onClicked: popup.completionClicked(completer.completionAt(model.index)) - crop: false } - Label { - text: model.shortcode color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text + text: model.shortcode } - Label { - text: "(" + model.packname + ")" color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.placeholderText + text: "(" + model.packname + ")" } - } - } - DelegateChoice { roleValue: "room" RowLayout { id: del - anchors.centerIn: centerRowContent ? parent : undefined spacing: rowSpacing Avatar { - height: popup.avatarHeight - width: popup.avatarWidth displayName: model.roomName + height: popup.avatarHeight roomid: model.roomid url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + width: popup.avatarWidth + onClicked: { popup.completionClicked(completer.completionAt(model.index)); popup.completionSelected(model.roomid); } } - Label { - text: model.roomName - font.pixelSize: popup.avatarHeight * 0.5 color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text + font.pixelSize: popup.avatarHeight * 0.5 + text: model.roomName textFormat: Text.RichText } - } - } - DelegateChoice { roleValue: "roomAliases" RowLayout { id: del - anchors.centerIn: parent spacing: rowSpacing Avatar { - height: popup.avatarHeight - width: popup.avatarWidth displayName: model.roomName + height: popup.avatarHeight roomid: model.roomid url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + width: popup.avatarWidth + onClicked: popup.completionClicked(completer.completionAt(model.index)) } - Label { - text: model.roomName color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text + text: model.roomName textFormat: Text.RichText } - Label { - text: "(" + model.roomAlias + ")" color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.placeholderText + text: "(" + model.roomAlias + ")" textFormat: Text.RichText } - } - } - } - } + onContentYChanged: deadTimer.restart() + + Timer { + id: deadTimer + interval: 50 + } } - - background: Rectangle { - color: timelineRoot.palette.base - border.color: timelineRoot.palette.mid + onCompleterNameChanged: { + if (completerName) { + completer = TimelineManager.completerFor(completerName, completerName == "room" ? "" : room.roomId); + completer.setSearchString(""); + } else { + completer = undefined; + } + currentIndex = -1; } - } diff --git a/qml/ElidedLabel.qml b/qml/ElidedLabel.qml index cb2c058b..68b31629 100644 --- a/qml/ElidedLabel.qml +++ b/qml/ElidedLabel.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.9 import QtQuick.Controls 2.5 import im.nheko @@ -10,21 +8,25 @@ import im.nheko Label { id: root - property alias fullText: metrics.text property alias elideWidth: metrics.elideWidth - property int fullTextWidth: Math.ceil(metrics.advanceWidth) + property alias fullText: metrics.text + property int fullTextWidth: Math.ceil(metrics2.advanceWidth + 4) color: timelineRoot.palette.text - text: (textFormat == Text.PlainText) ? metrics.elidedText : TimelineManager.escapeEmoji(TimelineManager.htmlEscape(metrics.elidedText)) - maximumLineCount: 1 elide: Text.ElideRight + maximumLineCount: 1 + text: (textFormat == Text.PlainText) ? metrics.elidedText : TimelineManager.escapeEmoji(TimelineManager.htmlEscape(metrics.elidedText)) textFormat: Text.PlainText TextMetrics { id: metrics - - font.pointSize: root.font.pointSize elide: Text.ElideRight + font: root.font + } + TextMetrics { + id: metrics2 + //elide: Text.ElideRight + font: root.font + text: metrics.text } - } diff --git a/qml/EncryptionIndicator.qml b/qml/EncryptionIndicator.qml index ca030182..b69c0028 100644 --- a/qml/EncryptionIndicator.qml +++ b/qml/EncryptionIndicator.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.12 import QtQuick.Controls 2.1 import QtQuick.Window 2.15 @@ -12,28 +10,36 @@ Image { id: stateImg property bool encrypted: false - property int trust: Crypto.Unverified - property string sourceUrl: { if (!encrypted) - return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?"; - + return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?"; switch (trust) { - case Crypto.Verified: + case Crypto.Verified: return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?"; - case Crypto.TOFU: + case Crypto.TOFU: return "image://colorimage/:/icons/icons/ui/shield-filled.svg?"; - case Crypto.Unverified: + case Crypto.Unverified: return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?"; - default: + default: return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?"; } } + property int trust: Crypto.Unverified - width: 16 + ToolTip.text: { + if (!encrypted) + return qsTr("This message is not encrypted!"); + switch (trust) { + case Crypto.Verified: + return qsTr("Encrypted by a verified device"); + case Crypto.TOFU: + return qsTr("Encrypted by an unverified device, but you have trusted that user so far."); + default: + return qsTr("Encrypted by an unverified device or the key is from an untrusted source like the key backup."); + } + } + ToolTip.visible: ma.hovered height: 16 - sourceSize.height: height * Screen.devicePixelRatio - sourceSize.width: width * Screen.devicePixelRatio source: { if (encrypted) { switch (trust) { @@ -48,23 +54,11 @@ Image { return sourceUrl + Nheko.theme.error; } } - ToolTip.visible: ma.hovered - ToolTip.text: { - if (!encrypted) - return qsTr("This message is not encrypted!"); - - switch (trust) { - case Crypto.Verified: - return qsTr("Encrypted by a verified device"); - case Crypto.TOFU: - return qsTr("Encrypted by an unverified device, but you have trusted that user so far."); - default: - return qsTr("Encrypted by an unverified device or the key is from an untrusted source like the key backup."); - } - } + sourceSize.height: height * Screen.devicePixelRatio + sourceSize.width: width * Screen.devicePixelRatio + width: 16 HoverHandler { id: ma } - } diff --git a/qml/ForwardCompleter.qml b/qml/ForwardCompleter.qml index 3ff8ca19..395e12b5 100644 --- a/qml/ForwardCompleter.qml +++ b/qml/ForwardCompleter.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./delegates/" +import "delegates" import QtQuick 2.9 import QtQuick.Controls 2.3 import im.nheko @@ -17,66 +15,65 @@ Popup { mid = mid_in; } - x: Math.round(parent.width / 2 - width / 2) - y: Math.round(parent.height / 4) + leftPadding: 10 modal: true palette: timelineRoot.palette parent: Overlay.overlay - width: timelineRoot.width * 0.8 - leftPadding: 10 rightPadding: 10 + width: timelineRoot.width * 0.8 + x: Math.round(parent.width / 2 - width / 2) + y: Math.round(parent.height / 4) + + Overlay.modal: Rectangle { + color: Qt.rgba(timelineRoot.palette.window.r, timelineRoot.palette.window.g, timelineRoot.palette.window.b, 0.7) + } + background: Rectangle { + color: timelineRoot.palette.window + } + onOpened: { roomTextInput.forceActiveFocus(); } Column { id: forwardColumn - spacing: 5 Label { id: titleLabel - - text: qsTr("Forward Message") - font.bold: true bottomPadding: 10 color: timelineRoot.palette.text + font.bold: true + text: qsTr("Forward Message") } - Reply { id: replyPreview - property var modelData: room ? room.getDump(mid, "") : { - } + property var modelData: room ? room.getDump(mid, "") : {} - width: parent.width - - userColor: TimelineManager.userColor(modelData.userId, timelineRoot.palette.window) blurhash: modelData.blurhash ?? "" body: modelData.body ?? "" - formattedBody: modelData.formattedBody ?? "" + encryptionError: modelData.encryptionError ?? "" eventId: modelData.eventId ?? "" filename: modelData.filename ?? "" filesize: modelData.filesize ?? "" + formattedBody: modelData.formattedBody ?? "" + isOnlyEmoji: modelData.isOnlyEmoji ?? false + originalWidth: modelData.originalWidth ?? 0 proportionalHeight: modelData.proportionalHeight ?? 1 type: modelData.type ?? MtxEvent.UnknownMessage typeString: modelData.typeString ?? "" url: modelData.url ?? "" - originalWidth: modelData.originalWidth ?? 0 - isOnlyEmoji: modelData.isOnlyEmoji ?? false + userColor: TimelineManager.userColor(modelData.userId, timelineRoot.palette.window) userId: modelData.userId ?? "" userName: modelData.userName ?? "" - encryptionError: modelData.encryptionError ?? "" + width: parent.width } - MatrixTextField { id: roomTextInput - - width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2 color: timelineRoot.palette.text - onTextEdited: { - completerPopup.completer.searchString = text; - } + width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2 + Keys.onPressed: { if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) { event.accepted = true; @@ -92,43 +89,31 @@ Popup { event.accepted = true; } } + onTextEdited: { + completerPopup.completer.searchString = text; + } } - Completer { id: completerPopup - - width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2 - completerName: "room" - fullWidth: true - centerRowContent: false avatarHeight: 24 avatarWidth: 24 bottomToTop: false + centerRowContent: false + completerName: "room" + fullWidth: true + width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2 } - } - Connections { function onCompletionSelected(id) { room.forwardMessage(messageContextMenu.eventId, id); forwardMessagePopup.close(); } - function onCountChanged() { if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count)) completerPopup.currentIndex = 0; - } target: completerPopup } - - background: Rectangle { - color: timelineRoot.palette.window - } - - Overlay.modal: Rectangle { - color: Qt.rgba(timelineRoot.palette.window.r, timelineRoot.palette.window.g, timelineRoot.palette.window.b, 0.7) - } - } diff --git a/qml/ImageButton.qml b/qml/ImageButton.qml index e4d0bbe6..66686ce1 100644 --- a/qml/ImageButton.qml +++ b/qml/ImageButton.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./ui" +import "ui" import QtQuick 2.3 import QtQuick.Controls 2.3 import im.nheko @@ -11,36 +9,32 @@ import im.nheko AbstractButton { id: button - property alias cursor: mouseArea.cursorShape - property string image: undefined - property color highlightColor: timelineRoot.palette.highlight property color buttonTextColor: timelineRoot.palette.placeholderText property bool changeColorOnHover: true + property alias cursor: mouseArea.cursorShape + property color highlightColor: timelineRoot.palette.highlight + property string image: undefined property bool ripple: true focusPolicy: Qt.NoFocus - width: 16 height: 16 + width: 16 Image { id: buttonImg // Workaround, can't get icon.source working for now... anchors.fill: parent - source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : "" fillMode: Image.PreserveAspectFit + source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : "" } - NhekoCursorShape { id: mouseArea - anchors.fill: parent cursorShape: Qt.PointingHandCursor } - Ripple { - enabled: button.ripple color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5) + enabled: button.ripple } - } diff --git a/qml/MatrixText.qml b/qml/MatrixText.qml index ccad597c..5a423981 100644 --- a/qml/MatrixText.qml +++ b/qml/MatrixText.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.5 import QtQuick.Controls 2.3 import im.nheko @@ -12,27 +10,27 @@ TextEdit { property alias cursorShape: cs.cursorShape - textFormat: TextEdit.RichText - readOnly: true - focus: false - wrapMode: Text.Wrap - selectByMouse: !Settings.mobileMode + ToolTip.text: hoveredLink + ToolTip.visible: hoveredLink || false // this always has to be enabled, otherwise you can't click links anymore! //enabled: selectByMouse color: timelineRoot.palette.text - onLinkActivated: Nheko.openLink(link) - ToolTip.visible: hoveredLink || false - ToolTip.text: hoveredLink + focus: false + readOnly: true + selectByMouse: !Settings.mobileMode + textFormat: TextEdit.RichText + wrapMode: Text.Wrap + // Setting a tooltip delay makes the hover text empty .-. //ToolTip.delay: Nheko.tooltipDelay Component.onCompleted: { TimelineManager.fixImageRendering(r.textDocument, r); } + onLinkActivated: Nheko.openLink(link) NhekoCursorShape { id: cs anchors.fill: parent cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor } - } diff --git a/qml/MatrixTextField.qml b/qml/MatrixTextField.qml index 071174ee..1acf0d9b 100644 --- a/qml/MatrixTextField.qml +++ b/qml/MatrixTextField.qml @@ -1,74 +1,66 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import im.nheko - ColumnLayout { id: c + property color backgroundColor: timelineRoot.palette.base property alias color: labelC.color - property alias textPadding: input.padding - property alias text: input.text + property alias echoMode: input.echoMode + property alias font: input.font property alias label: labelC.text property alias placeholderText: input.placeholderText - property alias font: input.font - property alias echoMode: input.echoMode property alias selectByMouse: input.selectByMouse + property alias text: input.text + property alias textPadding: input.padding - Timer { - id: timer - interval: 350 - onTriggered: editingFinished() - } - - onTextChanged: timer.restart() - - signal textEdited signal accepted signal editingFinished - - function forceActiveFocus() { - input.forceActiveFocus(); - } + signal textEdited function clear() { input.clear(); } + function forceActiveFocus() { + input.forceActiveFocus(); + } ToolTip.delay: Nheko.tooltipDelay ToolTip.visible: hover.hovered - spacing: 0 - Item { - Layout.fillWidth: true - Layout.preferredHeight: labelC.contentHeight - Layout.margins: input.padding - Layout.bottomMargin: Nheko.paddingSmall - visible: labelC.text + onTextChanged: timer.restart() + Timer { + id: timer + interval: 350 + + onTriggered: editingFinished() + } + Item { + Layout.bottomMargin: Nheko.paddingSmall + Layout.fillWidth: true + Layout.margins: input.padding + Layout.preferredHeight: labelC.contentHeight + visible: labelC.text z: 1 Label { id: labelC - - y: contentHeight + input.padding + Nheko.paddingSmall - enabled: false - - palette: timelineRoot.palette color: timelineRoot.palette.text + enabled: false + font.letterSpacing: input.font.pixelSize * 0.02 font.pixelSize: input.font.pixelSize font.weight: Font.DemiBold - font.letterSpacing: input.font.pixelSize * 0.02 - width: parent.width - + palette: timelineRoot.palette state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : "" + width: parent.width + y: contentHeight + input.padding + Nheko.paddingSmall states: State { name: "focused" @@ -77,76 +69,63 @@ ColumnLayout { target: labelC y: 0 } - PropertyChanges { - target: input opacity: 1 + target: input } - } - transitions: Transition { from: "" - to: "focused" reversible: true + to: "focused" NumberAnimation { - target: labelC + alwaysRunToEnd: true + duration: 210 + easing.type: Easing.InCubic properties: "y" - duration: 210 - easing.type: Easing.InCubic - alwaysRunToEnd: true + target: labelC } - NumberAnimation { - target: input - properties: "opacity" + alwaysRunToEnd: true duration: 210 easing.type: Easing.InCubic - alwaysRunToEnd: true + properties: "opacity" + target: input } - } } } - TextField { id: input Layout.fillWidth: true - - palette: timelineRoot.palette color: labelC.color - opacity: labelC.text ? 0 : 1 focus: true - - onTextEdited: c.textEdited() - onAccepted: c.accepted() - onEditingFinished: c.editingFinished() + opacity: labelC.text ? 0 : 1 + palette: timelineRoot.palette background: Rectangle { id: backgroundRect - color: labelC.text ? "transparent" : backgroundColor } + onAccepted: c.accepted() + onEditingFinished: c.editingFinished() + onTextEdited: c.textEdited() } - Rectangle { id: blueBar - Layout.fillWidth: true - color: timelineRoot.palette.highlight height: 1 Rectangle { id: blackBar - - anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter - height: parent.height*2 - width: 0 + anchors.top: parent.top color: timelineRoot.palette.text + height: parent.height * 2 + width: 0 states: State { name: "focused" @@ -156,29 +135,22 @@ ColumnLayout { target: blackBar width: blueBar.width } - } - transitions: Transition { from: "" - to: "focused" reversible: true - + to: "focused" NumberAnimation { - target: blackBar - properties: "width" + alwaysRunToEnd: true duration: 310 easing.type: Easing.InCubic - alwaysRunToEnd: true + properties: "width" + target: blackBar } - } - } - } - HoverHandler { id: hover enabled: c.ToolTip.text diff --git a/qml/MessageInput.qml b/qml/MessageInput.qml index 2dcdf407..7072f1d9 100644 --- a/qml/MessageInput.qml +++ b/qml/MessageInput.qml @@ -1,10 +1,8 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./emoji" -import "./voip" +import "emoji" +import "voip" import QtQuick 2.12 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 @@ -14,51 +12,45 @@ import im.nheko Rectangle { id: inputBar - color: timelineRoot.palette.window - Layout.fillWidth: true - Layout.preferredHeight: row.implicitHeight - Layout.minimumHeight: 40 property bool showAllButtons: width > 450 || (messageInput.length == 0 && !messageInput.inputMethodComposing) + Layout.fillWidth: true + Layout.minimumHeight: 40 + Layout.preferredHeight: row.implicitHeight + color: timelineRoot.palette.window Component { id: placeCallDialog - PlaceCall { } - } - Component { id: screenShareDialog - ScreenShare { } - } - RowLayout { id: row - - visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false anchors.fill: parent spacing: 0 + visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false ImageButton { - visible: CallManager.callsSupported && showAllButtons - opacity: CallManager.haveCallInvite ? 0.3 : 1 Layout.alignment: Qt.AlignBottom - hoverEnabled: true - width: 22 - height: 22 - image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg" - ToolTip.visible: hovered - ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call") Layout.margins: 8 + ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call") + ToolTip.visible: hovered + height: 22 + hoverEnabled: true + image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg" + opacity: CallManager.haveCallInvite ? 0.3 : 1 + visible: CallManager.callsSupported && showAllButtons + width: 22 + onClicked: { if (room) { if (CallManager.haveCallInvite) { - return ; + return; } else if (CallManager.isOnCall) { CallManager.hangUp(); } else { @@ -69,18 +61,18 @@ Rectangle { } } } - ImageButton { - visible: showAllButtons Layout.alignment: Qt.AlignBottom - hoverEnabled: true - width: 22 - height: 22 - image: ":/icons/icons/ui/attach.svg" Layout.margins: 8 - onClicked: room.input.openFileSelection() - ToolTip.visible: hovered ToolTip.text: qsTr("Send a file") + ToolTip.visible: hovered + height: 22 + hoverEnabled: true + image: ":/icons/icons/ui/attach.svg" + visible: showAllButtons + width: 22 + + onClicked: room.input.openFileSelection() Rectangle { anchors.fill: parent @@ -91,101 +83,57 @@ Rectangle { anchors.fill: parent running: parent.visible } - } - } - ScrollView { id: textInput - Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true Layout.maximumHeight: Window.height / 4 Layout.minimumHeight: fontMetrics.lineSpacing Layout.preferredHeight: contentHeight - Layout.fillWidth: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - contentWidth: availableWidth TextArea { id: messageInput property int completerTriggeredAt: 0 + property string lastChar function insertCompletion(completion) { messageInput.remove(completerTriggeredAt, cursorPosition); messageInput.insert(cursorPosition, completion); } - function openCompleter(pos, type) { - if (popup.opened) return; + if (popup.opened) + return; completerTriggeredAt = pos; completer.completerName = type; popup.open(); - completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)+messageInput.preeditText); + completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText); } - function positionCursorAtEnd() { cursorPosition = messageInput.length; } - function positionCursorAtStart() { cursorPosition = 0; } - selectByMouse: true + background: null + bottomPadding: 8 + color: timelineRoot.palette.text + focus: true + leftPadding: inputBar.showAllButtons ? 0 : 8 + padding: 0 placeholderText: qsTr("Write a message...") placeholderTextColor: timelineRoot.palette.placeholderText - color: timelineRoot.palette.text - width: textInput.width - verticalAlignment: TextEdit.AlignVCenter - wrapMode: TextEdit.Wrap - padding: 0 + selectByMouse: true topPadding: 8 - bottomPadding: 8 - leftPadding: inputBar.showAllButtons? 0 : 8 - focus: true - property string lastChar - onTextChanged: { - if (room) - room.input.updateState(selectionStart, selectionEnd, cursorPosition, text); - forceActiveFocus(); - if (cursorPosition > 0) - lastChar = text.charAt(cursorPosition-1) - else - lastChar = '' - if (lastChar == '@') { - messageInput.openCompleter(selectionStart-1, "user"); - } else if (lastChar == ':') { - messageInput.openCompleter(selectionStart-1, "emoji"); - } else if (lastChar == '#') { - messageInput.openCompleter(selectionStart-1, "roomAliases"); - } else if (lastChar == "~") { - messageInput.openCompleter(selectionStart-1, "customEmoji"); - } - } - onCursorPositionChanged: { - if (!room) - return ; + verticalAlignment: TextEdit.AlignVCenter + width: textInput.width + wrapMode: TextEdit.Wrap - room.input.updateState(selectionStart, selectionEnd, cursorPosition, text); - if (popup.opened && cursorPosition <= completerTriggeredAt) - popup.close(); - - if (popup.opened) - completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)+messageInput.preeditText); - - } - onPreeditTextChanged: { - if (popup.opened) - completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)+messageInput.preeditText); - } - onSelectionStartChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text) - onSelectionEndChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text) - // Ensure that we get escape key press events first. - Keys.onShortcutOverride: event.accepted = (popup.opened && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter || event.key === Qt.Key_Space)) Keys.onPressed: { if (event.matches(StandardKey.Paste)) { room.input.paste(false); @@ -194,10 +142,8 @@ Rectangle { // close popup if user enters space after colon if (cursorPosition == completerTriggeredAt + 1) popup.close(); - if (popup.opened && completer.count <= 0) popup.close(); - } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) { messageInput.clear(); } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) { @@ -212,7 +158,8 @@ Rectangle { completer.completerName = ""; popup.close(); } else if (event.matches(StandardKey.InsertLineSeparator)) { - if (popup.opened) popup.close(); + if (popup.opened) + popup.close(); } else if (event.matches(StandardKey.InsertParagraphSeparator)) { if (popup.opened) { var currentCompletion = completer.currentCompletion(); @@ -242,16 +189,16 @@ Rectangle { console.log('"' + t + '"'); if (t == '@') { messageInput.openCompleter(pos, "user"); - return ; + return; } else if (t == ' ' || t == '\t') { messageInput.openCompleter(pos + 1, "user"); - return ; + return; } else if (t == ':') { messageInput.openCompleter(pos, "emoji"); - return ; + return; } else if (t == '~') { messageInput.openCompleter(pos, "customEmoji"); - return ; + return; } pos = pos - 1; } @@ -301,21 +248,53 @@ Rectangle { } } } - background: null + // Ensure that we get escape key press events first. + Keys.onShortcutOverride: event.accepted = (popup.opened && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter || event.key === Qt.Key_Space)) + onCursorPositionChanged: { + if (!room) + return; + room.input.updateState(selectionStart, selectionEnd, cursorPosition, text); + if (popup.opened && cursorPosition <= completerTriggeredAt) + popup.close(); + if (popup.opened) + completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText); + } + onPreeditTextChanged: { + if (popup.opened) + completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText); + } + onSelectionEndChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text) + onSelectionStartChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text) + onTextChanged: { + if (room) + room.input.updateState(selectionStart, selectionEnd, cursorPosition, text); + forceActiveFocus(); + if (cursorPosition > 0) + lastChar = text.charAt(cursorPosition - 1); + else + lastChar = ''; + if (lastChar == '@') { + messageInput.openCompleter(selectionStart - 1, "user"); + } else if (lastChar == ':') { + messageInput.openCompleter(selectionStart - 1, "emoji"); + } else if (lastChar == '#') { + messageInput.openCompleter(selectionStart - 1, "roomAliases"); + } else if (lastChar == "~") { + messageInput.openCompleter(selectionStart - 1, "customEmoji"); + } + } Connections { function onRoomChanged() { messageInput.clear(); if (room) messageInput.append(room.input.text); - completer.completerName = ""; messageInput.forceActiveFocus(); } target: timelineView } - Connections { function onCompletionClicked(completion) { messageInput.insertCompletion(completion); @@ -323,49 +302,42 @@ Rectangle { target: completer } - Popup { id: popup - + background: null + padding: 0 x: messageInput.positionToRectangle(messageInput.completerTriggeredAt).x y: messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height - background: null - padding: 0 + enter: Transition { + NumberAnimation { + duration: 100 + from: 0 + property: "opacity" + to: 1 + } + } + exit: Transition { + NumberAnimation { + duration: 100 + from: 1 + property: "opacity" + to: 0 + } + } Completer { - anchors.fill: parent id: completer + anchors.fill: parent rowMargin: 2 rowSpacing: 0 } - - enter: Transition { - NumberAnimation { - property: "opacity" - from: 0 - to: 1 - duration: 100 - } - - } - - exit: Transition { - NumberAnimation { - property: "opacity" - from: 1 - to: 0 - duration: 100 - } - } } - Connections { function onInsertText(text) { messageInput.remove(messageInput.selectionStart, messageInput.selectionEnd); messageInput.insert(messageInput.cursorPosition, text); } - function onTextChanged(newText) { messageInput.text = newText; messageInput.cursorPosition = newText.length; @@ -374,20 +346,17 @@ Rectangle { ignoreUnknownSignals: true target: room ? room.input : null } - Connections { - function onReplyChanged() { + function onEditChanged() { messageInput.forceActiveFocus(); } - - function onEditChanged() { + function onReplyChanged() { messageInput.forceActiveFocus(); } ignoreUnknownSignals: true target: room } - Connections { function onFocusInput() { messageInput.forceActiveFocus(); @@ -395,83 +364,74 @@ Rectangle { target: TimelineManager } - MouseArea { + acceptedButtons: Qt.MiddleButton // workaround for wrong cursor shape on some platforms anchors.fill: parent - acceptedButtons: Qt.MiddleButton cursorShape: Qt.IBeamCursor + onClicked: room.input.paste(true) } - } - } - ImageButton { id: stickerButton - visible: showAllButtons - Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.margins: 8 - hoverEnabled: true - width: 22 - height: 22 - image: ":/icons/icons/ui/sticky-note-solid.svg" - ToolTip.visible: hovered ToolTip.text: qsTr("Stickers") - onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function(row) { - room.input.sticker(stickerPopup.model.sourceModel, row); - TimelineManager.focusMessageInput(); - }) + ToolTip.visible: hovered + height: 22 + hoverEnabled: true + image: ":/icons/icons/ui/sticky-note-solid.svg" + visible: showAllButtons + width: 22 + + onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function (row) { + room.input.sticker(stickerPopup.model.sourceModel, row); + TimelineManager.focusMessageInput(); + }) StickerPicker { id: stickerPopup - colors: timelineRoot.palette } - } - ImageButton { id: emojiButton - Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.margins: 8 - hoverEnabled: true - width: 22 - height: 22 - image: ":/icons/icons/ui/smile.svg" - ToolTip.visible: hovered ToolTip.text: qsTr("Emoji") - onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) { - messageInput.insert(messageInput.cursorPosition, emoji); - TimelineManager.focusMessageInput(); - }) - } + ToolTip.visible: hovered + height: 22 + hoverEnabled: true + image: ":/icons/icons/ui/smile.svg" + width: 22 + onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function (emoji) { + messageInput.insert(messageInput.cursorPosition, emoji); + TimelineManager.focusMessageInput(); + }) + } ImageButton { Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.margins: 8 - hoverEnabled: true - width: 22 - height: 22 - image: ":/icons/icons/ui/send.svg" Layout.rightMargin: 8 - ToolTip.visible: hovered ToolTip.text: qsTr("Send") + ToolTip.visible: hovered + height: 22 + hoverEnabled: true + image: ":/icons/icons/ui/send.svg" + width: 22 + onClicked: { room.input.send(); } } - } - Text { anchors.centerIn: parent - visible: room ? (!room.permissions.canSend(MtxEvent.TextMessage)) : false - text: qsTr("You don't have permission to send messages in this room") color: timelineRoot.palette.text + text: qsTr("You don't have permission to send messages in this room") + visible: room ? (!room.permissions.canSend(MtxEvent.TextMessage)) : false } - } diff --git a/qml/MessageView.qml b/qml/MessageView.qml index 4108cd44..986b743c 100644 --- a/qml/MessageView.qml +++ b/qml/MessageView.qml @@ -1,13 +1,11 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./components" -import "./delegates" -import "./emoji" -import "./ui" -import "./dialogs" +import "components" +import "delegates" +import "emoji" +import "ui" +import "dialogs" import Qt.labs.platform 1.1 as Platform import QtQuick 2.15 import QtQuick.Controls 2.15 @@ -15,69 +13,240 @@ import QtQuick.Layouts 1.2 import QtQuick.Window 2.13 import im.nheko - Item { id: chatRoot - property int padding: Nheko.paddingMedium property int availableWidth: width + property int padding: Nheko.paddingMedium ScrollBar { id: scrollbar - parent: chat.parent - anchors.top: parent.top - anchors.right: parent.right anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.top: parent.top + parent: chat.parent } ListView { id: chat + property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - (scrollbar.interactive ? scrollbar.width : 0) + + ScrollBar.vertical: scrollbar anchors.fill: parent - - property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - (scrollbar.interactive? scrollbar.width : 0) - - displayMarginBeginning: height / 2 - displayMarginEnd: height / 2 - model: room + anchors.rightMargin: scrollbar.interactive ? scrollbar.width : 0 // reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107 //onModelChanged: if (room) room.sendReset() //reuseItems: true boundsBehavior: Flickable.StopAtBounds + displayMarginBeginning: height / 2 + displayMarginEnd: height / 2 + model: room //pixelAligned: true spacing: 2 verticalLayoutDirection: ListView.BottomToTop - onCountChanged: { - // Mark timeline as read - if (atYEnd && room) model.currentIndex = 0; + delegate: Item { + id: wrapper + + required property string blurhash + required property string body + required property string callType + required property string day + required property string duration + required property int encryptionError + required property string eventId + required property string filename + required property string filesize + required property string formattedBody + required property int index + required property bool isEditable + required property bool isEdited + required property bool isEncrypted + required property bool isOnlyEmoji + required property bool isSender + required property bool isStateEvent + required property int originalWidth + required property string previousMessageDay + required property bool previousMessageIsStateEvent + required property string previousMessageUserId + required property double proportionalHeight + required property var reactions + required property int relatedEventCacheBuster + required property string replyTo + required property string roomName + required property string roomTopic + property bool scrolledToThis: eventId === chat.model.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) + required property int status + required property string thumbnailUrl + required property var timestamp + required property int trustlevel + required property int type + required property string typeString + required property string url + required property string userId + required property string userName + + anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined + height: section.active ? (section.item?.implicitHeight ?? 0) + timelinerow.height : timelinerow.height + width: chat.delegateMaxWidth + + Loader { + id: section + + property string day: wrapper.day + property bool isSender: wrapper.isSender + property bool isStateEvent: wrapper.isStateEvent + property int parentWidth: parent.width + property string previousMessageDay: wrapper.previousMessageDay + property bool previousMessageIsStateEvent: wrapper.previousMessageIsStateEvent + property string previousMessageUserId: wrapper.previousMessageUserId + property date timestamp: wrapper.timestamp + property string userId: wrapper.userId + property string userName: wrapper.userName + + active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent + //asynchronous: true + sourceComponent: sectionHeader + visible: status == Loader.Ready + z: 4 + } + TimelineRow { + id: timelinerow + blurhash: wrapper.blurhash + body: wrapper.body + callType: wrapper.callType + duration: wrapper.duration + encryptionError: wrapper.encryptionError + eventId: chat.model, wrapper.eventId + filename: wrapper.filename + filesize: wrapper.filesize + formattedBody: wrapper.formattedBody + isEditable: wrapper.isEditable + isEdited: wrapper.isEdited + isEncrypted: wrapper.isEncrypted + isOnlyEmoji: wrapper.isOnlyEmoji + isSender: wrapper.isSender + isStateEvent: wrapper.isStateEvent + originalWidth: wrapper.originalWidth + proportionalHeight: wrapper.proportionalHeight + reactions: wrapper.reactions + relatedEventCacheBuster: wrapper.relatedEventCacheBuster + replyTo: wrapper.replyTo + roomName: wrapper.roomName + roomTopic: wrapper.roomTopic + status: wrapper.status + thumbnailUrl: wrapper.thumbnailUrl + timestamp: wrapper.timestamp + trustlevel: wrapper.trustlevel + type: chat.model, wrapper.type + typeString: wrapper.typeString + url: wrapper.url + userId: wrapper.userId + userName: wrapper.userName + y: section.item?.implicitHeight ?? 0 + + background: Rectangle { + id: scrollHighlight + color: timelineRoot.palette.highlight + enabled: false + opacity: 0 + visible: true + z: 1 + + states: State { + name: "revealed" + when: wrapper.scrolledToThis + } + transitions: Transition { + from: "" + to: "revealed" + + SequentialAnimation { + PropertyAnimation { + duration: 500 + easing.type: Easing.InOutQuad + from: 0 + properties: "opacity" + target: scrollHighlight + to: 1 + } + PropertyAnimation { + duration: 500 + easing.type: Easing.InOutQuad + from: 1 + properties: "opacity" + target: scrollHighlight + to: 0 + } + ScriptAction { + script: chat.model.eventShown() + } + } + } + } + + onHoveredChanged: { + if (!Settings.mobileMode && hovered) { + if (!messageActions.hovered) { + messageActions.attached = timelinerow; + messageActions.model = timelinerow; + } + } + } + } + Connections { + function onMovementEnded() { + if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height) + chat.model.currentIndex = index; + } + + target: chat + } + } + footer: Item { + anchors.horizontalCenter: parent.horizontalCenter + anchors.margins: Nheko.paddingLarge + // hacky, but works + height: (loadingSpinner.item?.implicitHeight ?? 0) + 2 * Nheko.paddingLarge + visible: chat.model && chat.model.paginationInProgress + + Spinner { + id: loadingSpinner + anchors.centerIn: parent + anchors.margins: Nheko.paddingLarge + foreground: timelineRoot.palette.mid + running: chat.model && chat.model.paginationInProgress + z: 3 + } } - ScrollBar.vertical: scrollbar - - anchors.rightMargin: scrollbar.interactive? scrollbar.width : 0 + onCountChanged: { + // Mark timeline as read + if (atYEnd && room) + model.currentIndex = 0; + } Control { id: messageActions property Item attached: null - property alias model: row.model // use comma to update on scroll property var attachedPos: chat.contentY, attached ? chat.mapFromItem(attached, attached ? attached.width - width : 0, -height) : null - padding: Nheko.paddingSmall + property alias model: row.model hoverEnabled: true + padding: Nheko.paddingSmall visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered) x: attached ? attachedPos.x : 0 y: attached ? attachedPos.y + Nheko.paddingSmall : 0 z: 10 background: Rectangle { - color: timelineRoot.palette.window border.color: timelineRoot.palette.placeholderText border.width: 1 + color: timelineRoot.palette.window radius: padding } - contentItem: RowLayout { id: row @@ -91,124 +260,116 @@ Item { delegate: TextButton { required property string modelData - visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false - Layout.preferredHeight: fontMetrics.height font.family: Settings.emojiFont - text: modelData + visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false + onClicked: { room.input.reaction(row.model.eventId, modelData); TimelineManager.focusMessageInput(); } } } - ImageButton { id: editButton - - visible: !!row.model && row.model.isEditable - buttonTextColor: timelineRoot.palette.placeholderText - width: 16 - hoverEnabled: true - image: ":/icons/icons/ui/edit.svg" - ToolTip.visible: hovered ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Edit") + ToolTip.visible: hovered + buttonTextColor: timelineRoot.palette.placeholderText + hoverEnabled: true + image: ":/icons/icons/ui/edit.svg" + visible: !!row.model && row.model.isEditable + width: 16 + onClicked: { if (row.model.isEditable) - chat.model.editAction(row.model.eventId); - + chat.model.editAction(row.model.eventId); } } - ImageButton { id: reactButton - - visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false - width: 16 - hoverEnabled: true - image: ":/icons/icons/ui/smile.svg" - ToolTip.visible: hovered ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("React") - onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, function(emoji) { - var event_id = row.model ? row.model.eventId : ""; - room.input.reaction(event_id, emoji); - TimelineManager.focusMessageInput(); - }) - } + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/smile.svg" + visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false + width: 16 + onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, function (emoji) { + var event_id = row.model ? row.model.eventId : ""; + room.input.reaction(event_id, emoji); + TimelineManager.focusMessageInput(); + }) + } ImageButton { id: replyButton - - visible: chat.model ? chat.model.permissions.canSend(MtxEvent.TextMessage) : false - width: 16 - hoverEnabled: true - image: ":/icons/icons/ui/reply.svg" - ToolTip.visible: hovered ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Reply") + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/reply.svg" + visible: chat.model ? chat.model.permissions.canSend(MtxEvent.TextMessage) : false + width: 16 + onClicked: chat.model.replyAction(row.model.eventId) } - ImageButton { id: optionsButton - - width: 16 - hoverEnabled: true - image: ":/icons/icons/ui/options.svg" - ToolTip.visible: hovered ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Options") + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/options.svg" + width: 16 + onClicked: messageContextMenu.show(row.model.eventId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton) } - } - } - Shortcut { sequence: StandardKey.MoveToPreviousPage + onActivated: { chat.contentY = chat.contentY - chat.height / 2; chat.returnToBounds(); } } - Shortcut { sequence: StandardKey.MoveToNextPage + onActivated: { chat.contentY = chat.contentY + chat.height / 2; chat.returnToBounds(); } } - Shortcut { sequence: StandardKey.Cancel + onActivated: { if (chat.model.reply) - chat.model.reply = undefined; + chat.model.reply = undefined; else - chat.model.edit = undefined; + chat.model.edit = undefined; } } - Shortcut { sequence: "Alt+Up" + onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0) } - Shortcut { sequence: "Alt+Down" + onActivated: { var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1; chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : null; } } - Shortcut { sequence: "Alt+F" + onActivated: { if (chat.model.reply) { var forwardMess = forwardCompleterComponent.createObject(timelineRoot); @@ -219,14 +380,13 @@ Item { } } } - Shortcut { sequence: "Ctrl+E" + onActivated: { chat.model.edit = chat.model.reply; } } - Connections { function onFocusChanged() { readTimer.running = TimelineManager.isWindowFocused; @@ -234,329 +394,130 @@ Item { target: TimelineManager } - Timer { id: readTimer + interval: 1000 // force current read index to update onTriggered: { if (chat.model) - chat.model.setCurrentIndex(chat.model.currentIndex); - + chat.model.setCurrentIndex(chat.model.currentIndex); } - interval: 1000 } - Component { id: sectionHeader - Column { - topPadding: userName_.visible? 4: 0 - bottomPadding: Settings.bubbles? (isSender && previousMessageDay == day? 0 : 2) : 3 + bottomPadding: Settings.bubbles ? (isSender && previousMessageDay == day ? 0 : 2) : 3 + height: ((previousMessageDay !== day) ? dateBubble.height : 0) + (isStateEvent ? 0 : userName.height + 8) spacing: 8 + topPadding: userName_.visible ? 4 : 0 visible: (previousMessageUserId !== userId || previousMessageDay !== day || isStateEvent !== previousMessageIsStateEvent) width: parentWidth - height: ((previousMessageDay !== day) ? dateBubble.height : 0) + (isStateEvent? 0 : userName.height +8 ) Label { id: dateBubble - anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined - visible: room && previousMessageDay !== day - text: room ? room.formatDateSeparator(timestamp) : "" color: timelineRoot.palette.text height: Math.round(fontMetrics.height * 1.4) - width: contentWidth * 1.2 horizontalAlignment: Text.AlignHCenter + text: room ? room.formatDateSeparator(timestamp) : "" verticalAlignment: Text.AlignVCenter + visible: room && previousMessageDay !== day + width: contentWidth * 1.2 background: Rectangle { - radius: parent.height / 2 color: timelineRoot.palette.window + radius: parent.height / 2 } - } - Row { + id: userInfo + + property int remainingWidth: chat.delegateMaxWidth - spacing - messageUserAvatar.width + height: userName_.height spacing: 8 visible: !isStateEvent && (!isSender || !Settings.bubbles) - id: userInfo Avatar { id: messageUserAvatar - - width: Nheko.avatarSize * (Settings.smallAvatars? 0.5 : 1) - height: Nheko.avatarSize * (Settings.smallAvatars? 0.5 : 1) - url: !room ? "" : room.avatarUrl(userId).replace("mxc://", "image://MxcImage/") - displayName: userName - userid: userId - onClicked: room.openUserProfile(userId) - ToolTip.visible: messageUserAvatar.hovered ToolTip.delay: Nheko.tooltipDelay ToolTip.text: userid - } + ToolTip.visible: messageUserAvatar.hovered + displayName: userName + height: Nheko.avatarSize * (Settings.smallAvatars ? 0.5 : 1) + url: !room ? "" : room.avatarUrl(userId).replace("mxc://", "image://MxcImage/") + userid: userId + width: Nheko.avatarSize * (Settings.smallAvatars ? 0.5 : 1) + onClicked: room.openUserProfile(userId) + } Connections { function onRoomAvatarUrlChanged() { messageUserAvatar.url = chat.model.avatarUrl(userId).replace("mxc://", "image://MxcImage/"); } - function onScrollToIndex(index) { chat.positionViewAtIndex(index, ListView.Center); } target: chat.model } - property int remainingWidth: chat.delegateMaxWidth - spacing - messageUserAvatar.width AbstractButton { - contentItem: ElidedLabel { - id: userName_ - fullText: userName - color: TimelineManager.userColor(userId, timelineRoot.palette.base) - textFormat: Text.RichText - elideWidth: Math.min(userInfo.remainingWidth-Math.min(statusMsg.implicitWidth,userInfo.remainingWidth/3), userName_.fullTextWidth) - } - ToolTip.visible: hovered ToolTip.delay: Nheko.tooltipDelay ToolTip.text: userId - onClicked: chat.model.openUserProfile(userId) + ToolTip.visible: hovered leftInset: 0 - rightInset: 0 leftPadding: 0 + rightInset: 0 rightPadding: 0 + contentItem: ElidedLabel { + id: userName_ + color: TimelineManager.userColor(userId, timelineRoot.palette.base) + elideWidth: Math.min(userInfo.remainingWidth - Math.min(statusMsg.implicitWidth, userInfo.remainingWidth / 3), userName_.fullTextWidth + Nheko.paddingMedium) + fullText: userName + textFormat: Text.RichText + } + + onClicked: chat.model.openUserProfile(userId) + NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } - } - Label { id: statusMsg color: timelineRoot.palette.placeholderText + elide: Text.ElideRight + font.italic: true text: Presence.userStatus(userId) textFormat: Text.PlainText - elide: Text.ElideRight width: userInfo.remainingWidth - userName_.width - parent.spacing - font.italic: true Connections { - target: Presence - function onPresenceChanged(id) { - if (id == userId) statusMsg.text = Presence.userStatus(userId); + if (id == userId) + statusMsg.text = Presence.userStatus(userId); } - } - } - } - - } - - } - - delegate: Item { - id: wrapper - - required property double proportionalHeight - required property int type - required property string typeString - required property int originalWidth - required property string blurhash - required property string body - required property string formattedBody - required property string eventId - required property string filename - required property string filesize - required property string url - required property string thumbnailUrl - required property string duration - required property bool isOnlyEmoji - required property bool isSender - required property bool isEncrypted - required property bool isEditable - required property bool isEdited - required property bool isStateEvent - required property bool previousMessageIsStateEvent - required property string replyTo - required property string userId - required property string roomTopic - required property string roomName - required property string callType - required property var reactions - required property int trustlevel - required property int encryptionError - required property var timestamp - required property int status - required property int index - required property int relatedEventCacheBuster - required property string previousMessageUserId - required property string day - required property string previousMessageDay - required property string userName - property bool scrolledToThis: eventId === chat.model.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) - - anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined - width: chat.delegateMaxWidth - height: section.active ? (section.item?.implicitHeight ?? 0) + timelinerow.height : timelinerow.height - - Loader { - id: section - - property int parentWidth: parent.width - property string userId: wrapper.userId - property string previousMessageUserId: wrapper.previousMessageUserId - property string day: wrapper.day - property string previousMessageDay: wrapper.previousMessageDay - property bool previousMessageIsStateEvent: wrapper.previousMessageIsStateEvent - property bool isStateEvent: wrapper.isStateEvent - property bool isSender: wrapper.isSender - property string userName: wrapper.userName - property date timestamp: wrapper.timestamp - - z: 4 - active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent - //asynchronous: true - sourceComponent: sectionHeader - visible: status == Loader.Ready - } - - TimelineRow { - id: timelinerow - - proportionalHeight: wrapper.proportionalHeight - type: chat.model, wrapper.type - typeString: wrapper.typeString - originalWidth: wrapper.originalWidth - blurhash: wrapper.blurhash - body: wrapper.body - formattedBody: wrapper.formattedBody - eventId: chat.model, wrapper.eventId - filename: wrapper.filename - filesize: wrapper.filesize - url: wrapper.url - thumbnailUrl: wrapper.thumbnailUrl - duration: wrapper.duration - isOnlyEmoji: wrapper.isOnlyEmoji - isSender: wrapper.isSender - isEncrypted: wrapper.isEncrypted - isEditable: wrapper.isEditable - isEdited: wrapper.isEdited - isStateEvent: wrapper.isStateEvent - replyTo: wrapper.replyTo - userId: wrapper.userId - userName: wrapper.userName - roomTopic: wrapper.roomTopic - roomName: wrapper.roomName - callType: wrapper.callType - reactions: wrapper.reactions - trustlevel: wrapper.trustlevel - encryptionError: wrapper.encryptionError - timestamp: wrapper.timestamp - status: wrapper.status - relatedEventCacheBuster: wrapper.relatedEventCacheBuster - y: section.item?.implicitHeight ?? 0 - - onHoveredChanged: { - if (!Settings.mobileMode && hovered) { - if (!messageActions.hovered) { - messageActions.attached = timelinerow; - messageActions.model = timelinerow; + target: Presence } } } - background: Rectangle { - id: scrollHighlight - - opacity: 0 - visible: true - z: 1 - enabled: false - color: timelineRoot.palette.highlight - - states: State { - name: "revealed" - when: wrapper.scrolledToThis - } - - transitions: Transition { - from: "" - to: "revealed" - - SequentialAnimation { - PropertyAnimation { - target: scrollHighlight - properties: "opacity" - easing.type: Easing.InOutQuad - from: 0 - to: 1 - duration: 500 - } - - PropertyAnimation { - target: scrollHighlight - properties: "opacity" - easing.type: Easing.InOutQuad - from: 1 - to: 0 - duration: 500 - } - - ScriptAction { - script: chat.model.eventShown() - } - - } - - } - - } } - - Connections { - function onMovementEnded() { - if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height) - chat.model.currentIndex = index; - - } - - target: chat - } - - } - - footer: Item { - anchors.horizontalCenter: parent.horizontalCenter - anchors.margins: Nheko.paddingLarge - visible: chat.model && chat.model.paginationInProgress - // hacky, but works - height: (loadingSpinner.item?.implicitHeight ?? 0) + 2 * Nheko.paddingLarge - - Spinner { - id: loadingSpinner - - anchors.centerIn: parent - anchors.margins: Nheko.paddingLarge - running: chat.model && chat.model.paginationInProgress - foreground: timelineRoot.palette.mid - z: 3 - } - } } - Platform.Menu { id: messageContextMenu property string eventId + property int eventType + property bool isEditable + property bool isEncrypted + property bool isSender property string link property string text - property int eventType - property bool isEncrypted - property bool isEditable - property bool isSender function show(eventId_, eventType_, isSender_, isEncrypted_, isEditable_, link_, text_, showAt_) { eventId = eventId_; @@ -565,17 +526,17 @@ Item { isEditable = isEditable_; isSender = isSender_; if (text_) - text = text_; + text = text_; else - text = ""; + text = ""; if (link_) - link = link_; + link = link_; else - link = ""; + link = ""; if (showAt_) - open(showAt_); + open(showAt_); else - open(); + open(); } Component { @@ -585,66 +546,66 @@ Item { property string eventId - title: qsTr("Reason for removal") prompt: qsTr("Enter reason for removal or hit enter for no reason:") - onAccepted: function(text) { + title: qsTr("Reason for removal") + + onAccepted: function (text) { room.redactEvent(eventId, text); } } } - Platform.MenuItem { - visible: messageContextMenu.text enabled: visible text: qsTr("&Copy") + visible: messageContextMenu.text + onTriggered: Clipboard.text = messageContextMenu.text } - Platform.MenuItem { - visible: messageContextMenu.link enabled: visible text: qsTr("Copy &link location") + visible: messageContextMenu.link + onTriggered: Clipboard.text = messageContextMenu.link } - Platform.MenuItem { id: reactionOption - - visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false text: qsTr("Re&act") - onTriggered: emojiPopup.show(null, function(emoji) { - room.input.reaction(messageContextMenu.eventId, emoji); - }) - } + visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false + onTriggered: emojiPopup.show(null, function (emoji) { + room.input.reaction(messageContextMenu.eventId, emoji); + }) + } Platform.MenuItem { - visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false text: qsTr("Repl&y") + visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false + onTriggered: room.replyAction(messageContextMenu.eventId) } - Platform.MenuItem { - visible: messageContextMenu.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false) enabled: visible text: qsTr("&Edit") + visible: messageContextMenu.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false) + onTriggered: room.editAction(messageContextMenu.eventId) } - Platform.MenuItem { - visible: (room ? room.permissions.canChange(MtxEvent.PinnedEvents) : false) enabled: visible text: visible && room.pinnedMessages.includes(messageContextMenu.eventId) ? qsTr("Un&pin") : qsTr("&Pin") + visible: (room ? room.permissions.canChange(MtxEvent.PinnedEvents) : false) + onTriggered: visible && room.pinnedMessages.includes(messageContextMenu.eventId) ? room.unpin(messageContextMenu.eventId) : room.pin(messageContextMenu.eventId) } - Platform.MenuItem { text: qsTr("Read receip&ts") + onTriggered: room.showReadReceipts(messageContextMenu.eventId) } - Platform.MenuItem { - visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker || messageContextMenu.eventType == MtxEvent.TextMessage || messageContextMenu.eventType == MtxEvent.LocationMessage || messageContextMenu.eventType == MtxEvent.EmoteMessage || messageContextMenu.eventType == MtxEvent.NoticeMessage text: qsTr("&Forward") + visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker || messageContextMenu.eventType == MtxEvent.TextMessage || messageContextMenu.eventType == MtxEvent.LocationMessage || messageContextMenu.eventType == MtxEvent.EmoteMessage || messageContextMenu.eventType == MtxEvent.NoticeMessage + onTriggered: { var forwardMess = forwardCompleterComponent.createObject(timelineRoot); forwardMess.setMessageEventId(messageContextMenu.eventId); @@ -652,28 +613,27 @@ Item { timelineRoot.destroyOnClose(forwardMess); } } - Platform.MenuItem { text: qsTr("&Mark as read") } - Platform.MenuItem { text: qsTr("View raw message") + onTriggered: room.viewRawMessage(messageContextMenu.eventId) } - Platform.MenuItem { - // TODO(Nico): Fix this still being iterated over, when using keyboard to select options - visible: messageContextMenu.isEncrypted enabled: visible text: qsTr("View decrypted raw message") + // TODO(Nico): Fix this still being iterated over, when using keyboard to select options + visible: messageContextMenu.isEncrypted + onTriggered: room.viewDecryptedRawMessage(messageContextMenu.eventId) } - Platform.MenuItem { - visible: (room ? room.permissions.canRedact() : false) || messageContextMenu.isSender text: qsTr("Remo&ve message") - onTriggered: function() { + visible: (room ? room.permissions.canRedact() : false) || messageContextMenu.isSender + + onTriggered: function () { var dialog = removeReason.createObject(timelineRoot); dialog.eventId = messageContextMenu.eventId; dialog.show(); @@ -681,44 +641,39 @@ Item { timelineRoot.destroyOnClose(dialog); } } - Platform.MenuItem { - visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker enabled: visible text: qsTr("&Save as") + visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker + onTriggered: room.saveMedia(messageContextMenu.eventId) } - Platform.MenuItem { - visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker enabled: visible text: qsTr("&Open in external program") + visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker + onTriggered: room.openMedia(messageContextMenu.eventId) } - Platform.MenuItem { - visible: messageContextMenu.eventId enabled: visible text: qsTr("Copy link to eve&nt") + visible: messageContextMenu.eventId + onTriggered: room.copyLinkToEvent(messageContextMenu.eventId) } - } - Component { id: forwardCompleterComponent - ForwardCompleter { } - } - Platform.Menu { id: replyContextMenu - property string text - property string link property string eventId + property string link + property string text function show(text_, link_, eventId_) { text = text_; @@ -728,85 +683,96 @@ Item { } Platform.MenuItem { - visible: replyContextMenu.text enabled: visible text: qsTr("&Copy") + visible: replyContextMenu.text + onTriggered: Clipboard.text = replyContextMenu.text } - Platform.MenuItem { - visible: replyContextMenu.link enabled: visible text: qsTr("Copy &link location") + visible: replyContextMenu.link + onTriggered: Clipboard.text = replyContextMenu.link } - Platform.MenuItem { - visible: true enabled: visible text: qsTr("&Go to quoted message") + visible: true + onTriggered: chat.model.showEvent(replyContextMenu.eventId) } - } RoundButton { id: toEndButton - anchors { - bottom: parent.bottom - right: scrollbar.left - bottomMargin: Nheko.paddingMedium+(fullWidth-width)/2 - rightMargin: Nheko.paddingMedium+(fullWidth-width)/2 - } + property int fullWidth: 40 - width: 0 - height: width - radius: width/2 - onClicked: chat.positionViewAtBeginning(); + flat: true + height: width hoverEnabled: true + radius: width / 2 + width: 0 background: Rectangle { - color: toEndButton.down ? timelineRoot.palette.highlight : timelineRoot.palette.button - opacity: enabled ? 1 : 0.3 border.color: toEndButton.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText border.width: 1 + color: toEndButton.down ? timelineRoot.palette.highlight : timelineRoot.palette.button + opacity: enabled ? 1 : 0.3 radius: toEndButton.radius } - states: [ State { name: "" - PropertyChanges { target: toEndButton; width: 0 } + + PropertyChanges { + target: toEndButton + width: 0 + } }, State { name: "shown" when: !chat.atYEnd - PropertyChanges { target: toEndButton; width: toEndButton.fullWidth } + + PropertyChanges { + target: toEndButton + width: toEndButton.fullWidth + } } ] + transitions: Transition { + from: "" + reversible: true + to: "shown" + SequentialAnimation { + PauseAnimation { + duration: 500 + } + PropertyAnimation { + duration: 200 + easing.type: Easing.InOutQuad + properties: "width" + target: toEndButton + } + } + } + + onClicked: chat.positionViewAtBeginning() + + anchors { + bottom: parent.bottom + bottomMargin: Nheko.paddingMedium + (fullWidth - width) / 2 + right: scrollbar.left + rightMargin: Nheko.paddingMedium + (fullWidth - width) / 2 + } Image { id: buttonImg anchors.fill: parent anchors.margins: Nheko.paddingMedium - source: "image://colorimage/:/icons/icons/ui/download.svg?" + (toEndButton.down ? timelineRoot.palette.highlightedText : timelineRoot.palette.placeholderText) fillMode: Image.PreserveAspectFit - } - - transitions: Transition { - from: "" - to: "shown" - reversible: true - - SequentialAnimation { - PauseAnimation { duration: 500 } - PropertyAnimation { - target: toEndButton - properties: "width" - easing.type: Easing.InOutQuad - duration: 200 - } - } + source: "image://colorimage/:/icons/icons/ui/download.svg?" + (toEndButton.down ? timelineRoot.palette.highlightedText : timelineRoot.palette.placeholderText) } } } diff --git a/qml/NhekoBusyIndicator.qml b/qml/NhekoBusyIndicator.qml index aec5f2c9..10e99828 100644 --- a/qml/NhekoBusyIndicator.qml +++ b/qml/NhekoBusyIndicator.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 @@ -10,38 +8,41 @@ import im.nheko BusyIndicator { id: control - contentItem: Item { - implicitWidth: 64 implicitHeight: 64 + implicitWidth: 64 Item { id: item - height: Math.min(parent.height, parent.width) - width: height opacity: control.running ? 1 : 0 + width: height - RotationAnimator { - target: item - running: control.visible && control.running - from: 0 - to: 360 - loops: Animation.Infinite - duration: 2000 + Behavior on opacity { + OpacityAnimator { + duration: 250 + } } + RotationAnimator { + duration: 2000 + from: 0 + loops: Animation.Infinite + running: control.visible && control.running + target: item + to: 360 + } Repeater { id: repeater - model: 6 Rectangle { - implicitWidth: radius * 2 - implicitHeight: radius * 2 - radius: item.height / 6 color: timelineRoot.palette.text + implicitHeight: radius * 2 + implicitWidth: radius * 2 opacity: (index + 2) / (repeater.count + 2) + radius: item.height / 6 + transform: [ Translate { y: -Math.min(item.width, item.height) * 0.5 + item.height / 6 @@ -53,18 +54,7 @@ BusyIndicator { } ] } - } - - Behavior on opacity { - OpacityAnimator { - duration: 250 - } - - } - } - } - } diff --git a/qml/NotificationWarning.qml b/qml/NotificationWarning.qml index 02e64c5a..70d3f352 100644 --- a/qml/NotificationWarning.qml +++ b/qml/NotificationWarning.qml @@ -1,39 +1,33 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import im.nheko Item { - implicitHeight: warningRect.visible ? warningDisplay.implicitHeight : 0 - height: implicitHeight Layout.fillWidth: true + height: implicitHeight + implicitHeight: warningRect.visible ? warningDisplay.implicitHeight : 0 Rectangle { id: warningRect - - visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom) - color: timelineRoot.palette.base anchors.fill: parent + color: timelineRoot.palette.base + visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom) z: 3 Label { id: warningDisplay - + anchors.bottom: parent.bottom anchors.left: parent.left anchors.leftMargin: 10 anchors.right: parent.right anchors.rightMargin: 10 - anchors.bottom: parent.bottom color: Nheko.theme.red text: qsTr("You are about to notify the whole room") textFormat: Text.PlainText } - } - } diff --git a/qml/PrivacyScreen.qml b/qml/PrivacyScreen.qml index 71159076..997c018c 100644 --- a/qml/PrivacyScreen.qml +++ b/qml/PrivacyScreen.qml @@ -1,6 +1,5 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later //import QtGraphicalEffects 1.0 @@ -11,8 +10,8 @@ import im.nheko Item { id: privacyScreen - property var timelineRoot property int screenTimeout + property var timelineRoot Connections { function onFocusChanged() { @@ -22,29 +21,26 @@ Item { } else { if (timelineRoot.visible) screenSaverTimer.start(); - } } target: TimelineManager } - Timer { id: screenSaverTimer - interval: screenTimeout * 1000 running: !MainWindow.active + onTriggered: { screenSaver.state = "Visible"; } } - Item { id: screenSaver - - state: "Invisible" anchors.fill: parent + state: "Invisible" visible: false + states: [ State { name: "Visible" @@ -53,26 +49,22 @@ Item { target: screenSaver visible: true } - PropertyChanges { - target: screenSaver opacity: 1 + target: screenSaver } - }, State { name: "Invisible" PropertyChanges { - target: screenSaver opacity: 0 + target: screenSaver } - PropertyChanges { target: screenSaver visible: false } - } ] transitions: [ @@ -82,20 +74,17 @@ Item { SequentialAnimation { NumberAnimation { - target: screenSaver - property: "opacity" duration: 250 easing.type: Easing.InQuad - } - - NumberAnimation { + property: "opacity" + target: screenSaver + } + NumberAnimation { + duration: 0 + property: "visible" target: screenSaver - property: "visible" - duration: 0 } - } - }, Transition { from: "Invisible" @@ -103,20 +92,17 @@ Item { SequentialAnimation { NumberAnimation { - target: screenSaver - property: "visible" duration: 0 - } - - NumberAnimation { + property: "visible" target: screenSaver - property: "opacity" + } + NumberAnimation { duration: 500 easing.type: Easing.InQuad + property: "opacity" + target: screenSaver } - } - } ] @@ -127,7 +113,5 @@ Item { // source: timelineRoot // radius: 50 //} - } - } diff --git a/qml/QuickSwitcher.qml b/qml/QuickSwitcher.qml index 29fa1f4a..c988fda5 100644 --- a/qml/QuickSwitcher.qml +++ b/qml/QuickSwitcher.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 @@ -12,33 +10,35 @@ Popup { id: quickSwitcher property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4) + property int textMargin: Nheko.paddingSmall background: null - width: Math.min(Math.max(Math.round(parent.width / 2),450),parent.width) // limiting width to parent.width/2 can be a bit narrow + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + modal: true + palette: timelineRoot.palette + parent: Overlay.overlay + width: Math.min(Math.max(Math.round(parent.width / 2), 450), parent.width) // limiting width to parent.width/2 can be a bit narrow x: Math.round(parent.width / 2 - contentWidth / 2) y: Math.round(parent.height / 4) - modal: true - closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside - parent: Overlay.overlay - palette: timelineRoot.palette + + Overlay.modal: Rectangle { + color: "#aa1E1E1E" + } + onOpened: { roomTextInput.forceActiveFocus(); } - property int textMargin: Nheko.paddingSmall - Column{ + Column { anchors.fill: parent spacing: 1 MatrixTextField { id: roomTextInput - - width: parent.width - font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6) color: timelineRoot.palette.text - onTextEdited: { - completerPopup.completer.searchString = text; - } + font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6) + width: parent.width + Keys.onPressed: { if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) { event.accepted = true; @@ -46,49 +46,42 @@ Popup { } else if (event.key == Qt.Key_Down || event.key == Qt.Key_Tab) { event.accepted = true; if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) - completerPopup.up(); + completerPopup.up(); else - completerPopup.down(); + completerPopup.down(); } else if (event.matches(StandardKey.InsertParagraphSeparator)) { completerPopup.finishCompletion(); event.accepted = true; } } + onTextEdited: { + completerPopup.completer.searchString = text; + } } - Completer { id: completerPopup - - visible: roomTextInput.text.length > 0 - width: parent.width - completerName: "room" - bottomToTop: false - fullWidth: true avatarHeight: quickSwitcher.textHeight avatarWidth: quickSwitcher.textHeight + bottomToTop: false centerRowContent: false + completerName: "room" + fullWidth: true rowMargin: Math.round(quickSwitcher.textMargin / 2) rowSpacing: quickSwitcher.textMargin + visible: roomTextInput.text.length > 0 + width: parent.width } } - Connections { function onCompletionSelected(id) { Rooms.setCurrentRoom(id); quickSwitcher.close(); } - function onCountChanged() { if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count)) - completerPopup.currentIndex = 0; - + completerPopup.currentIndex = 0; } target: completerPopup } - - Overlay.modal: Rectangle { - color: "#aa1E1E1E" - } - } diff --git a/qml/Reactions.qml b/qml/Reactions.qml index f0f7c39e..753994c8 100644 --- a/qml/Reactions.qml +++ b/qml/Reactions.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.6 import QtQuick.Controls 2.2 import im.nheko @@ -12,58 +10,54 @@ import im.nheko Flow { id: reactionFlow + property string eventId + // highlight colors for selfReactedEvent background property real highlightHue: timelineRoot.palette.highlight.hslHue - property real highlightSat: timelineRoot.palette.highlight.hslSaturation property real highlightLight: timelineRoot.palette.highlight.hslLightness - property string eventId + property real highlightSat: timelineRoot.palette.highlight.hslSaturation property alias reactions: repeater.model spacing: 4 Repeater { id: repeater - delegate: AbstractButton { id: reaction - - hoverEnabled: true - implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding * 2 - implicitHeight: contentItem.childrenRect.height - ToolTip.visible: hovered ToolTip.delay: Nheko.tooltipDelay - onClicked: { - console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent); - room.input.reaction(reactionFlow.eventId, modelData.key); - } - Component.onCompleted: { - ToolTip.text = Qt.binding(function() { - if (textMetrics.elidedText === textMetrics.text) { - return modelData.users; - } - return modelData.displayKey + "\n" + modelData.users; - }) - } + ToolTip.visible: hovered + hoverEnabled: true + implicitHeight: contentItem.childrenRect.height + implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding * 2 + background: Rectangle { + anchors.centerIn: parent + border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? timelineRoot.palette.highlight : timelineRoot.palette.text + border.width: 1 + color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : timelineRoot.palette.window + implicitHeight: reaction.implicitHeight + implicitWidth: reaction.implicitWidth + radius: reaction.height / 2 + } contentItem: Row { anchors.centerIn: parent - spacing: reactionText.implicitHeight / 4 leftPadding: reactionText.implicitHeight / 2 rightPadding: reactionText.implicitHeight / 2 + spacing: reactionText.implicitHeight / 4 TextMetrics { id: textMetrics - - font.family: Settings.emojiFont elide: Text.ElideRight elideWidth: 150 + font.family: Settings.emojiFont text: modelData.displayKey } - Text { id: reactionText - anchors.baseline: reactionCounter.baseline + color: reaction.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.text + font.family: Settings.emojiFont + maximumLineCount: 1 text: { // When an emoji font is selected that doesn't have …, it is dropped from elidedText. So we add it back. if (textMetrics.elidedText !== modelData.displayKey) { @@ -73,42 +67,34 @@ Flow { } return textMetrics.elidedText; } - font.family: Settings.emojiFont - color: reaction.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.text - maximumLineCount: 1 } - Rectangle { id: divider - + color: (reaction.hovered || modelData.selfReactedEvent !== '') ? timelineRoot.palette.highlight : timelineRoot.palette.text height: Math.floor(reactionCounter.implicitHeight * 1.4) width: 1 - color: (reaction.hovered || modelData.selfReactedEvent !== '') ? timelineRoot.palette.highlight : timelineRoot.palette.text } - Text { id: reactionCounter - anchors.verticalCenter: divider.verticalCenter - text: modelData.count - font: reaction.font color: reaction.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.text + font: reaction.font + text: modelData.count } - } - background: Rectangle { - anchors.centerIn: parent - implicitWidth: reaction.implicitWidth - implicitHeight: reaction.implicitHeight - border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? timelineRoot.palette.highlight : timelineRoot.palette.text - color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : timelineRoot.palette.window - border.width: 1 - radius: reaction.height / 2 + Component.onCompleted: { + ToolTip.text = Qt.binding(function () { + if (textMetrics.elidedText === textMetrics.text) { + return modelData.users; + } + return modelData.displayKey + "\n" + modelData.users; + }); + } + onClicked: { + console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent); + room.input.reaction(reactionFlow.eventId, modelData.key); } - } - } - } diff --git a/qml/ReplyPopup.qml b/qml/ReplyPopup.qml index 5f685909..ab1d4dc9 100644 --- a/qml/ReplyPopup.qml +++ b/qml/ReplyPopup.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./delegates/" +import "delegates" import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 @@ -11,76 +9,71 @@ import im.nheko Rectangle { id: replyPopup - Layout.fillWidth: true - visible: room && (room.reply || room.edit) + color: timelineRoot.palette.window // Height of child, plus margins, plus border implicitHeight: (room && room.reply ? replyPreview.height : closeEditButton.height) + Nheko.paddingSmall - color: timelineRoot.palette.window + visible: room && (room.reply || room.edit) z: 3 Reply { id: replyPreview - property var modelData: room ? room.getDump(room.reply, room.id) : { - } + property var modelData: room ? room.getDump(room.reply, room.id) : {} - visible: room && room.reply anchors.left: parent.left - anchors.leftMargin: replyPopup.width < 450? Nheko.paddingSmall : (CallManager.callsSupported? 2*(22+16) : 1*(22+16)) + anchors.leftMargin: replyPopup.width < 450 ? Nheko.paddingSmall : (CallManager.callsSupported ? 2 * (22 + 16) : 1 * (22 + 16)) anchors.right: parent.right - anchors.rightMargin: replyPopup.width < 450? 2*(22+16) : 3*(22+16) + anchors.rightMargin: replyPopup.width < 450 ? 2 * (22 + 16) : 3 * (22 + 16) anchors.top: parent.top anchors.topMargin: Nheko.paddingSmall - userColor: TimelineManager.userColor(modelData.userId, timelineRoot.palette.window) blurhash: modelData.blurhash ?? "" body: modelData.body ?? "" - formattedBody: modelData.formattedBody ?? "" + encryptionError: modelData.encryptionError ?? 0 eventId: modelData.eventId ?? "" filename: modelData.filename ?? "" filesize: modelData.filesize ?? "" + formattedBody: modelData.formattedBody ?? "" + isOnlyEmoji: modelData.isOnlyEmoji ?? false + originalWidth: modelData.originalWidth ?? 0 proportionalHeight: modelData.proportionalHeight ?? 1 type: modelData.type ?? MtxEvent.UnknownMessage typeString: modelData.typeString ?? "" url: modelData.url ?? "" - originalWidth: modelData.originalWidth ?? 0 - isOnlyEmoji: modelData.isOnlyEmoji ?? false + userColor: TimelineManager.userColor(modelData.userId, timelineRoot.palette.window) userId: modelData.userId ?? "" userName: modelData.userName ?? "" - encryptionError: modelData.encryptionError ?? 0 + visible: room && room.reply width: parent.width } - ImageButton { id: closeReplyButton - - visible: room && room.reply + ToolTip.text: qsTr("Close") + ToolTip.visible: closeReplyButton.hovered + anchors.margins: Nheko.paddingSmall anchors.right: replyPreview.right anchors.top: replyPreview.top - anchors.margins: Nheko.paddingSmall - hoverEnabled: true - width: 16 height: 16 + hoverEnabled: true image: ":/icons/icons/ui/dismiss.svg" - ToolTip.visible: closeReplyButton.hovered - ToolTip.text: qsTr("Close") + visible: room && room.reply + width: 16 + onClicked: room.reply = undefined } - ImageButton { id: closeEditButton - - visible: room && room.edit - anchors.right: parent.right + ToolTip.text: qsTr("Cancel Edit") + ToolTip.visible: closeEditButton.hovered anchors.margins: 8 + anchors.right: parent.right anchors.top: parent.top + height: 22 hoverEnabled: true image: ":/icons/icons/ui/dismiss_edit.svg" + visible: room && room.edit width: 22 - height: 22 - ToolTip.visible: closeEditButton.hovered - ToolTip.text: qsTr("Cancel Edit") + onClicked: room.edit = undefined } - } diff --git a/qml/Root.qml b/qml/Root.qml index 8edca57b..4dc80f5b 100644 --- a/qml/Root.qml +++ b/qml/Root.qml @@ -1,15 +1,13 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./delegates" -import "./device-verification" -import "./dialogs" -import "./emoji" -import "./pages" -import "./voip" -import "./ui" +import "delegates" +import "device-verification" +import "dialogs" +import "emoji" +import "pages" +import "voip" +import "ui" import Qt.labs.platform 1.1 as Platform import QtQuick 2.15 import QtQuick.Controls 2.15 @@ -19,7 +17,16 @@ import im.nheko Pane { id: timelineRoot - + function destroyOnClose(obj) { + if (obj.closing != undefined) + obj.closing.connect(() => obj.destroy(1000)); + else if (obj.aboutToHide != undefined) + obj.aboutToHide.connect(() => obj.destroy(1000)); + } + function destroyOnClosed(obj) { + obj.aboutToHide.connect(() => obj.destroy(1000)); + } + background: null padding: 0 @@ -33,270 +40,203 @@ Pane { // running: true // repeat: true //} - EmojiPicker { id: emojiPopup - colors: palette model: TimelineManager.completerFor("allemoji", "") } - Component { id: userProfileComponent - UserProfile { } - } - Component { id: roomSettingsComponent - RoomSettings { } - } - Component { id: roomMembersComponent - RoomMembers { } - } - Component { id: mobileCallInviteDialog - CallInvite { } - } - Component { id: quickSwitcherComponent - QuickSwitcher { } - } - Component { id: deviceVerificationDialog - DeviceVerification { } - } - Component { id: inviteDialog - InviteDialog { } - } - Component { id: packSettingsComponent - ImagePackSettingsDialog { } - } - Component { id: readReceiptsDialog - ReadReceipts { } - } - Component { id: rawMessageDialog - RawMessageDialog { } - } - Component { id: logoutDialog - LogoutDialog { } - } - Component { id: joinRoomDialog - JoinRoomDialog { } - } - Component { id: leaveRoomComponent - LeaveRoomDialog { } - } - Component { id: imageOverlay - ImageOverlay { } - } - Component { id: userSettingsPage - UserSettingsPage { } - } - Shortcut { sequence: StandardKey.Quit + onActivated: Qt.quit() } - Shortcut { sequence: "Ctrl+K" + onActivated: { var quickSwitch = quickSwitcherComponent.createObject(timelineRoot); quickSwitch.open(); destroyOnClosed(quickSwitch); } } - Shortcut { // Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit sequences: ["Alt+A", "Ctrl+Shift+A"] + onActivated: Rooms.nextRoomWithActivity() } - Shortcut { sequence: "Ctrl+Down" + onActivated: Rooms.nextRoom() } - Shortcut { sequence: "Ctrl+Up" + onActivated: Rooms.previousRoom() } - Connections { + function onOpenJoinRoomDialog() { + var dialog = joinRoomDialog.createObject(timelineRoot); + dialog.show(); + destroyOnClose(dialog); + } function onOpenLogoutDialog() { var dialog = logoutDialog.createObject(timelineRoot); dialog.open(); destroyOnClose(dialog); } - function onOpenJoinRoomDialog() { - var dialog = joinRoomDialog.createObject(timelineRoot); - dialog.show(); - destroyOnClose(dialog); - } - target: Nheko } - Connections { function onNewDeviceVerificationRequest(flow) { var dialog = deviceVerificationDialog.createObject(timelineRoot, { - "flow": flow - }); + "flow": flow + }); dialog.show(); destroyOnClose(dialog); } target: VerificationManager } - - function destroyOnClose(obj) { - if (obj.closing != undefined) obj.closing.connect(() => obj.destroy(1000)); - else if (obj.aboutToHide != undefined) obj.aboutToHide.connect(() => obj.destroy(1000)); - } - - function destroyOnClosed(obj) { - obj.aboutToHide.connect(() => obj.destroy(1000)); - } - Connections { + function onOpenInviteUsersDialog(invitees) { + var dialog = inviteDialog.createObject(timelineRoot, { + "roomId": Rooms.currentRoom.roomId, + "plainRoomName": Rooms.currentRoom.plainRoomName, + "invitees": invitees + }); + dialog.show(); + destroyOnClose(dialog); + } + function onOpenLeaveRoomDialog(roomid, reason) { + var dialog = leaveRoomComponent.createObject(timelineRoot, { + "roomId": roomid, + "reason": reason + }); + dialog.open(); + destroyOnClose(dialog); + } function onOpenProfile(profile) { var userProfile = userProfileComponent.createObject(timelineRoot, { - "profile": profile - }); + "profile": profile + }); userProfile.show(); destroyOnClose(userProfile); } - + function onOpenRoomMembersDialog(members, room) { + var membersDialog = roomMembersComponent.createObject(timelineRoot, { + "members": members, + "room": room + }); + membersDialog.show(); + destroyOnClose(membersDialog); + } + function onOpenRoomSettingsDialog(settings) { + var roomSettings = roomSettingsComponent.createObject(timelineRoot, { + "roomSettings": settings + }); + roomSettings.show(); + destroyOnClose(roomSettings); + } + function onShowImageOverlay(room, eventId, url, proportionalHeight, originalWidth) { + var dialog = imageOverlay.createObject(timelineRoot, { + "room": room, + "eventId": eventId, + "url": url, + "originalWidth": originalWidth ?? 0, + "proportionalHeight": proportionalHeight ?? 0 + }); + dialog.showFullScreen(); + destroyOnClose(dialog); + } function onShowImagePackSettings(room, packlist) { var packSet = packSettingsComponent.createObject(timelineRoot, { - "room": room, - "packlist": packlist - }); + "room": room, + "packlist": packlist + }); packSet.show(); destroyOnClose(packSet); } - function onOpenRoomMembersDialog(members, room) { - var membersDialog = roomMembersComponent.createObject(timelineRoot, { - "members": members, - "room": room - }); - membersDialog.show(); - destroyOnClose(membersDialog); - } - - function onOpenRoomSettingsDialog(settings) { - var roomSettings = roomSettingsComponent.createObject(timelineRoot, { - "roomSettings": settings - }); - roomSettings.show(); - destroyOnClose(roomSettings); - } - - function onOpenInviteUsersDialog(invitees) { - var dialog = inviteDialog.createObject(timelineRoot, { - "roomId": Rooms.currentRoom.roomId, - "plainRoomName": Rooms.currentRoom.plainRoomName, - "invitees": invitees - }); - dialog.show(); - destroyOnClose(dialog); - } - - function onOpenLeaveRoomDialog(roomid, reason) { - var dialog = leaveRoomComponent.createObject(timelineRoot, { - "roomId": roomid, - "reason": reason - }); - dialog.open(); - destroyOnClose(dialog); - } - - function onShowImageOverlay(room, eventId, url, proportionalHeight, originalWidth) { - var dialog = imageOverlay.createObject(timelineRoot, { - "room": room, - "eventId": eventId, - "url": url, - "originalWidth": originalWidth ?? 0, - "proportionalHeight": proportionalHeight ?? 0 - }); - dialog.showFullScreen(); - destroyOnClose(dialog); - } - target: TimelineManager } - Connections { function onNewInviteState() { if (CallManager.haveCallInvite && Settings.mobileMode) { @@ -308,144 +248,122 @@ Pane { target: CallManager } - SelfVerificationCheck { } - InputDialog { id: uiaPassPrompt - echoMode: TextInput.Password - title: UIA.title prompt: qsTr("Please enter your login password to continue:") - onAccepted: (t) => { + title: UIA.title + + onAccepted: t => { return UIA.continuePassword(t); } } - InputDialog { id: uiaEmailPrompt - - title: UIA.title prompt: qsTr("Please enter a valid email address to continue:") - onAccepted: (t) => { + title: UIA.title + + onAccepted: t => { return UIA.continueEmail(t); } } - PhoneNumberInputDialog { id: uiaPhoneNumberPrompt - - title: UIA.title prompt: qsTr("Please enter a valid phone number to continue:") + title: UIA.title + onAccepted: (p, t) => { return UIA.continuePhoneNumber(p, t); } } - InputDialog { id: uiaTokenPrompt - - title: UIA.title prompt: qsTr("Please enter the token, which has been sent to you:") - onAccepted: (t) => { + title: UIA.title + + onAccepted: t => { return UIA.submit3pidToken(t); } } - Platform.MessageDialog { id: uiaErrorDialog - buttons: Platform.MessageDialog.Ok } - Platform.MessageDialog { id: uiaConfirmationLinkDialog - buttons: Platform.MessageDialog.Ok text: qsTr("Wait for the confirmation link to arrive, then continue.") + onAccepted: UIA.continue3pidReceived() } - Connections { - function onPassword() { - console.log("UIA: password needed"); - uiaPassPrompt.show(); - } - - function onEmail() { - uiaEmailPrompt.show(); - } - - function onPhoneNumber() { - uiaPhoneNumberPrompt.show(); - } - - function onPrompt3pidToken() { - uiaTokenPrompt.show(); - } - function onConfirm3pidToken() { uiaConfirmationLinkDialog.open(); } - + function onEmail() { + uiaEmailPrompt.show(); + } function onError(msg) { uiaErrorDialog.text = msg; uiaErrorDialog.open(); } + function onPassword() { + console.log("UIA: password needed"); + uiaPassPrompt.show(); + } + function onPhoneNumber() { + uiaPhoneNumberPrompt.show(); + } + function onPrompt3pidToken() { + uiaTokenPrompt.show(); + } target: UIA } - StackView { id: mainWindow - anchors.fill: parent initialItem: welcomePage } - Component { id: welcomePage - WelcomePage { } } - Component { id: chatPage - ChatPage { } } - Component { id: loginPage - LoginPage { } } - Component { id: registerPage - RegisterPage { } } - - Snackbar { id: snackbar } - + Snackbar { + id: snackbar + } Connections { - function onSwitchToChatPage() { - mainWindow.replace(null, chatPage); - } - function onSwitchToLoginPage(error) { - mainWindow.replace(welcomePage, {}, loginPage, {"error": error}, StackView.PopTransition); - } function onShowNotification(msg) { snackbar.showNotification(msg); console.log("New snack: " + msg); } + function onSwitchToChatPage() { + mainWindow.replace(null, chatPage); + } + function onSwitchToLoginPage(error) { + mainWindow.replace(welcomePage, {}, loginPage, { + "error": error + }, StackView.PopTransition); + } + target: MainWindow } - } diff --git a/qml/SelfVerificationCheck.qml b/qml/SelfVerificationCheck.qml index 772eb17b..ccdeed6e 100644 --- a/qml/SelfVerificationCheck.qml +++ b/qml/SelfVerificationCheck.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./components/" +import "components" import Qt.labs.platform 1.1 as P import QtQuick 2.15 import QtQuick.Controls 2.15 @@ -11,68 +9,61 @@ import QtQuick.Layouts 1.3 import im.nheko Item { - visible: false enabled: false + visible: false Dialog { id: showRecoverKeyDialog property string recoveryKey: "" - parent: Overlay.overlay anchors.centerIn: parent - height: content.height + implicitFooterHeight + implicitHeaderHeight - width: content.width - padding: 0 - modal: true - standardButtons: Dialog.Ok closePolicy: Popup.NoAutoClose - - ColumnLayout { - id: content - - spacing: 0 - - Label { - Layout.margins: Nheko.paddingMedium - Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4 - Layout.fillWidth: true - text: qsTr("This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200!") - color: timelineRoot.palette.text - wrapMode: Text.Wrap - } - - TextEdit { - Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4 - Layout.alignment: Qt.AlignHCenter - horizontalAlignment: TextEdit.AlignHCenter - verticalAlignment: TextEdit.AlignVCenter - readOnly: true - selectByMouse: true - text: showRecoverKeyDialog.recoveryKey - color: timelineRoot.palette.text - font.bold: true - wrapMode: TextEdit.Wrap - } - - } + height: content.height + implicitFooterHeight + implicitHeaderHeight + modal: true + padding: 0 + parent: Overlay.overlay + standardButtons: Dialog.Ok + width: content.width background: Rectangle { - color: timelineRoot.palette.window border.color: Nheko.theme.separator border.width: 1 + color: timelineRoot.palette.window radius: Nheko.paddingSmall } - } + ColumnLayout { + id: content + spacing: 0 + Label { + Layout.fillWidth: true + Layout.margins: Nheko.paddingMedium + Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4 + color: timelineRoot.palette.text + text: qsTr("This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200!") + wrapMode: Text.Wrap + } + TextEdit { + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4 + color: timelineRoot.palette.text + font.bold: true + horizontalAlignment: TextEdit.AlignHCenter + readOnly: true + selectByMouse: true + text: showRecoverKeyDialog.recoveryKey + verticalAlignment: TextEdit.AlignVCenter + wrapMode: TextEdit.Wrap + } + } + } P.MessageDialog { id: successDialog - buttons: P.MessageDialog.Ok text: qsTr("Encryption setup successfully") } - P.MessageDialog { id: failureDialog @@ -81,199 +72,185 @@ Item { buttons: P.MessageDialog.Ok text: qsTr("Failed to setup encryption: %1").arg(errorMessage) } - MainWindowDialog { id: bootstrapCrosssigning + background: Rectangle { + border.color: Nheko.theme.separator + border.width: 1 + color: timelineRoot.palette.window + radius: Nheko.paddingSmall + } onAccepted: SelfVerificationStatus.setupCrosssigning(storeSecretsOnline.checked, usePassword.checked ? passwordField.text : "", useOnlineKeyBackup.checked) GridLayout { id: grid - - width: bootstrapCrosssigning.useableWidth + columnSpacing: 0 columns: 2 rowSpacing: 0 - columnSpacing: 0 + width: bootstrapCrosssigning.useableWidth z: 1 Label { - Layout.margins: Nheko.paddingMedium Layout.alignment: Qt.AlignHCenter Layout.columnSpan: 2 + Layout.margins: Nheko.paddingMedium + color: timelineRoot.palette.text font.pointSize: fontMetrics.font.pointSize * 2 text: qsTr("Setup Encryption") - color: timelineRoot.palette.text wrapMode: Text.Wrap } - Label { - Layout.margins: Nheko.paddingMedium Layout.alignment: Qt.AlignLeft Layout.columnSpan: 2 + Layout.margins: Nheko.paddingMedium Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 - text: qsTr("Hello and welcome to Matrix!\nIt seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!") color: timelineRoot.palette.text + text: qsTr("Hello and welcome to Matrix!\nIt seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!") wrapMode: Text.Wrap } - Label { - Layout.margins: Nheko.paddingMedium Layout.alignment: Qt.AlignLeft Layout.columnSpan: 1 + Layout.margins: Nheko.paddingMedium Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 - text: "Store secrets online.\nYou have a few secrets to make all the encryption magic work. While you can keep them stored only locally, we recommend storing them encrypted on the server. Otherwise it will be painful to recover them. Only disable this if you are paranoid and like losing your data!" color: timelineRoot.palette.text + text: "Store secrets online.\nYou have a few secrets to make all the encryption magic work. While you can keep them stored only locally, we recommend storing them encrypted on the server. Otherwise it will be painful to recover them. Only disable this if you are paranoid and like losing your data!" wrapMode: Text.Wrap } - Item { - Layout.margins: Nheko.paddingMedium - Layout.preferredHeight: storeSecretsOnline.height Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.fillWidth: true + Layout.margins: Nheko.paddingMedium + Layout.preferredHeight: storeSecretsOnline.height ToggleButton { id: storeSecretsOnline - checked: true + onClicked: console.log("Store secrets toggled: " + checked) } - } - Label { - Layout.margins: Nheko.paddingMedium Layout.alignment: Qt.AlignLeft Layout.columnSpan: 1 - Layout.rowSpan: 2 + Layout.margins: Nheko.paddingMedium Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 - visible: storeSecretsOnline.checked - text: "Set an online backup password.\nWe recommend you DON'T set a password and instead only rely on the recovery key. You will get a recovery key in any case when storing the cross-signing secrets online, but passwords are usually not very random, so they are easier to attack than a completely random recovery key. If you choose to use a password, DON'T make it the same as your login password, otherwise your server can read all your encrypted messages. (You don't want that.)" + Layout.rowSpan: 2 color: timelineRoot.palette.text + text: "Set an online backup password.\nWe recommend you DON'T set a password and instead only rely on the recovery key. You will get a recovery key in any case when storing the cross-signing secrets online, but passwords are usually not very random, so they are easier to attack than a completely random recovery key. If you choose to use a password, DON'T make it the same as your login password, otherwise your server can read all your encrypted messages. (You don't want that.)" + visible: storeSecretsOnline.checked wrapMode: Text.Wrap } - Item { - Layout.margins: Nheko.paddingMedium - Layout.topMargin: Nheko.paddingLarge - Layout.preferredHeight: storeSecretsOnline.height Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.rowSpan: usePassword.checked ? 1 : 2 Layout.fillWidth: true + Layout.margins: Nheko.paddingMedium + Layout.preferredHeight: storeSecretsOnline.height + Layout.rowSpan: usePassword.checked ? 1 : 2 + Layout.topMargin: Nheko.paddingLarge visible: storeSecretsOnline.checked ToggleButton { id: usePassword - checked: false } - } - MatrixTextField { id: passwordField - - Layout.margins: Nheko.paddingMedium - Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.columnSpan: 1 Layout.fillWidth: true - visible: storeSecretsOnline.checked && usePassword.checked - echoMode: TextInput.Password - } - - Label { Layout.margins: Nheko.paddingMedium + Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 + echoMode: TextInput.Password + visible: storeSecretsOnline.checked && usePassword.checked + } + Label { Layout.alignment: Qt.AlignLeft Layout.columnSpan: 1 + Layout.margins: Nheko.paddingMedium Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 - text: "Use online key backup.\nStore the keys for your messages securely encrypted online. In general you do want this, because it protects your messages from becoming unreadable, if you log out by accident. It does however carry a small security risk, if you ever share your recovery key by accident. Currently this also has some other weaknesses, that might allow the server to insert new keys into your backup. The server will however never be able to read your messages." color: timelineRoot.palette.text + text: "Use online key backup.\nStore the keys for your messages securely encrypted online. In general you do want this, because it protects your messages from becoming unreadable, if you log out by accident. It does however carry a small security risk, if you ever share your recovery key by accident. Currently this also has some other weaknesses, that might allow the server to insert new keys into your backup. The server will however never be able to read your messages." wrapMode: Text.Wrap } - Item { - Layout.margins: Nheko.paddingMedium - Layout.preferredHeight: storeSecretsOnline.height Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.fillWidth: true + Layout.margins: Nheko.paddingMedium + Layout.preferredHeight: storeSecretsOnline.height ToggleButton { id: useOnlineKeyBackup - checked: true + onClicked: console.log("Online key backup toggled: " + checked) } - } - } - - background: Rectangle { - color: timelineRoot.palette.window - border.color: Nheko.theme.separator - border.width: 1 - radius: Nheko.paddingSmall - } - } - MainWindowDialog { id: verifyMasterKey - standardButtons: Dialog.Cancel GridLayout { id: masterGrid - - width: verifyMasterKey.useableWidth columns: 1 + width: verifyMasterKey.useableWidth z: 1 Label { - Layout.margins: Nheko.paddingMedium Layout.alignment: Qt.AlignHCenter + Layout.margins: Nheko.paddingMedium + color: timelineRoot.palette.text //Layout.columnSpan: 2 font.pointSize: fontMetrics.font.pointSize * 2 text: qsTr("Activate Encryption") - color: timelineRoot.palette.text wrapMode: Text.Wrap } - Label { - Layout.margins: Nheko.paddingMedium Layout.alignment: Qt.AlignLeft + Layout.margins: Nheko.paddingMedium //Layout.columnSpan: 2 Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 - text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.") color: timelineRoot.palette.text + text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.") wrapMode: Text.Wrap } - FlatButton { Layout.alignment: Qt.AlignHCenter text: qsTr("verify") + onClicked: { SelfVerificationStatus.verifyMasterKey(); verifyMasterKey.close(); } } - FlatButton { - visible: SelfVerificationStatus.hasSSSS Layout.alignment: Qt.AlignHCenter text: qsTr("enter passphrase") + visible: SelfVerificationStatus.hasSSSS + onClicked: { SelfVerificationStatus.verifyMasterKeyWithPassphrase(); verifyMasterKey.close(); } } - } - } - Connections { + function onSetupCompleted() { + successDialog.open(); + } + function onSetupFailed(m) { + failureDialog.errorMessage = m; + failureDialog.open(); + } + function onShowRecoveryKey(key) { + showRecoverKeyDialog.recoveryKey = key; + showRecoverKeyDialog.open(); + } function onStatusChanged() { console.log("STATUS CHANGED: " + SelfVerificationStatus.status); if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) { @@ -286,21 +263,6 @@ Item { } } - function onShowRecoveryKey(key) { - showRecoverKeyDialog.recoveryKey = key; - showRecoverKeyDialog.open(); - } - - function onSetupCompleted() { - successDialog.open(); - } - - function onSetupFailed(m) { - failureDialog.errorMessage = m; - failureDialog.open(); - } - target: SelfVerificationStatus } - } diff --git a/qml/StatusIndicator.qml b/qml/StatusIndicator.qml index 4290c2c4..8f665880 100644 --- a/qml/StatusIndicator.qml +++ b/qml/StatusIndicator.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.5 import QtQuick.Controls 2.1 import im.nheko @@ -10,15 +8,9 @@ import im.nheko ImageButton { id: indicator - required property int status required property string eventId + required property int status - width: 16 - height: 16 - hoverEnabled: true - changeColorOnHover: (status == MtxEvent.Read) - cursor: (status == MtxEvent.Read) ? Qt.PointingHandCursor : Qt.ArrowCursor - ToolTip.visible: hovered && status != MtxEvent.Empty ToolTip.text: { switch (status) { case MtxEvent.Failed: @@ -33,11 +25,11 @@ ImageButton { return ""; } } - onClicked: { - if (status == MtxEvent.Read) - room.showReadReceipts(eventId); - - } + ToolTip.visible: hovered && status != MtxEvent.Empty + changeColorOnHover: (status == MtxEvent.Read) + cursor: (status == MtxEvent.Read) ? Qt.PointingHandCursor : Qt.ArrowCursor + height: 16 + hoverEnabled: true image: { switch (status) { case MtxEvent.Failed: @@ -52,4 +44,10 @@ ImageButton { return ""; } } + width: 16 + + onClicked: { + if (status == MtxEvent.Read) + room.showReadReceipts(eventId); + } } diff --git a/qml/TimelineRow.qml b/qml/TimelineRow.qml index 88104c63..6f319a63 100644 --- a/qml/TimelineRow.qml +++ b/qml/TimelineRow.qml @@ -1,10 +1,8 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./delegates" -import "./emoji" +import "delegates" +import "emoji" import QtQuick 2.15 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 @@ -14,69 +12,42 @@ import im.nheko AbstractButton { id: r - required property double proportionalHeight - required property int type - required property string typeString - required property int originalWidth required property string blurhash required property string body - required property string formattedBody + required property string callType + required property int duration + required property int encryptionError required property string eventId required property string filename required property string filesize - required property string url - required property string thumbnailUrl - required property bool isOnlyEmoji - required property bool isSender - required property bool isEncrypted + required property string formattedBody required property bool isEditable required property bool isEdited + required property bool isEncrypted + required property bool isOnlyEmoji + required property bool isSender required property bool isStateEvent + required property int originalWidth + required property double proportionalHeight + required property var reactions + required property int relatedEventCacheBuster required property string replyTo + required property string roomName + required property string roomTopic + required property int status + required property string thumbnailUrl + required property var timestamp + required property int trustlevel + required property int type + required property string typeString + required property string url required property string userId required property string userName - required property string roomTopic - required property string roomName - required property string callType - required property var reactions - required property int trustlevel - required property int encryptionError - required property int duration - required property var timestamp - required property int status - required property int relatedEventCacheBuster + height: row.height + (reactionRow.height > 0 ? reactionRow.height - 2 : 0) hoverEnabled: true - width: parent.width - height: row.height+(reactionRow.height > 0 ? reactionRow.height-2 : 0 ) - Rectangle { - color: (Settings.messageHoverHighlight && hovered) ? timelineRoot.palette.alternateBase : "transparent" - anchors.fill: parent - // this looks better without margins - TapHandler { - acceptedButtons: Qt.RightButton - onSingleTapped: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText) - gesturePolicy: TapHandler.ReleaseWithinBounds - acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad - } - } - - - onPressAndHold: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText) - onDoubleClicked: chat.model.reply = eventId - - DragHandler { - id: draghandler - yAxis.enabled: false - xAxis.maximum: 100 - xAxis.minimum: -100 - onActiveChanged: { - if(!active && (x < -70 || x > 70)) - chat.model.reply = eventId - } - } states: State { name: "dragging" when: draghandler.active @@ -84,212 +55,234 @@ AbstractButton { transitions: Transition { from: "dragging" to: "" + PropertyAnimation { - target: r - properties: "x" - easing.type: Easing.InOutQuad - to: 0 duration: 100 + easing.type: Easing.InOutQuad + properties: "x" + target: r + to: 0 } } onClicked: { - let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX-row.x-msg.x, pressY-row.y-msg.y-contentItem.y); + let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX - row.x - msg.x, pressY - row.y - msg.y - contentItem.y); if (link) { - Nheko.openLink(link) + Nheko.openLink(link); } } + onDoubleClicked: chat.model.reply = eventId + onPressAndHold: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText) Rectangle { - id: row - property bool bubbleOnRight : isSender && Settings.bubbles - anchors.leftMargin: isStateEvent || Settings.smallAvatars? 0 : Nheko.avatarSize+8 // align bubble with section header - anchors.left: isStateEvent? undefined : (bubbleOnRight? undefined : parent.left) - anchors.right: isStateEvent? undefined: (bubbleOnRight? parent.right : undefined) - anchors.horizontalCenter: isStateEvent? parent.horizontalCenter : undefined - property int maxWidth: (parent.width-(Settings.smallAvatars || isStateEvent? 0 : Nheko.avatarSize+8))*(Settings.bubbles && !isStateEvent? 0.9 : 1) - width: Settings.bubbles? Math.min(maxWidth,Math.max(reply.implicitWidth+8,contentItem.implicitWidth+metadata.width+20)) : maxWidth - height: msg.height+msg.anchors.margins*2 + anchors.fill: parent + color: (Settings.messageHoverHighlight && hovered) ? timelineRoot.palette.alternateBase : "transparent" + + // this looks better without margins + TapHandler { + acceptedButtons: Qt.RightButton + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad + gesturePolicy: TapHandler.ReleaseWithinBounds + + onSingleTapped: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText) + } + } + DragHandler { + id: draghandler + xAxis.maximum: 100 + xAxis.minimum: -100 + yAxis.enabled: false + + onActiveChanged: { + if (!active && (x < -70 || x > 70)) + chat.model.reply = eventId; + } + } + Rectangle { + id: row - property color userColor: TimelineManager.userColor(userId, timelineRoot.palette.base) property color bgColor: timelineRoot.palette.base + property bool bubbleOnRight: isSender && Settings.bubbles + property int maxWidth: (parent.width - (Settings.smallAvatars || isStateEvent ? 0 : Nheko.avatarSize + 8)) * (Settings.bubbles && !isStateEvent ? 0.9 : 1) + property color userColor: TimelineManager.userColor(userId, timelineRoot.palette.base) + + anchors.horizontalCenter: isStateEvent ? parent.horizontalCenter : undefined + anchors.left: isStateEvent ? undefined : (bubbleOnRight ? undefined : parent.left) + anchors.leftMargin: isStateEvent || Settings.smallAvatars ? 0 : Nheko.avatarSize + 8 // align bubble with section header + anchors.right: isStateEvent ? undefined : (bubbleOnRight ? parent.right : undefined) color: (Settings.bubbles && !isStateEvent) ? Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.2)) : "#00000000" + height: msg.height + msg.anchors.margins * 2 radius: 4 + width: Settings.bubbles ? Math.min(maxWidth, Math.max(reply.implicitWidth + 8, contentItem.implicitWidth + metadata.width + 20)) : maxWidth GridLayout { + id: msg + columnSpacing: 2 + columns: Settings.bubbles ? 1 : 2 + rowSpacing: 0 + rows: Settings.bubbles ? 3 : 2 + anchors { left: parent.left - top: parent.top - right: parent.right - margins: (Settings.bubbles && ! isStateEvent)? 4 : 2 leftMargin: 4 + margins: (Settings.bubbles && !isStateEvent) ? 4 : 2 + right: parent.right + top: parent.top } - id: msg - rowSpacing: 0 - columnSpacing: 2 - columns: Settings.bubbles? 1 : 2 - rows: Settings.bubbles? 3 : 2 // fancy reply, if this is a reply Reply { - Layout.row: 0 - Layout.column: 0 - Layout.fillWidth: true - Layout.maximumWidth: Settings.bubbles? Number.MAX_VALUE : implicitWidth - Layout.bottomMargin: visible? 2 : 0 - Layout.preferredHeight: height id: reply - function fromModel(role) { return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null; } - visible: replyTo - userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, timelineRoot.palette.base) + + Layout.bottomMargin: visible ? 2 : 0 + Layout.column: 0 + Layout.fillWidth: true + Layout.maximumWidth: Settings.bubbles ? Number.MAX_VALUE : implicitWidth + Layout.preferredHeight: height + Layout.row: 0 blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? "" body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? "" - formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? "" + callType: r.relatedEventCacheBuster, fromModel(Room.Voip) ?? "" + duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? 0 + encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? 0 eventId: fromModel(Room.EventId) ?? "" filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? "" filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? "" + formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? "" + isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false + isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false + originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0 proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1 + relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0 + roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? "" + roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? "" + thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? "" type: r.relatedEventCacheBuster, fromModel(Room.Type) ?? MtxEvent.UnknownMessage typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? "" url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? "" - originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0 - isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false - isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false + userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, timelineRoot.palette.base) userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? "" userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? "" - thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? "" - duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? 0 - roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? "" - roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? "" - callType: r.relatedEventCacheBuster, fromModel(Room.Voip) ?? "" - encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? 0 - relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0 + visible: replyTo } // actual message content MessageDelegate { - Layout.row: 1 + id: contentItem Layout.column: 0 Layout.fillWidth: true Layout.preferredHeight: height - id: contentItem - + Layout.row: 1 blurhash: r.blurhash body: r.body - formattedBody: r.formattedBody + callType: r.callType + duration: r.duration + encryptionError: r.encryptionError eventId: r.eventId filename: r.filename filesize: r.filesize + formattedBody: r.formattedBody + isOnlyEmoji: r.isOnlyEmoji + isReply: false + isStateEvent: r.isStateEvent + metadataWidth: metadata.width + originalWidth: r.originalWidth proportionalHeight: r.proportionalHeight + relatedEventCacheBuster: r.relatedEventCacheBuster + roomName: r.roomName + roomTopic: r.roomTopic + thumbnailUrl: r.thumbnailUrl type: r.type typeString: r.typeString ?? "" url: r.url - thumbnailUrl: r.thumbnailUrl - duration: r.duration - originalWidth: r.originalWidth - isOnlyEmoji: r.isOnlyEmoji - isStateEvent: r.isStateEvent userId: r.userId userName: r.userName - roomTopic: r.roomTopic - roomName: r.roomName - callType: r.callType - encryptionError: r.encryptionError - relatedEventCacheBuster: r.relatedEventCacheBuster - isReply: false - metadataWidth: metadata.width } - Row { id: metadata - Layout.column: Settings.bubbles? 0 : 1 - Layout.row: Settings.bubbles? 2 : 0 - Layout.rowSpan: Settings.bubbles? 1 : 2 - Layout.bottomMargin: -2 - Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles)? -height-Layout.bottomMargin : 0 + + property int iconSize: Math.floor(fontMetrics.ascent * scaling) + property double scaling: Settings.bubbles ? 0.75 : 1 + Layout.alignment: Qt.AlignTop | Qt.AlignRight + Layout.bottomMargin: -2 + Layout.column: Settings.bubbles ? 0 : 1 Layout.preferredWidth: implicitWidth - visible: !isStateEvent + Layout.row: Settings.bubbles ? 2 : 0 + Layout.rowSpan: Settings.bubbles ? 1 : 2 + Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles) ? -height - Layout.bottomMargin : 0 spacing: 2 - - property double scaling: Settings.bubbles? 0.75 : 1 - - property int iconSize: Math.floor(fontMetrics.ascent*scaling) + visible: !isStateEvent StatusIndicator { Layout.alignment: Qt.AlignRight | Qt.AlignTop - height: parent.iconSize - width: parent.iconSize - status: r.status - eventId: r.eventId anchors.verticalCenter: ts.verticalCenter - } - - Image { - visible: isEdited || eventId == chat.model.edit - Layout.alignment: Qt.AlignRight | Qt.AlignTop + eventId: r.eventId height: parent.iconSize + status: r.status width: parent.iconSize - sourceSize.width: parent.iconSize * Screen.devicePixelRatio - sourceSize.height: parent.iconSize * Screen.devicePixelRatio - source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == chat.model.edit) ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) - ToolTip.visible: editHovered.hovered + } + Image { + Layout.alignment: Qt.AlignRight | Qt.AlignTop ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Edited") + ToolTip.visible: editHovered.hovered anchors.verticalCenter: ts.verticalCenter + height: parent.iconSize + source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == chat.model.edit) ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) + sourceSize.height: parent.iconSize * Screen.devicePixelRatio + sourceSize.width: parent.iconSize * Screen.devicePixelRatio + visible: isEdited || eventId == chat.model.edit + width: parent.iconSize HoverHandler { id: editHovered } - } - EncryptionIndicator { - visible: room.isEncrypted - encrypted: isEncrypted - trust: trustlevel Layout.alignment: Qt.AlignRight | Qt.AlignTop - height: parent.iconSize - width: parent.iconSize - sourceSize.width: parent.iconSize * Screen.devicePixelRatio - sourceSize.height: parent.iconSize * Screen.devicePixelRatio anchors.verticalCenter: ts.verticalCenter + encrypted: isEncrypted + height: parent.iconSize + sourceSize.height: parent.iconSize * Screen.devicePixelRatio + sourceSize.width: parent.iconSize * Screen.devicePixelRatio + trust: trustlevel + visible: room.isEncrypted + width: parent.iconSize } - Label { id: ts Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.preferredWidth: implicitWidth - text: timestamp.toLocaleTimeString(Locale.ShortFormat) - color: timelineRoot.palette.inactive.text - ToolTip.visible: ma.hovered ToolTip.delay: Nheko.tooltipDelay ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate) - font.pointSize: fontMetrics.font.pointSize*parent.scaling + ToolTip.visible: ma.hovered + color: timelineRoot.palette.inactive.text + font.pointSize: fontMetrics.font.pointSize * parent.scaling + text: timestamp.toLocaleTimeString(Locale.ShortFormat) + HoverHandler { id: ma } - } } } } Reactions { + id: reactionRow + eventId: r.eventId + layoutDirection: row.bubbleOnRight ? Qt.RightToLeft : Qt.LeftToRight + reactions: r.reactions + width: row.maxWidth + anchors { + left: row.bubbleOnRight ? undefined : row.left + right: row.bubbleOnRight ? row.right : undefined top: row.bottom topMargin: -2 - left: row.bubbleOnRight? undefined : row.left - right: row.bubbleOnRight? row.right : undefined } - width: row.maxWidth - layoutDirection: row.bubbleOnRight? Qt.RightToLeft : Qt.LeftToRight - - id: reactionRow - - reactions: r.reactions - eventId: r.eventId } } diff --git a/qml/TimelineView.qml b/qml/TimelineView.qml index 3eb5d618..75cd733f 100644 --- a/qml/TimelineView.qml +++ b/qml/TimelineView.qml @@ -1,14 +1,12 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./components" -import "./delegates" -import "./device-verification" -import "./emoji" -import "./ui" -import "./voip" +import "components" +import "delegates" +import "device-verification" +import "emoji" +import "ui" +import "voip" import Qt.labs.platform 1.1 as Platform import QtQuick 2.15 import QtQuick.Controls 2.5 @@ -23,55 +21,50 @@ Item { property var room: null property var roomPreview: null property bool showBackButton: false + clip: true Shortcut { sequence: StandardKey.Close + onActivated: Rooms.resetCurrentRoom() } - Label { - visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid) anchors.centerIn: parent - text: qsTr("No room open") - font.pointSize: 24 color: timelineRoot.palette.text + font.pointSize: 24 + text: qsTr("No room open") + visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid) } - Spinner { - visible: TimelineManager.isInitialSync anchors.centerIn: parent foreground: timelineRoot.palette.mid - running: TimelineManager.isInitialSync // height is somewhat arbitrary here... don't set width because width scales w/ height height: parent.height / 16 + running: TimelineManager.isInitialSync + visible: TimelineManager.isInitialSync z: 3 } - ColumnLayout { id: timelineLayout - - visible: room != null && !room.isSpace - enabled: visible anchors.fill: parent + enabled: visible spacing: 0 + visible: room != null && !room.isSpace TopBar { showBackButton: timelineView.showBackButton } - Rectangle { Layout.fillWidth: true + color: Nheko.theme.separator height: 1 z: 3 - color: Nheko.theme.separator } - Rectangle { id: msgView - - Layout.fillWidth: true Layout.fillHeight: true + Layout.fillWidth: true color: timelineRoot.palette.base ColumnLayout { @@ -80,7 +73,6 @@ Item { StackLayout { id: stackLayout - currentIndex: 0 Connections { @@ -90,129 +82,107 @@ Item { target: timelineView } - MessageView { - implicitHeight: msgView.height - typingIndicator.height Layout.fillWidth: true + implicitHeight: msgView.height - typingIndicator.height } - Loader { source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? "voip/VideoCall.qml" : "" + onLoaded: TimelineManager.setVideoCallItem() } - } - TypingIndicator { id: typingIndicator } - } - } - CallInviteBar { id: callInviteBar - Layout.fillWidth: true z: 3 } - ActiveCallBar { Layout.fillWidth: true z: 3 } - Rectangle { Layout.fillWidth: true - z: 3 - height: 1 color: Nheko.theme.separator + height: 1 + z: 3 } - - UploadBox { } - NotificationWarning { } - ReplyPopup { } - MessageInput { } - } - ColumnLayout { id: preview + property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "") property string roomId: room ? room.roomId : (roomPreview ? roomPreview.roomid : "") property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "") property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "") - property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "") - visible: room != null && room.isSpace || roomPreview != null - enabled: visible anchors.fill: parent anchors.margins: Nheko.paddingLarge + enabled: visible spacing: Nheko.paddingLarge + visible: room != null && room.isSpace || roomPreview != null Item { Layout.fillHeight: true } - Avatar { - url: parent.avatarUrl.replace("mxc://", "image://MxcImage/") - roomid: parent.roomId + Layout.alignment: Qt.AlignHCenter displayName: parent.roomName - height: 130 - width: 130 - Layout.alignment: Qt.AlignHCenter enabled: false + height: 130 + roomid: parent.roomId + url: parent.avatarUrl.replace("mxc://", "image://MxcImage/") + width: 130 } - RowLayout { - spacing: Nheko.paddingMedium Layout.alignment: Qt.AlignHCenter + spacing: Nheko.paddingMedium MatrixText { - text: preview.roomName == "" ? qsTr("No preview available") : preview.roomName font.pixelSize: 24 + text: preview.roomName == "" ? qsTr("No preview available") : preview.roomName } - ImageButton { + ToolTip.text: qsTr("Settings") + ToolTip.visible: hovered + hoverEnabled: true image: ":/icons/icons/ui/settings.svg" visible: !!room - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Settings") + onClicked: TimelineManager.openRoomSettings(room.roomId) } - } - RowLayout { - visible: !!room - spacing: Nheko.paddingMedium Layout.alignment: Qt.AlignHCenter + spacing: Nheko.paddingMedium + visible: !!room MatrixText { - text: qsTr("%1 member(s)").arg(room ? room.roomMemberCount : 0) cursorShape: Qt.PointingHandCursor + text: qsTr("%1 member(s)").arg(room ? room.roomMemberCount : 0) } - ImageButton { - image: ":/icons/icons/ui/people.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("View members of %1").arg(room.roomName) + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/people.svg" + onClicked: TimelineManager.openRoomMembers(room) } - } - ScrollView { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true @@ -220,97 +190,88 @@ Item { Layout.rightMargin: Nheko.paddingLarge TextArea { - text: TimelineManager.escapeEmoji(preview.roomTopic) - wrapMode: TextEdit.WordWrap - textFormat: TextEdit.RichText - readOnly: true background: null - selectByMouse: true color: timelineRoot.palette.text horizontalAlignment: TextEdit.AlignHCenter + readOnly: true + selectByMouse: true + text: TimelineManager.escapeEmoji(preview.roomTopic) + textFormat: TextEdit.RichText + wrapMode: TextEdit.WordWrap + onLinkActivated: Nheko.openLink(link) NhekoCursorShape { anchors.fill: parent cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor } - } - } - FlatButton { - visible: roomPreview && !roomPreview.isInvite Layout.alignment: Qt.AlignHCenter text: qsTr("join the conversation") + visible: roomPreview && !roomPreview.isInvite + onClicked: Rooms.joinPreview(roomPreview.roomid) } - FlatButton { - visible: roomPreview && roomPreview.isInvite Layout.alignment: Qt.AlignHCenter text: qsTr("accept invite") + visible: roomPreview && roomPreview.isInvite + onClicked: Rooms.acceptInvite(roomPreview.roomid) } - FlatButton { - visible: roomPreview && roomPreview.isInvite Layout.alignment: Qt.AlignHCenter text: qsTr("decline invite") + visible: roomPreview && roomPreview.isInvite + onClicked: Rooms.declineInvite(roomPreview.roomid) } - Item { - visible: room != null Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 2) + visible: room != null } - Item { Layout.fillHeight: true } - } - ImageButton { id: backToRoomsButton - - anchors.top: parent.top + ToolTip.text: qsTr("Back to room list") + ToolTip.visible: hovered anchors.left: parent.left anchors.margins: Nheko.paddingMedium - width: Nheko.avatarSize - height: Nheko.avatarSize - visible: (room == null || room.isSpace) && showBackButton + anchors.top: parent.top enabled: visible + height: Nheko.avatarSize image: ":/icons/icons/ui/angle-arrow-left.svg" - ToolTip.visible: hovered - ToolTip.text: qsTr("Back to room list") + visible: (room == null || room.isSpace) && showBackButton + width: Nheko.avatarSize + onClicked: Rooms.resetCurrentRoom() } - NhekoDropArea { anchors.fill: parent roomid: room ? room.roomId : "" } - Connections { function onOpenReadReceiptsDialog(rr) { var dialog = readReceiptsDialog.createObject(timelineRoot, { - "readReceipts": rr, - "room": room - }); + "readReceipts": rr, + "room": room + }); dialog.show(); timelineRoot.destroyOnClose(dialog); } - function onShowRawMessageDialog(rawMessage) { var dialog = rawMessageDialog.createObject(timelineRoot, { - "rawMessage": rawMessage - }); + "rawMessage": rawMessage + }); dialog.show(); timelineRoot.destroyOnClose(dialog); } target: room } - } diff --git a/qml/ToggleButton.qml b/qml/ToggleButton.qml index 40c429cf..a9a059eb 100644 --- a/qml/ToggleButton.qml +++ b/qml/ToggleButton.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.5 import QtQuick 2.12 import QtQuick.Controls 2.12 @@ -10,36 +8,31 @@ import im.nheko Switch { id: toggleButton - implicitWidth: indicatorItem.width indicator: Item { id: indicatorItem - - implicitWidth: 48 implicitHeight: 24 + implicitWidth: 48 y: parent.height / 2 - height / 2 Rectangle { + border.color: "#cccccc" + color: toggleButton.checked ? "skyblue" : "grey" height: 3 * parent.height / 4 radius: height / 2 width: parent.width - height x: radius y: parent.height / 2 - height / 2 - color: toggleButton.checked ? "skyblue" : "grey" - border.color: "#cccccc" } - Rectangle { - x: toggleButton.checked ? parent.width - width : 0 - y: parent.height / 2 - height / 2 - width: parent.height + border.color: "#ebebeb" + color: toggleButton.enabled ? "whitesmoke" : "#cccccc" height: width radius: width / 2 - color: toggleButton.enabled ? "whitesmoke" : "#cccccc" - border.color: "#ebebeb" + width: parent.height + x: toggleButton.checked ? parent.width - width : 0 + y: parent.height / 2 - height / 2 } - } - } diff --git a/qml/TopBar.qml b/qml/TopBar.qml index 92a3fe78..7ebaed35 100644 --- a/qml/TopBar.qml +++ b/qml/TopBar.qml @@ -1,190 +1,144 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import Qt.labs.platform 1.1 as Platform import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.2 import QtQuick.Window 2.15 import im.nheko - -import "./delegates" +import "delegates" Pane { id: topBar - property bool showBackButton: false - property string roomName: room ? room.roomName : qsTr("No room selected") - property string roomId: room ? room.roomId : "" property string avatarUrl: room ? room.roomAvatarUrl : "" - property string roomTopic: room ? room.roomTopic : "" - property bool isEncrypted: room ? room.isEncrypted : false - property int trustlevel: room ? room.trustlevel : Crypto.Unverified - property bool isDirect: room ? room.isDirect : false property string directChatOtherUserId: room ? room.directChatOtherUserId : "" + property bool isDirect: room ? room.isDirect : false + property bool isEncrypted: room ? room.isEncrypted : false + property string roomId: room ? room.roomId : "" + property string roomName: room ? room.roomName : qsTr("No room selected") + property string roomTopic: room ? room.roomTopic : "" + property bool showBackButton: false + property int trustlevel: room ? room.trustlevel : Crypto.Unverified Layout.fillWidth: true implicitHeight: topBarC.height + Nheko.paddingMedium * 2 + padding: 0 z: 3 - padding: 0 background: Rectangle { color: timelineRoot.palette.window } - - TapHandler { - onSingleTapped: { - if (eventPoint.position.y > topBar.height - (pinnedMessages.visible ? pinnedMessages.height : 0) - (widgets.visible ? widgets.height : 0)) { - eventPoint.accepted = true - return; - } - if (showBackButton && eventPoint.position.x < Nheko.paddingMedium + backToRoomsButton.width) { - eventPoint.accepted = true - return; - } - if (eventPoint.position.x > topBar.width - Nheko.paddingMedium - roomOptionsButton.width) { - eventPoint.accepted = true - return; - } - - if (room) { - let p = topBar.mapToItem(roomTopicC, eventPoint.position.x, eventPoint.position.y); - let link = roomTopicC.linkAt(p.x, p.y); - - if (link) { - Nheko.openLink(link); - } else { - TimelineManager.openRoomSettings(room.roomId); - } - } - - eventPoint.accepted = true; - } - gesturePolicy: TapHandler.ReleaseWithinBounds - } - - HoverHandler { - grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything - } - contentItem: Item { GridLayout { id: topBarC anchors.left: parent.left - anchors.right: parent.right anchors.margins: Nheko.paddingMedium + anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter columnSpacing: Nheko.paddingSmall rowSpacing: Nheko.paddingSmall ImageButton { id: backToRoomsButton - - Layout.column: 0 - Layout.row: 0 - Layout.rowSpan: 2 Layout.alignment: Qt.AlignVCenter + Layout.column: 0 Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium - visible: showBackButton - image: ":/icons/icons/ui/angle-arrow-left.svg" - ToolTip.visible: hovered + Layout.row: 0 + Layout.rowSpan: 2 ToolTip.text: qsTr("Back to room list") + ToolTip.visible: hovered + image: ":/icons/icons/ui/angle-arrow-left.svg" + visible: showBackButton + onClicked: Rooms.resetCurrentRoom() } - Avatar { + Layout.alignment: Qt.AlignVCenter Layout.column: 1 Layout.row: 0 Layout.rowSpan: 2 - Layout.alignment: Qt.AlignVCenter - width: Nheko.avatarSize - height: Nheko.avatarSize - url: avatarUrl.replace("mxc://", "image://MxcImage/") - roomid: roomId - userid: isDirect ? directChatOtherUserId : "" displayName: roomName enabled: false + height: Nheko.avatarSize + roomid: roomId + url: avatarUrl.replace("mxc://", "image://MxcImage/") + userid: isDirect ? directChatOtherUserId : "" + width: Nheko.avatarSize } - Label { - Layout.fillWidth: true Layout.column: 2 + Layout.fillWidth: true Layout.row: 0 color: timelineRoot.palette.text - font.pointSize: fontMetrics.font.pointSize * 1.1 - text: roomName - maximumLineCount: 1 elide: Text.ElideRight + font.pointSize: fontMetrics.font.pointSize * 1.1 + maximumLineCount: 1 + text: roomName textFormat: Text.RichText } - MatrixText { id: roomTopicC - Layout.fillWidth: true Layout.column: 2 - Layout.row: 1 + Layout.fillWidth: true Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines - selectByMouse: false - enabled: false + Layout.row: 1 clip: true + enabled: false + selectByMouse: false text: roomTopic } - AbstractButton { Layout.column: 3 - Layout.row: 0 - Layout.rowSpan: 2 Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium - - contentItem: EncryptionIndicator { - sourceSize.height: parent.Layout.preferredHeight * Screen.devicePixelRatio - sourceSize.width: parent.Layout.preferredWidth * Screen.devicePixelRatio - visible: isEncrypted - encrypted: isEncrypted - trust: trustlevel - enabled: false - } - - background: null - + Layout.row: 0 + Layout.rowSpan: 2 ToolTip.delay: Nheko.tooltipDelay ToolTip.text: { if (!isEncrypted) - return qsTr("This room is not encrypted!"); - + return qsTr("This room is not encrypted!"); switch (trustlevel) { - case Crypto.Verified: + case Crypto.Verified: return qsTr("This room contains only verified devices."); - case Crypto.TOFU: + case Crypto.TOFU: return qsTr("This room contains verified devices and devices which have never changed their master key."); - default: + default: return qsTr("This room contains unverified devices!"); } } ToolTip.visible: hovered + background: null + + contentItem: EncryptionIndicator { + enabled: false + encrypted: isEncrypted + sourceSize.height: parent.Layout.preferredHeight * Screen.devicePixelRatio + sourceSize.width: parent.Layout.preferredWidth * Screen.devicePixelRatio + trust: trustlevel + visible: isEncrypted + } onClicked: TimelineManager.openRoomMembers(room) } - ImageButton { id: pinButton property bool pinsShown: !Settings.hiddenPins.includes(roomId) - visible: !!room && room.pinnedMessages.length > 0 - Layout.column: 4 - Layout.row: 0 - Layout.rowSpan: 2 Layout.alignment: Qt.AlignVCenter + Layout.column: 4 Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium - image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg" - ToolTip.visible: hovered + Layout.row: 0 + Layout.rowSpan: 2 ToolTip.text: qsTr("Show or hide pinned messages") + ToolTip.visible: hovered + image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg" + visible: !!room && room.pinnedMessages.length > 0 + onClicked: { var ps = Settings.hiddenPins; if (pinsShown) { @@ -197,156 +151,168 @@ Pane { } Settings.hiddenPins = ps; } - } - ImageButton { id: roomOptionsButton - - visible: !!room - Layout.column: 5 - Layout.row: 0 - Layout.rowSpan: 2 Layout.alignment: Qt.AlignVCenter + Layout.column: 5 Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium - image: ":/icons/icons/ui/options.svg" - ToolTip.visible: hovered + Layout.row: 0 + Layout.rowSpan: 2 ToolTip.text: qsTr("Room options") + ToolTip.visible: hovered + image: ":/icons/icons/ui/options.svg" + visible: !!room + onClicked: roomOptionsMenu.open(roomOptionsButton) Platform.Menu { id: roomOptionsMenu - Platform.MenuItem { - visible: room ? room.permissions.canInvite() : false text: qsTr("Invite users") + visible: room ? room.permissions.canInvite() : false + onTriggered: TimelineManager.openInviteUsers(roomId) } - Platform.MenuItem { text: qsTr("Members") + onTriggered: TimelineManager.openRoomMembers(room) } - Platform.MenuItem { text: qsTr("Leave room") + onTriggered: TimelineManager.openLeaveRoomDialog(roomId) } - Platform.MenuItem { text: qsTr("Settings") + onTriggered: TimelineManager.openRoomSettings(roomId) } - } - } - ScrollView { id: pinnedMessages - - Layout.row: 2 Layout.column: 2 Layout.columnSpan: 3 - Layout.fillWidth: true Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4) - - visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId) - clip: true - - palette: timelineRoot.palette + Layout.row: 2 ScrollBar.horizontal.visible: false + clip: true + palette: timelineRoot.palette + visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId) ListView { - - spacing: Nheko.paddingSmall model: room ? room.pinnedMessages : undefined + spacing: Nheko.paddingSmall + delegate: RowLayout { required property string modelData - width: ListView.view.width height: implicitHeight + width: ListView.view.width Reply { property var e: room ? room.getDump(modelData, "") : {} + Layout.fillWidth: true Layout.preferredHeight: height - - userColor: TimelineManager.userColor(e.userId, timelineRoot.palette.window) blurhash: e.blurhash ?? "" body: e.body ?? "" - formattedBody: e.formattedBody ?? "" + encryptionError: e.encryptionError ?? "" eventId: e.eventId ?? "" filename: e.filename ?? "" filesize: e.filesize ?? "" + formattedBody: e.formattedBody ?? "" + isOnlyEmoji: e.isOnlyEmoji ?? false + originalWidth: e.originalWidth ?? 0 proportionalHeight: e.proportionalHeight ?? 1 type: e.type ?? MtxEvent.UnknownMessage typeString: e.typeString ?? "" url: e.url ?? "" - originalWidth: e.originalWidth ?? 0 - isOnlyEmoji: e.isOnlyEmoji ?? false + userColor: TimelineManager.userColor(e.userId, timelineRoot.palette.window) userId: e.userId ?? "" userName: e.userName ?? "" - encryptionError: e.encryptionError ?? "" } - ImageButton { id: deletePinButton - + Layout.alignment: Qt.AlignTop | Qt.AlignLeft Layout.preferredHeight: 16 Layout.preferredWidth: 16 - Layout.alignment: Qt.AlignTop | Qt.AlignLeft - visible: room.permissions.canChange(MtxEvent.PinnedEvents) - + ToolTip.text: qsTr("Unpin") + ToolTip.visible: hovered hoverEnabled: true image: ":/icons/icons/ui/dismiss.svg" - ToolTip.visible: hovered - ToolTip.text: qsTr("Unpin") + visible: room.permissions.canChange(MtxEvent.PinnedEvents) onClicked: room.unpin(modelData) } } - } } - ScrollView { id: widgets - - Layout.row: 3 Layout.column: 2 Layout.columnSpan: 3 - Layout.fillWidth: true Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5) - - visible: !!room && room.widgetLinks.length > 0 && !Settings.hiddenWidgets.includes(roomId) - clip: true - - palette: timelineRoot.palette + Layout.row: 3 ScrollBar.horizontal.visible: false + clip: true + palette: timelineRoot.palette + visible: !!room && room.widgetLinks.length > 0 && !Settings.hiddenWidgets.includes(roomId) ListView { - - spacing: Nheko.paddingSmall model: room ? room.widgetLinks : undefined + spacing: Nheko.paddingSmall + delegate: MatrixText { required property var modelData color: timelineRoot.palette.text text: modelData } - } } } - NhekoCursorShape { - anchors.fill: parent anchors.bottomMargin: (pinnedMessages.visible ? pinnedMessages.height : 0) + (widgets.visible ? widgets.height : 0) + anchors.fill: parent cursorShape: Qt.PointingHandCursor } } + + TapHandler { + gesturePolicy: TapHandler.ReleaseWithinBounds + + onSingleTapped: { + if (eventPoint.position.y > topBar.height - (pinnedMessages.visible ? pinnedMessages.height : 0) - (widgets.visible ? widgets.height : 0)) { + eventPoint.accepted = true; + return; + } + if (showBackButton && eventPoint.position.x < Nheko.paddingMedium + backToRoomsButton.width) { + eventPoint.accepted = true; + return; + } + if (eventPoint.position.x > topBar.width - Nheko.paddingMedium - roomOptionsButton.width) { + eventPoint.accepted = true; + return; + } + if (room) { + let p = topBar.mapToItem(roomTopicC, eventPoint.position.x, eventPoint.position.y); + let link = roomTopicC.linkAt(p.x, p.y); + if (link) { + Nheko.openLink(link); + } else { + TimelineManager.openRoomSettings(room.roomId); + } + } + eventPoint.accepted = true; + } + } + HoverHandler { + grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything + } } diff --git a/qml/TypingIndicator.qml b/qml/TypingIndicator.qml index 64f8b02a..1cec1a9b 100644 --- a/qml/TypingIndicator.qml +++ b/qml/TypingIndicator.qml @@ -1,38 +1,32 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import im.nheko Item { - implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height) Layout.fillWidth: true + implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height) Rectangle { id: typingRect - - visible: (room && room.typingUsers.length > 0) - color: timelineRoot.palette.base anchors.fill: parent + color: timelineRoot.palette.base + visible: (room && room.typingUsers.length > 0) z: 3 Label { id: typingDisplay - + anchors.bottom: parent.bottom anchors.left: parent.left anchors.leftMargin: 10 anchors.right: parent.right anchors.rightMargin: 10 - anchors.bottom: parent.bottom color: timelineRoot.palette.text text: room ? room.formatTypingUsers(room.typingUsers, timelineRoot.palette.base) : "" textFormat: Text.RichText } - } - } diff --git a/qml/UploadBox.qml b/qml/UploadBox.qml index a9e40a51..33b3bcf1 100644 --- a/qml/UploadBox.qml +++ b/qml/UploadBox.qml @@ -1,10 +1,7 @@ // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./components" -import "./ui" - +import "components" +import "ui" import QtQuick 2.9 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.3 @@ -12,31 +9,30 @@ import im.nheko Page { id: uploadPopup - visible: room && room.input.uploads.length > 0 + Layout.fillWidth: true Layout.preferredHeight: 200 clip: true - - Layout.fillWidth: true - padding: Nheko.paddingMedium + visible: room && room.input.uploads.length > 0 + background: Rectangle { + color: timelineRoot.palette.base + } contentItem: ListView { id: uploadsList anchors.horizontalCenter: parent.horizontalCenter boundsBehavior: Flickable.StopAtBounds + model: room ? room.input.uploads : undefined + orientation: ListView.Horizontal + spacing: Nheko.paddingMedium + width: Math.min(contentWidth, parent.availableWidth) ScrollBar.horizontal: ScrollBar { id: scr } - - orientation: ListView.Horizontal - width: Math.min(contentWidth, parent.availableWidth) - model: room ? room.input.uploads : undefined - spacing: Nheko.paddingMedium - delegate: Pane { + height: uploadPopup.availableHeight - buttons.height - (scr.visible ? scr.height : 0) padding: Nheko.paddingSmall - height: uploadPopup.availableHeight - buttons.height - (scr.visible? scr.height : 0) width: uploadPopup.availableHeight - buttons.height background: Rectangle { @@ -45,45 +41,45 @@ Page { } contentItem: ColumnLayout { Image { + property string typeStr: switch (modelData.mediaType) { + case MediaUpload.Video: + return "video-file"; + case MediaUpload.Audio: + return "music"; + case MediaUpload.Image: + return "image"; + default: + return "zip"; + } + Layout.fillHeight: true Layout.fillWidth: true - + fillMode: Image.PreserveAspectFit + mipmap: true + smooth: true + source: (modelData.thumbnail != "") ? modelData.thumbnail : ("image://colorimage/:/icons/icons/ui/" + typeStr + ".svg?" + timelineRoot.palette.placeholderText) sourceSize.height: height sourceSize.width: width - fillMode: Image.PreserveAspectFit - smooth: true - mipmap: true - - property string typeStr: switch(modelData.mediaType) { - case MediaUpload.Video: return "video-file"; - case MediaUpload.Audio: return "music"; - case MediaUpload.Image: return "image"; - default: return "zip"; - } - source: (modelData.thumbnail != "") ? modelData.thumbnail : ("image://colorimage/:/icons/icons/ui/"+typeStr+".svg?" + timelineRoot.palette.placeholderText) } MatrixTextField { Layout.fillWidth: true text: modelData.filename + onTextEdited: modelData.filename = text } } } } - footer: DialogButtonBox { id: buttons - standardButtons: DialogButtonBox.Cancel - Button { - text: qsTr("Upload %n file(s)", "", (room ? room.input.uploads.length : 0)) - DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole - } + onAccepted: room.input.acceptUploads() onRejected: room.input.declineUploads() - } - background: Rectangle { - color: timelineRoot.palette.base + Button { + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + text: qsTr("Upload %n file(s)", "", (room ? room.input.uploads.length : 0)) + } } } diff --git a/qml/components/AdaptiveLayout.qml b/qml/components/AdaptiveLayout.qml index a71862ef..b2dbfbcf 100644 --- a/qml/components/AdaptiveLayout.qml +++ b/qml/components/AdaptiveLayout.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.12 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.12 @@ -12,67 +10,18 @@ Container { //Component.onCompleted: { // parent.width = Qt.binding(function() { return calculatedWidth; }) //} - id: container - property bool singlePageMode: width < 800 - property int splitterGrabMargin: Nheko.paddingSmall - property alias pageIndex: view.currentIndex - property Component handle - property Component handleToucharea - - onSinglePageModeChanged: if (!singlePageMode) pageIndex = 0 - - Component.onCompleted: { - for (var i = 0; i < count - 1; i++) { - let handle_ = handle.createObject(contentChildren[i]); - let split_ = handleToucharea.createObject(contentChildren[i]); - contentChildren[i].width = Qt.binding(function() { - return split_.calculatedWidth; - }); - contentChildren[i].splitterWidth = Qt.binding(function() { - return handle_.width; - }); - } - contentChildren[count - 1].width = Qt.binding(function() { - if (container.singlePageMode) { - return container.width; - } else { - var w = container.width; - for (var i = 0; i < count - 1; i++) { - if (contentChildren[i].width) - w = w - contentChildren[i].width; - - } - return w; - } - }); - contentChildren[count - 1].splitterWidth = 0; - for (var i = 0; i < count; i++) { - contentChildren[i].height = Qt.binding(function() { - return container.height; - }); - contentChildren[i].children[0].height = Qt.binding(function() { - return container.height; - }); - } - } - - handle: Rectangle { - z: 3 + property Component handle: Rectangle { + anchors.right: parent.right color: Nheko.theme.separator height: container.height width: visible ? 1 : 0 - anchors.right: parent.right + z: 3 } - - handleToucharea: Item { + property Component handleToucharea: Item { id: splitter - property int minimumWidth: parent.minimumWidth - property int maximumWidth: parent.maximumWidth - property int collapsedWidth: parent.collapsedWidth - property bool collapsible: parent.collapsible property int calculatedWidth: { if (!visible) return 0; @@ -81,6 +30,10 @@ Container { else return (collapsible && x < minimumWidth) ? collapsedWidth : x; } + property int collapsedWidth: parent.collapsedWidth + property bool collapsible: parent.collapsible + property int maximumWidth: parent.maximumWidth + property int minimumWidth: parent.minimumWidth enabled: !container.singlePageMode height: container.height @@ -89,49 +42,82 @@ Container { z: 3 NhekoCursorShape { + cursorShape: Qt.SizeHorCursor height: parent.height width: container.splitterGrabMargin * 2 x: -container.splitterGrabMargin - cursorShape: Qt.SizeHorCursor } - DragHandler { id: dragHandler - enabled: !container.singlePageMode - xAxis.enabled: true - yAxis.enabled: false - xAxis.minimum: splitter.minimumWidth - 1 - xAxis.maximum: splitter.maximumWidth - margin: container.splitterGrabMargin grabPermissions: PointerHandler.CanTakeOverFromAnything | PointerHandler.ApprovesTakeOverByHandlersOfSameType + margin: container.splitterGrabMargin + xAxis.enabled: true + xAxis.maximum: splitter.maximumWidth + xAxis.minimum: splitter.minimumWidth - 1 + yAxis.enabled: false + onActiveChanged: { if (!active) { splitter.x = splitter.calculatedWidth; splitter.parent.preferredWidth = splitter.calculatedWidth; } - } } - HoverHandler { enabled: !container.singlePageMode margin: container.splitterGrabMargin } - } + property alias pageIndex: view.currentIndex + property bool singlePageMode: width < 800 + property int splitterGrabMargin: Nheko.paddingSmall contentItem: ListView { id: view - - model: container.contentModel - snapMode: ListView.SnapOneItem - orientation: ListView.Horizontal + boundsBehavior: Flickable.StopAtBounds + currentIndex: container.singlePageMode ? container.pageIndex : 0 + highlightMoveDuration: container.singlePageMode ? 200 : 0 highlightRangeMode: ListView.StrictlyEnforceRange interactive: singlePageMode - highlightMoveDuration: container.singlePageMode ? 200 : 0 - currentIndex: container.singlePageMode ? container.pageIndex : 0 - boundsBehavior: Flickable.StopAtBounds + model: container.contentModel + orientation: ListView.Horizontal + snapMode: ListView.SnapOneItem } + Component.onCompleted: { + for (var i = 0; i < count - 1; i++) { + let handle_ = handle.createObject(contentChildren[i]); + let split_ = handleToucharea.createObject(contentChildren[i]); + contentChildren[i].width = Qt.binding(function () { + return split_.calculatedWidth; + }); + contentChildren[i].splitterWidth = Qt.binding(function () { + return handle_.width; + }); + } + contentChildren[count - 1].width = Qt.binding(function () { + if (container.singlePageMode) { + return container.width; + } else { + var w = container.width; + for (var i = 0; i < count - 1; i++) { + if (contentChildren[i].width) + w = w - contentChildren[i].width; + } + return w; + } + }); + contentChildren[count - 1].splitterWidth = 0; + for (var i = 0; i < count; i++) { + contentChildren[i].height = Qt.binding(function () { + return container.height; + }); + contentChildren[i].children[0].height = Qt.binding(function () { + return container.height; + }); + } + } + onSinglePageModeChanged: if (!singlePageMode) + pageIndex = 0 } diff --git a/qml/components/AdaptiveLayoutElement.qml b/qml/components/AdaptiveLayoutElement.qml index 666a47cc..6e96d023 100644 --- a/qml/components/AdaptiveLayoutElement.qml +++ b/qml/components/AdaptiveLayoutElement.qml @@ -1,27 +1,25 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.12 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.12 Item { - property int minimumWidth: 100 - property int maximumWidth: 400 + property bool collapsed: width < minimumWidth property int collapsedWidth: 40 property bool collapsible: true - property bool collapsed: width < minimumWidth - property int splitterWidth: 1 + property int maximumWidth: 400 + property int minimumWidth: 100 property int preferredWidth: 100 + property int splitterWidth: 1 Component.onCompleted: { children[0].width = Qt.binding(() => { - return parent.singlePageMode ? parent.width : width - splitterWidth; - }); + return parent.singlePageMode ? parent.width : width - splitterWidth; + }); children[0].height = Qt.binding(() => { - return parent.height; - }); + return parent.height; + }); } } diff --git a/qml/components/AvatarListTile.qml b/qml/components/AvatarListTile.qml index a6573739..498e0d9f 100644 --- a/qml/components/AvatarListTile.qml +++ b/qml/components/AvatarListTile.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 @@ -12,86 +10,81 @@ import im.nheko Rectangle { id: tile - property color background: timelineRoot.palette.window - property color importantText: timelineRoot.palette.text - property color unimportantText: timelineRoot.palette.placeholderText - property color bubbleBackground: timelineRoot.palette.highlight - property color bubbleText: timelineRoot.palette.highlightedText property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) required property string avatarUrl - required property string title - required property string subtitle - required property int index - required property int selectedIndex + property color background: timelineRoot.palette.window + property color bubbleBackground: timelineRoot.palette.highlight + property color bubbleText: timelineRoot.palette.highlightedText property bool crop: true + property color importantText: timelineRoot.palette.text + required property int index property alias roomid: avatar.roomid + required property int selectedIndex + required property string subtitle + required property string title + property color unimportantText: timelineRoot.palette.placeholderText property alias userid: avatar.userid color: background height: avatarSize + 2 * Nheko.paddingMedium - width: ListView.view.width state: "normal" + width: ListView.view.width + states: [ State { name: "highlight" when: hovered.hovered && !(index == selectedIndex) PropertyChanges { - target: tile background: timelineRoot.palette.dark - importantText: timelineRoot.palette.brightText - unimportantText: timelineRoot.palette.brightText bubbleBackground: timelineRoot.palette.highlight bubbleText: timelineRoot.palette.highlightedText + importantText: timelineRoot.palette.brightText + target: tile + unimportantText: timelineRoot.palette.brightText } - }, State { name: "selected" when: index == selectedIndex PropertyChanges { - target: tile background: timelineRoot.palette.highlight - importantText: timelineRoot.palette.highlightedText - unimportantText: timelineRoot.palette.highlightedText bubbleBackground: timelineRoot.palette.highlightedText bubbleText: timelineRoot.palette.highlight + importantText: timelineRoot.palette.highlightedText + target: tile + unimportantText: timelineRoot.palette.highlightedText } - } ] HoverHandler { id: hovered } - RowLayout { - spacing: Nheko.paddingMedium anchors.fill: parent anchors.margins: Nheko.paddingMedium + spacing: Nheko.paddingMedium Avatar { id: avatar - - enabled: false Layout.alignment: Qt.AlignVCenter - height: avatarSize - width: avatarSize - url: tile.avatarUrl.replace("mxc://", "image://MxcImage/") - displayName: title crop: tile.crop + displayName: title + enabled: false + height: avatarSize + url: tile.avatarUrl.replace("mxc://", "image://MxcImage/") + width: avatarSize } - ColumnLayout { id: textContent - Layout.alignment: Qt.AlignLeft Layout.fillWidth: true Layout.minimumWidth: 100 - width: parent.width - avatar.width Layout.preferredWidth: parent.width - avatar.width spacing: Nheko.paddingSmall + width: parent.width - avatar.width RowLayout { Layout.fillWidth: true @@ -104,33 +97,25 @@ Rectangle { fullText: title textFormat: Text.PlainText } - Item { Layout.fillWidth: true } - } - RowLayout { Layout.fillWidth: true spacing: 0 ElidedLabel { color: tile.unimportantText - font.pixelSize: fontMetrics.font.pixelSize * 0.9 elideWidth: textContent.width - Nheko.paddingSmall + font.pixelSize: fontMetrics.font.pixelSize * 0.9 fullText: subtitle textFormat: Text.PlainText } - Item { Layout.fillWidth: true } - } - } - } - } diff --git a/qml/components/FlatButton.qml b/qml/components/FlatButton.qml index 4a5ed9df..1cc40a64 100644 --- a/qml/components/FlatButton.qml +++ b/qml/components/FlatButton.qml @@ -1,6 +1,5 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later //import QtGraphicalEffects 1.12 @@ -13,11 +12,18 @@ import im.nheko Button { id: control + property string iconImage: "" + + hoverEnabled: true implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.70) implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight) - hoverEnabled: true - property string iconImage: "" + background: Rectangle { + color: Qt.lighter(timelineRoot.palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1)) + //height: control.contentItem.implicitHeight * 2 + //width: control.contentItem.implicitWidth * 2 + radius: height / 8 + } //DropShadow { // anchors.fill: control.background @@ -29,38 +35,29 @@ Button { // color: "#80000000" // source: control.background //} - contentItem: RowLayout { - spacing: 0 anchors.centerIn: parent - Image { - Layout.leftMargin: Nheko.paddingMedium - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - Layout.preferredHeight: fontMetrics.font.pixelSize * 1.5 - Layout.preferredWidth: fontMetrics.font.pixelSize * 1.5 - visible: !!iconImage - source: iconImage - } + spacing: 0 + Image { + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.leftMargin: Nheko.paddingMedium + Layout.preferredHeight: fontMetrics.font.pixelSize * 1.5 + Layout.preferredWidth: fontMetrics.font.pixelSize * 1.5 + source: iconImage + visible: !!iconImage + } Text { Layout.alignment: Qt.AlignHCenter - text: control.text + //font.capitalization: Font.AllUppercase + color: timelineRoot.palette.light + elide: Text.ElideRight //font: control.font font.capitalization: Font.AllUppercase font.pointSize: Math.ceil(fontMetrics.font.pointSize * 1.5) - //font.capitalization: Font.AllUppercase - color: timelineRoot.palette.light horizontalAlignment: Text.AlignHCenter + text: control.text verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight } } - - background: Rectangle { - //height: control.contentItem.implicitHeight * 2 - //width: control.contentItem.implicitWidth * 2 - radius: height / 8 - color: Qt.lighter(timelineRoot.palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1)) - } - } diff --git a/qml/components/MainWindowDialog.qml b/qml/components/MainWindowDialog.qml index dcdcebfc..b9d03c40 100644 --- a/qml/components/MainWindowDialog.qml +++ b/qml/components/MainWindowDialog.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import Qt.labs.platform 1.1 as P import QtQuick 2.15 import QtQuick.Controls 2.15 @@ -13,30 +11,28 @@ Dialog { default property alias inner: scroll.data property int useableWidth: scroll.width - scroll.ScrollBar.vertical.width - parent: Overlay.overlay anchors.centerIn: parent - height: (Math.floor(parent.height / 2) - Nheko.paddingLarge) * 2 - width: (Math.floor(parent.width / 2) - Nheko.paddingLarge) * 2 - padding: 0 - modal: true - standardButtons: Dialog.Ok | Dialog.Cancel closePolicy: Popup.NoAutoClose + height: (Math.floor(parent.height / 2) - Nheko.paddingLarge) * 2 + modal: true + padding: 0 + parent: Overlay.overlay + standardButtons: Dialog.Ok | Dialog.Cancel + width: (Math.floor(parent.width / 2) - Nheko.paddingLarge) * 2 + + background: Rectangle { + border.color: Nheko.theme.separator + border.width: 1 + color: timelineRoot.palette.window + radius: Nheko.paddingSmall + } contentChildren: [ ScrollView { id: scroll - - clip: true - anchors.fill: parent ScrollBar.horizontal.visible: false ScrollBar.vertical.visible: true + anchors.fill: parent + clip: true } ] - - background: Rectangle { - color: timelineRoot.palette.window - border.color: Nheko.theme.separator - border.width: 1 - radius: Nheko.paddingSmall - } - } diff --git a/qml/components/TextButton.qml b/qml/components/TextButton.qml index 99d8ebee..28f816c0 100644 --- a/qml/components/TextButton.qml +++ b/qml/components/TextButton.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import "../ui" import QtQuick 2.15 import QtQuick.Controls 2.15 @@ -11,37 +9,32 @@ import im.nheko // for cursor shape AbstractButton { id: button + property color buttonTextColor: timelineRoot.palette.placeholderText property alias cursor: mouseArea.cursorShape property color highlightColor: timelineRoot.palette.highlight - property color buttonTextColor: timelineRoot.palette.placeholderText focusPolicy: Qt.NoFocus - width: buttonText.implicitWidth height: buttonText.implicitHeight - implicitWidth: buttonText.implicitWidth implicitHeight: buttonText.implicitHeight + implicitWidth: buttonText.implicitWidth + width: buttonText.implicitWidth Label { id: buttonText - anchors.centerIn: parent - padding: 0 - text: button.text color: button.hovered ? highlightColor : buttonTextColor font: button.font - verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter + padding: 0 + text: button.text + verticalAlignment: Text.AlignVCenter } - NhekoCursorShape { id: mouseArea - anchors.fill: parent cursorShape: Qt.PointingHandCursor } - Ripple { color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5) } - } diff --git a/qml/delegates/Encrypted.qml b/qml/delegates/Encrypted.qml index 37efe16c..06728c2b 100644 --- a/qml/delegates/Encrypted.qml +++ b/qml/delegates/Encrypted.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 @@ -15,32 +13,31 @@ Rectangle { required property int encryptionError required property string eventId - radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium - width: parent.width? parent.width : 0 - implicitWidth: encryptedText.implicitWidth+24+Nheko.paddingMedium*3 // Column doesn't provide a useful implicitWidth, should be replaced by ColumnLayout - height: contents.implicitHeight + Nheko.paddingMedium * 2 color: timelineRoot.palette.alternateBase + height: contents.implicitHeight + Nheko.paddingMedium * 2 + implicitWidth: encryptedText.implicitWidth + 24 + Nheko.paddingMedium * 3 // Column doesn't provide a useful implicitWidth, should be replaced by ColumnLayout + radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium + width: parent.width ? parent.width : 0 RowLayout { id: contents - anchors.fill: parent anchors.margins: Nheko.paddingMedium spacing: Nheko.paddingMedium Image { - source: "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.error Layout.alignment: Qt.AlignVCenter - width: 24 height: width + source: "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.error + width: 24 } - Column { - spacing: Nheko.paddingSmall Layout.fillWidth: true + spacing: Nheko.paddingSmall MatrixText { id: encryptedText + color: timelineRoot.palette.text text: { switch (encryptionError) { case Olm.MissingSession: @@ -59,19 +56,15 @@ Rectangle { return qsTr("Unknown decryption error"); } } - color: timelineRoot.palette.text width: parent.width } - Button { palette: timelineRoot.palette - visible: encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex text: qsTr("Request key") + visible: encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex + onClicked: room.requestKeyForEvent(eventId) } - } - } - } diff --git a/qml/delegates/FileMessage.qml b/qml/delegates/FileMessage.qml index d90858be..654afc42 100644 --- a/qml/delegates/FileMessage.qml +++ b/qml/delegates/FileMessage.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.12 import QtQuick.Layouts 1.2 import im.nheko @@ -11,86 +9,71 @@ Item { required property string eventId required property string filename required property string filesize - - height: row.height + (Settings.bubbles? 16: 24) - width: parent.width - implicitWidth: row.implicitWidth+metadataWidth - property int metadataWidth property bool fitsMetadata: true + property int metadataWidth + + height: row.height + (Settings.bubbles ? 16 : 24) + implicitWidth: row.implicitWidth + metadataWidth + width: parent.width RowLayout { id: row - anchors.centerIn: parent - width: parent.width - (Settings.bubbles? 16 : 24) spacing: 15 + width: parent.width - (Settings.bubbles ? 16 : 24) Rectangle { id: button - color: timelineRoot.palette.light - radius: 22 height: 44 + radius: 22 width: 44 Image { id: img - + anchors.centerIn: parent + fillMode: Image.Pad height: 40 - width: 40 + source: "qrc:/icons/icons/ui/download.svg" sourceSize.height: 40 sourceSize.width: 40 - - anchors.centerIn: parent - source: "qrc:/icons/icons/ui/download.svg" - fillMode: Image.Pad + width: 40 } - TapHandler { - onSingleTapped: room.saveMedia(eventId) gesturePolicy: TapHandler.ReleaseWithinBounds - } + onSingleTapped: room.saveMedia(eventId) + } NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } - } - ColumnLayout { id: col - Text { id: filename_ - Layout.fillWidth: true + color: timelineRoot.palette.text + elide: Text.ElideRight text: filename textFormat: Text.PlainText - elide: Text.ElideRight - color: timelineRoot.palette.text } - Text { id: filesize_ - Layout.fillWidth: true + color: timelineRoot.palette.text + elide: Text.ElideRight text: filesize textFormat: Text.PlainText - elide: Text.ElideRight - color: timelineRoot.palette.text } - } - } - Rectangle { - color: timelineRoot.palette.alternateBase - z: -1 - radius: 10 anchors.fill: parent + color: timelineRoot.palette.alternateBase + radius: 10 visible: !Settings.bubbles // the bubble in a bubble looks odd + z: -1 } - } diff --git a/qml/delegates/ImageMessage.qml b/qml/delegates/ImageMessage.qml index d474a419..b8a4abeb 100644 --- a/qml/delegates/ImageMessage.qml +++ b/qml/delegates/ImageMessage.qml @@ -1,102 +1,85 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.3 import im.nheko AbstractButton { - required property int type - required property int originalWidth - required property double proportionalHeight - required property string url required property string blurhash required property string body - required property string filename - required property bool isReply - required property string eventId property double divisor: isReply ? 5 : 3 - - property int tempWidth: originalWidth < 1? 400: originalWidth - - implicitWidth: Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1)) - width: Math.min(parent?.width ?? implicitWidth,implicitWidth) - height: width*proportionalHeight - hoverEnabled: true - + required property string eventId + required property string filename + property bool fitsMetadata: (parent.width - width) > metadataWidth + 4 + required property bool isReply property int metadataWidth - property bool fitsMetadata: (parent.width - width) > metadataWidth+4 + required property int originalWidth + required property double proportionalHeight + property int tempWidth: originalWidth < 1 ? 400 : originalWidth + required property int type + required property string url + + height: width * proportionalHeight + hoverEnabled: true + implicitWidth: Math.round(tempWidth * Math.min((timelineView.height / divisor) / (tempWidth * proportionalHeight), 1)) + width: Math.min(parent?.width ?? implicitWidth, implicitWidth) + + onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId) Image { id: blurhash_ - anchors.fill: parent - visible: img.status != Image.Ready - source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + timelineRoot.palette.placeholderText) asynchronous: true fillMode: Image.PreserveAspectFit - sourceSize.width: parent.width * Screen.devicePixelRatio + source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + timelineRoot.palette.placeholderText) sourceSize.height: parent.height * Screen.devicePixelRatio + sourceSize.width: parent.width * Screen.devicePixelRatio + visible: img.status != Image.Ready } - Image { id: img - - visible: !mxcimage.loaded anchors.fill: parent - source: url.replace("mxc://", "image://MxcImage/") + "?scale" asynchronous: true fillMode: Image.PreserveAspectFit - smooth: true mipmap: true - + smooth: true + source: url.replace("mxc://", "image://MxcImage/") + "?scale" + sourceSize.height: Math.min(Screen.desktopAvailableHeight, (originalWidth < 1 ? Screen.desktopAvailableHeight : originalWidth * proportionalHeight)) * Screen.devicePixelRatio sourceSize.width: Math.min(Screen.desktopAvailableWidth, originalWidth < 1 ? Screen.desktopAvailableWidth : originalWidth) * Screen.devicePixelRatio - sourceSize.height: Math.min(Screen.desktopAvailableHeight, (originalWidth < 1 ? Screen.desktopAvailableHeight : originalWidth*proportionalHeight)) * Screen.devicePixelRatio + visible: !mxcimage.loaded } - MxcAnimatedImage { id: mxcimage - - visible: loaded anchors.fill: parent - roomm: room - play: !Settings.animateImagesOnHover || parent.hovered eventId: parent.eventId + play: !Settings.animateImagesOnHover || parent.hovered + roomm: room + visible: loaded } - - onClicked :Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId); - Item { id: overlay - anchors.fill: parent visible: parent.hovered Rectangle { id: container - - width: parent.width - implicitHeight: imgcaption.implicitHeight anchors.bottom: overlay.bottom color: timelineRoot.palette.window + implicitHeight: imgcaption.implicitHeight opacity: 0.75 + width: parent.width } - Text { id: imgcaption - anchors.fill: container + color: timelineRoot.palette.text elide: Text.ElideMiddle horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter // See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530 text: filename ? filename : body - color: timelineRoot.palette.text + verticalAlignment: Text.AlignVCenter } - } - } diff --git a/qml/delegates/MessageDelegate.qml b/qml/delegates/MessageDelegate.qml index b159ab41..21253fa0 100644 --- a/qml/delegates/MessageDelegate.qml +++ b/qml/delegates/MessageDelegate.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.6 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.2 @@ -12,35 +10,35 @@ import im.nheko Item { id: d - required property bool isReply - property alias child: chooser.child - implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : 0 - required property double proportionalHeight - required property int type - required property string typeString - required property int originalWidth - required property int duration required property string blurhash required property string body - required property string formattedBody + required property string callType + property alias child: chooser.child + required property int duration + required property int encryptionError required property string eventId required property string filename required property string filesize - required property string url - required property string thumbnailUrl + property bool fitsMetadata: (chooser.child && chooser.child.fitsMetadata) ? chooser.child.fitsMetadata : false + required property string formattedBody required property bool isOnlyEmoji + required property bool isReply required property bool isStateEvent + property int metadataWidth + required property int originalWidth + required property double proportionalHeight + required property int relatedEventCacheBuster + required property string roomName + required property string roomTopic + required property string thumbnailUrl + required property int type + required property string typeString + required property string url required property string userId required property string userName - required property string roomTopic - required property string roomName - required property string callType - required property int encryptionError - required property int relatedEventCacheBuster - property bool fitsMetadata: (chooser.child && chooser.child.fitsMetadata) ? chooser.child.fitsMetadata : false - property int metadataWidth height: chooser.child ? chooser.child.height : Nheko.paddingLarge + implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : 0 DelegateChooser { id: chooser @@ -48,97 +46,84 @@ Item { //role: "type" //< not supported in our custom implementation, have to use roleValue roleValue: type //anchors.fill: parent - - width: parent.width? parent.width: 0 // this should get rid of "cannot read property 'width' of null" + width: parent.width ? parent.width : 0 // this should get rid of "cannot read property 'width' of null" DelegateChoice { roleValue: MtxEvent.UnknownMessage Placeholder { - typeString: d.typeString text: "Unretrieved event" + typeString: d.typeString } - } - DelegateChoice { roleValue: MtxEvent.TextMessage TextMessage { - formatted: d.formattedBody body: d.body + formatted: d.formattedBody isOnlyEmoji: d.isOnlyEmoji isReply: d.isReply metadataWidth: d.metadataWidth } - } - DelegateChoice { roleValue: MtxEvent.NoticeMessage NoticeMessage { - formatted: d.formattedBody body: d.body + formatted: d.formattedBody isOnlyEmoji: d.isOnlyEmoji isReply: d.isReply isStateEvent: d.isStateEvent metadataWidth: d.metadataWidth } - } - DelegateChoice { roleValue: MtxEvent.EmoteMessage NoticeMessage { - formatted: TimelineManager.escapeEmoji(d.userName) + " " + d.formattedBody - color: TimelineManager.userColor(d.userId, timelineRoot.palette.base) body: d.body + color: TimelineManager.userColor(d.userId, timelineRoot.palette.base) + formatted: TimelineManager.escapeEmoji(d.userName) + " " + d.formattedBody isOnlyEmoji: d.isOnlyEmoji isReply: d.isReply isStateEvent: d.isStateEvent metadataWidth: d.metadataWidth } - } - DelegateChoice { roleValue: MtxEvent.ImageMessage ImageMessage { - type: d.type - originalWidth: d.originalWidth - proportionalHeight: d.proportionalHeight - url: d.url blurhash: d.blurhash body: d.body + eventId: d.eventId filename: d.filename isReply: d.isReply - eventId: d.eventId metadataWidth: d.metadataWidth + originalWidth: d.originalWidth + proportionalHeight: d.proportionalHeight + type: d.type + url: d.url } - } - DelegateChoice { roleValue: MtxEvent.Sticker ImageMessage { - type: d.type - originalWidth: d.originalWidth - proportionalHeight: d.proportionalHeight - url: d.url blurhash: d.blurhash body: d.body + eventId: d.eventId filename: d.filename isReply: d.isReply - eventId: d.eventId metadataWidth: d.metadataWidth + originalWidth: d.originalWidth + proportionalHeight: d.proportionalHeight + type: d.type + url: d.url } - } - DelegateChoice { roleValue: MtxEvent.FileMessage @@ -148,45 +133,39 @@ Item { filesize: d.filesize metadataWidth: d.metadataWidth } - } - DelegateChoice { roleValue: MtxEvent.VideoMessage PlayableMediaMessage { - proportionalHeight: d.proportionalHeight - type: d.type - originalWidth: d.originalWidth - thumbnailUrl: d.thumbnailUrl - eventId: d.eventId - url: d.url body: d.body - filesize: d.filesize duration: d.duration + eventId: d.eventId + filesize: d.filesize metadataWidth: d.metadataWidth + originalWidth: d.originalWidth + proportionalHeight: d.proportionalHeight + thumbnailUrl: d.thumbnailUrl + type: d.type + url: d.url } - } - DelegateChoice { roleValue: MtxEvent.AudioMessage PlayableMediaMessage { - proportionalHeight: d.proportionalHeight - type: d.type - originalWidth: d.originalWidth - thumbnailUrl: d.thumbnailUrl - eventId: d.eventId - url: d.url body: d.body - filesize: d.filesize duration: d.duration + eventId: d.eventId + filesize: d.filesize metadataWidth: d.metadataWidth + originalWidth: d.originalWidth + proportionalHeight: d.proportionalHeight + thumbnailUrl: d.thumbnailUrl + type: d.type + url: d.url } - } - DelegateChoice { roleValue: MtxEvent.Redacted @@ -194,27 +173,22 @@ Item { metadataWidth: d.metadataWidth } } - DelegateChoice { roleValue: MtxEvent.Redaction Pill { - text: qsTr("%1 removed a message").arg(d.userName) isStateEvent: d.isStateEvent + text: qsTr("%1 removed a message").arg(d.userName) } - } - DelegateChoice { roleValue: MtxEvent.Encryption Pill { - text: qsTr("%1 enabled encryption").arg(d.userName) isStateEvent: d.isStateEvent + text: qsTr("%1 enabled encryption").arg(d.userName) } - } - DelegateChoice { roleValue: MtxEvent.Encrypted @@ -222,122 +196,100 @@ Item { encryptionError: d.encryptionError eventId: d.eventId } - } - DelegateChoice { roleValue: MtxEvent.Name NoticeMessage { body: formatted + formatted: d.roomName ? qsTr("%2 changed the room name to: %1").arg(d.roomName).arg(d.userName) : qsTr("%1 removed the room name").arg(d.userName) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: d.roomName ? qsTr("%2 changed the room name to: %1").arg(d.roomName).arg(d.userName) : qsTr("%1 removed the room name").arg(d.userName) } - } - DelegateChoice { roleValue: MtxEvent.Topic NoticeMessage { body: formatted + formatted: d.roomTopic ? qsTr("%2 changed the topic to: %1").arg(d.roomTopic).arg(d.userName) : qsTr("%1 removed the topic").arg(d.userName) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: d.roomTopic ? qsTr("%2 changed the topic to: %1").arg(d.roomTopic).arg(d.userName): qsTr("%1 removed the topic").arg(d.userName) } - } - DelegateChoice { roleValue: MtxEvent.Avatar NoticeMessage { body: formatted + formatted: qsTr("%1 changed the room avatar").arg(d.userName) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: qsTr("%1 changed the room avatar").arg(d.userName) } - } - DelegateChoice { roleValue: MtxEvent.PinnedEvents NoticeMessage { body: formatted + formatted: qsTr("%1 changed the pinned messages.").arg(d.userName) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: qsTr("%1 changed the pinned messages.").arg(d.userName) } - } - DelegateChoice { roleValue: MtxEvent.ImagePackInRoom NoticeMessage { body: formatted + formatted: d.relatedEventCacheBuster, room.formatImagePackEvent(d.eventId) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatImagePackEvent(d.eventId) } - } - - DelegateChoice { roleValue: MtxEvent.CanonicalAlias NoticeMessage { body: formatted + formatted: qsTr("%1 changed the addresses for this room.").arg(d.userName) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: qsTr("%1 changed the addresses for this room.").arg(d.userName) } - } - DelegateChoice { roleValue: MtxEvent.SpaceParent NoticeMessage { body: formatted + formatted: qsTr("%1 changed the parent spaces for this room.").arg(d.userName) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: qsTr("%1 changed the parent spaces for this room.").arg(d.userName) } - } - DelegateChoice { roleValue: MtxEvent.RoomCreate NoticeMessage { body: formatted + formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId) } - } - DelegateChoice { roleValue: MtxEvent.CallInvite NoticeMessage { body: formatted - isOnlyEmoji: false - isReply: d.isReply - isStateEvent: d.isStateEvent formatted: { switch (d.callType) { case "voice": @@ -348,101 +300,88 @@ Item { return qsTr("%1 placed a call.").arg(d.userName); } } + isOnlyEmoji: false + isReply: d.isReply + isStateEvent: d.isStateEvent } - } - DelegateChoice { roleValue: MtxEvent.CallAnswer NoticeMessage { body: formatted + formatted: qsTr("%1 answered the call.").arg(d.userName) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: qsTr("%1 answered the call.").arg(d.userName) } - } - DelegateChoice { roleValue: MtxEvent.CallHangUp NoticeMessage { body: formatted + formatted: qsTr("%1 ended the call.").arg(d.userName) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: qsTr("%1 ended the call.").arg(d.userName) } - } - DelegateChoice { roleValue: MtxEvent.CallCandidates NoticeMessage { body: formatted + formatted: qsTr("%1 is negotiating the call...").arg(d.userName) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: qsTr("%1 is negotiating the call...").arg(d.userName) } - } - DelegateChoice { roleValue: MtxEvent.PowerLevels NoticeMessage { body: formatted + formatted: d.relatedEventCacheBuster, room.formatPowerLevelEvent(d.eventId) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatPowerLevelEvent(d.eventId) } - } - DelegateChoice { roleValue: MtxEvent.RoomJoinRules NoticeMessage { body: formatted + formatted: d.relatedEventCacheBuster, room.formatJoinRuleEvent(d.eventId) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatJoinRuleEvent(d.eventId) } - } - DelegateChoice { roleValue: MtxEvent.RoomHistoryVisibility NoticeMessage { body: formatted + formatted: d.relatedEventCacheBuster, room.formatHistoryVisibilityEvent(d.eventId) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatHistoryVisibilityEvent(d.eventId) } - } - DelegateChoice { roleValue: MtxEvent.RoomGuestAccess NoticeMessage { body: formatted + formatted: d.relatedEventCacheBuster, room.formatGuestAccessEvent(d.eventId) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatGuestAccessEvent(d.eventId) } - } - DelegateChoice { roleValue: MtxEvent.Member @@ -450,149 +389,125 @@ Item { width: parent?.width NoticeMessage { + Layout.fillWidth: true body: formatted + formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId) isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - Layout.fillWidth: true - formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId) } - Button { - visible: d.relatedEventCacheBuster, room.showAcceptKnockButton(d.eventId) palette: timelineRoot.palette text: qsTr("Allow them in") + visible: d.relatedEventCacheBuster, room.showAcceptKnockButton(d.eventId) + onClicked: room.acceptKnock(eventId) } - } - } - DelegateChoice { roleValue: MtxEvent.KeyVerificationRequest NoticeMessage { body: formatted + formatted: "KeyVerificationRequest" isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: "KeyVerificationRequest" } - } - DelegateChoice { roleValue: MtxEvent.KeyVerificationStart NoticeMessage { body: formatted + formatted: "KeyVerificationStart" isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: "KeyVerificationStart" } - } - DelegateChoice { roleValue: MtxEvent.KeyVerificationReady NoticeMessage { body: formatted + formatted: "KeyVerificationReady" isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: "KeyVerificationReady" } - } - DelegateChoice { roleValue: MtxEvent.KeyVerificationCancel NoticeMessage { body: formatted + formatted: "KeyVerificationCancel" isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: "KeyVerificationCancel" } - } - DelegateChoice { roleValue: MtxEvent.KeyVerificationKey NoticeMessage { body: formatted + formatted: "KeyVerificationKey" isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: "KeyVerificationKey" } - } - DelegateChoice { roleValue: MtxEvent.KeyVerificationMac NoticeMessage { body: formatted - isOnlyEmoji: false - isReply: d.isReply - isStateEvent: d.isStateEvent formatted: "KeyVerificationMac" + isOnlyEmoji: false + isReply: d.isReply + isStateEvent: d.isStateEvent } - } - DelegateChoice { roleValue: MtxEvent.KeyVerificationDone NoticeMessage { body: formatted + formatted: "KeyVerificationDone" isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: "KeyVerificationDone" } - } - DelegateChoice { roleValue: MtxEvent.KeyVerificationDone NoticeMessage { body: formatted + formatted: "KeyVerificationDone" isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: "KeyVerificationDone" } - } - DelegateChoice { roleValue: MtxEvent.KeyVerificationAccept NoticeMessage { body: formatted + formatted: "KeyVerificationAccept" isOnlyEmoji: false isReply: d.isReply isStateEvent: d.isStateEvent - formatted: "KeyVerificationAccept" } - } - DelegateChoice { Placeholder { typeString: d.typeString } - } - } - } diff --git a/qml/delegates/NoticeMessage.qml b/qml/delegates/NoticeMessage.qml index 2533fa88..b588e286 100644 --- a/qml/delegates/NoticeMessage.qml +++ b/qml/delegates/NoticeMessage.qml @@ -1,16 +1,14 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.5 import im.nheko - TextMessage { property bool isStateEvent - font.italic: true + color: timelineRoot.palette.placeholderText - font.pointSize: isStateEvent? 0.8*Settings.fontSize : Settings.fontSize - horizontalAlignment: isStateEvent? Text.AlignHCenter : undefined + font.italic: true + font.pointSize: isStateEvent ? 0.8 * Settings.fontSize : Settings.fontSize + horizontalAlignment: isStateEvent ? Text.AlignHCenter : undefined } diff --git a/qml/delegates/Pill.qml b/qml/delegates/Pill.qml index c148a212..7059a342 100644 --- a/qml/delegates/Pill.qml +++ b/qml/delegates/Pill.qml @@ -1,22 +1,20 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.5 import QtQuick.Controls 2.1 import im.nheko Label { property bool isStateEvent + color: timelineRoot.palette.text - horizontalAlignment: Text.AlignHCenter height: Math.round(fontMetrics.height * 1.4) + horizontalAlignment: Text.AlignHCenter width: contentWidth * 1.2 background: Rectangle { - radius: parent.height / 2 color: timelineRoot.palette.alternateBase + radius: parent.height / 2 } - } diff --git a/qml/delegates/Placeholder.qml b/qml/delegates/Placeholder.qml index 7600ee26..da4ebceb 100644 --- a/qml/delegates/Placeholder.qml +++ b/qml/delegates/Placeholder.qml @@ -1,15 +1,13 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import im.nheko MatrixText { required property string typeString - text: qsTr("unimplemented event: ") + typeString -// width: parent.width + // width: parent.width color: timelineRoot.palette.inactive.text + text: qsTr("unimplemented event: ") + typeString } diff --git a/qml/delegates/PlayableMediaMessage.qml b/qml/delegates/PlayableMediaMessage.qml index fec49e9a..0d3807ee 100644 --- a/qml/delegates/PlayableMediaMessage.qml +++ b/qml/delegates/PlayableMediaMessage.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import "../" import "../ui/media" import QtMultimedia @@ -14,99 +12,88 @@ import im.nheko Item { id: content - required property double proportionalHeight - required property int type - required property int originalWidth - required property int duration - required property string thumbnailUrl - required property string eventId - required property string url required property string body - required property string filesize property double divisor: isReply ? 4 : 2 - property int tempWidth: originalWidth < 1? 400: originalWidth - implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1)) : 500 - width: Math.min(parent.width, implicitWidth) - height: (type == MtxEvent.VideoMessage ? width*proportionalHeight : 80) + fileInfoLabel.height - implicitHeight: height - + required property int duration + required property string eventId + required property string filesize + property bool fitsMetadata: (parent.width - fileInfoLabel.width) > metadataWidth + 4 property int metadataWidth - property bool fitsMetadata: (parent.width - fileInfoLabel.width) > metadataWidth+4 + required property int originalWidth + required property double proportionalHeight + property int tempWidth: originalWidth < 1 ? 400 : originalWidth + required property string thumbnailUrl + required property int type + required property string url + + height: (type == MtxEvent.VideoMessage ? width * proportionalHeight : 80) + fileInfoLabel.height + implicitHeight: height + implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth * Math.min((timelineView.height / divisor) / (tempWidth * proportionalHeight), 1)) : 500 + width: Math.min(parent.width, implicitWidth) MxcMedia { id: mxcmedia - roomm: room + videoOutput: videoOutput audioOutput: AudioOutput { muted: mediaControls.muted volume: mediaControls.desiredVolume } - videoOutput: videoOutput } - Rectangle { id: videoContainer - color: type == MtxEvent.VideoMessage ? timelineRoot.palette.window : "transparent" - width: parent.width height: parent.height - fileInfoLabel.height + width: parent.width TapHandler { onTapped: Settings.openVideoExternal ? room.openMedia(eventId) : mediaControls.showControls() } - Image { anchors.fill: parent - source: thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : "" asynchronous: true fillMode: Image.PreserveAspectFit + source: thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : "" VideoOutput { id: videoOutput - - visible: type == MtxEvent.VideoMessage - clip: true anchors.fill: parent + clip: true fillMode: VideoOutput.PreserveAspectFit //flushMode: VideoOutput.FirstFrame orientation: mxcmedia.orientation + visible: type == MtxEvent.VideoMessage } - } - } - MediaControls { id: mediaControls - + anchors.bottom: fileInfoLabel.top anchors.left: content.left anchors.right: content.right - anchors.bottom: fileInfoLabel.top - playingVideo: type == MtxEvent.VideoMessage - positionValue: mxcmedia.position duration: mediaLoaded ? mxcmedia.duration : content.duration mediaLoaded: mxcmedia.loaded mediaState: mxcmedia.state - onPositionChanged: mxcmedia.position = position - onPlayPauseActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play() + playingVideo: type == MtxEvent.VideoMessage + positionValue: mxcmedia.position + onLoadActivated: mxcmedia.eventId = eventId + onPlayPauseActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play() + onPositionChanged: mxcmedia.position = position } // information about file name and file size Label { id: fileInfoLabel - anchors.bottom: content.bottom + color: timelineRoot.palette.text + elide: Text.ElideRight text: body + " [" + filesize + "]" textFormat: Text.RichText - elide: Text.ElideRight - color: timelineRoot.palette.text background: Rectangle { color: timelineRoot.palette.base } - } - } diff --git a/qml/delegates/Redacted.qml b/qml/delegates/Redacted.qml index ff96938c..df86f229 100644 --- a/qml/delegates/Redacted.qml +++ b/qml/delegates/Redacted.qml @@ -1,49 +1,49 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import im.nheko -Rectangle{ +Rectangle { + property bool fitsMetadata: parent.width - redactedLayout.width > metadataWidth + 4 + property int metadataWidth + color: timelineRoot.palette.alternateBase height: redactedLayout.implicitHeight + Nheko.paddingSmall implicitWidth: redactedLayout.implicitWidth + 2 * Nheko.paddingMedium - width: Math.min(parent.width,implicitWidth+1) radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall - color: timelineRoot.palette.alternateBase - property int metadataWidth - property bool fitsMetadata: parent.width - redactedLayout.width > metadataWidth + 4 + width: Math.min(parent.width, implicitWidth + 1) RowLayout { id: redactedLayout anchors.centerIn: parent - width: parent.width - 2 * Nheko.paddingMedium spacing: Nheko.paddingSmall + width: parent.width - 2 * Nheko.paddingMedium Image { id: trashImg Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.preferredWidth: fontMetrics.font.pixelSize Layout.preferredHeight: fontMetrics.font.pixelSize + Layout.preferredWidth: fontMetrics.font.pixelSize source: "image://colorimage/:/icons/icons/ui/delete.svg?" + timelineRoot.palette.text } Label { id: redactedLabel - Layout.margins: 0 - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.preferredWidth: implicitWidth - Layout.fillWidth: true - property var redactedPair: room.formatRedactedEvent(eventId) - text: redactedPair["first"] - wrapMode: Label.WordWrap - color: timelineRoot.palette.text + property var redactedPair: room.formatRedactedEvent(eventId) + + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.fillWidth: true + Layout.margins: 0 + Layout.preferredWidth: implicitWidth ToolTip.text: redactedPair["second"] ToolTip.visible: hh.hovered + color: timelineRoot.palette.text + text: redactedPair["first"] + wrapMode: Label.WordWrap + HoverHandler { id: hh } diff --git a/qml/delegates/Reply.qml b/qml/delegates/Reply.qml index d9a17f18..93f6bde7 100644 --- a/qml/delegates/Reply.qml +++ b/qml/delegates/Reply.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import Qt.labs.platform 1.1 as Platform import QtQuick 2.12 import QtQuick.Controls 2.3 @@ -14,129 +12,124 @@ import "../" AbstractButton { id: r - property color userColor: "red" - property double proportionalHeight - property int type - property string typeString - property int originalWidth property string blurhash property string body - property string formattedBody - property string eventId - property string filename - property string filesize - property string url - property bool isOnlyEmoji - property bool isStateEvent - property string userId - property string userName - property string thumbnailUrl - property string roomTopic - property string roomName property string callType property int duration property int encryptionError - property int relatedEventCacheBuster + property string eventId + property string filename + property string filesize + property string formattedBody + property bool isOnlyEmoji + property bool isStateEvent property int maxWidth + property int originalWidth + property double proportionalHeight + property int relatedEventCacheBuster + property string roomName + property string roomTopic + property string thumbnailUrl + property int type + property string typeString + property string url + property color userColor: "red" + property string userId + property string userName height: replyContainer.height implicitHeight: replyContainer.height - implicitWidth: visible? colorLine.width+Math.max(replyContainer.implicitWidth,userName_.fullTextWidth) : 0 // visible? seems to be causing issues + implicitWidth: visible ? colorLine.width + Math.max(replyContainer.implicitWidth, userName_.fullTextWidth) : 0 // visible? seems to be causing issues + + onClicked: { + let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX - colorLine.width, pressY - userName_.implicitHeight); + if (link) { + Nheko.openLink(link); + } else { + room.showEvent(r.eventId); + } + } + onPressAndHold: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(pressX - colorLine.width, pressY - userName_.implicitHeight), r.eventId) NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } - Rectangle { id: colorLine - - anchors.top: replyContainer.top anchors.bottom: replyContainer.bottom - width: 4 + anchors.top: replyContainer.top color: TimelineManager.userColor(userId, timelineRoot.palette.base) + width: 4 } - - onClicked: { - let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX-colorLine.width, pressY - userName_.implicitHeight); - if (link) { - Nheko.openLink(link) - } else { - room.showEvent(r.eventId) - } - } - onPressAndHold: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(pressX-colorLine.width, pressY - userName_.implicitHeight), r.eventId) - ColumnLayout { id: replyContainer - anchors.left: colorLine.right - width: parent.width - 4 spacing: 0 + width: parent.width - 4 TapHandler { acceptedButtons: Qt.RightButton - onSingleTapped: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(eventPoint.position.x, eventPoint.position.y - userName_.implicitHeight), r.eventId) - gesturePolicy: TapHandler.ReleaseWithinBounds acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad - } + gesturePolicy: TapHandler.ReleaseWithinBounds + onSingleTapped: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(eventPoint.position.x, eventPoint.position.y - userName_.implicitHeight), r.eventId) + } AbstractButton { - Layout.leftMargin: 4 Layout.fillWidth: true + Layout.leftMargin: 4 + contentItem: ElidedLabel { id: userName_ - fullText: userName color: r.userColor + elideWidth: width + fullText: userName textFormat: Text.RichText width: parent.width - elideWidth: width } + onClicked: room.openUserProfile(userId) } - MessageDelegate { + id: reply + Layout.fillWidth: true Layout.leftMargin: 4 Layout.preferredHeight: height - id: reply blurhash: r.blurhash body: r.body - formattedBody: r.formattedBody + callType: r.callType + duration: r.duration + // This is disabled so that left clicking the reply goes to its location + enabled: false + encryptionError: r.encryptionError eventId: r.eventId filename: r.filename filesize: r.filesize + formattedBody: r.formattedBody + isOnlyEmoji: r.isOnlyEmoji + isReply: true + isStateEvent: r.isStateEvent + originalWidth: r.originalWidth proportionalHeight: r.proportionalHeight + relatedEventCacheBuster: r.relatedEventCacheBuster + roomName: r.roomName + roomTopic: r.roomTopic + thumbnailUrl: r.thumbnailUrl type: r.type typeString: r.typeString ?? "" url: r.url - thumbnailUrl: r.thumbnailUrl - duration: r.duration - originalWidth: r.originalWidth - isOnlyEmoji: r.isOnlyEmoji - isStateEvent: r.isStateEvent userId: r.userId userName: r.userName - roomTopic: r.roomTopic - roomName: r.roomName - callType: r.callType - relatedEventCacheBuster: r.relatedEventCacheBuster - encryptionError: r.encryptionError - // This is disabled so that left clicking the reply goes to its location - enabled: false - Layout.fillWidth: true - isReply: true } - } - Rectangle { id: backgroundItem - z: -1 - anchors.fill: replyContainer - property color userColor: TimelineManager.userColor(userId, timelineRoot.palette.base) property color bgColor: timelineRoot.palette.base - color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1)) - } + property color userColor: TimelineManager.userColor(userId, timelineRoot.palette.base) + anchors.fill: replyContainer + color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1)) + z: -1 + } } diff --git a/qml/delegates/TextMessage.qml b/qml/delegates/TextMessage.qml index db643340..7b2cb5b1 100644 --- a/qml/delegates/TextMessage.qml +++ b/qml/delegates/TextMessage.qml @@ -1,21 +1,25 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import QtQuick import QtQuick.Controls import im.nheko MatrixText { required property string body + property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body + property bool fitsMetadata: positionAt(width, height - 4) == positionAt(width - metadataWidth - 10, height - 4) + required property string formatted required property bool isOnlyEmoji required property bool isReply - required property string formatted - property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body property int metadataWidth - property bool fitsMetadata: positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4) + + clip: isReply + enabled: !Settings.mobileMode + font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize + height: isReply ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight + selectByMouse: !Settings.mobileMode && !isReply // table border-collapse doesn't seem to work text: " @@ -37,16 +41,10 @@ MatrixText { " + formatted.replace(/
/g, "").replace(//g, "").replace(/<\/del>/g, "").replace(//g, "").replace(/<\/strike>/g, "") width: parent?.width - height: isReply ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight - clip: isReply - selectByMouse: !Settings.mobileMode && !isReply - enabled: !Settings.mobileMode - font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize NhekoCursorShape { - enabled: isReply anchors.fill: parent cursorShape: Qt.PointingHandCursor + enabled: isReply } - } diff --git a/qml/device-verification/DeviceVerification.qml b/qml/device-verification/DeviceVerification.qml index 9d1ccf4e..eb842d2e 100644 --- a/qml/device-verification/DeviceVerification.qml +++ b/qml/device-verification/DeviceVerification.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.10 import QtQuick.Controls 2.3 import QtQuick.Window 2.13 @@ -13,74 +11,56 @@ ApplicationWindow { property var flow - onClosing: VerificationManager.removeVerificationFlow(flow) - title: stack.currentItem.title_ + color: timelineRoot.palette.window + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + minimumHeight: stack.implicitHeight modality: Qt.NonModal palette: timelineRoot.palette - color: timelineRoot.palette.window - minimumHeight: stack.implicitHeight + title: stack.currentItem.title_ width: stack.implicitWidth - flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + + onClosing: VerificationManager.removeVerificationFlow(flow) StackView { id: stack - anchors.fill: parent - initialItem: newVerificationRequest - implicitWidth: currentItem.implicitWidth implicitHeight: currentItem.implicitHeight + implicitWidth: currentItem.implicitWidth + initialItem: newVerificationRequest } - Component { id: newVerificationRequest - NewVerificationRequest { } - } - Component { id: waiting - Waiting { } - } - Component { id: success - Success { } - } - Component { id: failed - Failed { } - } - Component { id: digitVerification - DigitVerification { } - } - Component { id: emojiVerification - EmojiVerification { } - } - Item { state: flow.state + states: [ State { name: "PromptStartVerification" @@ -88,7 +68,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(newVerificationRequest) } - }, State { name: "CompareEmoji" @@ -96,7 +75,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(emojiVerification) } - }, State { name: "CompareNumber" @@ -104,7 +82,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(digitVerification) } - }, State { name: "WaitingForKeys" @@ -112,7 +89,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(waiting) } - }, State { name: "WaitingForOtherToAccept" @@ -120,7 +96,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(waiting) } - }, State { name: "WaitingForMac" @@ -128,7 +103,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(waiting) } - }, State { name: "Success" @@ -136,7 +110,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(success) } - }, State { name: "Failed" @@ -144,9 +117,7 @@ ApplicationWindow { StateChangeScript { script: stack.replace(failed) } - } ] } - } diff --git a/qml/device-verification/DigitVerification.qml b/qml/device-verification/DigitVerification.qml index e7c82882..db716bf2 100644 --- a/qml/device-verification/DigitVerification.qml +++ b/qml/device-verification/DigitVerification.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.3 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.10 @@ -10,6 +8,7 @@ import im.nheko Pane { property string title: qsTr("Verification Code") + background: Rectangle { color: timelineRoot.palette.window } @@ -19,61 +18,57 @@ Pane { spacing: 16 Label { - Layout.preferredWidth: 400 Layout.fillWidth: true - wrapMode: Text.Wrap - text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!") + Layout.preferredWidth: 400 color: timelineRoot.palette.text + text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!") verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + } + Item { + Layout.fillHeight: true } - - Item { Layout.fillHeight: true; } RowLayout { Layout.alignment: Qt.AlignHCenter Label { + color: timelineRoot.palette.text font.pixelSize: Qt.application.font.pixelSize * 2 text: flow.sasList[0] - color: timelineRoot.palette.text } - Label { + color: timelineRoot.palette.text font.pixelSize: Qt.application.font.pixelSize * 2 text: flow.sasList[1] - color: timelineRoot.palette.text } - Label { + color: timelineRoot.palette.text font.pixelSize: Qt.application.font.pixelSize * 2 text: flow.sasList[2] - color: timelineRoot.palette.text } - } - Item { Layout.fillHeight: true; } - + Item { + Layout.fillHeight: true + } RowLayout { Button { Layout.alignment: Qt.AlignLeft text: qsTr("They do not match!") + onClicked: { flow.cancel(); dialog.close(); } } - Item { Layout.fillWidth: true } - Button { Layout.alignment: Qt.AlignRight text: qsTr("They match!") + onClicked: flow.next() } - } - } - } diff --git a/qml/device-verification/EmojiElement.qml b/qml/device-verification/EmojiElement.qml index d02ede48..622fb128 100644 --- a/qml/device-verification/EmojiElement.qml +++ b/qml/device-verification/EmojiElement.qml @@ -1,17 +1,15 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.3 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.10 Rectangle { color: "red" + height: Qt.application.font.pixelSize * 4 implicitHeight: Qt.application.font.pixelSize * 4 implicitWidth: col.width - height: Qt.application.font.pixelSize * 4 width: col.width ColumnLayout { @@ -22,17 +20,14 @@ Rectangle { anchors.bottom: parent.bottom Label { - height: font.pixelSize * 2 Layout.alignment: Qt.AlignHCenter - text: col.emoji.emoji font.pixelSize: Qt.application.font.pixelSize * 2 + height: font.pixelSize * 2 + text: col.emoji.emoji } - Label { Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom text: col.emoji.description } - } - } diff --git a/qml/device-verification/EmojiVerification.qml b/qml/device-verification/EmojiVerification.qml index 3131b9d2..9daea479 100644 --- a/qml/device-verification/EmojiVerification.qml +++ b/qml/device-verification/EmojiVerification.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.3 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.10 @@ -10,6 +8,7 @@ import im.nheko Pane { property string title: qsTr("Verification Code") + background: Rectangle { color: timelineRoot.palette.window } @@ -19,345 +18,345 @@ Pane { spacing: 16 Label { - Layout.preferredWidth: 400 Layout.fillWidth: true - wrapMode: Text.Wrap - text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!") + Layout.preferredWidth: 400 color: timelineRoot.palette.text + text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!") verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + } + Item { + Layout.fillHeight: true } - - Item { Layout.fillHeight: true; } RowLayout { id: emojis property var mapping: [{ - "number": 0, - "emoji": "🐶", - "description": "Dog", - "unicode": "U+1F436" - }, { - "number": 1, - "emoji": "🐱", - "description": "Cat", - "unicode": "U+1F431" - }, { - "number": 2, - "emoji": "🦁", - "description": "Lion", - "unicode": "U+1F981" - }, { - "number": 3, - "emoji": "🐎", - "description": "Horse", - "unicode": "U+1F40E" - }, { - "number": 4, - "emoji": "🦄", - "description": "Unicorn", - "unicode": "U+1F984" - }, { - "number": 5, - "emoji": "🐷", - "description": "Pig", - "unicode": "U+1F437" - }, { - "number": 6, - "emoji": "🐘", - "description": "Elephant", - "unicode": "U+1F418" - }, { - "number": 7, - "emoji": "🐰", - "description": "Rabbit", - "unicode": "U+1F430" - }, { - "number": 8, - "emoji": "🐼", - "description": "Panda", - "unicode": "U+1F43C" - }, { - "number": 9, - "emoji": "🐓", - "description": "Rooster", - "unicode": "U+1F413" - }, { - "number": 10, - "emoji": "🐧", - "description": "Penguin", - "unicode": "U+1F427" - }, { - "number": 11, - "emoji": "🐢", - "description": "Turtle", - "unicode": "U+1F422" - }, { - "number": 12, - "emoji": "🐟", - "description": "Fish", - "unicode": "U+1F41F" - }, { - "number": 13, - "emoji": "🐙", - "description": "Octopus", - "unicode": "U+1F419" - }, { - "number": 14, - "emoji": "🦋", - "description": "Butterfly", - "unicode": "U+1F98B" - }, { - "number": 15, - "emoji": "🌷", - "description": "Flower", - "unicode": "U+1F337" - }, { - "number": 16, - "emoji": "🌳", - "description": "Tree", - "unicode": "U+1F333" - }, { - "number": 17, - "emoji": "🌵", - "description": "Cactus", - "unicode": "U+1F335" - }, { - "number": 18, - "emoji": "🍄", - "description": "Mushroom", - "unicode": "U+1F344" - }, { - "number": 19, - "emoji": "🌏", - "description": "Globe", - "unicode": "U+1F30F" - }, { - "number": 20, - "emoji": "🌙", - "description": "Moon", - "unicode": "U+1F319" - }, { - "number": 21, - "emoji": "☁️", - "description": "Cloud", - "unicode": "U+2601U+FE0F" - }, { - "number": 22, - "emoji": "🔥", - "description": "Fire", - "unicode": "U+1F525" - }, { - "number": 23, - "emoji": "🍌", - "description": "Banana", - "unicode": "U+1F34C" - }, { - "number": 24, - "emoji": "🍎", - "description": "Apple", - "unicode": "U+1F34E" - }, { - "number": 25, - "emoji": "🍓", - "description": "Strawberry", - "unicode": "U+1F353" - }, { - "number": 26, - "emoji": "🌽", - "description": "Corn", - "unicode": "U+1F33D" - }, { - "number": 27, - "emoji": "🍕", - "description": "Pizza", - "unicode": "U+1F355" - }, { - "number": 28, - "emoji": "🎂", - "description": "Cake", - "unicode": "U+1F382" - }, { - "number": 29, - "emoji": "❤️", - "description": "Heart", - "unicode": "U+2764U+FE0F" - }, { - "number": 30, - "emoji": "😀", - "description": "Smiley", - "unicode": "U+1F600" - }, { - "number": 31, - "emoji": "🤖", - "description": "Robot", - "unicode": "U+1F916" - }, { - "number": 32, - "emoji": "🎩", - "description": "Hat", - "unicode": "U+1F3A9" - }, { - "number": 33, - "emoji": "👓", - "description": "Glasses", - "unicode": "U+1F453" - }, { - "number": 34, - "emoji": "🔧", - "description": "Spanner", - "unicode": "U+1F527" - }, { - "number": 35, - "emoji": "🎅", - "description": "Santa", - "unicode": "U+1F385" - }, { - "number": 36, - "emoji": "👍", - "description": "Thumbs Up", - "unicode": "U+1F44D" - }, { - "number": 37, - "emoji": "☂️", - "description": "Umbrella", - "unicode": "U+2602U+FE0F" - }, { - "number": 38, - "emoji": "⌛", - "description": "Hourglass", - "unicode": "U+231B" - }, { - "number": 39, - "emoji": "⏰", - "description": "Clock", - "unicode": "U+23F0" - }, { - "number": 40, - "emoji": "🎁", - "description": "Gift", - "unicode": "U+1F381" - }, { - "number": 41, - "emoji": "💡", - "description": "Light Bulb", - "unicode": "U+1F4A1" - }, { - "number": 42, - "emoji": "📕", - "description": "Book", - "unicode": "U+1F4D5" - }, { - "number": 43, - "emoji": "✏️", - "description": "Pencil", - "unicode": "U+270FU+FE0F" - }, { - "number": 44, - "emoji": "📎", - "description": "Paperclip", - "unicode": "U+1F4CE" - }, { - "number": 45, - "emoji": "✂️", - "description": "Scissors", - "unicode": "U+2702U+FE0F" - }, { - "number": 46, - "emoji": "🔒", - "description": "Lock", - "unicode": "U+1F512" - }, { - "number": 47, - "emoji": "🔑", - "description": "Key", - "unicode": "U+1F511" - }, { - "number": 48, - "emoji": "🔨", - "description": "Hammer", - "unicode": "U+1F528" - }, { - "number": 49, - "emoji": "☎️", - "description": "Telephone", - "unicode": "U+260EU+FE0F" - }, { - "number": 50, - "emoji": "🏁", - "description": "Flag", - "unicode": "U+1F3C1" - }, { - "number": 51, - "emoji": "🚂", - "description": "Train", - "unicode": "U+1F682" - }, { - "number": 52, - "emoji": "🚲", - "description": "Bicycle", - "unicode": "U+1F6B2" - }, { - "number": 53, - "emoji": "✈️", - "description": "Aeroplane", - "unicode": "U+2708U+FE0F" - }, { - "number": 54, - "emoji": "🚀", - "description": "Rocket", - "unicode": "U+1F680" - }, { - "number": 55, - "emoji": "🏆", - "description": "Trophy", - "unicode": "U+1F3C6" - }, { - "number": 56, - "emoji": "⚽", - "description": "Ball", - "unicode": "U+26BD" - }, { - "number": 57, - "emoji": "🎸", - "description": "Guitar", - "unicode": "U+1F3B8" - }, { - "number": 58, - "emoji": "🎺", - "description": "Trumpet", - "unicode": "U+1F3BA" - }, { - "number": 59, - "emoji": "🔔", - "description": "Bell", - "unicode": "U+1F514" - }, { - "number": 60, - "emoji": "⚓", - "description": "Anchor", - "unicode": "U+2693" - }, { - "number": 61, - "emoji": "🎧", - "description": "Headphones", - "unicode": "U+1F3A7" - }, { - "number": 62, - "emoji": "📁", - "description": "Folder", - "unicode": "U+1F4C1" - }, { - "number": 63, - "emoji": "📌", - "description": "Pin", - "unicode": "U+1F4CC" - }] + "number": 0, + "emoji": "🐶", + "description": "Dog", + "unicode": "U+1F436" + }, { + "number": 1, + "emoji": "🐱", + "description": "Cat", + "unicode": "U+1F431" + }, { + "number": 2, + "emoji": "🦁", + "description": "Lion", + "unicode": "U+1F981" + }, { + "number": 3, + "emoji": "🐎", + "description": "Horse", + "unicode": "U+1F40E" + }, { + "number": 4, + "emoji": "🦄", + "description": "Unicorn", + "unicode": "U+1F984" + }, { + "number": 5, + "emoji": "🐷", + "description": "Pig", + "unicode": "U+1F437" + }, { + "number": 6, + "emoji": "🐘", + "description": "Elephant", + "unicode": "U+1F418" + }, { + "number": 7, + "emoji": "🐰", + "description": "Rabbit", + "unicode": "U+1F430" + }, { + "number": 8, + "emoji": "🐼", + "description": "Panda", + "unicode": "U+1F43C" + }, { + "number": 9, + "emoji": "🐓", + "description": "Rooster", + "unicode": "U+1F413" + }, { + "number": 10, + "emoji": "🐧", + "description": "Penguin", + "unicode": "U+1F427" + }, { + "number": 11, + "emoji": "🐢", + "description": "Turtle", + "unicode": "U+1F422" + }, { + "number": 12, + "emoji": "🐟", + "description": "Fish", + "unicode": "U+1F41F" + }, { + "number": 13, + "emoji": "🐙", + "description": "Octopus", + "unicode": "U+1F419" + }, { + "number": 14, + "emoji": "🦋", + "description": "Butterfly", + "unicode": "U+1F98B" + }, { + "number": 15, + "emoji": "🌷", + "description": "Flower", + "unicode": "U+1F337" + }, { + "number": 16, + "emoji": "🌳", + "description": "Tree", + "unicode": "U+1F333" + }, { + "number": 17, + "emoji": "🌵", + "description": "Cactus", + "unicode": "U+1F335" + }, { + "number": 18, + "emoji": "🍄", + "description": "Mushroom", + "unicode": "U+1F344" + }, { + "number": 19, + "emoji": "🌏", + "description": "Globe", + "unicode": "U+1F30F" + }, { + "number": 20, + "emoji": "🌙", + "description": "Moon", + "unicode": "U+1F319" + }, { + "number": 21, + "emoji": "☁️", + "description": "Cloud", + "unicode": "U+2601U+FE0F" + }, { + "number": 22, + "emoji": "🔥", + "description": "Fire", + "unicode": "U+1F525" + }, { + "number": 23, + "emoji": "🍌", + "description": "Banana", + "unicode": "U+1F34C" + }, { + "number": 24, + "emoji": "🍎", + "description": "Apple", + "unicode": "U+1F34E" + }, { + "number": 25, + "emoji": "🍓", + "description": "Strawberry", + "unicode": "U+1F353" + }, { + "number": 26, + "emoji": "🌽", + "description": "Corn", + "unicode": "U+1F33D" + }, { + "number": 27, + "emoji": "🍕", + "description": "Pizza", + "unicode": "U+1F355" + }, { + "number": 28, + "emoji": "🎂", + "description": "Cake", + "unicode": "U+1F382" + }, { + "number": 29, + "emoji": "❤️", + "description": "Heart", + "unicode": "U+2764U+FE0F" + }, { + "number": 30, + "emoji": "😀", + "description": "Smiley", + "unicode": "U+1F600" + }, { + "number": 31, + "emoji": "🤖", + "description": "Robot", + "unicode": "U+1F916" + }, { + "number": 32, + "emoji": "🎩", + "description": "Hat", + "unicode": "U+1F3A9" + }, { + "number": 33, + "emoji": "👓", + "description": "Glasses", + "unicode": "U+1F453" + }, { + "number": 34, + "emoji": "🔧", + "description": "Spanner", + "unicode": "U+1F527" + }, { + "number": 35, + "emoji": "🎅", + "description": "Santa", + "unicode": "U+1F385" + }, { + "number": 36, + "emoji": "👍", + "description": "Thumbs Up", + "unicode": "U+1F44D" + }, { + "number": 37, + "emoji": "☂️", + "description": "Umbrella", + "unicode": "U+2602U+FE0F" + }, { + "number": 38, + "emoji": "⌛", + "description": "Hourglass", + "unicode": "U+231B" + }, { + "number": 39, + "emoji": "⏰", + "description": "Clock", + "unicode": "U+23F0" + }, { + "number": 40, + "emoji": "🎁", + "description": "Gift", + "unicode": "U+1F381" + }, { + "number": 41, + "emoji": "💡", + "description": "Light Bulb", + "unicode": "U+1F4A1" + }, { + "number": 42, + "emoji": "📕", + "description": "Book", + "unicode": "U+1F4D5" + }, { + "number": 43, + "emoji": "✏️", + "description": "Pencil", + "unicode": "U+270FU+FE0F" + }, { + "number": 44, + "emoji": "📎", + "description": "Paperclip", + "unicode": "U+1F4CE" + }, { + "number": 45, + "emoji": "✂️", + "description": "Scissors", + "unicode": "U+2702U+FE0F" + }, { + "number": 46, + "emoji": "🔒", + "description": "Lock", + "unicode": "U+1F512" + }, { + "number": 47, + "emoji": "🔑", + "description": "Key", + "unicode": "U+1F511" + }, { + "number": 48, + "emoji": "🔨", + "description": "Hammer", + "unicode": "U+1F528" + }, { + "number": 49, + "emoji": "☎️", + "description": "Telephone", + "unicode": "U+260EU+FE0F" + }, { + "number": 50, + "emoji": "🏁", + "description": "Flag", + "unicode": "U+1F3C1" + }, { + "number": 51, + "emoji": "🚂", + "description": "Train", + "unicode": "U+1F682" + }, { + "number": 52, + "emoji": "🚲", + "description": "Bicycle", + "unicode": "U+1F6B2" + }, { + "number": 53, + "emoji": "✈️", + "description": "Aeroplane", + "unicode": "U+2708U+FE0F" + }, { + "number": 54, + "emoji": "🚀", + "description": "Rocket", + "unicode": "U+1F680" + }, { + "number": 55, + "emoji": "🏆", + "description": "Trophy", + "unicode": "U+1F3C6" + }, { + "number": 56, + "emoji": "⚽", + "description": "Ball", + "unicode": "U+26BD" + }, { + "number": 57, + "emoji": "🎸", + "description": "Guitar", + "unicode": "U+1F3B8" + }, { + "number": 58, + "emoji": "🎺", + "description": "Trumpet", + "unicode": "U+1F3BA" + }, { + "number": 59, + "emoji": "🔔", + "description": "Bell", + "unicode": "U+1F514" + }, { + "number": 60, + "emoji": "⚓", + "description": "Anchor", + "unicode": "U+2693" + }, { + "number": 61, + "emoji": "🎧", + "description": "Headphones", + "unicode": "U+1F3A7" + }, { + "number": 62, + "emoji": "📁", + "description": "Folder", + "unicode": "U+1F4C1" + }, { + "number": 63, + "emoji": "📌", + "description": "Pin", + "unicode": "U+1F4CC" + }] Layout.alignment: Qt.AlignHCenter Repeater { id: repeater - model: 7 delegate: Rectangle { @@ -376,49 +375,42 @@ Pane { Label { //height: font.pixelSize * 2 Layout.alignment: Qt.AlignHCenter - text: col.emoji.emoji - font.pixelSize: Qt.application.font.pixelSize * 2 - font.family: Settings.emojiFont color: timelineRoot.palette.text + font.family: Settings.emojiFont + font.pixelSize: Qt.application.font.pixelSize * 2 + text: col.emoji.emoji } - Label { Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom - text: col.emoji.description color: timelineRoot.palette.text + text: col.emoji.description } - } - } - } - } - Item { Layout.fillHeight: true; } - + Item { + Layout.fillHeight: true + } RowLayout { Button { Layout.alignment: Qt.AlignLeft text: qsTr("They do not match!") + onClicked: { flow.cancel(); dialog.close(); } } - Item { Layout.fillWidth: true } - Button { Layout.alignment: Qt.AlignRight text: qsTr("They match!") + onClicked: flow.next() } - } - } - } diff --git a/qml/device-verification/Failed.qml b/qml/device-verification/Failed.qml index c0313cba..86650ea7 100644 --- a/qml/device-verification/Failed.qml +++ b/qml/device-verification/Failed.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.3 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.10 @@ -10,6 +8,7 @@ import im.nheko Pane { property string title: qsTr("Verification failed") + background: Rectangle { color: timelineRoot.palette.window } @@ -20,10 +19,9 @@ Pane { Text { id: content - - Layout.preferredWidth: 400 Layout.fillWidth: true - wrapMode: Text.Wrap + Layout.preferredWidth: 400 + color: timelineRoot.palette.text text: { switch (flow.error) { case DeviceVerificationFlow.UnknownMethod: @@ -42,25 +40,22 @@ Pane { return qsTr("Unknown verification error."); } } - color: timelineRoot.palette.text verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + } + Item { + Layout.fillHeight: true } - - Item { Layout.fillHeight: true; } - RowLayout { Item { Layout.fillWidth: true } - Button { Layout.alignment: Qt.AlignRight text: qsTr("Close") + onClicked: dialog.close() } - } - } - } diff --git a/qml/device-verification/NewVerificationRequest.qml b/qml/device-verification/NewVerificationRequest.qml index 31668a19..8d5a69d1 100644 --- a/qml/device-verification/NewVerificationRequest.qml +++ b/qml/device-verification/NewVerificationRequest.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.3 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.10 @@ -10,6 +8,7 @@ import im.nheko Pane { property string title: flow.sender ? qsTr("Send Verification Request") : qsTr("Received Verification Request") + background: Rectangle { color: timelineRoot.palette.window } @@ -19,11 +18,10 @@ Pane { spacing: 16 Label { - // Self verification - - Layout.preferredWidth: 400 Layout.fillWidth: true - wrapMode: Text.Wrap + // Self verification + Layout.preferredWidth: 400 + color: timelineRoot.palette.text text: { if (flow.sender) { if (flow.isSelfVerification) @@ -42,34 +40,31 @@ Pane { return qsTr("Your device (%1) has requested to be verified.").arg(flow.deviceId); } } - color: timelineRoot.palette.text verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + } + Item { + Layout.fillHeight: true } - - Item { Layout.fillHeight: true; } - RowLayout { Button { Layout.alignment: Qt.AlignLeft text: flow.sender ? qsTr("Cancel") : qsTr("Deny") + onClicked: { flow.cancel(); dialog.close(); } } - Item { Layout.fillWidth: true } - Button { Layout.alignment: Qt.AlignRight text: flow.sender ? qsTr("Start verification") : qsTr("Accept") + onClicked: flow.next() } - } - } - } diff --git a/qml/device-verification/Success.qml b/qml/device-verification/Success.qml index 38db3ab2..b9899a8f 100644 --- a/qml/device-verification/Success.qml +++ b/qml/device-verification/Success.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.3 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.10 @@ -10,6 +8,7 @@ import im.nheko Pane { property string title: qsTr("Successful Verification") + background: Rectangle { color: timelineRoot.palette.window } @@ -20,30 +19,26 @@ Pane { Label { id: content - - Layout.preferredWidth: 400 Layout.fillWidth: true - wrapMode: Text.Wrap - text: qsTr("Verification successful! Both sides verified their devices!") + Layout.preferredWidth: 400 color: timelineRoot.palette.text + text: qsTr("Verification successful! Both sides verified their devices!") verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + } + Item { + Layout.fillHeight: true } - - Item { Layout.fillHeight: true; } - RowLayout { Item { Layout.fillWidth: true } - Button { Layout.alignment: Qt.AlignRight text: qsTr("Close") + onClicked: dialog.close() } - } - } - } diff --git a/qml/device-verification/Waiting.qml b/qml/device-verification/Waiting.qml index 7f941182..414803bc 100644 --- a/qml/device-verification/Waiting.qml +++ b/qml/device-verification/Waiting.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import "../ui" import QtQuick 2.3 import QtQuick.Controls 2.3 @@ -11,6 +9,7 @@ import im.nheko Pane { property string title: qsTr("Waiting for other party…") + background: Rectangle { color: timelineRoot.palette.window } @@ -21,10 +20,9 @@ Pane { Label { id: content - - Layout.preferredWidth: 400 Layout.fillWidth: true - wrapMode: Text.Wrap + Layout.preferredWidth: 400 + color: timelineRoot.palette.text text: { switch (flow.state) { case "WaitingForOtherToAccept": @@ -35,33 +33,32 @@ Pane { return qsTr("Waiting for other side to complete the verification process."); } } - color: timelineRoot.palette.text verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + } + Item { + Layout.fillHeight: true } - - Item { Layout.fillHeight: true; } Spinner { Layout.alignment: Qt.AlignHCenter foreground: timelineRoot.palette.mid } - Item { Layout.fillHeight: true; } - + Item { + Layout.fillHeight: true + } RowLayout { Button { Layout.alignment: Qt.AlignLeft text: qsTr("Cancel") + onClicked: { flow.cancel(); dialog.close(); } } - Item { Layout.fillWidth: true } - } - } - } diff --git a/qml/dialogs/CreateDirect.qml b/qml/dialogs/CreateDirect.qml index 825d8b0d..06b3bba3 100644 --- a/qml/dialogs/CreateDirect.qml +++ b/qml/dialogs/CreateDirect.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import QtQuick 2.15 import QtQuick.Window 2.13 import QtQuick.Layouts 1.3 @@ -13,13 +11,31 @@ import im.nheko ApplicationWindow { id: createDirectRoot - title: qsTr("Create Direct Chat") + + property bool otherUserHasE2ee: profile ? profile.deviceList.rowCount() > 0 : true property var profile - property bool otherUserHasE2ee: profile? profile.deviceList.rowCount() > 0 : true - minimumHeight: layout.implicitHeight + footer.implicitHeight + Nheko.paddingLarge*2 + + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + minimumHeight: layout.implicitHeight + footer.implicitHeight + Nheko.paddingLarge * 2 minimumWidth: Math.max(footer.implicitWidth, layout.implicitWidth) modality: Qt.NonModal - flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + title: qsTr("Create Direct Chat") + + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Cancel + + onAccepted: { + profile.startChat(encryption.checked); + createDirectRoot.close(); + } + onRejected: createDirectRoot.close() + + Button { + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + enabled: userID.isValidMxid + text: "Start Direct Chat" + } + } onVisibilityChanged: { userID.forceActiveFocus(); @@ -27,90 +43,79 @@ ApplicationWindow { Shortcut { sequence: StandardKey.Cancel + onActivated: createDirectRoot.close() } - ColumnLayout { id: layout anchors.fill: parent anchors.margins: Nheko.paddingLarge - spacing: userID.height/4 + spacing: userID.height / 4 GridLayout { Layout.fillWidth: true - rows: 2 + columnSpacing: Nheko.paddingMedium columns: 2 rowSpacing: Nheko.paddingSmall - columnSpacing: Nheko.paddingMedium + rows: 2 Avatar { - Layout.rowSpan: 2 - Layout.preferredWidth: Nheko.avatarSize - Layout.preferredHeight: Nheko.avatarSize Layout.alignment: Qt.AlignLeft - userid: profile? profile.userid : "" - url: profile? profile.avatarUrl.replace("mxc://", "image://MxcImage/") : null - displayName: profile? profile.displayName : "" + Layout.preferredHeight: Nheko.avatarSize + Layout.preferredWidth: Nheko.avatarSize + Layout.rowSpan: 2 + displayName: profile ? profile.displayName : "" enabled: false + url: profile ? profile.avatarUrl.replace("mxc://", "image://MxcImage/") : null + userid: profile ? profile.userid : "" } Label { Layout.fillWidth: true - text: profile? profile.displayName : "" color: TimelineManager.userColor(userID.text, timelineRoot.palette.window) font.pointSize: fontMetrics.font.pointSize + text: profile ? profile.displayName : "" } - Label { Layout.fillWidth: true - text: userID.text color: timelineRoot.palette.placeholderText font.pointSize: fontMetrics.font.pointSize * 0.9 + text: userID.text } } - MatrixTextField { id: userID + property bool isValidMxid: text.match("@.+?:.{3,}") + Layout.fillWidth: true focus: true label: qsTr("User to invite") placeholderText: qsTr("@user:server.tld") + onTextChanged: { - if(isValidMxid) { + if (isValidMxid) { profile = TimelineManager.getGlobalUserProfile(text); } else profile = null; } } - RowLayout { Layout.fillWidth: true + Label { - Layout.fillWidth: true Layout.alignment: Qt.AlignLeft - text: qsTr("Encryption") + Layout.fillWidth: true color: timelineRoot.palette.text + text: qsTr("Encryption") } ToggleButton { - Layout.alignment: Qt.AlignRight id: encryption + Layout.alignment: Qt.AlignRight checked: otherUserHasE2ee } } - - Item {Layout.fillHeight: true} - } - footer: DialogButtonBox { - standardButtons: DialogButtonBox.Cancel - Button { - text: "Start Direct Chat" - DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole - enabled: userID.isValidMxid - } - onRejected: createDirectRoot.close(); - onAccepted: { - profile.startChat(encryption.checked) - createDirectRoot.close() + Item { + Layout.fillHeight: true } } } diff --git a/qml/dialogs/CreateRoom.qml b/qml/dialogs/CreateRoom.qml index 941715c6..f465b436 100644 --- a/qml/dialogs/CreateRoom.qml +++ b/qml/dialogs/CreateRoom.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import QtQuick 2.15 import QtQuick.Window 2.13 import QtQuick.Layouts 1.3 @@ -12,11 +10,32 @@ import im.nheko ApplicationWindow { id: createRoomRoot - title: qsTr("Create Room") - minimumWidth: Math.max(rootLayout.implicitWidth+2*rootLayout.anchors.margins, footer.implicitWidth + Nheko.paddingLarge) - minimumHeight: rootLayout.implicitHeight+footer.implicitHeight+2*rootLayout.anchors.margins - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + minimumHeight: rootLayout.implicitHeight + footer.implicitHeight + 2 * rootLayout.anchors.margins + minimumWidth: Math.max(rootLayout.implicitWidth + 2 * rootLayout.anchors.margins, footer.implicitWidth + Nheko.paddingLarge) + modality: Qt.NonModal + title: qsTr("Create Room") + + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Cancel + + onAccepted: { + var preset = 0; + if (isPublic.checked) { + preset = 1; + } else { + preset = isTrusted.checked ? 2 : 0; + } + Nheko.createRoom(newRoomName.text, newRoomTopic.text, newRoomAlias.text, isEncrypted.checked, preset); + createRoomRoot.close(); + } + onRejected: createRoomRoot.close() + + Button { + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + text: qsTr("Create Room") + } + } onVisibilityChanged: { newRoomName.forceActiveFocus(); @@ -24,6 +43,7 @@ ApplicationWindow { Shortcut { sequence: StandardKey.Cancel + onActivated: createRoomRoot.close() } GridLayout { @@ -37,7 +57,6 @@ ApplicationWindow { id: newRoomName Layout.columnSpan: 2 Layout.fillWidth: true - focus: true label: qsTr("Name") placeholderText: qsTr("No name") @@ -46,23 +65,21 @@ ApplicationWindow { id: newRoomTopic Layout.columnSpan: 2 Layout.fillWidth: true - focus: true label: qsTr("Topic") placeholderText: qsTr("No topic") } - Item { Layout.preferredHeight: newRoomName.height / 2 } - RowLayout { Layout.columnSpan: 2 Layout.fillWidth: true + Label { Layout.preferredWidth: implicitWidth - text: qsTr("#") color: timelineRoot.palette.text + text: qsTr("#") } MatrixTextField { id: newRoomAlias @@ -70,88 +87,73 @@ ApplicationWindow { placeholderText: qsTr("Alias") } Label { - Layout.preferredWidth: implicitWidth property string userName: userInfoGrid.profile.userid - text: userName.substring(userName.indexOf(":")) + + Layout.preferredWidth: implicitWidth color: timelineRoot.palette.text + text: userName.substring(userName.indexOf(":")) } } Label { - Layout.preferredWidth: implicitWidth Layout.alignment: Qt.AlignLeft - text: qsTr("Public") + Layout.preferredWidth: implicitWidth + ToolTip.delay: Nheko.tooltipDelay + ToolTip.text: qsTr("Public rooms can be joined by anyone, private rooms need explicit invites.") + ToolTip.visible: privateHover.hovered color: timelineRoot.palette.text + text: qsTr("Public") + HoverHandler { id: privateHover } - ToolTip.visible: privateHover.hovered - ToolTip.text: qsTr("Public rooms can be joined by anyone, private rooms need explicit invites.") - ToolTip.delay: Nheko.tooltipDelay } ToggleButton { + id: isPublic Layout.alignment: Qt.AlignRight Layout.preferredWidth: implicitWidth - id: isPublic checked: false } Label { - Layout.preferredWidth: implicitWidth Layout.alignment: Qt.AlignLeft - text: qsTr("Trusted") + Layout.preferredWidth: implicitWidth + ToolTip.delay: Nheko.tooltipDelay + ToolTip.text: qsTr("All invitees are given the same power level as the creator") + ToolTip.visible: trustedHover.hovered color: timelineRoot.palette.text + text: qsTr("Trusted") + HoverHandler { id: trustedHover } - ToolTip.visible: trustedHover.hovered - ToolTip.text: qsTr("All invitees are given the same power level as the creator") - ToolTip.delay: Nheko.tooltipDelay } ToggleButton { + id: isTrusted Layout.alignment: Qt.AlignRight Layout.preferredWidth: implicitWidth - id: isTrusted checked: false enabled: !isPublic.checked } Label { - Layout.preferredWidth: implicitWidth Layout.alignment: Qt.AlignLeft - text: qsTr("Encryption") + Layout.preferredWidth: implicitWidth + ToolTip.delay: Nheko.tooltipDelay + ToolTip.text: qsTr("Caution: Encryption cannot be disabled") + ToolTip.visible: encryptionHover.hovered color: timelineRoot.palette.text + text: qsTr("Encryption") + HoverHandler { id: encryptionHover } - ToolTip.visible: encryptionHover.hovered - ToolTip.text: qsTr("Caution: Encryption cannot be disabled") - ToolTip.delay: Nheko.tooltipDelay } ToggleButton { + id: isEncrypted Layout.alignment: Qt.AlignRight Layout.preferredWidth: implicitWidth - id: isEncrypted checked: false } - - Item {Layout.fillHeight: true} - } - footer: DialogButtonBox { - standardButtons: DialogButtonBox.Cancel - Button { - text: qsTr("Create Room") - DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole - } - onRejected: createRoomRoot.close(); - onAccepted: { - var preset = 0; - - if (isPublic.checked) { - preset = 1; - } - else { - preset = isTrusted.checked ? 2 : 0; - } - Nheko.createRoom(newRoomName.text, newRoomTopic.text, newRoomAlias.text, isEncrypted.checked, preset) - createRoomRoot.close(); + Item { + Layout.fillHeight: true } } } diff --git a/qml/dialogs/HiddenEventsDialog.qml b/qml/dialogs/HiddenEventsDialog.qml index 1954aac4..617ead18 100644 --- a/qml/dialogs/HiddenEventsDialog.qml +++ b/qml/dialogs/HiddenEventsDialog.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import QtQuick 2.12 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.3 @@ -11,117 +9,108 @@ import im.nheko ApplicationWindow { id: hiddenEventsDialog - property string roomid: "" - property string roomName: "" property var onAccepted: undefined + property string roomName: "" + property string roomid: "" - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowTitleHint - minimumWidth: 250 minimumHeight: 220 - - HiddenEvents { - id: hiddenEvents - - roomid: hiddenEventsDialog.roomid - } - + minimumWidth: 250 + modality: Qt.NonModal title: { if (roomid) { return qsTr("Hidden events for %1").arg(roomName); - } - else { + } else { return qsTr("Hidden events"); } } - Shortcut { - sequence: StandardKey.Cancel - onActivated: dbb.rejected() - } - - ColumnLayout { - spacing: Nheko.paddingMedium - anchors.margins: Nheko.paddingMedium - anchors.fill: parent - - MatrixText { - id: promptLabel - text: { - if (roomid) { - return qsTr("These events will be shown in %1:").arg(roomName); - } - else { - return qsTr("These events will be shown in all rooms:"); - } - } - font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2) - Layout.fillWidth: true - Layout.fillHeight: false - } - - GridLayout { - columns: 2 - rowSpacing: Nheko.paddingMedium - Layout.fillWidth: true - Layout.fillHeight: true - - MatrixText { - text: qsTr("User events") - ToolTip.text: qsTr("Joins, leaves, avatar and name changes, bans, …") - ToolTip.visible: hh1.hovered - Layout.fillWidth: true - - HoverHandler { - id: hh1 - } - } - - ToggleButton { - Layout.alignment: Qt.AlignRight - checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Member) - onToggled: hiddenEvents.toggle(MtxEvent.Member) - } - - MatrixText { - text: qsTr("Power level changes") - ToolTip.text: qsTr("Sent when a moderator is added/removed or the permissions of a room are changed.") - ToolTip.visible: hh2.hovered - Layout.fillWidth: true - - HoverHandler { - id: hh2 - } - } - - ToggleButton { - Layout.alignment: Qt.AlignRight - checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.PowerLevels) - onToggled: hiddenEvents.toggle(MtxEvent.PowerLevels) - } - - MatrixText { - text: qsTr("Stickers") - Layout.fillWidth: true - } - - ToggleButton { - Layout.alignment: Qt.AlignRight - checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Sticker) - onToggled: hiddenEvents.toggle(MtxEvent.Sticker) - } - } - } - footer: DialogButtonBox { id: dbb - standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + onAccepted: { hiddenEvents.save(); hiddenEventsDialog.close(); } - onRejected: hiddenEventsDialog.close(); + onRejected: hiddenEventsDialog.close() } + HiddenEvents { + id: hiddenEvents + roomid: hiddenEventsDialog.roomid + } + Shortcut { + sequence: StandardKey.Cancel + + onActivated: dbb.rejected() + } + ColumnLayout { + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + spacing: Nheko.paddingMedium + + MatrixText { + id: promptLabel + Layout.fillHeight: false + Layout.fillWidth: true + font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2) + text: { + if (roomid) { + return qsTr("These events will be shown in %1:").arg(roomName); + } else { + return qsTr("These events will be shown in all rooms:"); + } + } + } + GridLayout { + Layout.fillHeight: true + Layout.fillWidth: true + columns: 2 + rowSpacing: Nheko.paddingMedium + + MatrixText { + Layout.fillWidth: true + ToolTip.text: qsTr("Joins, leaves, avatar and name changes, bans, …") + ToolTip.visible: hh1.hovered + text: qsTr("User events") + + HoverHandler { + id: hh1 + } + } + ToggleButton { + Layout.alignment: Qt.AlignRight + checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Member) + + onToggled: hiddenEvents.toggle(MtxEvent.Member) + } + MatrixText { + Layout.fillWidth: true + ToolTip.text: qsTr("Sent when a moderator is added/removed or the permissions of a room are changed.") + ToolTip.visible: hh2.hovered + text: qsTr("Power level changes") + + HoverHandler { + id: hh2 + } + } + ToggleButton { + Layout.alignment: Qt.AlignRight + checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.PowerLevels) + + onToggled: hiddenEvents.toggle(MtxEvent.PowerLevels) + } + MatrixText { + Layout.fillWidth: true + text: qsTr("Stickers") + } + ToggleButton { + Layout.alignment: Qt.AlignRight + checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Sticker) + + onToggled: hiddenEvents.toggle(MtxEvent.Sticker) + } + } + } } diff --git a/qml/dialogs/ImageOverlay.qml b/qml/dialogs/ImageOverlay.qml index 31d579e1..6d36fb4d 100644 --- a/qml/dialogs/ImageOverlay.qml +++ b/qml/dialogs/ImageOverlay.qml @@ -1,113 +1,99 @@ // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.15 import QtQuick.Window 2.15 - -import ".." - +import "../" import im.nheko Window { id: imageOverlay - required property string url required property string eventId - required property Room room required property int originalWidth required property double proportionalHeight - - flags: Qt.FramelessWindowHint + required property Room room + required property string url //visibility: Window.FullScreen - color: Qt.rgba(0.2,0.2,0.2,0.66) + color: Qt.rgba(0.2, 0.2, 0.2, 0.66) + flags: Qt.FramelessWindowHint Shortcut { sequence: StandardKey.Cancel + onActivated: imageOverlay.close() } - - - Item { id: imgContainer - property int imgSrcWidth: (originalWidth && originalWidth > 200) ? originalWidth : Screen.width property int imgSrcHeight: proportionalHeight ? imgSrcWidth * proportionalHeight : Screen.height + property int imgSrcWidth: (originalWidth && originalWidth > 200) ? originalWidth : Screen.width height: Math.min(parent.height, imgSrcHeight) width: Math.min(parent.width, imgSrcWidth) - x: (parent.width - width) y: (parent.height - height) + onScaleChanged: { + if (scale > 10) + scale = 10; + if (scale < 0.1) + scale = 0.1; + } + Image { id: img - visible: !mxcimage.loaded + property bool loaded: status == Image.Ready + anchors.fill: parent - source: url.replace("mxc://", "image://MxcImage/") asynchronous: true fillMode: Image.PreserveAspectFit - smooth: true mipmap: true - property bool loaded: status == Image.Ready + smooth: true + source: url.replace("mxc://", "image://MxcImage/") + visible: !mxcimage.loaded } - MxcAnimatedImage { id: mxcimage - - visible: loaded anchors.fill: parent - roomm: imageOverlay.room - play: !Settings.animateImagesOnHover || mouseArea.hovered eventId: imageOverlay.eventId - } - - onScaleChanged: { - if (scale > 10) scale = 10; - if (scale < 0.1) scale = 0.1 + play: !Settings.animateImagesOnHover || mouseArea.hovered + roomm: imageOverlay.room + visible: loaded } } - Item { anchors.fill: parent - PinchHandler { - target: imgContainer maximumScale: 10 minimumScale: 0.1 + target: imgContainer } - WheelHandler { property: "scale" target: imgContainer } - DragHandler { target: imgContainer } - HoverHandler { id: mouseArea } } - - - Row { - anchors.top: parent.top - anchors.right: parent.right anchors.margins: Nheko.paddingLarge + anchors.right: parent.right + anchors.top: parent.top spacing: Nheko.paddingMedium ImageButton { height: 48 - width: 48 hoverEnabled: true image: ":/icons/icons/ui/download.svg" + width: 48 + //ToolTip.visible: hovered //ToolTip.delay: Nheko.tooltipDelay //ToolTip.text: qsTr("Download") @@ -122,14 +108,14 @@ Window { } ImageButton { height: 48 - width: 48 hoverEnabled: true image: ":/icons/icons/ui/dismiss.svg" + width: 48 + //ToolTip.visible: hovered //ToolTip.delay: Nheko.tooltipDelay //ToolTip.text: qsTr("Close") onClicked: imageOverlay.close() } } - } diff --git a/qml/dialogs/ImagePackEditorDialog.qml b/qml/dialogs/ImagePackEditorDialog.qml index b5d926c7..f2cec59e 100644 --- a/qml/dialogs/ImagePackEditorDialog.qml +++ b/qml/dialogs/ImagePackEditorDialog.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import "../components" import Qt.labs.platform 1.1 import QtQuick 2.12 @@ -15,316 +13,23 @@ ApplicationWindow { id: win property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) - property SingleImagePackModel imagePack property int currentImageIndex: -1 + property SingleImagePackModel imagePack readonly property int stickerDim: 128 readonly property int stickerDimPad: 128 + Nheko.paddingSmall - title: qsTr("Editing image pack") - height: 600 - width: 600 - palette: timelineRoot.palette color: timelineRoot.palette.base - modality: Qt.WindowModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - - AdaptiveLayout { - id: adaptiveView - - anchors.fill: parent - singlePageMode: false - pageIndex: 0 - - AdaptiveLayoutElement { - id: packlistC - - visible: Settings.groupView - minimumWidth: 200 - collapsedWidth: 200 - preferredWidth: 300 - maximumWidth: 300 - clip: true - - ListView { - //required property bool isEmote - //required property bool isSticker - - model: imagePack - - - header: AvatarListTile { - title: imagePack.packname - avatarUrl: imagePack.avatarUrl - roomid: imagePack.statekey - subtitle: imagePack.statekey - index: -1 - selectedIndex: currentImageIndex - - TapHandler { - onSingleTapped: currentImageIndex = -1 - } - - Rectangle { - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - height: parent.height - Nheko.paddingSmall * 2 - width: 3 - color: timelineRoot.palette.highlight - } - - } - - footer: Button { - palette: timelineRoot.palette - onClicked: addFilesDialog.open() - width: ListView.view.width - text: qsTr("Add images") - - FileDialog { - id: addFilesDialog - - folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) - fileMode: FileDialog.OpenFiles - nameFilters: [qsTr("Images (*.png *.webp *.gif *.jpg *.jpeg)")] - title: qsTr("Select images for pack") - acceptLabel: qsTr("Add to pack") - onAccepted: imagePack.addStickers(files) - } - - } - - delegate: AvatarListTile { - id: packItem - - property color background: timelineRoot.palette.window - property color importantText: timelineRoot.palette.text - property color unimportantText: timelineRoot.palette.placeholderText - property color bubbleBackground: timelineRoot.palette.highlight - property color bubbleText: timelineRoot.palette.highlightedText - required property string shortCode - required property string url - required property string body - - title: shortCode - subtitle: body - avatarUrl: url - selectedIndex: currentImageIndex - crop: false - - TapHandler { - onSingleTapped: currentImageIndex = index - } - - } - - } - - } - - AdaptiveLayoutElement { - id: packinfoC - - Rectangle { - color: timelineRoot.palette.window - - GridLayout { - anchors.fill: parent - anchors.margins: Nheko.paddingMedium - visible: currentImageIndex == -1 - enabled: visible - columns: 2 - rowSpacing: Nheko.paddingLarge - - Avatar { - Layout.columnSpan: 2 - url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/") - displayName: imagePack.packname - roomid: imagePack.statekey - height: 130 - width: 130 - crop: false - Layout.alignment: Qt.AlignHCenter - - ImageButton { - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Change the overview image for this pack") - anchors.left: parent.left - anchors.top: parent.top - anchors.leftMargin: Nheko.paddingMedium - anchors.topMargin: Nheko.paddingMedium - image: ":/icons/icons/ui/edit.svg" - onClicked: addAvatarDialog.open() - - FileDialog { - id: addAvatarDialog - - folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) - fileMode: FileDialog.OpenFile - nameFilters: [qsTr("Overview Image (*.png *.webp *.jpg *.jpeg)")] - title: qsTr("Select overview image for pack") - onAccepted: imagePack.setAvatar(file) - } - } - } - - MatrixTextField { - id: statekeyField - - visible: imagePack.roomid - Layout.fillWidth: true - Layout.columnSpan: 2 - label: qsTr("State key") - text: imagePack.statekey - onTextEdited: imagePack.statekey = text - } - - MatrixTextField { - Layout.fillWidth: true - Layout.columnSpan: 2 - label: qsTr("Packname") - text: imagePack.packname - onTextEdited: imagePack.packname = text - } - - MatrixTextField { - Layout.fillWidth: true - Layout.columnSpan: 2 - label: qsTr("Attribution") - text: imagePack.attribution - onTextEdited: imagePack.attribution = text - } - - MatrixText { - Layout.margins: statekeyField.textPadding - font.weight: Font.DemiBold - font.letterSpacing: font.pixelSize * 0.02 - text: qsTr("Use as Emoji") - } - - ToggleButton { - checked: imagePack.isEmotePack - onCheckedChanged: imagePack.isEmotePack = checked - Layout.alignment: Qt.AlignRight - } - - MatrixText { - Layout.margins: statekeyField.textPadding - font.weight: Font.DemiBold - font.letterSpacing: font.pixelSize * 0.02 - text: qsTr("Use as Sticker") - } - - ToggleButton { - checked: imagePack.isStickerPack - onCheckedChanged: imagePack.isStickerPack = checked - Layout.alignment: Qt.AlignRight - } - - Item { - Layout.columnSpan: 2 - Layout.fillHeight: true - } - - } - - GridLayout { - anchors.fill: parent - anchors.margins: Nheko.paddingMedium - visible: currentImageIndex >= 0 - enabled: visible - columns: 2 - rowSpacing: Nheko.paddingLarge - - Avatar { - Layout.columnSpan: 2 - url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/") + "?scale" - displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) - roomid: displayName - height: 130 - width: 130 - crop: false - Layout.alignment: Qt.AlignHCenter - } - - MatrixTextField { - Layout.fillWidth: true - Layout.columnSpan: 2 - label: qsTr("Shortcode") - text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) - onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode) - } - - MatrixTextField { - id: bodyField - - Layout.fillWidth: true - Layout.columnSpan: 2 - label: qsTr("Body") - text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body) - onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body) - } - - MatrixText { - Layout.margins: bodyField.textPadding - font.weight: Font.DemiBold - font.letterSpacing: font.pixelSize * 0.02 - text: qsTr("Use as Emoji") - } - - ToggleButton { - checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote) - onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsEmote) - Layout.alignment: Qt.AlignRight - } - - MatrixText { - Layout.margins: bodyField.textPadding - font.weight: Font.DemiBold - font.letterSpacing: font.pixelSize * 0.02 - text: qsTr("Use as Sticker") - } - - ToggleButton { - checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker) - onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsSticker) - Layout.alignment: Qt.AlignRight - } - - MatrixText { - Layout.margins: bodyField.textPadding - font.weight: Font.DemiBold - font.letterSpacing: font.pixelSize * 0.02 - text: qsTr("Remove from pack") - } - - Button { - text: qsTr("Remove") - onClicked: { - let temp = currentImageIndex; - currentImageIndex = -1; - imagePack.remove(temp); - } - Layout.alignment: Qt.AlignRight - } - - Item { - Layout.columnSpan: 2 - Layout.fillHeight: true - } - - } - - } - - } - - } + height: 600 + modality: Qt.WindowModal + palette: timelineRoot.palette + title: qsTr("Editing image pack") + width: 600 footer: DialogButtonBox { id: buttons - standardButtons: DialogButtonBox.Save | DialogButtonBox.Cancel + onAccepted: { imagePack.save(); win.close(); @@ -332,4 +37,269 @@ ApplicationWindow { onRejected: win.close() } + AdaptiveLayout { + id: adaptiveView + anchors.fill: parent + pageIndex: 0 + singlePageMode: false + + AdaptiveLayoutElement { + id: packlistC + clip: true + collapsedWidth: 200 + maximumWidth: 300 + minimumWidth: 200 + preferredWidth: 300 + visible: Settings.groupView + + ListView { + //required property bool isEmote + //required property bool isSticker + model: imagePack + + delegate: AvatarListTile { + id: packItem + + property color background: timelineRoot.palette.window + required property string body + property color bubbleBackground: timelineRoot.palette.highlight + property color bubbleText: timelineRoot.palette.highlightedText + property color importantText: timelineRoot.palette.text + required property string shortCode + property color unimportantText: timelineRoot.palette.placeholderText + required property string url + + avatarUrl: url + crop: false + selectedIndex: currentImageIndex + subtitle: body + title: shortCode + + TapHandler { + onSingleTapped: currentImageIndex = index + } + } + footer: Button { + palette: timelineRoot.palette + text: qsTr("Add images") + width: ListView.view.width + + onClicked: addFilesDialog.open() + + FileDialog { + id: addFilesDialog + acceptLabel: qsTr("Add to pack") + fileMode: FileDialog.OpenFiles + folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) + nameFilters: [qsTr("Images (*.png *.webp *.gif *.jpg *.jpeg)")] + title: qsTr("Select images for pack") + + onAccepted: imagePack.addStickers(files) + } + } + header: AvatarListTile { + avatarUrl: imagePack.avatarUrl + index: -1 + roomid: imagePack.statekey + selectedIndex: currentImageIndex + subtitle: imagePack.statekey + title: imagePack.packname + + TapHandler { + onSingleTapped: currentImageIndex = -1 + } + Rectangle { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + color: timelineRoot.palette.highlight + height: parent.height - Nheko.paddingSmall * 2 + width: 3 + } + } + } + } + AdaptiveLayoutElement { + id: packinfoC + Rectangle { + color: timelineRoot.palette.window + + GridLayout { + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + columns: 2 + enabled: visible + rowSpacing: Nheko.paddingLarge + visible: currentImageIndex == -1 + + Avatar { + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 2 + crop: false + displayName: imagePack.packname + height: 130 + roomid: imagePack.statekey + url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/") + width: 130 + + ImageButton { + ToolTip.text: qsTr("Change the overview image for this pack") + ToolTip.visible: hovered + anchors.left: parent.left + anchors.leftMargin: Nheko.paddingMedium + anchors.top: parent.top + anchors.topMargin: Nheko.paddingMedium + hoverEnabled: true + image: ":/icons/icons/ui/edit.svg" + + onClicked: addAvatarDialog.open() + + FileDialog { + id: addAvatarDialog + fileMode: FileDialog.OpenFile + folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) + nameFilters: [qsTr("Overview Image (*.png *.webp *.jpg *.jpeg)")] + title: qsTr("Select overview image for pack") + + onAccepted: imagePack.setAvatar(file) + } + } + } + MatrixTextField { + id: statekeyField + Layout.columnSpan: 2 + Layout.fillWidth: true + label: qsTr("State key") + text: imagePack.statekey + visible: imagePack.roomid + + onTextEdited: imagePack.statekey = text + } + MatrixTextField { + Layout.columnSpan: 2 + Layout.fillWidth: true + label: qsTr("Packname") + text: imagePack.packname + + onTextEdited: imagePack.packname = text + } + MatrixTextField { + Layout.columnSpan: 2 + Layout.fillWidth: true + label: qsTr("Attribution") + text: imagePack.attribution + + onTextEdited: imagePack.attribution = text + } + MatrixText { + Layout.margins: statekeyField.textPadding + font.letterSpacing: font.pixelSize * 0.02 + font.weight: Font.DemiBold + text: qsTr("Use as Emoji") + } + ToggleButton { + Layout.alignment: Qt.AlignRight + checked: imagePack.isEmotePack + + onCheckedChanged: imagePack.isEmotePack = checked + } + MatrixText { + Layout.margins: statekeyField.textPadding + font.letterSpacing: font.pixelSize * 0.02 + font.weight: Font.DemiBold + text: qsTr("Use as Sticker") + } + ToggleButton { + Layout.alignment: Qt.AlignRight + checked: imagePack.isStickerPack + + onCheckedChanged: imagePack.isStickerPack = checked + } + Item { + Layout.columnSpan: 2 + Layout.fillHeight: true + } + } + GridLayout { + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + columns: 2 + enabled: visible + rowSpacing: Nheko.paddingLarge + visible: currentImageIndex >= 0 + + Avatar { + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 2 + crop: false + displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) + height: 130 + roomid: displayName + url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/") + "?scale" + width: 130 + } + MatrixTextField { + Layout.columnSpan: 2 + Layout.fillWidth: true + label: qsTr("Shortcode") + text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) + + onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode) + } + MatrixTextField { + id: bodyField + Layout.columnSpan: 2 + Layout.fillWidth: true + label: qsTr("Body") + text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body) + + onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body) + } + MatrixText { + Layout.margins: bodyField.textPadding + font.letterSpacing: font.pixelSize * 0.02 + font.weight: Font.DemiBold + text: qsTr("Use as Emoji") + } + ToggleButton { + Layout.alignment: Qt.AlignRight + checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote) + + onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsEmote) + } + MatrixText { + Layout.margins: bodyField.textPadding + font.letterSpacing: font.pixelSize * 0.02 + font.weight: Font.DemiBold + text: qsTr("Use as Sticker") + } + ToggleButton { + Layout.alignment: Qt.AlignRight + checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker) + + onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsSticker) + } + MatrixText { + Layout.margins: bodyField.textPadding + font.letterSpacing: font.pixelSize * 0.02 + font.weight: Font.DemiBold + text: qsTr("Remove from pack") + } + Button { + Layout.alignment: Qt.AlignRight + text: qsTr("Remove") + + onClicked: { + let temp = currentImageIndex; + currentImageIndex = -1; + imagePack.remove(temp); + } + } + Item { + Layout.columnSpan: 2 + Layout.fillHeight: true + } + } + } + } + } } diff --git a/qml/dialogs/ImagePackSettingsDialog.qml b/qml/dialogs/ImagePackSettingsDialog.qml index a0e1962d..1686ece7 100644 --- a/qml/dialogs/ImagePackSettingsDialog.qml +++ b/qml/dialogs/ImagePackSettingsDialog.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import "../components" import QtQuick 2.12 import QtQuick.Controls 2.12 @@ -13,96 +11,70 @@ import im.nheko ApplicationWindow { id: win - property Room room - property ImagePackListModel packlist property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) property SingleImagePackModel currentPack: packlist.packAt(currentPackIndex) property int currentPackIndex: 0 + property ImagePackListModel packlist + property Room room readonly property int stickerDim: 128 readonly property int stickerDimPad: 128 + Nheko.paddingSmall - title: qsTr("Image pack settings") - height: 600 - width: 800 - palette: timelineRoot.palette color: timelineRoot.palette.base - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 600 + modality: Qt.NonModal + palette: timelineRoot.palette + title: qsTr("Image pack settings") + width: 800 + + footer: DialogButtonBox { + id: buttons + Button { + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + text: qsTr("Close") + + onClicked: win.close() + } + } Component { id: packEditor - ImagePackEditorDialog { } - } - AdaptiveLayout { id: adaptiveView - anchors.fill: parent - singlePageMode: false pageIndex: 0 + singlePageMode: false AdaptiveLayoutElement { id: packlistC - - visible: Settings.groupView - minimumWidth: 200 collapsedWidth: 200 - preferredWidth: 300 maximumWidth: 300 + minimumWidth: 200 + preferredWidth: 300 + visible: Settings.groupView ListView { - model: packlist clip: true - - - footer: ColumnLayout { - Button { - palette: timelineRoot.palette - onClicked: { - var dialog = packEditor.createObject(timelineRoot, { - "imagePack": packlist.newPack(false) - }); - dialog.show(); - timelineRoot.destroyOnClose(dialog); - } - width: packlistC.width - visible: !packlist.containsAccountPack - text: qsTr("Create account pack") - } - - Button { - palette: timelineRoot.palette - onClicked: { - var dialog = packEditor.createObject(timelineRoot, { - "imagePack": packlist.newPack(true) - }); - dialog.show(); - timelineRoot.destroyOnClose(dialog); - } - width: packlistC.width - visible: room.permissions.canChange(MtxEvent.ImagePackInRoom) - text: qsTr("New room pack") - } - - } + model: packlist delegate: AvatarListTile { id: packItem property color background: timelineRoot.palette.window - property color importantText: timelineRoot.palette.text - property color unimportantText: timelineRoot.palette.placeholderText property color bubbleBackground: timelineRoot.palette.highlight property color bubbleText: timelineRoot.palette.highlightedText required property string displayName required property bool fromAccountData required property bool fromCurrentRoom + property color importantText: timelineRoot.palette.text required property string statekey + property color unimportantText: timelineRoot.palette.placeholderText - title: displayName + roomid: statekey + selectedIndex: currentPackIndex subtitle: { if (fromAccountData) return qsTr("Private pack"); @@ -111,31 +83,55 @@ ApplicationWindow { else return qsTr("Globally enabled pack"); } - selectedIndex: currentPackIndex - roomid: statekey + title: displayName TapHandler { onSingleTapped: currentPackIndex = index } - } + footer: ColumnLayout { + Button { + palette: timelineRoot.palette + text: qsTr("Create account pack") + visible: !packlist.containsAccountPack + width: packlistC.width + onClicked: { + var dialog = packEditor.createObject(timelineRoot, { + "imagePack": packlist.newPack(false) + }); + dialog.show(); + timelineRoot.destroyOnClose(dialog); + } + } + Button { + palette: timelineRoot.palette + text: qsTr("New room pack") + visible: room.permissions.canChange(MtxEvent.ImagePackInRoom) + width: packlistC.width + + onClicked: { + var dialog = packEditor.createObject(timelineRoot, { + "imagePack": packlist.newPack(true) + }); + dialog.show(); + timelineRoot.destroyOnClose(dialog); + } + } + } } - } - AdaptiveLayoutElement { id: packinfoC - Rectangle { color: timelineRoot.palette.window ColumnLayout { id: packinfo - property string packName: currentPack ? currentPack.packname : "" property string attribution: currentPack ? currentPack.attribution : "" property string avatarUrl: currentPack ? currentPack.avatarUrl : "" + property string packName: currentPack ? currentPack.packname : "" property string statekey: currentPack ? currentPack.statekey : "" anchors.fill: parent @@ -143,117 +139,92 @@ ApplicationWindow { spacing: Nheko.paddingLarge Avatar { - url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/") - displayName: packinfo.packName - roomid: packinfo.statekey - height: 100 - width: 100 Layout.alignment: Qt.AlignHCenter + displayName: packinfo.packName enabled: false + height: 100 + roomid: packinfo.statekey + url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/") + width: 100 } - MatrixText { - text: packinfo.packName + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2 font.pixelSize: Math.ceil(fontMetrics.pixelSize * 1.1) horizontalAlignment: TextEdit.AlignHCenter + text: packinfo.packName + } + MatrixText { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2 - } - - MatrixText { + horizontalAlignment: TextEdit.AlignHCenter text: packinfo.attribution wrapMode: TextEdit.Wrap - horizontalAlignment: TextEdit.AlignHCenter - Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2 } - GridLayout { Layout.alignment: Qt.AlignHCenter - visible: currentPack && currentPack.roomid != "" columns: 2 rowSpacing: Nheko.paddingMedium + visible: currentPack && currentPack.roomid != "" MatrixText { text: qsTr("Enable globally") } - ToggleButton { + Layout.alignment: Qt.AlignRight ToolTip.text: qsTr("Enables this pack to be used in all rooms") checked: currentPack ? currentPack.isGloballyEnabled : false + onCheckedChanged: currentPack.isGloballyEnabled = checked - Layout.alignment: Qt.AlignRight } - } - Button { Layout.alignment: Qt.AlignHCenter - text: qsTr("Edit") enabled: currentPack.canEdit + text: qsTr("Edit") + onClicked: { var dialog = packEditor.createObject(timelineRoot, { - "imagePack": currentPack - }); + "imagePack": currentPack + }); dialog.show(); timelineRoot.destroyOnClose(dialog); } } - GridView { Layout.fillHeight: true Layout.fillWidth: true - model: currentPack - cellWidth: stickerDimPad - cellHeight: stickerDimPad boundsBehavior: Flickable.StopAtBounds + cacheBuffer: 500 + cellHeight: stickerDimPad + cellWidth: stickerDimPad clip: true currentIndex: -1 // prevent sorting from stealing focus - cacheBuffer: 500 - + model: currentPack // Individual emoji delegate: AbstractButton { - width: stickerDim - height: stickerDim - hoverEnabled: true ToolTip.text: ":" + model.shortCode + ": - " + model.body ToolTip.visible: hovered - - contentItem: Image { - height: stickerDim - width: stickerDim - source: model.url.replace("mxc://", "image://MxcImage/") + "?scale" - fillMode: Image.PreserveAspectFit - } + height: stickerDim + hoverEnabled: true + width: stickerDim background: Rectangle { anchors.fill: parent color: hovered ? timelineRoot.palette.highlight : 'transparent' radius: 5 } - + contentItem: Image { + fillMode: Image.PreserveAspectFit + height: stickerDim + source: model.url.replace("mxc://", "image://MxcImage/") + "?scale" + width: stickerDim + } } - } - } - } - } - } - - footer: DialogButtonBox { - id: buttons - - Button { - text: qsTr("Close") - DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole - onClicked: win.close() - } - - } - } diff --git a/qml/dialogs/InputDialog.qml b/qml/dialogs/InputDialog.qml index a383018f..77470819 100644 --- a/qml/dialogs/InputDialog.qml +++ b/qml/dialogs/InputDialog.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import QtQuick 2.12 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.3 @@ -12,53 +10,26 @@ import im.nheko ApplicationWindow { id: inputDialog - property alias prompt: promptLabel.text property alias echoMode: statusInput.echoMode property var onAccepted: undefined - - modality: Qt.NonModal - flags: Qt.Dialog - width: 350 - height: fontMetrics.lineSpacing * 7 + property alias prompt: promptLabel.text function forceActiveFocus() { statusInput.forceActiveFocus(); } - Shortcut { - sequence: StandardKey.Cancel - onActivated: dbb.rejected() - } - - ColumnLayout { - spacing: Nheko.paddingMedium - anchors.margins: Nheko.paddingMedium - anchors.fill: parent - - Label { - id: promptLabel - - color: timelineRoot.palette.text - } - - MatrixTextField { - id: statusInput - - Layout.fillWidth: true - onAccepted: dbb.accepted() - focus: true - } - - } + flags: Qt.Dialog + height: fontMetrics.lineSpacing * 7 + modality: Qt.NonModal + width: 350 footer: DialogButtonBox { id: dbb - standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + onAccepted: { if (inputDialog.onAccepted) inputDialog.onAccepted(statusInput.text); - inputDialog.close(); } onRejected: { @@ -66,4 +37,26 @@ ApplicationWindow { } } + Shortcut { + sequence: StandardKey.Cancel + + onActivated: dbb.rejected() + } + ColumnLayout { + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + spacing: Nheko.paddingMedium + + Label { + id: promptLabel + color: timelineRoot.palette.text + } + MatrixTextField { + id: statusInput + Layout.fillWidth: true + focus: true + + onAccepted: dbb.accepted() + } + } } diff --git a/qml/dialogs/InviteDialog.qml b/qml/dialogs/InviteDialog.qml index bc79dc3e..73fffdaf 100644 --- a/qml/dialogs/InviteDialog.qml +++ b/qml/dialogs/InviteDialog.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 @@ -12,9 +10,9 @@ import im.nheko ApplicationWindow { id: inviteDialogRoot - property string roomId - property string plainRoomName property InviteesModel invitees + property string plainRoomName + property string roomId function addInvite() { if (inviteeEntry.isValidMxid) { @@ -22,43 +20,57 @@ ApplicationWindow { inviteeEntry.clear(); } } - function cleanUpAndClose() { if (inviteeEntry.isValidMxid) addInvite(); - invitees.accept(); close(); } - title: qsTr("Invite users to %1").arg(plainRoomName) - height: 380 - width: 340 - palette: timelineRoot.palette color: timelineRoot.palette.window flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 380 + palette: timelineRoot.palette + title: qsTr("Invite users to %1").arg(plainRoomName) + width: 340 + + footer: DialogButtonBox { + id: buttons + Button { + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + enabled: invitees.count > 0 + text: qsTr("Invite") + + onClicked: cleanUpAndClose() + } + Button { + DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole + text: qsTr("Cancel") + + onClicked: inviteDialogRoot.close() + } + } Shortcut { sequence: "Ctrl+Enter" + onActivated: cleanUpAndClose() } - Shortcut { sequence: StandardKey.Cancel + onActivated: inviteDialogRoot.close() } - ColumnLayout { anchors.fill: parent anchors.margins: Nheko.paddingMedium spacing: Nheko.paddingMedium Label { - text: qsTr("User ID to invite") Layout.fillWidth: true color: timelineRoot.palette.text + text: qsTr("User ID to invite") } - RowLayout { spacing: Nheko.paddingMedium @@ -67,120 +79,88 @@ ApplicationWindow { property bool isValidMxid: text.match("@.+?:.{3,}") + Layout.fillWidth: true backgroundColor: timelineRoot.palette.window placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.") - Layout.fillWidth: true - onAccepted: { - if (isValidMxid) - addInvite(); - } Component.onCompleted: forceActiveFocus() - Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier)) Keys.onPressed: { if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier)) cleanUpAndClose(); - + } + Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier)) + onAccepted: { + if (isValidMxid) + addInvite(); } } - Button { - text: qsTr("Add") enabled: inviteeEntry.isValidMxid + text: qsTr("Add") + onClicked: addInvite() } - } - ListView { id: inviteesList - - Layout.fillWidth: true Layout.fillHeight: true + Layout.fillWidth: true model: invitees delegate: ItemDelegate { id: del - + height: layout.implicitHeight + Nheko.paddingSmall * 2 hoverEnabled: true width: ListView.view.width - height: layout.implicitHeight + Nheko.paddingSmall * 2 - onClicked: TimelineManager.openGlobalUserProfile(model.mxid) + background: Rectangle { color: del.hovered ? timelineRoot.palette.dark : inviteDialogRoot.color } + onClicked: TimelineManager.openGlobalUserProfile(model.mxid) + RowLayout { id: layout - - spacing: Nheko.paddingMedium anchors.centerIn: parent + spacing: Nheko.paddingMedium width: del.width - Nheko.paddingSmall * 2 Avatar { - width: Nheko.avatarSize - height: Nheko.avatarSize - userid: model.mxid - url: model.avatarUrl.replace("mxc://", "image://MxcImage/") displayName: model.displayName enabled: false + height: Nheko.avatarSize + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + userid: model.mxid + width: Nheko.avatarSize } - ColumnLayout { spacing: Nheko.paddingSmall Label { - text: model.displayName color: TimelineManager.userColor(model ? model.mxid : "", del.background.color) font.pointSize: fontMetrics.font.pointSize + text: model.displayName } - Label { - text: model.mxid color: del.hovered ? timelineRoot.palette.brightText : timelineRoot.palette.placeholderText font.pointSize: fontMetrics.font.pointSize * 0.9 + text: model.mxid } - } - Item { Layout.fillWidth: true } - ImageButton { image: ":/icons/icons/ui/dismiss.svg" + onClicked: invitees.removeUser(model.mxid) } - } - NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } - } - } - } - - footer: DialogButtonBox { - id: buttons - - Button { - text: qsTr("Invite") - DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole - enabled: invitees.count > 0 - onClicked: cleanUpAndClose() - } - - Button { - text: qsTr("Cancel") - DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole - onClicked: inviteDialogRoot.close() - } - - } - } diff --git a/qml/dialogs/JoinRoomDialog.qml b/qml/dialogs/JoinRoomDialog.qml index a4f77b65..7d342f58 100644 --- a/qml/dialogs/JoinRoomDialog.qml +++ b/qml/dialogs/JoinRoomDialog.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import QtQuick 2.12 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.3 @@ -11,50 +9,18 @@ import im.nheko ApplicationWindow { id: joinRoomRoot - - title: qsTr("Join room") - modality: Qt.WindowModal - flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - palette: timelineRoot.palette color: timelineRoot.palette.window - width: 350 + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint height: fontMetrics.lineSpacing * 7 - - Shortcut { - sequence: StandardKey.Cancel - onActivated: dbb.rejected() - } - - ColumnLayout { - spacing: Nheko.paddingMedium - anchors.margins: Nheko.paddingMedium - anchors.fill: parent - - Label { - id: promptLabel - - text: qsTr("Room ID or alias") - color: timelineRoot.palette.text - } - - MatrixTextField { - id: input - - focus: true - Layout.fillWidth: true - onAccepted: { - if (input.text.match("#.+?:.{3,}")) - dbb.accepted(); - - } - } - - } + modality: Qt.WindowModal + palette: timelineRoot.palette + title: qsTr("Join room") + width: 350 footer: DialogButtonBox { id: dbb - standardButtons: DialogButtonBox.Cancel + onAccepted: { Nheko.joinRoom(input.text); joinRoomRoot.close(); @@ -64,11 +30,36 @@ ApplicationWindow { } Button { - text: "Join" - enabled: input.text.match("#.+?:.{3,}") DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + enabled: input.text.match("#.+?:.{3,}") + text: "Join" } - } + Shortcut { + sequence: StandardKey.Cancel + + onActivated: dbb.rejected() + } + ColumnLayout { + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + spacing: Nheko.paddingMedium + + Label { + id: promptLabel + color: timelineRoot.palette.text + text: qsTr("Room ID or alias") + } + MatrixTextField { + id: input + Layout.fillWidth: true + focus: true + + onAccepted: { + if (input.text.match("#.+?:.{3,}")) + dbb.accepted(); + } + } + } } diff --git a/qml/dialogs/LeaveRoomDialog.qml b/qml/dialogs/LeaveRoomDialog.qml index d2ef2691..50925a6f 100644 --- a/qml/dialogs/LeaveRoomDialog.qml +++ b/qml/dialogs/LeaveRoomDialog.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import Qt.labs.platform 1.1 as P import QtQuick 2.15 import QtQuick.Controls 2.15 @@ -11,12 +9,13 @@ import im.nheko P.MessageDialog { id: leaveRoomRoot - required property string roomId property string reason: "" + required property string roomId - title: qsTr("Leave room") - text: qsTr("Are you sure you want to leave?") - modality: Qt.ApplicationModal buttons: P.MessageDialog.Ok | P.MessageDialog.Cancel + modality: Qt.ApplicationModal + text: qsTr("Are you sure you want to leave?") + title: qsTr("Leave room") + onAccepted: Rooms.leave(roomId, reason) } diff --git a/qml/dialogs/LogoutDialog.qml b/qml/dialogs/LogoutDialog.qml index 0589ff22..7a1c699a 100644 --- a/qml/dialogs/LogoutDialog.qml +++ b/qml/dialogs/LogoutDialog.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import Qt.labs.platform 1.1 as P import QtQuick 2.15 import QtQuick.Controls 2.15 @@ -10,11 +8,11 @@ import im.nheko P.MessageDialog { id: logoutRoot - - title: qsTr("Log out") - text: CallManager.isOnCall ? qsTr("A call is in progress. Log out?") : qsTr("Are you sure you want to log out?") - modality: Qt.WindowModal - flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint buttons: P.MessageDialog.Ok | P.MessageDialog.Cancel + flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + modality: Qt.WindowModal + text: CallManager.isOnCall ? qsTr("A call is in progress. Log out?") : qsTr("Are you sure you want to log out?") + title: qsTr("Log out") + onAccepted: Nheko.logout() } diff --git a/qml/dialogs/PhoneNumberInputDialog.qml b/qml/dialogs/PhoneNumberInputDialog.qml index 8d88b3c5..0f3ba979 100644 --- a/qml/dialogs/PhoneNumberInputDialog.qml +++ b/qml/dialogs/PhoneNumberInputDialog.qml @@ -1,10 +1,8 @@ // SPDX-FileCopyrightText: 2021 Mirian Margiani // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import QtQuick 2.12 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.3 @@ -13,32 +11,42 @@ import im.nheko ApplicationWindow { id: inputDialog - property alias prompt: promptLabel.text property alias echoMode: statusInput.echoMode property var onAccepted: undefined + property alias prompt: promptLabel.text - modality: Qt.NonModal flags: Qt.Dialog - width: 350 height: fontMetrics.lineSpacing * 7 + modality: Qt.NonModal + width: 350 + + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + + onAccepted: { + if (inputDialog.onAccepted) + inputDialog.onAccepted(numberPrefix.model.get(numberPrefix.currentIndex).i, statusInput.text); + inputDialog.close(); + } + onRejected: { + inputDialog.close(); + } + } GridLayout { - rowSpacing: Nheko.paddingMedium - columnSpacing: Nheko.paddingMedium - anchors.margins: Nheko.paddingMedium anchors.fill: parent + anchors.margins: Nheko.paddingMedium + columnSpacing: Nheko.paddingMedium columns: 2 + rowSpacing: Nheko.paddingMedium Label { id: promptLabel - Layout.columnSpan: 2 color: timelineRoot.palette.text } - ComboBox { id: numberPrefix - editable: false delegate: ItemDelegate { @@ -49,255 +57,214 @@ ApplicationWindow { //n=name,i=ISO,p=prefix -- see countries.js.md for source model: ListModel { ListElement { - n: "Afghanistan" i: "AF" + n: "Afghanistan" p: "+93" } - ListElement { - n: "Åland Islands" i: "AX" + n: "Åland Islands" p: "+358 18" } - ListElement { - n: "Albania" i: "AL" + n: "Albania" p: "+355" } - ListElement { - n: "Algeria" i: "DZ" + n: "Algeria" p: "+213" } - ListElement { - n: "American Samoa" i: "AS" + n: "American Samoa" p: "+1 684" } - ListElement { - n: "Andorra" i: "AD" + n: "Andorra" p: "+376" } - ListElement { - n: "Angola" i: "AO" + n: "Angola" p: "+244" } - ListElement { - n: "Anguilla" i: "AI" + n: "Anguilla" p: "+1 264" } - ListElement { - n: "Antigua and Barbuda" i: "AG" + n: "Antigua and Barbuda" p: "+1 268" } - ListElement { - n: "Argentina" i: "AR" + n: "Argentina" p: "+54" } - ListElement { - n: "Armenia" i: "AM" + n: "Armenia" p: "+374" } - ListElement { - n: "Aruba" i: "AW" + n: "Aruba" p: "+297" } - ListElement { - n: "Ascension" i: "SH" + n: "Ascension" p: "+247" } - ListElement { - n: "Australia" i: "AU" + n: "Australia" p: "+61" } - ListElement { - n: "Australian Antarctic Territory" i: "AQ" + n: "Australian Antarctic Territory" p: "+672 1" } //ListElement{n:"Australian External Territories";i:"";p:"+672"} // NO ISO - ListElement { - n: "Austria" i: "AT" + n: "Austria" p: "+43" } - ListElement { - n: "Azerbaijan" i: "AZ" + n: "Azerbaijan" p: "+994" } - ListElement { - n: "Bahamas" i: "BS" + n: "Bahamas" p: "+1 242" } - ListElement { - n: "Bahrain" i: "BH" + n: "Bahrain" p: "+973" } - ListElement { - n: "Bangladesh" i: "BD" + n: "Bangladesh" p: "+880" } - ListElement { - n: "Barbados" i: "BB" + n: "Barbados" p: "+1 246" } - ListElement { - n: "Barbuda" i: "AG" + n: "Barbuda" p: "+1 268" } - ListElement { - n: "Belarus" i: "BY" + n: "Belarus" p: "+375" } - ListElement { - n: "Belgium" i: "BE" + n: "Belgium" p: "+32" } - ListElement { - n: "Belize" i: "BZ" + n: "Belize" p: "+501" } - ListElement { - n: "Benin" i: "BJ" + n: "Benin" p: "+229" } - ListElement { - n: "Bermuda" i: "BM" + n: "Bermuda" p: "+1 441" } - ListElement { - n: "Bhutan" i: "BT" + n: "Bhutan" p: "+975" } - ListElement { - n: "Bolivia" i: "BO" + n: "Bolivia" p: "+591" } - ListElement { - n: "Bonaire" i: "BQ" + n: "Bonaire" p: "+599 7" } - ListElement { - n: "Bosnia and Herzegovina" i: "BA" + n: "Bosnia and Herzegovina" p: "+387" } - ListElement { - n: "Botswana" i: "BW" + n: "Botswana" p: "+267" } - ListElement { - n: "Brazil" i: "BR" + n: "Brazil" p: "+55" } - ListElement { - n: "British Indian Ocean Territory" i: "IO" + n: "British Indian Ocean Territory" p: "+246" } - ListElement { - n: "Brunei Darussalam" i: "BN" + n: "Brunei Darussalam" p: "+673" } - ListElement { - n: "Bulgaria" i: "BG" + n: "Bulgaria" p: "+359" } - ListElement { - n: "Burkina Faso" i: "BF" + n: "Burkina Faso" p: "+226" } - ListElement { - n: "Burundi" i: "BI" + n: "Burundi" p: "+257" } - ListElement { - n: "Cambodia" i: "KH" + n: "Cambodia" p: "+855" } - ListElement { - n: "Cameroon" i: "CM" + n: "Cameroon" p: "+237" } - ListElement { - n: "Canada" i: "CA" + n: "Canada" p: "+1" } - ListElement { - n: "Cape Verde" i: "CV" + n: "Cape Verde" p: "+238" } //ListElement{n:"Caribbean Netherlands";i:"";p:"+599 3"} // NO ISO @@ -305,1440 +272,1188 @@ ApplicationWindow { //ListElement{n:"Caribbean Netherlands";i:"";p:"+599 4"} // NO ISO //ListElement{n:"Caribbean Netherlands";i:"";p:"+599 7"} // NO ISO ListElement { - n: "Cayman Islands" i: "KY" + n: "Cayman Islands" p: "+1 345" } - ListElement { - n: "Central African Republic" i: "CF" + n: "Central African Republic" p: "+236" } - ListElement { - n: "Chad" i: "TD" + n: "Chad" p: "+235" } - ListElement { - n: "Chatham Island (New Zealand)" i: "NZ" + n: "Chatham Island (New Zealand)" p: "+64" } - ListElement { - n: "Chile" i: "CL" + n: "Chile" p: "+56" } - ListElement { - n: "China" i: "CN" + n: "China" p: "+86" } - ListElement { - n: "Christmas Island" i: "CX" + n: "Christmas Island" p: "+61 89164" } - ListElement { - n: "Cocos (Keeling) Islands" i: "CC" + n: "Cocos (Keeling) Islands" p: "+61 89162" } - ListElement { - n: "Colombia" i: "CO" + n: "Colombia" p: "+57" } - ListElement { - n: "Comoros" i: "KM" + n: "Comoros" p: "+269" } - ListElement { - n: "Congo (Democratic Republic of the)" i: "CD" + n: "Congo (Democratic Republic of the)" p: "+243" } - ListElement { - n: "Congo" i: "CG" + n: "Congo" p: "+242" } - ListElement { - n: "Cook Islands" i: "CK" + n: "Cook Islands" p: "+682" } - ListElement { - n: "Costa Rica" i: "CR" + n: "Costa Rica" p: "+506" } - ListElement { - n: "Côte d'Ivoire" i: "CI" + n: "Côte d'Ivoire" p: "+225" } - ListElement { - n: "Croatia" i: "HR" + n: "Croatia" p: "+385" } - ListElement { - n: "Cuba" i: "CU" + n: "Cuba" p: "+53" } - ListElement { - n: "Curaçao" i: "CW" + n: "Curaçao" p: "+599 9" } - ListElement { - n: "Cyprus" i: "CY" + n: "Cyprus" p: "+357" } - ListElement { - n: "Czech Republic" i: "CZ" + n: "Czech Republic" p: "+420" } - ListElement { - n: "Denmark" i: "DK" + n: "Denmark" p: "+45" } //ListElement{n:"Diego Garcia";i:"";p:"+246"} // NO ISO, OCC. BY GB - ListElement { - n: "Djibouti" i: "DJ" + n: "Djibouti" p: "+253" } - ListElement { - n: "Dominica" i: "DM" + n: "Dominica" p: "+1 767" } - ListElement { - n: "Dominican Republic" i: "DO" + n: "Dominican Republic" p: "+1 809" } - ListElement { - n: "Dominican Republic" i: "DO" + n: "Dominican Republic" p: "+1 829" } - ListElement { - n: "Dominican Republic" i: "DO" + n: "Dominican Republic" p: "+1 849" } - ListElement { - n: "Easter Island" i: "CL" + n: "Easter Island" p: "+56" } - ListElement { - n: "Ecuador" i: "EC" + n: "Ecuador" p: "+593" } - ListElement { - n: "Egypt" i: "EG" + n: "Egypt" p: "+20" } - ListElement { - n: "El Salvador" i: "SV" + n: "El Salvador" p: "+503" } - ListElement { - n: "Equatorial Guinea" i: "GQ" + n: "Equatorial Guinea" p: "+240" } - ListElement { - n: "Eritrea" i: "ER" + n: "Eritrea" p: "+291" } - ListElement { - n: "Estonia" i: "EE" + n: "Estonia" p: "+372" } - ListElement { - n: "eSwatini" i: "SZ" + n: "eSwatini" p: "+268" } - ListElement { - n: "Ethiopia" i: "ET" + n: "Ethiopia" p: "+251" } - ListElement { - n: "Falkland Islands (Malvinas)" i: "FK" + n: "Falkland Islands (Malvinas)" p: "+500" } - ListElement { - n: "Faroe Islands" i: "FO" + n: "Faroe Islands" p: "+298" } - ListElement { - n: "Fiji" i: "FJ" + n: "Fiji" p: "+679" } - ListElement { - n: "Finland" i: "FI" + n: "Finland" p: "+358" } - ListElement { - n: "France" i: "FR" + n: "France" p: "+33" } //ListElement{n:"French Antilles";i:"";p:"+596"} // NO ISO - ListElement { - n: "French Guiana" i: "GF" + n: "French Guiana" p: "+594" } - ListElement { - n: "French Polynesia" i: "PF" + n: "French Polynesia" p: "+689" } - ListElement { - n: "Gabon" i: "GA" + n: "Gabon" p: "+241" } - ListElement { - n: "Gambia" i: "GM" + n: "Gambia" p: "+220" } - ListElement { - n: "Georgia" i: "GE" + n: "Georgia" p: "+995" } - ListElement { - n: "Germany" i: "DE" + n: "Germany" p: "+49" } - ListElement { - n: "Ghana" i: "GH" + n: "Ghana" p: "+233" } - ListElement { - n: "Gibraltar" i: "GI" + n: "Gibraltar" p: "+350" } - ListElement { - n: "Greece" i: "GR" + n: "Greece" p: "+30" } - ListElement { - n: "Greenland" i: "GL" + n: "Greenland" p: "+299" } - ListElement { - n: "Grenada" i: "GD" + n: "Grenada" p: "+1 473" } - ListElement { - n: "Guadeloupe" i: "GP" + n: "Guadeloupe" p: "+590" } - ListElement { - n: "Guam" i: "GU" + n: "Guam" p: "+1 671" } - ListElement { - n: "Guatemala" i: "GT" + n: "Guatemala" p: "+502" } - ListElement { - n: "Guernsey" i: "GG" + n: "Guernsey" p: "+44 1481" } - ListElement { - n: "Guernsey" i: "GG" + n: "Guernsey" p: "+44 7781" } - ListElement { - n: "Guernsey" i: "GG" + n: "Guernsey" p: "+44 7839" } - ListElement { - n: "Guernsey" i: "GG" + n: "Guernsey" p: "+44 7911" } - ListElement { - n: "Guinea-Bissau" i: "GW" + n: "Guinea-Bissau" p: "+245" } - ListElement { - n: "Guinea" i: "GN" + n: "Guinea" p: "+224" } - ListElement { - n: "Guyana" i: "GY" + n: "Guyana" p: "+592" } - ListElement { - n: "Haiti" i: "HT" + n: "Haiti" p: "+509" } - ListElement { - n: "Honduras" i: "HN" + n: "Honduras" p: "+504" } - ListElement { - n: "Hong Kong" i: "HK" + n: "Hong Kong" p: "+852" } - ListElement { - n: "Hungary" i: "HU" + n: "Hungary" p: "+36" } - ListElement { - n: "Iceland" i: "IS" + n: "Iceland" p: "+354" } - ListElement { - n: "India" i: "IN" + n: "India" p: "+91" } - ListElement { - n: "Indonesia" i: "ID" + n: "Indonesia" p: "+62" } - ListElement { - n: "Iran" i: "IR" + n: "Iran" p: "+98" } - ListElement { - n: "Iraq" i: "IQ" + n: "Iraq" p: "+964" } - ListElement { - n: "Ireland" i: "IE" + n: "Ireland" p: "+353" } - ListElement { - n: "Isle of Man" i: "IM" + n: "Isle of Man" p: "+44 1624" } - ListElement { - n: "Isle of Man" i: "IM" + n: "Isle of Man" p: "+44 7524" } - ListElement { - n: "Isle of Man" i: "IM" + n: "Isle of Man" p: "+44 7624" } - ListElement { - n: "Isle of Man" i: "IM" + n: "Isle of Man" p: "+44 7924" } - ListElement { - n: "Israel" i: "IL" + n: "Israel" p: "+972" } - ListElement { - n: "Italy" i: "IT" + n: "Italy" p: "+39" } - ListElement { - n: "Jamaica" i: "JM" + n: "Jamaica" p: "+1 876" } - ListElement { - n: "Jan Mayen" i: "SJ" + n: "Jan Mayen" p: "+47 79" } - ListElement { - n: "Japan" i: "JP" + n: "Japan" p: "+81" } - ListElement { - n: "Jersey" i: "JE" + n: "Jersey" p: "+44 1534" } - ListElement { - n: "Jordan" i: "JO" + n: "Jordan" p: "+962" } - ListElement { - n: "Kazakhstan" i: "KZ" + n: "Kazakhstan" p: "+7 6" } - ListElement { - n: "Kazakhstan" i: "KZ" + n: "Kazakhstan" p: "+7 7" } - ListElement { - n: "Kenya" i: "KE" + n: "Kenya" p: "+254" } - ListElement { - n: "Kiribati" i: "KI" + n: "Kiribati" p: "+686" } - ListElement { - n: "Korea (North)" i: "KP" + n: "Korea (North)" p: "+850" } - ListElement { - n: "Korea (South)" i: "KR" + n: "Korea (South)" p: "+82" } // TEMP. CODE - ListElement { - n: "Kosovo" i: "XK" + n: "Kosovo" p: "+383" } - ListElement { - n: "Kuwait" i: "KW" + n: "Kuwait" p: "+965" } - ListElement { - n: "Kyrgyzstan" i: "KG" + n: "Kyrgyzstan" p: "+996" } - ListElement { - n: "Laos" i: "LA" + n: "Laos" p: "+856" } - ListElement { - n: "Latvia" i: "LV" + n: "Latvia" p: "+371" } - ListElement { - n: "Lebanon" i: "LB" + n: "Lebanon" p: "+961" } - ListElement { - n: "Lesotho" i: "LS" + n: "Lesotho" p: "+266" } - ListElement { - n: "Liberia" i: "LR" + n: "Liberia" p: "+231" } - ListElement { - n: "Libya" i: "LY" + n: "Libya" p: "+218" } - ListElement { - n: "Liechtenstein" i: "LI" + n: "Liechtenstein" p: "+423" } - ListElement { - n: "Lithuania" i: "LT" + n: "Lithuania" p: "+370" } - ListElement { - n: "Luxembourg" i: "LU" + n: "Luxembourg" p: "+352" } - ListElement { - n: "Macau (Macao)" i: "MO" + n: "Macau (Macao)" p: "+853" } - ListElement { - n: "Madagascar" i: "MG" + n: "Madagascar" p: "+261" } - ListElement { - n: "Malawi" i: "MW" + n: "Malawi" p: "+265" } - ListElement { - n: "Malaysia" i: "MY" + n: "Malaysia" p: "+60" } - ListElement { - n: "Maldives" i: "MV" + n: "Maldives" p: "+960" } - ListElement { - n: "Mali" i: "ML" + n: "Mali" p: "+223" } - ListElement { - n: "Malta" i: "MT" + n: "Malta" p: "+356" } - ListElement { - n: "Marshall Islands" i: "MH" + n: "Marshall Islands" p: "+692" } - ListElement { - n: "Martinique" i: "MQ" + n: "Martinique" p: "+596" } - ListElement { - n: "Mauritania" i: "MR" + n: "Mauritania" p: "+222" } - ListElement { - n: "Mauritius" i: "MU" + n: "Mauritius" p: "+230" } - ListElement { - n: "Mayotte" i: "YT" + n: "Mayotte" p: "+262 269" } - ListElement { - n: "Mayotte" i: "YT" + n: "Mayotte" p: "+262 639" } - ListElement { - n: "Mexico" i: "MX" + n: "Mexico" p: "+52" } - ListElement { - n: "Micronesia (Federated States of)" i: "FM" + n: "Micronesia (Federated States of)" p: "+691" } - ListElement { - n: "Midway Island (USA)" i: "US" + n: "Midway Island (USA)" p: "+1 808" } - ListElement { - n: "Moldova" i: "MD" + n: "Moldova" p: "+373" } - ListElement { - n: "Monaco" i: "MC" + n: "Monaco" p: "+377" } - ListElement { - n: "Mongolia" i: "MN" + n: "Mongolia" p: "+976" } - ListElement { - n: "Montenegro" i: "ME" + n: "Montenegro" p: "+382" } - ListElement { - n: "Montserrat" i: "MS" + n: "Montserrat" p: "+1 664" } - ListElement { - n: "Morocco" i: "MA" + n: "Morocco" p: "+212" } - ListElement { - n: "Mozambique" i: "MZ" + n: "Mozambique" p: "+258" } - ListElement { - n: "Myanmar" i: "MM" + n: "Myanmar" p: "+95" } // NO OWN ISO, DISPUTED - ListElement { - n: "Nagorno-Karabakh" i: "AZ" + n: "Nagorno-Karabakh" p: "+374 47" } // NO OWN ISO, DISPUTED - ListElement { - n: "Nagorno-Karabakh" i: "AZ" + n: "Nagorno-Karabakh" p: "+374 97" } - ListElement { - n: "Namibia" i: "NA" + n: "Namibia" p: "+264" } - ListElement { - n: "Nauru" i: "NR" + n: "Nauru" p: "+674" } - ListElement { - n: "Nepal" i: "NP" + n: "Nepal" p: "+977" } - ListElement { - n: "Netherlands" i: "NL" + n: "Netherlands" p: "+31" } - ListElement { - n: "Nevis" i: "KN" + n: "Nevis" p: "+1 869" } - ListElement { - n: "New Caledonia" i: "NC" + n: "New Caledonia" p: "+687" } - ListElement { - n: "New Zealand" i: "NZ" + n: "New Zealand" p: "+64" } - ListElement { - n: "Nicaragua" i: "NI" + n: "Nicaragua" p: "+505" } - ListElement { - n: "Nigeria" i: "NG" + n: "Nigeria" p: "+234" } - ListElement { - n: "Niger" i: "NE" + n: "Niger" p: "+227" } - ListElement { - n: "Niue" i: "NU" + n: "Niue" p: "+683" } - ListElement { - n: "Norfolk Island" i: "NF" + n: "Norfolk Island" p: "+672 3" } // OCC. BY TR - ListElement { - n: "Northern Cyprus" i: "CY" + n: "Northern Cyprus" p: "+90 392" } - ListElement { - n: "Northern Ireland" i: "GB" + n: "Northern Ireland" p: "+44 28" } - ListElement { - n: "Northern Mariana Islands" i: "MP" + n: "Northern Mariana Islands" p: "+1 670" } - ListElement { - n: "North Macedonia" i: "MK" + n: "North Macedonia" p: "+389" } - ListElement { - n: "Norway" i: "NO" + n: "Norway" p: "+47" } - ListElement { - n: "Oman" i: "OM" + n: "Oman" p: "+968" } - ListElement { - n: "Pakistan" i: "PK" + n: "Pakistan" p: "+92" } - ListElement { - n: "Palau" i: "PW" + n: "Palau" p: "+680" } - ListElement { - n: "Palestine (State of)" i: "PS" + n: "Palestine (State of)" p: "+970" } - ListElement { - n: "Panama" i: "PA" + n: "Panama" p: "+507" } - ListElement { - n: "Papua New Guinea" i: "PG" + n: "Papua New Guinea" p: "+675" } - ListElement { - n: "Paraguay" i: "PY" + n: "Paraguay" p: "+595" } - ListElement { - n: "Peru" i: "PE" + n: "Peru" p: "+51" } - ListElement { - n: "Philippines" i: "PH" + n: "Philippines" p: "+63" } - ListElement { - n: "Pitcairn Islands" i: "PN" + n: "Pitcairn Islands" p: "+64" } - ListElement { - n: "Poland" i: "PL" + n: "Poland" p: "+48" } - ListElement { - n: "Portugal" i: "PT" + n: "Portugal" p: "+351" } - ListElement { - n: "Puerto Rico" i: "PR" + n: "Puerto Rico" p: "+1 787" } - ListElement { - n: "Puerto Rico" i: "PR" + n: "Puerto Rico" p: "+1 939" } - ListElement { - n: "Qatar" i: "QA" + n: "Qatar" p: "+974" } - ListElement { - n: "Réunion" i: "RE" + n: "Réunion" p: "+262" } - ListElement { - n: "Romania" i: "RO" + n: "Romania" p: "+40" } - ListElement { - n: "Russia" i: "RU" + n: "Russia" p: "+7" } - ListElement { - n: "Rwanda" i: "RW" + n: "Rwanda" p: "+250" } - ListElement { - n: "Saba" i: "BQ" + n: "Saba" p: "+599 4" } - ListElement { - n: "Saint Barthélemy" i: "BL" + n: "Saint Barthélemy" p: "+590" } - ListElement { - n: "Saint Helena" i: "SH" + n: "Saint Helena" p: "+290" } - ListElement { - n: "Saint Kitts and Nevis" i: "KN" + n: "Saint Kitts and Nevis" p: "+1 869" } - ListElement { - n: "Saint Lucia" i: "LC" + n: "Saint Lucia" p: "+1 758" } - ListElement { - n: "Saint Martin (France)" i: "MF" + n: "Saint Martin (France)" p: "+590" } - ListElement { - n: "Saint Pierre and Miquelon" i: "PM" + n: "Saint Pierre and Miquelon" p: "+508" } - ListElement { - n: "Saint Vincent and the Grenadines" i: "VC" + n: "Saint Vincent and the Grenadines" p: "+1 784" } - ListElement { - n: "Samoa" i: "WS" + n: "Samoa" p: "+685" } - ListElement { - n: "San Marino" i: "SM" + n: "San Marino" p: "+378" } - ListElement { - n: "São Tomé and Príncipe" i: "ST" + n: "São Tomé and Príncipe" p: "+239" } - ListElement { - n: "Saudi Arabia" i: "SA" + n: "Saudi Arabia" p: "+966" } - ListElement { - n: "Senegal" i: "SN" + n: "Senegal" p: "+221" } - ListElement { - n: "Serbia" i: "RS" + n: "Serbia" p: "+381" } - ListElement { - n: "Seychelles" i: "SC" + n: "Seychelles" p: "+248" } - ListElement { - n: "Sierra Leone" i: "SL" + n: "Sierra Leone" p: "+232" } - ListElement { - n: "Singapore" i: "SG" + n: "Singapore" p: "+65" } - ListElement { - n: "Sint Eustatius" i: "BQ" + n: "Sint Eustatius" p: "+599 3" } - ListElement { - n: "Sint Maarten (Netherlands)" i: "SX" + n: "Sint Maarten (Netherlands)" p: "+1 721" } - ListElement { - n: "Slovakia" i: "SK" + n: "Slovakia" p: "+421" } - ListElement { - n: "Slovenia" i: "SI" + n: "Slovenia" p: "+386" } - ListElement { - n: "Solomon Islands" i: "SB" + n: "Solomon Islands" p: "+677" } - ListElement { - n: "Somalia" i: "SO" + n: "Somalia" p: "+252" } - ListElement { - n: "South Africa" i: "ZA" + n: "South Africa" p: "+27" } - ListElement { - n: "South Georgia and the South Sandwich Islands" i: "GS" + n: "South Georgia and the South Sandwich Islands" p: "+500" } // NO OWN ISO, DISPUTED - ListElement { - n: "South Ossetia" i: "GE" + n: "South Ossetia" p: "+995 34" } - ListElement { - n: "South Sudan" i: "SS" + n: "South Sudan" p: "+211" } - ListElement { - n: "Spain" i: "ES" + n: "Spain" p: "+34" } - ListElement { - n: "Sri Lanka" i: "LK" + n: "Sri Lanka" p: "+94" } - ListElement { - n: "Sudan" i: "SD" + n: "Sudan" p: "+249" } - ListElement { - n: "Suriname" i: "SR" + n: "Suriname" p: "+597" } - ListElement { - n: "Svalbard" i: "SJ" + n: "Svalbard" p: "+47 79" } - ListElement { - n: "Sweden" i: "SE" + n: "Sweden" p: "+46" } - ListElement { - n: "Switzerland" i: "CH" + n: "Switzerland" p: "+41" } - ListElement { - n: "Syria" i: "SY" + n: "Syria" p: "+963" } - ListElement { - n: "Taiwan" i: "SJ" + n: "Taiwan" p: "+886" } - ListElement { - n: "Tajikistan" i: "TJ" + n: "Tajikistan" p: "+992" } - ListElement { - n: "Tanzania" i: "TZ" + n: "Tanzania" p: "+255" } - ListElement { - n: "Thailand" i: "TH" + n: "Thailand" p: "+66" } - ListElement { - n: "Timor-Leste" i: "TL" + n: "Timor-Leste" p: "+670" } - ListElement { - n: "Togo" i: "TG" + n: "Togo" p: "+228" } - ListElement { - n: "Tokelau" i: "TK" + n: "Tokelau" p: "+690" } - ListElement { - n: "Tonga" i: "TO" + n: "Tonga" p: "+676" } - ListElement { - n: "Transnistria" i: "MD" + n: "Transnistria" p: "+373 2" } - ListElement { - n: "Transnistria" i: "MD" + n: "Transnistria" p: "+373 5" } - ListElement { - n: "Trinidad and Tobago" i: "TT" + n: "Trinidad and Tobago" p: "+1 868" } - ListElement { - n: "Tristan da Cunha" i: "SH" + n: "Tristan da Cunha" p: "+290 8" } - ListElement { - n: "Tunisia" i: "TN" + n: "Tunisia" p: "+216" } - ListElement { - n: "Turkey" i: "TR" + n: "Turkey" p: "+90" } - ListElement { - n: "Turkmenistan" i: "TM" + n: "Turkmenistan" p: "+993" } - ListElement { - n: "Turks and Caicos Islands" i: "TC" + n: "Turks and Caicos Islands" p: "+1 649" } - ListElement { - n: "Tuvalu" i: "TV" + n: "Tuvalu" p: "+688" } - ListElement { - n: "Uganda" i: "UG" + n: "Uganda" p: "+256" } - ListElement { - n: "Ukraine" i: "UA" + n: "Ukraine" p: "+380" } - ListElement { - n: "United Arab Emirates" i: "AE" + n: "United Arab Emirates" p: "+971" } - ListElement { - n: "United Kingdom" i: "GB" + n: "United Kingdom" p: "+44" } - ListElement { - n: "United States" i: "US" + n: "United States" p: "+1" } - ListElement { - n: "Uruguay" i: "UY" + n: "Uruguay" p: "+598" } - ListElement { - n: "Uzbekistan" i: "UZ" + n: "Uzbekistan" p: "+998" } - ListElement { - n: "Vanuatu" i: "VU" + n: "Vanuatu" p: "+678" } - ListElement { - n: "Vatican City State (Holy See)" i: "VA" + n: "Vatican City State (Holy See)" p: "+379" } - ListElement { - n: "Vatican City State (Holy See)" i: "VA" + n: "Vatican City State (Holy See)" p: "+39 06 698" } - ListElement { - n: "Venezuela" i: "VE" + n: "Venezuela" p: "+58" } - ListElement { - n: "Vietnam" i: "VN" + n: "Vietnam" p: "+84" } - ListElement { - n: "Virgin Islands (British)" i: "VG" + n: "Virgin Islands (British)" p: "+1 284" } - ListElement { - n: "Virgin Islands (US)" i: "VI" + n: "Virgin Islands (US)" p: "+1 340" } - ListElement { - n: "Wake Island (USA)" i: "US" + n: "Wake Island (USA)" p: "+1 808" } - ListElement { - n: "Wallis and Futuna" i: "WF" + n: "Wallis and Futuna" p: "+681" } - ListElement { - n: "Yemen" i: "YE" + n: "Yemen" p: "+967" } - ListElement { - n: "Zambia" i: "ZM" + n: "Zambia" p: "+260" } // NO OWN ISO, DISPUTED? - ListElement { - n: "Zanzibar" i: "TZ" + n: "Zanzibar" p: "+255 24" } - ListElement { - n: "Zimbabwe" i: "ZW" + n: "Zimbabwe" p: "+263" } - } - } - MatrixTextField { id: statusInput - Layout.fillWidth: true } - } - - footer: DialogButtonBox { - standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel - onAccepted: { - if (inputDialog.onAccepted) - inputDialog.onAccepted(numberPrefix.model.get(numberPrefix.currentIndex).i, statusInput.text); - - inputDialog.close(); - } - onRejected: { - inputDialog.close(); - } - } - } diff --git a/qml/dialogs/RawMessageDialog.qml b/qml/dialogs/RawMessageDialog.qml index e8d08543..640bc3fc 100644 --- a/qml/dialogs/RawMessageDialog.qml +++ b/qml/dialogs/RawMessageDialog.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.15 import QtQuick.Controls 2.15 import im.nheko @@ -12,44 +10,40 @@ ApplicationWindow { property alias rawMessage: rawMessageView.text - height: 420 - width: 420 - palette: timelineRoot.palette color: timelineRoot.palette.window flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 420 + palette: timelineRoot.palette + width: 420 + + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Ok + + onAccepted: rawMessageRoot.close() + } Shortcut { sequence: StandardKey.Cancel + onActivated: rawMessageRoot.close() } - ScrollView { - anchors.margins: Nheko.paddingMedium anchors.fill: parent - palette: timelineRoot.palette + anchors.margins: Nheko.paddingMedium padding: Nheko.paddingMedium + palette: timelineRoot.palette TextArea { id: rawMessageView - - font: Nheko.monospaceFont() + anchors.fill: parent color: timelineRoot.palette.text + font: Nheko.monospaceFont() readOnly: true textFormat: Text.PlainText - anchors.fill: parent - background: Rectangle { color: timelineRoot.palette.base } - } - } - - footer: DialogButtonBox { - standardButtons: DialogButtonBox.Ok - onAccepted: rawMessageRoot.close() - } - } diff --git a/qml/dialogs/ReadReceipts.qml b/qml/dialogs/ReadReceipts.qml index 7bb7d62f..c9dd97ab 100644 --- a/qml/dialogs/ReadReceipts.qml +++ b/qml/dialogs/ReadReceipts.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 @@ -15,19 +13,25 @@ ApplicationWindow { property ReadReceiptsProxy readReceipts property Room room + color: timelineRoot.palette.window + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint height: 380 - width: 340 minimumHeight: 380 minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium palette: timelineRoot.palette - color: timelineRoot.palette.window - flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + width: 340 + + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Ok + + onAccepted: readReceiptsRoot.close() + } Shortcut { sequence: StandardKey.Cancel + onActivated: readReceiptsRoot.close() } - ColumnLayout { anchors.fill: parent anchors.margins: Nheko.paddingMedium @@ -35,97 +39,78 @@ ApplicationWindow { Label { id: headerTitle - - color: timelineRoot.palette.text Layout.alignment: Qt.AlignCenter - text: qsTr("Read receipts") + color: timelineRoot.palette.text font.pointSize: fontMetrics.font.pointSize * 1.5 + text: qsTr("Read receipts") } - ScrollView { - palette: timelineRoot.palette - padding: Nheko.paddingMedium - ScrollBar.horizontal.visible: false Layout.fillHeight: true - Layout.minimumHeight: 200 Layout.fillWidth: true + Layout.minimumHeight: 200 + ScrollBar.horizontal.visible: false + padding: Nheko.paddingMedium + palette: timelineRoot.palette ListView { id: readReceiptsList - - clip: true boundsBehavior: Flickable.StopAtBounds + clip: true model: readReceipts delegate: ItemDelegate { id: del - - onClicked: room.openUserProfile(model.mxid) - padding: Nheko.paddingMedium - width: ListView.view.width + ToolTip.text: model.mxid + ToolTip.visible: hovered height: receiptLayout.implicitHeight + Nheko.paddingSmall * 2 hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: model.mxid + padding: Nheko.paddingMedium + width: ListView.view.width + background: Rectangle { color: del.hovered ? timelineRoot.palette.dark : readReceiptsRoot.color } + onClicked: room.openUserProfile(model.mxid) + RowLayout { id: receiptLayout - - spacing: Nheko.paddingMedium anchors.fill: parent anchors.margins: Nheko.paddingSmall + spacing: Nheko.paddingMedium Avatar { - width: Nheko.avatarSize - height: Nheko.avatarSize - userid: model.mxid - url: model.avatarUrl.replace("mxc://", "image://MxcImage/") displayName: model.displayName enabled: false + height: Nheko.avatarSize + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + userid: model.mxid + width: Nheko.avatarSize } - ColumnLayout { spacing: Nheko.paddingSmall Label { - text: model.displayName color: TimelineManager.userColor(model ? model.mxid : "", timelineRoot.palette.window) font.pointSize: fontMetrics.font.pointSize + text: model.displayName } - Label { - text: model.timestamp color: timelineRoot.palette.placeholderText font.pointSize: fontMetrics.font.pointSize * 0.9 + text: model.timestamp } - } - Item { Layout.fillWidth: true } - } - NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } - } - } - } - } - - footer: DialogButtonBox { - standardButtons: DialogButtonBox.Ok - onAccepted: readReceiptsRoot.close() - } - } diff --git a/qml/dialogs/RoomDirectory.qml b/qml/dialogs/RoomDirectory.qml index 5b205fc9..0335defa 100644 --- a/qml/dialogs/RoomDirectory.qml +++ b/qml/dialogs/RoomDirectory.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import "../ui" import QtQuick 2.9 import QtQuick.Controls 2.3 @@ -13,181 +11,157 @@ import im.nheko ApplicationWindow { id: roomDirectoryWindow - property RoomDirectoryModel publicRooms + property RoomDirectoryModel publicRooms: RoomDirectoryModel { + } - visible: true - minimumWidth: 340 - minimumHeight: 340 - height: 420 - width: 650 - palette: timelineRoot.palette color: timelineRoot.palette.window - modality: Qt.WindowModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 420 + minimumHeight: 340 + minimumWidth: 340 + modality: Qt.WindowModal + palette: timelineRoot.palette title: qsTr("Explore Public Rooms") + visible: true + width: 650 + + header: RowLayout { + id: searchBarLayout + implicitHeight: roomSearch.height + spacing: Nheko.paddingMedium + width: parent.width + + MatrixTextField { + id: roomSearch + Layout.fillWidth: true + color: timelineRoot.palette.text + focus: true + font.pixelSize: fontMetrics.font.pixelSize + placeholderText: qsTr("Search for public rooms") + selectByMouse: true + + onTextChanged: searchTimer.restart() + } + MatrixTextField { + id: chooseServer + Layout.maximumWidth: 0.3 * header.width + Layout.minimumWidth: 0.3 * header.width + color: timelineRoot.palette.text + placeholderText: qsTr("Choose custom homeserver") + + onTextChanged: publicRooms.setMatrixServer(text) + } + Timer { + id: searchTimer + interval: 350 + + onTriggered: roomDirView.model.setSearchTerm(roomSearch.text) + } + } Shortcut { sequence: StandardKey.Cancel + onActivated: roomDirectoryWindow.close() } - ListView { id: roomDirView - anchors.fill: parent model: publicRooms delegate: Rectangle { id: roomDirDelegate + property int avatarSize: fontMetrics.height * 3.2 property color background: timelineRoot.palette.window property color importantText: timelineRoot.palette.text property color unimportantText: timelineRoot.palette.placeholderText - property int avatarSize: fontMetrics.height * 3.2 color: background height: avatarSize + Nheko.paddingLarge width: ListView.view.width RowLayout { - spacing: Nheko.paddingMedium anchors.fill: parent anchors.margins: Nheko.paddingMedium implicitHeight: textContent.implicitHeight + spacing: Nheko.paddingMedium Avatar { id: roomAvatar - Layout.alignment: Qt.AlignVCenter Layout.rightMargin: Nheko.paddingMedium - width: avatarSize - height: avatarSize - url: model.avatarUrl.replace("mxc://", "image://MxcImage/") - roomid: model.roomid displayName: model.name + height: avatarSize + roomid: model.roomid + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + width: avatarSize } - GridLayout { id: textContent - rows: 2 - columns: 2 - Layout.alignment: Qt.AlignLeft - width: parent.width - avatar.width Layout.preferredWidth: parent.width - avatar.width + columns: 2 + rows: 2 + width: parent.width - avatar.width ElidedLabel { - Layout.row: 0 Layout.column: 0 - Layout.fillWidth:true + Layout.fillWidth: true + Layout.row: 0 color: roomDirDelegate.importantText elideWidth: width fullText: model.name } - Label { id: roomTopic - - color: roomDirDelegate.unimportantText - Layout.row: 1 Layout.column: 0 - font.pointSize: fontMetrics.font.pointSize*0.9 - elide: Text.ElideRight - maximumLineCount: 2 Layout.fillWidth: true + Layout.row: 1 + color: roomDirDelegate.unimportantText + elide: Text.ElideRight + font.pointSize: fontMetrics.font.pointSize * 0.9 + maximumLineCount: 2 text: model.topic verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap } - Label { - Layout.alignment: Qt.AlignHCenter - Layout.row: 0 - Layout.column: 1 id: roomCount - + Layout.alignment: Qt.AlignHCenter + Layout.column: 1 + Layout.row: 0 color: roomDirDelegate.unimportantText - font.pointSize: fontMetrics.font.pointSize*0.9 + font.pointSize: fontMetrics.font.pointSize * 0.9 text: model.numMembers.toString() } - Button { - Layout.row: 1 - Layout.column: 1 id: joinRoomButton + Layout.column: 1 + Layout.row: 1 enabled: model.canJoin text: "Join" + onClicked: publicRooms.joinRoom(model.index) } - } - } - } - footer: Item { anchors.horizontalCenter: parent.horizontalCenter - width: parent.width - visible: !publicRooms.reachedEndOfPagination && publicRooms.loadingMoreRooms + anchors.margins: Nheko.paddingLarge // hacky but works height: loadingSpinner.height + 2 * Nheko.paddingLarge - anchors.margins: Nheko.paddingLarge + visible: !publicRooms.reachedEndOfPagination && publicRooms.loadingMoreRooms + width: parent.width Spinner { id: loadingSpinner - anchors.centerIn: parent anchors.margins: Nheko.paddingLarge - running: visible foreground: timelineRoot.palette.mid + running: visible } - } - } - - publicRooms: RoomDirectoryModel { - } - - header: RowLayout { - id: searchBarLayout - - spacing: Nheko.paddingMedium - width: parent.width - implicitHeight: roomSearch.height - - MatrixTextField { - id: roomSearch - - focus: true - Layout.fillWidth: true - selectByMouse: true - font.pixelSize: fontMetrics.font.pixelSize - color: timelineRoot.palette.text - placeholderText: qsTr("Search for public rooms") - onTextChanged: searchTimer.restart() - - Component.onCompleted: forceActiveFocus() - } - - MatrixTextField { - id: chooseServer - - Layout.minimumWidth: 0.3 * header.width - Layout.maximumWidth: 0.3 * header.width - color: timelineRoot.palette.text - placeholderText: qsTr("Choose custom homeserver") - onTextChanged: publicRooms.setMatrixServer(text) - } - - Timer { - id: searchTimer - - interval: 350 - onTriggered: roomDirView.model.setSearchTerm(roomSearch.text) - } - - } - } diff --git a/qml/dialogs/RoomMembers.qml b/qml/dialogs/RoomMembers.qml index 33379c67..0e0a3bad 100644 --- a/qml/dialogs/RoomMembers.qml +++ b/qml/dialogs/RoomMembers.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import "../ui" import QtQuick 2.12 import QtQuick.Controls 2.12 @@ -17,19 +15,25 @@ ApplicationWindow { property MemberList members property Room room - title: qsTr("Members of %1").arg(members.roomName) - height: 650 - width: 420 - minimumHeight: 420 - palette: timelineRoot.palette color: timelineRoot.palette.window flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 650 + minimumHeight: 420 + palette: timelineRoot.palette + title: qsTr("Members of %1").arg(members.roomName) + width: 420 + + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Ok + + onAccepted: roomMembersRoot.close() + } Shortcut { sequence: StandardKey.Cancel + onActivated: roomMembersRoot.close() } - ColumnLayout { anchors.fill: parent anchors.margins: Nheko.paddingMedium @@ -37,143 +41,135 @@ ApplicationWindow { Avatar { id: roomAvatar - - width: 130 + Layout.alignment: Qt.AlignHCenter + displayName: members.roomName height: width roomid: members.roomId - displayName: members.roomName - Layout.alignment: Qt.AlignHCenter url: members.avatarUrl.replace("mxc://", "image://MxcImage/") + width: 130 + onClicked: TimelineManager.openRoomSettings(members.roomId) } - ElidedLabel { - font.pixelSize: fontMetrics.font.pixelSize * 2 - fullText: qsTr("%n people in %1", "Summary above list of members", members.memberCount).arg(members.roomName) Layout.alignment: Qt.AlignHCenter elideWidth: parent.width - Nheko.paddingMedium + font.pixelSize: fontMetrics.font.pixelSize * 2 + fullText: qsTr("%n people in %1", "Summary above list of members", members.memberCount).arg(members.roomName) } - ImageButton { Layout.alignment: Qt.AlignHCenter - image: ":/icons/icons/ui/add-square-button.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("Invite more people") + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/add-square-button.svg" + onClicked: TimelineManager.openInviteUsers(members.roomId) } - MatrixTextField { id: searchBar - Layout.fillWidth: true placeholderText: qsTr("Search...") - onTextChanged: members.setFilterString(text) Component.onCompleted: forceActiveFocus() + onTextChanged: members.setFilterString(text) } - RowLayout { spacing: Nheko.paddingMedium Label { - text: qsTr("Sort by: ") color: Nheko.colors.text + text: qsTr("Sort by: ") } - ComboBox { - model: ListModel { - ListElement { data: MemberList.Mxid; text: qsTr("User ID") } - ListElement { data: MemberList.DisplayName; text: qsTr("Display name") } - ListElement { data: MemberList.Powerlevel; text: qsTr("Power level") } - } + Layout.fillWidth: true textRole: "text" valueRole: "data" + + model: ListModel { + ListElement { + data: MemberList.Mxid + text: qsTr("User ID") + } + ListElement { + data: MemberList.DisplayName + text: qsTr("Display name") + } + ListElement { + data: MemberList.Powerlevel + text: qsTr("Power level") + } + } + onCurrentValueChanged: members.sortBy(currentValue) - Layout.fillWidth: true } } - ScrollView { - palette: timelineRoot.palette - padding: Nheko.paddingMedium - ScrollBar.horizontal.visible: false Layout.fillHeight: true - Layout.minimumHeight: 200 Layout.fillWidth: true + Layout.minimumHeight: 200 + ScrollBar.horizontal.visible: false + padding: Nheko.paddingMedium + palette: timelineRoot.palette ListView { id: memberList - - clip: true boundsBehavior: Flickable.StopAtBounds + clip: true model: members - delegate: ItemDelegate { id: del - - onClicked: Rooms.currentRoom.openUserProfile(model.mxid) - padding: Nheko.paddingMedium - width: ListView.view.width height: memberLayout.implicitHeight + Nheko.paddingSmall * 2 hoverEnabled: true + padding: Nheko.paddingMedium + width: ListView.view.width + background: Rectangle { color: del.hovered ? timelineRoot.palette.dark : roomMembersRoot.color } + onClicked: Rooms.currentRoom.openUserProfile(model.mxid) + RowLayout { id: memberLayout - - spacing: Nheko.paddingMedium anchors.centerIn: parent + spacing: Nheko.paddingMedium width: parent.width - Nheko.paddingSmall * 2 Avatar { id: avatar - - width: Nheko.avatarSize - height: Nheko.avatarSize - userid: model.mxid - url: model.avatarUrl.replace("mxc://", "image://MxcImage/") displayName: model.displayName enabled: false + height: Nheko.avatarSize + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + userid: model.mxid + width: Nheko.avatarSize } - ColumnLayout { spacing: Nheko.paddingSmall ElidedLabel { - fullText: model.displayName color: TimelineManager.userColor(model ? model.mxid : "", del.background.color) + elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width font.pixelSize: fontMetrics.font.pixelSize - elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width + fullText: model.displayName } - ElidedLabel { - fullText: model.mxid color: del.hovered ? timelineRoot.palette.brightText : timelineRoot.palette.placeholderText - font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9) elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width + font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9) + fullText: model.mxid } - } - Item { Layout.fillWidth: true } - EncryptionIndicator { id: encryptInd - Layout.alignment: Qt.AlignRight - visible: room.isEncrypted - encrypted: room.isEncrypted - trust: encrypted ? model.trustlevel : Crypto.Unverified ToolTip.text: { if (!encrypted) return qsTr("This room is not encrypted!"); - switch (trust) { case Crypto.Verified: return qsTr("This user is verified."); @@ -183,42 +179,30 @@ ApplicationWindow { return qsTr("This user has unverified devices!"); } } + encrypted: room.isEncrypted + trust: encrypted ? model.trustlevel : Crypto.Unverified + visible: room.isEncrypted } - } - NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } - } - footer: Item { - width: parent.width - visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers + anchors.margins: Nheko.paddingMedium // use the default height if it's visible, otherwise no height at all height: membersLoadingSpinner.implicitHeight - anchors.margins: Nheko.paddingMedium + visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers + width: parent.width Spinner { id: membersLoadingSpinner - anchors.centerIn: parent implicitHeight: parent.visible ? 35 : 0 } - } - } - } - } - - footer: DialogButtonBox { - standardButtons: DialogButtonBox.Ok - onAccepted: roomMembersRoot.close() - } - } diff --git a/qml/dialogs/RoomSettings.qml b/qml/dialogs/RoomSettings.qml index 7d66913f..4a416830 100644 --- a/qml/dialogs/RoomSettings.qml +++ b/qml/dialogs/RoomSettings.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import "../ui" import Qt.labs.platform 1.1 as Platform import QtQuick 2.15 @@ -18,112 +16,109 @@ ApplicationWindow { property var roomSettings - minimumWidth: 340 - minimumHeight: 450 - width: 450 - height: 680 - palette: timelineRoot.palette color: timelineRoot.palette.window - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 680 + minimumHeight: 450 + minimumWidth: 340 + modality: Qt.NonModal + palette: timelineRoot.palette title: qsTr("Room Settings") + width: 450 + + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Ok + + onAccepted: close() + } Shortcut { sequence: StandardKey.Cancel + onActivated: roomSettingsDialog.close() } - Flickable { id: flickable - boundsBehavior: Flickable.StopAtBounds anchors.fill: parent + boundsBehavior: Flickable.StopAtBounds clip: true - flickableDirection: Flickable.VerticalFlick - contentWidth: roomSettingsDialog.width contentHeight: contentLayout1.height + contentWidth: roomSettingsDialog.width + flickableDirection: Flickable.VerticalFlick + ColumnLayout { id: contentLayout1 - width: parent.width spacing: Nheko.paddingMedium + width: parent.width Avatar { + Layout.alignment: Qt.AlignHCenter Layout.topMargin: Nheko.paddingMedium - url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/") - roomid: roomSettings.roomId displayName: roomSettings.roomName height: 130 + roomid: roomSettings.roomId + url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/") width: 130 - Layout.alignment: Qt.AlignHCenter + onClicked: { if (roomSettings.canChangeAvatar) roomSettings.updateAvatar(); - } } - Spinner { Layout.alignment: Qt.AlignHCenter - visible: roomSettings.isLoading foreground: timelineRoot.palette.mid running: roomSettings.isLoading + visible: roomSettings.isLoading } - Text { id: errorText - - color: "red" - visible: opacity > 0 - opacity: 0 Layout.alignment: Qt.AlignHCenter - wrapMode: Text.Wrap // somehow still doesn't wrap Layout.fillWidth: true + color: "red" + opacity: 0 + visible: opacity > 0 + wrapMode: Text.Wrap // somehow still doesn't wrap } - SequentialAnimation { id: hideErrorAnimation - running: false PauseAnimation { duration: 4000 } - NumberAnimation { - target: errorText - property: 'opacity' - to: 0 duration: 1000 + property: 'opacity' + target: errorText + to: 0 } - } - Connections { - target: roomSettings function onDisplayError(errorMessage) { errorText.text = errorMessage; errorText.opacity = 1; hideErrorAnimation.restart(); } - } + target: roomSettings + } TextEdit { id: roomName property bool isNameEditingAllowed: false - readOnly: !isNameEditingAllowed - textFormat: isNameEditingAllowed ? TextEdit.PlainText : TextEdit.RichText - text: isNameEditingAllowed ? roomSettings.plainRoomName : roomSettings.roomName - font.pixelSize: fontMetrics.font.pixelSize * 2 - color: timelineRoot.palette.text - Layout.alignment: Qt.AlignHCenter Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - nameChangeButton.anchors.leftMargin - (nameChangeButton.width * 2) + color: timelineRoot.palette.text + font.pixelSize: fontMetrics.font.pixelSize * 2 horizontalAlignment: TextEdit.AlignHCenter - wrapMode: TextEdit.Wrap + readOnly: !isNameEditingAllowed selectByMouse: true + text: isNameEditingAllowed ? roomSettings.plainRoomName : roomSettings.roomName + textFormat: isNameEditingAllowed ? TextEdit.PlainText : TextEdit.RichText + wrapMode: TextEdit.Wrap - Keys.onShortcutOverride: event.key === Qt.Key_Enter Keys.onPressed: { if (event.matches(StandardKey.InsertLineSeparator) || event.matches(StandardKey.InsertParagraphSeparator)) { roomSettings.changeName(roomName.text); @@ -131,18 +126,20 @@ ApplicationWindow { event.accepted = true; } } + Keys.onShortcutOverride: event.key === Qt.Key_Enter ImageButton { id: nameChangeButton - visible: roomSettings.canChangeName - anchors.leftMargin: Nheko.paddingSmall + ToolTip.delay: Nheko.tooltipDelay + ToolTip.text: qsTr("Change name of this room") + ToolTip.visible: hovered anchors.left: roomName.right + anchors.leftMargin: Nheko.paddingSmall anchors.verticalCenter: roomName.verticalCenter hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Change name of this room") - ToolTip.delay: Nheko.tooltipDelay image: roomName.isNameEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg" + visible: roomSettings.canChangeName + onClicked: { if (roomName.isNameEditingAllowed) { roomSettings.changeName(roomName.text); @@ -154,65 +151,60 @@ ApplicationWindow { } } } - } + Label { + Layout.alignment: Qt.AlignHCenter + color: timelineRoot.palette.text + text: qsTr("%n member(s)", "", roomSettings.memberCount) - Label { - text: qsTr("%n member(s)", "", roomSettings.memberCount) - Layout.alignment: Qt.AlignHCenter - color: timelineRoot.palette.text - - TapHandler { - onSingleTapped: TimelineManager.openRoomMembers(Rooms.getRoomById(roomSettings.roomId)) - } - - NhekoCursorShape { - cursorShape: Qt.PointingHandCursor - anchors.fill: parent - } - + TapHandler { + onSingleTapped: TimelineManager.openRoomMembers(Rooms.getRoomById(roomSettings.roomId)) } - + NhekoCursorShape { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + } + } TextArea { id: roomTopic + property bool cut: implicitHeight > 100 + property bool isTopicEditingAllowed: false property bool showMore: false - clip: true - Layout.maximumHeight: showMore? Number.POSITIVE_INFINITY : 100 - Layout.preferredHeight: implicitHeight + Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true Layout.leftMargin: Nheko.paddingLarge + Layout.maximumHeight: showMore ? Number.POSITIVE_INFINITY : 100 + Layout.preferredHeight: implicitHeight Layout.rightMargin: Nheko.paddingLarge - - property bool isTopicEditingAllowed: false - - readOnly: !isTopicEditingAllowed - textFormat: isTopicEditingAllowed ? TextEdit.PlainText : TextEdit.RichText - text: isTopicEditingAllowed ? roomSettings.plainRoomTopic : roomSettings.roomTopic - wrapMode: TextEdit.WordWrap background: null - selectByMouse: !Settings.mobileMode + clip: true color: timelineRoot.palette.text horizontalAlignment: TextEdit.AlignHCenter + readOnly: !isTopicEditingAllowed + selectByMouse: !Settings.mobileMode + text: isTopicEditingAllowed ? roomSettings.plainRoomTopic : roomSettings.roomTopic + textFormat: isTopicEditingAllowed ? TextEdit.PlainText : TextEdit.RichText + wrapMode: TextEdit.WordWrap + onLinkActivated: Nheko.openLink(link) NhekoCursorShape { anchors.fill: parent cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor } - } - ImageButton { id: topicChangeButton Layout.alignment: Qt.AlignHCenter - visible: roomSettings.canChangeTopic - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Change topic of this room") ToolTip.delay: Nheko.tooltipDelay + ToolTip.text: qsTr("Change topic of this room") + ToolTip.visible: hovered + hoverEnabled: true image: roomTopic.isTopicEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg" + visible: roomSettings.canChangeTopic + onClicked: { if (roomTopic.isTopicEditingAllowed) { roomSettings.changeTopic(roomTopic.text); @@ -225,223 +217,207 @@ ApplicationWindow { } } } - Item { - Layout.alignment: Qt.AlignHCenter id: showMorePlaceholder + Layout.alignment: Qt.AlignHCenter Layout.preferredHeight: showMoreButton.height Layout.preferredWidth: showMoreButton.width visible: roomTopic.cut } - GridLayout { + Layout.fillWidth: true + Layout.margins: Nheko.paddingMedium columns: 2 rowSpacing: Nheko.paddingMedium - Layout.margins: Nheko.paddingMedium - Layout.fillWidth: true Label { - text: qsTr("SETTINGS") - font.bold: true color: timelineRoot.palette.text + font.bold: true + text: qsTr("SETTINGS") } - Item { Layout.fillWidth: true } - Label { - text: qsTr("Notifications") Layout.fillWidth: true color: timelineRoot.palette.text + text: qsTr("Notifications") } - ComboBox { - model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")] + Layout.fillWidth: true currentIndex: roomSettings.notifications + model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")] + onActivated: { roomSettings.changeNotifications(index); } - Layout.fillWidth: true - WheelHandler{} // suppress scrolling changing values - } + WheelHandler { + } // suppress scrolling changing values + } Label { - text: qsTr("Room access") Layout.fillWidth: true color: timelineRoot.palette.text + text: qsTr("Room access") } - ComboBox { + Layout.fillWidth: true + currentIndex: roomSettings.accessJoinRules enabled: roomSettings.canChangeJoinRules model: { let opts = [qsTr("Anyone and guests"), qsTr("Anyone"), qsTr("Invited users")]; if (roomSettings.supportsKnocking) opts.push(qsTr("By knocking")); - if (roomSettings.supportsRestricted) opts.push(qsTr("Restricted by membership in other rooms")); - return opts; } - currentIndex: roomSettings.accessJoinRules + onActivated: { roomSettings.changeAccessRules(index); } - Layout.fillWidth: true - WheelHandler{} // suppress scrolling changing values - } + WheelHandler { + } // suppress scrolling changing values + } Label { - text: qsTr("Encryption") color: timelineRoot.palette.text + text: qsTr("Encryption") } - ToggleButton { id: encryptionToggle - + Layout.alignment: Qt.AlignRight checked: roomSettings.isEncryptionEnabled + onCheckedChanged: { if (roomSettings.isEncryptionEnabled) { checked = true; - return ; + return; } confirmEncryptionDialog.open(); } - Layout.alignment: Qt.AlignRight } - Platform.MessageDialog { id: confirmEncryptionDialog - - title: qsTr("End-to-End Encryption") + buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel + modality: Qt.NonModal text: qsTr("Encryption is currently experimental and things might break unexpectedly.
Please take note that it can't be disabled afterwards.") - modality: Qt.NonModal + title: qsTr("End-to-End Encryption") + onAccepted: { if (roomSettings.isEncryptionEnabled) - return ; - + return; roomSettings.enableEncryption(); } onRejected: { encryptionToggle.checked = false; } - buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel } - Label { + color: timelineRoot.palette.text text: qsTr("Sticker & Emote Settings") - color: timelineRoot.palette.text } - Button { - text: qsTr("Change") - ToolTip.text: qsTr("Change what packs are enabled, remove packs or create new ones") - onClicked: TimelineManager.openImagePackSettings(roomSettings.roomId) Layout.alignment: Qt.AlignRight - } + ToolTip.text: qsTr("Change what packs are enabled, remove packs or create new ones") + text: qsTr("Change") + onClicked: TimelineManager.openImagePackSettings(roomSettings.roomId) + } Label { - text: qsTr("Hidden events") color: timelineRoot.palette.text + text: qsTr("Hidden events") } - HiddenEventsDialog { id: hiddenEventsDialog - roomid: roomSettings.roomId roomName: roomSettings.roomName + roomid: roomSettings.roomId } - Button { - text: qsTr("Configure") - ToolTip.text: qsTr("Select events to hide in this room") - onClicked: hiddenEventsDialog.show() Layout.alignment: Qt.AlignRight - } + ToolTip.text: qsTr("Select events to hide in this room") + text: qsTr("Configure") + onClicked: hiddenEventsDialog.show() + } Item { // for adding extra space between sections Layout.fillWidth: true } - Item { // for adding extra space between sections Layout.fillWidth: true } - Label { - text: qsTr("INFO") + color: timelineRoot.palette.text font.bold: true - color: timelineRoot.palette.text + text: qsTr("INFO") } - Item { Layout.fillWidth: true } - Label { - text: qsTr("Internal ID") color: timelineRoot.palette.text + text: qsTr("Internal ID") } - - AbstractButton { // AbstractButton does not allow setting text color + AbstractButton { + // AbstractButton does not allow setting text color Layout.alignment: Qt.AlignRight Layout.fillWidth: true Layout.preferredHeight: idLabel.height - Label { // TextEdit does not trigger onClicked + + onClicked: { + textEdit.selectAll(); + textEdit.copy(); + toolTipTimer.start(); + } + + Label { + // TextEdit does not trigger onClicked id: idLabel - text: roomSettings.roomId - font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 0.8) - color: timelineRoot.palette.text - width: parent.width - horizontalAlignment: Text.AlignRight - wrapMode: Text.WrapAnywhere ToolTip.text: qsTr("Copied to clipboard") ToolTip.visible: toolTipTimer.running - } - TextEdit{ // label does not allow selection - id: textEdit - visible: false + color: timelineRoot.palette.text + font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 0.8) + horizontalAlignment: Text.AlignRight text: roomSettings.roomId + width: parent.width + wrapMode: Text.WrapAnywhere } - onClicked: { - textEdit.selectAll() - textEdit.copy() - toolTipTimer.start() + TextEdit { + // label does not allow selection + id: textEdit + text: roomSettings.roomId + visible: false } Timer { id: toolTipTimer } } - Label { - text: qsTr("Room Version") color: timelineRoot.palette.text + text: qsTr("Room Version") } - Label { - text: roomSettings.roomVersion - font.pixelSize: fontMetrics.font.pixelSize Layout.alignment: Qt.AlignRight color: timelineRoot.palette.text + font.pixelSize: fontMetrics.font.pixelSize + text: roomSettings.roomVersion } - } } } Button { id: showMoreButton anchors.horizontalCenter: flickable.horizontalCenter - y: Math.min(showMorePlaceholder.y+contentLayout1.y-flickable.contentY,flickable.height-height) + text: roomTopic.showMore ? qsTr("show less") : qsTr("show more") visible: roomTopic.cut - text: roomTopic.showMore? qsTr("show less") : qsTr("show more") - onClicked: {roomTopic.showMore = !roomTopic.showMore - console.log(flickable.visibleArea) + y: Math.min(showMorePlaceholder.y + contentLayout1.y - flickable.contentY, flickable.height - height) + + onClicked: { + roomTopic.showMore = !roomTopic.showMore; + console.log(flickable.visibleArea); } } - footer: DialogButtonBox { - standardButtons: DialogButtonBox.Ok - onAccepted: close() - } } diff --git a/qml/dialogs/UserProfile.qml b/qml/dialogs/UserProfile.qml index 512c5f5f..51bcce19 100644 --- a/qml/dialogs/UserProfile.qml +++ b/qml/dialogs/UserProfile.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import "../device-verification" import "../ui" import QtQuick 2.15 @@ -18,101 +16,232 @@ ApplicationWindow { property var profile - height: 650 - width: 420 - minimumWidth: 150 - minimumHeight: 150 - palette: timelineRoot.palette color: timelineRoot.palette.window - title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile") - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 650 + minimumHeight: 150 + minimumWidth: 150 + modality: Qt.NonModal + palette: timelineRoot.palette + title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile") + width: 420 Shortcut { sequence: StandardKey.Cancel + onActivated: userProfileDialog.close() } - ListView { id: devicelist - Layout.fillHeight: true Layout.fillWidth: true - clip: true - spacing: 8 - boundsBehavior: Flickable.StopAtBounds - model: profile.deviceList anchors.fill: parent anchors.margins: 10 + boundsBehavior: Flickable.StopAtBounds + clip: true footerPositioning: ListView.OverlayFooter + model: profile.deviceList + spacing: 8 + delegate: RowLayout { + required property string deviceId + required property string deviceName + required property string lastIp + required property var lastTs + required property int verificationStatus + spacing: 4 + width: devicelist.width + + ColumnLayout { + spacing: 0 + + RowLayout { + Text { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + color: timelineRoot.palette.text + elide: Text.ElideRight + font.bold: true + text: deviceId + } + Image { + Layout.preferredHeight: 16 + Layout.preferredWidth: 16 + source: { + switch (verificationStatus) { + case VerificationStatus.VERIFIED: + return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?green"; + case VerificationStatus.UNVERIFIED: + return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?#d6c020"; + case VerificationStatus.SELF: + return "image://colorimage/:/icons/icons/ui/checkmark.svg?green"; + default: + return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?#d6c020"; + } + } + sourceSize.height: 16 * Screen.devicePixelRatio + sourceSize.width: 16 * Screen.devicePixelRatio + visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE + } + ImageButton { + Layout.alignment: Qt.AlignTop + ToolTip.text: qsTr("Sign out this device.") + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/power-off.svg" + visible: profile.isSelf + + onClicked: profile.signOutDevice(deviceId) + } + } + RowLayout { + id: deviceNameRow + + property bool isEditingAllowed + + TextInput { + id: deviceNameField + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + color: timelineRoot.palette.text + readOnly: !deviceNameRow.isEditingAllowed + selectByMouse: true + text: deviceName + + onAccepted: { + profile.changeDeviceName(deviceId, deviceNameField.text); + deviceNameRow.isEditingAllowed = false; + } + } + ImageButton { + ToolTip.text: qsTr("Change device name.") + ToolTip.visible: hovered + hoverEnabled: true + image: deviceNameRow.isEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg" + visible: profile.isSelf + + onClicked: { + if (deviceNameRow.isEditingAllowed) { + profile.changeDeviceName(deviceId, deviceNameField.text); + deviceNameRow.isEditingAllowed = false; + } else { + deviceNameRow.isEditingAllowed = true; + deviceNameField.focus = true; + deviceNameField.selectAll(); + } + } + } + } + Text { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + color: timelineRoot.palette.text + elide: Text.ElideRight + text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp ? lastIp : "???") + visible: profile.isSelf + } + } + Image { + Layout.preferredHeight: 16 + Layout.preferredWidth: 16 + source: { + switch (verificationStatus) { + case VerificationStatus.VERIFIED: + return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?green"; + case VerificationStatus.UNVERIFIED: + return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?#d6c020"; + case VerificationStatus.SELF: + return "image://colorimage/:/icons/icons/ui/checkmark.svg?green"; + default: + return "image://colorimage/:/icons/icons/ui/shield-filled.svg?red"; + } + } + visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE + } + Button { + id: verifyButton + text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify") + visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled) + + onClicked: { + if (verificationStatus == VerificationStatus.VERIFIED) + profile.unverify(deviceId); + else + profile.verify(deviceId); + } + } + } + footer: DialogButtonBox { + alignment: Qt.AlignRight + standardButtons: DialogButtonBox.Ok + width: devicelist.width + z: 2 + + background: Rectangle { + anchors.fill: parent + color: timelineRoot.palette.window + } + + onAccepted: userProfileDialog.close() + } header: ColumnLayout { id: contentL - - width: devicelist.width spacing: 10 + width: devicelist.width Avatar { id: displayAvatar - - url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") - height: 130 - width: 130 - displayName: profile.displayName - userid: profile.userid Layout.alignment: Qt.AlignHCenter + displayName: profile.displayName + height: 130 + url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") + userid: profile.userid + width: 130 + onClicked: TimelineManager.openImageOverlay(null, profile.avatarUrl, "") ImageButton { - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change avatar globally.") : qsTr("Change avatar. Will only apply to this room.") + ToolTip.visible: hovered anchors.left: displayAvatar.left - anchors.top: displayAvatar.top anchors.leftMargin: Nheko.paddingMedium + anchors.top: displayAvatar.top anchors.topMargin: Nheko.paddingMedium - visible: profile.isSelf + hoverEnabled: true image: ":/icons/icons/ui/edit.svg" + visible: profile.isSelf + onClicked: profile.changeAvatar() } - } - Spinner { Layout.alignment: Qt.AlignHCenter + foreground: timelineRoot.palette.mid running: profile.isLoading visible: profile.isLoading - foreground: timelineRoot.palette.mid } - Text { id: errorText - - color: "red" - visible: opacity > 0 - opacity: 0 Layout.alignment: Qt.AlignHCenter + color: "red" + opacity: 0 + visible: opacity > 0 } - SequentialAnimation { id: hideErrorAnimation - running: false PauseAnimation { duration: 4000 } - NumberAnimation { - target: errorText - property: 'opacity' - to: 0 duration: 1000 + property: 'opacity' + target: errorText + to: 0 } - } - Connections { function onDisplayError(errorMessage) { errorText.text = errorMessage; @@ -122,22 +251,22 @@ ApplicationWindow { target: profile } - TextInput { id: displayUsername property bool isUsernameEditingAllowed - readOnly: !isUsernameEditingAllowed - text: profile.displayName - font.pixelSize: 20 - color: TimelineManager.userColor(profile.userid, timelineRoot.palette.window) - font.bold: true Layout.alignment: Qt.AlignHCenter Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - usernameChangeButton.anchors.leftMargin - (usernameChangeButton.width * 2) + color: TimelineManager.userColor(profile.userid, timelineRoot.palette.window) + font.bold: true + font.pixelSize: 20 horizontalAlignment: TextInput.AlignHCenter - wrapMode: TextInput.Wrap + readOnly: !isUsernameEditingAllowed selectByMouse: true + text: profile.displayName + wrapMode: TextInput.Wrap + onAccepted: { profile.changeUsername(displayUsername.text); displayUsername.isUsernameEditingAllowed = false; @@ -145,14 +274,15 @@ ApplicationWindow { ImageButton { id: usernameChangeButton - visible: profile.isSelf - anchors.leftMargin: Nheko.paddingSmall + ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change display name globally.") : qsTr("Change display name. Will only apply to this room.") + ToolTip.visible: hovered anchors.left: displayUsername.right + anchors.leftMargin: Nheko.paddingSmall anchors.verticalCenter: displayUsername.verticalCenter hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change display name globally.") : qsTr("Change display name. Will only apply to this room.") image: displayUsername.isUsernameEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg" + visible: profile.isSelf + onClicked: { if (displayUsername.isUsernameEditingAllowed) { profile.changeUsername(displayUsername.text); @@ -164,63 +294,54 @@ ApplicationWindow { } } } - } - MatrixText { - text: profile.userid Layout.alignment: Qt.AlignHCenter + text: profile.userid } - RowLayout { - visible: !profile.isGlobalUserProfile Layout.alignment: Qt.AlignHCenter spacing: Nheko.paddingSmall + visible: !profile.isGlobalUserProfile MatrixText { id: displayRoomname - - text: qsTr("Room: %1").arg(profile.room ? profile.room.roomName : "") + Layout.maximumWidth: parent.parent.width - (parent.spacing * 3) - 16 ToolTip.text: qsTr("This is a room-specific profile. The user's name and avatar may be different from their global versions.") ToolTip.visible: ma.hovered - Layout.maximumWidth: parent.parent.width - (parent.spacing * 3) - 16 horizontalAlignment: TextEdit.AlignHCenter + text: qsTr("Room: %1").arg(profile.room ? profile.room.roomName : "") HoverHandler { id: ma } - } - ImageButton { - image: ":/icons/icons/ui/world.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("Open the global profile for this user.") + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/world.svg" + onClicked: profile.openGlobalProfile() } - } - Button { id: verifyUserButton - - text: qsTr("Verify") Layout.alignment: Qt.AlignHCenter enabled: profile.userVerified != Crypto.Verified + text: qsTr("Verify") visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled + onClicked: profile.verify() } - EncryptionIndicator { + Layout.alignment: Qt.AlignHCenter Layout.preferredHeight: 16 Layout.preferredWidth: 16 + ToolTip.visible: false encrypted: profile.userVerificationEnabled trust: profile.userVerified - Layout.alignment: Qt.AlignHCenter - ToolTip.visible: false } - RowLayout { // ImageButton{ // image:":/icons/icons/ui/volume-off-indicator.svg" @@ -234,202 +355,45 @@ ApplicationWindow { // profile.ignoreUser() // } // } - Layout.alignment: Qt.AlignHCenter Layout.bottomMargin: 10 spacing: Nheko.paddingSmall ImageButton { - image: ":/icons/icons/ui/chat.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("Start a private chat.") + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/chat.svg" + onClicked: profile.startChat() } - ImageButton { - image: ":/icons/icons/ui/round-remove-button.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("Kick the user.") - onClicked: profile.kickUser() + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/round-remove-button.svg" visible: !profile.isGlobalUserProfile && profile.room.permissions.canKick() - } + onClicked: profile.kickUser() + } ImageButton { - image: ":/icons/icons/ui/ban.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("Ban the user.") - onClicked: profile.banUser() - visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan() - } - - ImageButton { - image: ":/icons/icons/ui/refresh.svg" - hoverEnabled: true ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/ban.svg" + visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan() + + onClicked: profile.banUser() + } + ImageButton { ToolTip.text: qsTr("Refresh device list.") + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/refresh.svg" + onClicked: profile.refreshDevices() } - } - } - - delegate: RowLayout { - required property int verificationStatus - required property string deviceId - required property string deviceName - required property string lastIp - required property var lastTs - - width: devicelist.width - spacing: 4 - - ColumnLayout { - spacing: 0 - - RowLayout { - Text { - Layout.fillWidth: true - Layout.alignment: Qt.AlignLeft - elide: Text.ElideRight - font.bold: true - color: timelineRoot.palette.text - text: deviceId - } - - Image { - Layout.preferredHeight: 16 - Layout.preferredWidth: 16 - visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE - sourceSize.height: 16 * Screen.devicePixelRatio - sourceSize.width: 16 * Screen.devicePixelRatio - source: { - switch (verificationStatus) { - case VerificationStatus.VERIFIED: - return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?green"; - case VerificationStatus.UNVERIFIED: - return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?#d6c020"; - case VerificationStatus.SELF: - return "image://colorimage/:/icons/icons/ui/checkmark.svg?green"; - default: - return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?#d6c020"; - } - } - } - - ImageButton { - Layout.alignment: Qt.AlignTop - image: ":/icons/icons/ui/power-off.svg" - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Sign out this device.") - onClicked: profile.signOutDevice(deviceId) - visible: profile.isSelf - } - - } - - RowLayout { - id: deviceNameRow - - property bool isEditingAllowed - - TextInput { - id: deviceNameField - - readOnly: !deviceNameRow.isEditingAllowed - text: deviceName - color: timelineRoot.palette.text - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - selectByMouse: true - onAccepted: { - profile.changeDeviceName(deviceId, deviceNameField.text); - deviceNameRow.isEditingAllowed = false; - } - } - - ImageButton { - visible: profile.isSelf - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Change device name.") - image: deviceNameRow.isEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg" - onClicked: { - if (deviceNameRow.isEditingAllowed) { - profile.changeDeviceName(deviceId, deviceNameField.text); - deviceNameRow.isEditingAllowed = false; - } else { - deviceNameRow.isEditingAllowed = true; - deviceNameField.focus = true; - deviceNameField.selectAll(); - } - } - } - - } - - Text { - visible: profile.isSelf - Layout.fillWidth: true - Layout.alignment: Qt.AlignLeft - elide: Text.ElideRight - color: timelineRoot.palette.text - text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp ? lastIp : "???") - } - - } - - Image { - Layout.preferredHeight: 16 - Layout.preferredWidth: 16 - visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE - source: { - switch (verificationStatus) { - case VerificationStatus.VERIFIED: - return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?green"; - case VerificationStatus.UNVERIFIED: - return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?#d6c020"; - case VerificationStatus.SELF: - return "image://colorimage/:/icons/icons/ui/checkmark.svg?green"; - default: - return "image://colorimage/:/icons/icons/ui/shield-filled.svg?red"; - } - } - } - - Button { - id: verifyButton - - visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled) - text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify") - onClicked: { - if (verificationStatus == VerificationStatus.VERIFIED) - profile.unverify(deviceId); - else - profile.verify(deviceId); - } - } - - } - - footer: DialogButtonBox { - z: 2 - width: devicelist.width - alignment: Qt.AlignRight - standardButtons: DialogButtonBox.Ok - onAccepted: userProfileDialog.close() - - background: Rectangle { - anchors.fill: parent - color: timelineRoot.palette.window - } - - } - } - } diff --git a/qml/emoji/EmojiPicker.qml b/qml/emoji/EmojiPicker.qml index 64ab4e29..c2441704 100644 --- a/qml/emoji/EmojiPicker.qml +++ b/qml/emoji/EmojiPicker.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import "../" import QtQuick 2.9 import QtQuick.Controls 2.3 @@ -15,12 +13,12 @@ Menu { property var callback property var colors - property alias model: gridView.model - property var textArea property string emojiCategory: "people" property real highlightHue: timelineRoot.palette.highlight.hslHue - property real highlightSat: timelineRoot.palette.highlight.hslSaturation property real highlightLight: timelineRoot.palette.highlight.hslLightness + property real highlightSat: timelineRoot.palette.highlight.hslSaturation + property alias model: gridView.model + property var textArea function show(showAt, callback) { console.debug("Showing emojiPicker"); @@ -28,13 +26,13 @@ Menu { popup(showAt ? showAt : null); } - margins: 0 bottomPadding: 1 - leftPadding: 1 - rightPadding: 1 - modal: true - focus: true closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + focus: true + leftPadding: 1 + margins: 0 + modal: true + rightPadding: 1 //height: columnView.implicitHeight + 4 //width: columnView.implicitWidth width: 7 * 52 + 20 @@ -46,28 +44,27 @@ Menu { ColumnLayout { id: columnView - - spacing: 0 - anchors.leftMargin: 3 - anchors.rightMargin: 3 anchors.bottom: parent.bottom anchors.left: parent.left + anchors.leftMargin: 3 anchors.right: parent.right + anchors.rightMargin: 3 anchors.topMargin: 2 + spacing: 0 // Search field TextField { id: emojiSearch - - Layout.topMargin: 3 Layout.preferredWidth: 7 * 52 + 20 - 6 - palette: timelineRoot.palette + Layout.topMargin: 3 background: null - placeholderTextColor: timelineRoot.palette.placeholderText color: timelineRoot.palette.text + palette: timelineRoot.palette placeholderText: qsTr("Search") - selectByMouse: true + placeholderTextColor: timelineRoot.palette.placeholderText rightPadding: clearSearch.width + selectByMouse: true + onTextChanged: searchTimer.restart() onVisibleChanged: { if (visible) @@ -78,74 +75,71 @@ Menu { Timer { id: searchTimer - interval: 350 // tweak as needed? + onTriggered: { emojiPopup.model.searchString = emojiSearch.text; emojiPopup.model.category = Emoji.Category.Search; } } - ToolButton { id: clearSearch - - visible: emojiSearch.text !== '' - icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) - focusPolicy: Qt.NoFocus - onClicked: emojiSearch.clear() - hoverEnabled: true background: null + focusPolicy: Qt.NoFocus + hoverEnabled: true + icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) + visible: emojiSearch.text !== '' + + onClicked: emojiSearch.clear() anchors { - verticalCenter: parent.verticalCenter right: parent.right + verticalCenter: parent.verticalCenter } // clear the default hover effects. - Image { height: parent.height - 2 * Nheko.paddingSmall - width: height source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) + width: height anchors { - verticalCenter: parent.verticalCenter - right: parent.right margins: Nheko.paddingSmall + right: parent.right + verticalCenter: parent.verticalCenter } - } - } - } // emoji grid GridView { id: gridView - + Layout.leftMargin: 4 Layout.preferredHeight: cellHeight * 5 Layout.preferredWidth: 7 * 52 + 20 - Layout.leftMargin: 4 - cellWidth: 52 - cellHeight: 52 boundsBehavior: Flickable.StopAtBounds + cacheBuffer: 500 + cellHeight: 52 + cellWidth: 52 clip: true currentIndex: -1 // prevent sorting from stealing focus - cacheBuffer: 500 + ScrollBar.vertical: ScrollBar { + id: emojiScroll + } // Individual emoji delegate: AbstractButton { - width: 48 - height: 48 - hoverEnabled: true ToolTip.text: model.shortName ToolTip.visible: hovered - // TODO: maybe add favorites at some point? - onClicked: { - console.debug("Picked " + model.unicode); - emojiPopup.close(); - callback(model.unicode); + height: 48 + hoverEnabled: true + width: 48 + + background: Rectangle { + anchors.fill: parent + color: hovered ? timelineRoot.palette.highlight : 'transparent' + radius: 5 } // give the emoji a little oomf @@ -159,97 +153,73 @@ Menu { // color: "#80000000" // source: parent.contentItem // } - contentItem: Text { - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + color: timelineRoot.palette.text font.family: Settings.emojiFont font.pixelSize: 36 + horizontalAlignment: Text.AlignHCenter text: model.unicode.replace('\ufe0f', '') - color: timelineRoot.palette.text + verticalAlignment: Text.AlignVCenter } - background: Rectangle { - anchors.fill: parent - color: hovered ? timelineRoot.palette.highlight : 'transparent' - radius: 5 + // TODO: maybe add favorites at some point? + onClicked: { + console.debug("Picked " + model.unicode); + emojiPopup.close(); + callback(model.unicode); } - } - - ScrollBar.vertical: ScrollBar { - id: emojiScroll - } - } // Separator Rectangle { - visible: emojiSearch.text === '' Layout.fillWidth: true Layout.preferredHeight: 1 color: emojiPopup.Nheko.theme.separator + visible: emojiSearch.text === '' } // Category picker row RowLayout { - visible: emojiSearch.text === '' + Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.bottomMargin: 0 Layout.preferredHeight: 42 implicitHeight: 42 - Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + visible: emojiSearch.text === '' // Display the normal categories Repeater { - model: [ // TODO: Would like to get 'simple' icons for the categories { - image: ":/icons/icons/emoji-categories/people.svg", - category: Emoji.Category.People - }, - - { - image: ":/icons/icons/emoji-categories/nature.svg", - category: Emoji.Category.Nature - }, - - { - image: ":/icons/icons/emoji-categories/foods.svg", - category: Emoji.Category.Food - }, - - { - image: ":/icons/icons/emoji-categories/activity.svg", - category: Emoji.Category.Activity - }, - - { - image: ":/icons/icons/emoji-categories/travel.svg", - category: Emoji.Category.Travel - }, - - { - image: ":/icons/icons/emoji-categories/objects.svg", - category: Emoji.Category.Objects - }, - - { - image: ":/icons/icons/emoji-categories/symbols.svg", - category: Emoji.Category.Symbols - }, - - { - image: ":/icons/icons/emoji-categories/flags.svg", - category: Emoji.Category.Flags - } - - ] + "image": ":/icons/icons/emoji-categories/people.svg", + "category": Emoji.Category.People + }, { + "image": ":/icons/icons/emoji-categories/nature.svg", + "category": Emoji.Category.Nature + }, { + "image": ":/icons/icons/emoji-categories/foods.svg", + "category": Emoji.Category.Food + }, { + "image": ":/icons/icons/emoji-categories/activity.svg", + "category": Emoji.Category.Activity + }, { + "image": ":/icons/icons/emoji-categories/travel.svg", + "category": Emoji.Category.Travel + }, { + "image": ":/icons/icons/emoji-categories/objects.svg", + "category": Emoji.Category.Objects + }, { + "image": ":/icons/icons/emoji-categories/symbols.svg", + "category": Emoji.Category.Symbols + }, { + "image": ":/icons/icons/emoji-categories/flags.svg", + "category": Emoji.Category.Flags + }] delegate: AbstractButton { - Layout.preferredWidth: 36 Layout.preferredHeight: 36 - hoverEnabled: true + Layout.preferredWidth: 36 ToolTip.text: { switch (modelData.category) { case Emoji.Category.People: @@ -271,6 +241,27 @@ Menu { } } ToolTip.visible: hovered + hoverEnabled: true + + background: Rectangle { + anchors.fill: parent + border.color: emojiPopup.model.category === modelData.category ? timelineRoot.palette.highlight : 'transparent' + color: emojiPopup.model.category === modelData.category ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : 'transparent' + radius: 5 + } + contentItem: Image { + fillMode: Image.Pad + height: 32 + horizontalAlignment: Image.AlignHCenter + mipmap: true + smooth: true + source: "image://colorimage/" + modelData.image + "?" + (hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) + sourceSize.height: 32 * Screen.devicePixelRatio + sourceSize.width: 32 * Screen.devicePixelRatio + verticalAlignment: Image.AlignVCenter + width: 32 + } + onClicked: { //emojiPopup.model.category = model.category; gridView.positionViewAtIndex(emojiPopup.model.sourceModel.categoryToIndex(modelData.category), GridView.Beginning); @@ -278,40 +269,14 @@ Menu { MouseArea { id: mouseArea - anchors.fill: parent - onPressed: mouse.accepted = false cursorShape: Qt.PointingHandCursor - } - contentItem: Image { - horizontalAlignment: Image.AlignHCenter - verticalAlignment: Image.AlignVCenter - fillMode: Image.Pad - height: 32 - width: 32 - smooth: true - mipmap: true - sourceSize.width: 32 * Screen.devicePixelRatio - sourceSize.height: 32 * Screen.devicePixelRatio - source: "image://colorimage/" + modelData.image + "?" + (hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) + onPressed: mouse.accepted = false } - - background: Rectangle { - anchors.fill: parent - color: emojiPopup.model.category === modelData.category ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : 'transparent' - radius: 5 - border.color: emojiPopup.model.category === modelData.category ? timelineRoot.palette.highlight : 'transparent' - } - } - } - } - } - } - } diff --git a/qml/emoji/StickerPicker.qml b/qml/emoji/StickerPicker.qml index f8f436e6..716bf2a0 100644 --- a/qml/emoji/StickerPicker.qml +++ b/qml/emoji/StickerPicker.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import "../" import QtQuick 2.9 import QtQuick.Controls 2.3 @@ -14,15 +12,15 @@ Menu { property var callback property var colors - property string roomid - property alias model: gridView.model - property var textArea property real highlightHue: timelineRoot.palette.highlight.hslHue - property real highlightSat: timelineRoot.palette.highlight.hslSaturation property real highlightLight: timelineRoot.palette.highlight.hslLightness + property real highlightSat: timelineRoot.palette.highlight.hslSaturation + property alias model: gridView.model + property string roomid readonly property int stickerDim: 128 readonly property int stickerDimPad: 128 + Nheko.paddingSmall readonly property int stickersPerRow: 3 + property var textArea function show(showAt, roomid_, callback) { console.debug("Showing sticker picker"); @@ -31,13 +29,13 @@ Menu { popup(showAt ? showAt : null); } - margins: 0 bottomPadding: 1 - leftPadding: 1 - rightPadding: 1 - modal: true - focus: true closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + focus: true + leftPadding: 1 + margins: 0 + modal: true + rightPadding: 1 width: stickersPerRow * stickerDimPad + 20 Rectangle { @@ -47,28 +45,27 @@ Menu { ColumnLayout { id: columnView - - spacing: 0 - anchors.leftMargin: 3 - anchors.rightMargin: 3 anchors.bottom: parent.bottom anchors.left: parent.left + anchors.leftMargin: 3 anchors.right: parent.right + anchors.rightMargin: 3 anchors.topMargin: 2 + spacing: 0 // Search field TextField { id: emojiSearch - - Layout.topMargin: 3 Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - 6 - palette: timelineRoot.palette + Layout.topMargin: 3 background: null - placeholderTextColor: timelineRoot.palette.placeholderText color: timelineRoot.palette.text + palette: timelineRoot.palette placeholderText: qsTr("Search") - selectByMouse: true + placeholderTextColor: timelineRoot.palette.placeholderText rightPadding: clearSearch.width + selectByMouse: true + onTextChanged: searchTimer.restart() onVisibleChanged: { if (visible) @@ -79,97 +76,85 @@ Menu { Timer { id: searchTimer - interval: 350 // tweak as needed? + onTriggered: stickerPopup.model.searchString = emojiSearch.text } - ToolButton { id: clearSearch - - visible: emojiSearch.text !== '' - icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) - focusPolicy: Qt.NoFocus - onClicked: emojiSearch.clear() - hoverEnabled: true background: null + focusPolicy: Qt.NoFocus + hoverEnabled: true + icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) + visible: emojiSearch.text !== '' + + onClicked: emojiSearch.clear() anchors { - verticalCenter: parent.verticalCenter right: parent.right + verticalCenter: parent.verticalCenter } // clear the default hover effects. - Image { height: parent.height - 2 * Nheko.paddingSmall - width: height source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) + width: height anchors { - verticalCenter: parent.verticalCenter - right: parent.right margins: Nheko.paddingSmall + right: parent.right + verticalCenter: parent.verticalCenter } - } - } - } // emoji grid GridView { id: gridView - - model: roomid ? TimelineManager.completerFor("stickers", roomid) : null + Layout.leftMargin: 4 Layout.preferredHeight: cellHeight * 3.5 Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Layout.leftMargin: 4 - cellWidth: stickerDimPad - cellHeight: stickerDimPad boundsBehavior: Flickable.StopAtBounds + cacheBuffer: 500 + cellHeight: stickerDimPad + cellWidth: stickerDimPad clip: true currentIndex: -1 // prevent sorting from stealing focus - cacheBuffer: 500 + model: roomid ? TimelineManager.completerFor("stickers", roomid) : null + ScrollBar.vertical: ScrollBar { + id: emojiScroll + } // Individual emoji delegate: AbstractButton { - width: stickerDim - height: stickerDim - hoverEnabled: true ToolTip.text: ":" + model.shortcode + ": - " + model.body ToolTip.visible: hovered - // TODO: maybe add favorites at some point? - onClicked: { - console.debug("Picked " + model.shortcode); - stickerPopup.close(); - callback(model.originalRow); - } - - contentItem: Image { - height: stickerDim - width: stickerDim - source: model.url.replace("mxc://", "image://MxcImage/") + "?scale" - fillMode: Image.PreserveAspectFit - } + height: stickerDim + hoverEnabled: true + width: stickerDim background: Rectangle { anchors.fill: parent color: hovered ? timelineRoot.palette.highlight : 'transparent' radius: 5 } + contentItem: Image { + fillMode: Image.PreserveAspectFit + height: stickerDim + source: model.url.replace("mxc://", "image://MxcImage/") + "?scale" + width: stickerDim + } + // TODO: maybe add favorites at some point? + onClicked: { + console.debug("Picked " + model.shortcode); + stickerPopup.close(); + callback(model.originalRow); + } } - - ScrollBar.vertical: ScrollBar { - id: emojiScroll - } - } - } - } - } diff --git a/qml/pages/LoginPage.qml b/qml/pages/LoginPage.qml index 1941c577..2aa2fcdd 100644 --- a/qml/pages/LoginPage.qml +++ b/qml/pages/LoginPage.qml @@ -1,189 +1,172 @@ // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.2 import QtQuick.Window 2.15 import im.nheko -import "../components/" -import "../ui/" +import "../components" +import "../ui" import "../" Item { id: loginPage - property int maxExpansion: 400 property string error: login.error + property int maxExpansion: 400 Login { id: login } - ScrollView { id: scroll - - clip: false - palette: timelineRoot.palette ScrollBar.horizontal.visible: false anchors.left: parent.left + anchors.margins: Nheko.paddingLarge anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - height: Math.min(loginPage.height, col.implicitHeight) - anchors.margins: Nheko.paddingLarge - + clip: false contentWidth: availableWidth + height: Math.min(loginPage.height, col.implicitHeight) + palette: timelineRoot.palette ColumnLayout { id: col - - spacing: Nheko.paddingMedium - anchors.horizontalCenter: parent.horizontalCenter - width: Math.min(loginPage.maxExpansion, scroll.width- Nheko.paddingLarge*2) + spacing: Nheko.paddingMedium + width: Math.min(loginPage.maxExpansion, scroll.width - Nheko.paddingLarge * 2) Image { Layout.alignment: Qt.AlignHCenter - source: "qrc:/logos/login.png" height: 128 + source: "qrc:/logos/login.png" width: 128 } - RowLayout { + Layout.fillWidth: true spacing: Nheko.paddingLarge - Layout.fillWidth: true MatrixTextField { id: matrixIdLabel + Keys.forwardTo: [pwBtn, ssoRepeater] + ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :.\nYou can also put your homeserver address there, if your server doesn't support .well-known lookup.\nExample: @user:server.my\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.") label: qsTr("Matrix ID") placeholderText: qsTr("e.g @joe:matrix.org") + onEditingFinished: login.mxid = text - - ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :.\nYou can also put your homeserver address there, if your server doesn't support .well-known lookup.\nExample: @user:server.my\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.") - Keys.forwardTo: [pwBtn, ssoRepeater] } - - Spinner { - height: matrixIdLabel.height/2 Layout.alignment: Qt.AlignBottom - - visible: running - running: login.lookingUpHs foreground: timelineRoot.palette.mid + height: matrixIdLabel.height / 2 + running: login.lookingUpHs + visible: running } } - MatrixText { - textFormat: Text.PlainText color: Nheko.theme.error text: login.mxidError + textFormat: Text.PlainText visible: text } - MatrixTextField { id: passwordLabel - Layout.fillWidth: true - label: qsTr("Password") - echoMode: TextInput.Password - ToolTip.text: qsTr("Your password.") - visible: login.passwordSupported Keys.forwardTo: [pwBtn, ssoRepeater] + Layout.fillWidth: true + ToolTip.text: qsTr("Your password.") + echoMode: TextInput.Password + label: qsTr("Password") + visible: login.passwordSupported } - MatrixTextField { id: deviceNameLabel + Keys.forwardTo: [pwBtn, ssoRepeater] Layout.fillWidth: true + ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.") label: qsTr("Device name") placeholderText: login.initialDeviceName() - ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.") - Keys.forwardTo: [pwBtn, ssoRepeater] } - MatrixTextField { id: hsLabel - enabled: visible - visible: login.homeserverNeeded - + Keys.forwardTo: [pwBtn, ssoRepeater] Layout.fillWidth: true + ToolTip.text: qsTr("The address that can be used to contact you homeservers client API.\nExample: https://server.my:8787") + enabled: visible label: qsTr("Homeserver address") placeholderText: qsTr("server.my:8787") text: login.homeserver - onEditingFinished: login.homeserver = text - ToolTip.text: qsTr("The address that can be used to contact you homeservers client API.\nExample: https://server.my:8787") - Keys.forwardTo: [pwBtn, ssoRepeater] - } + visible: login.homeserverNeeded + onEditingFinished: login.homeserver = text + } Item { - height: Nheko.avatarSize Layout.fillWidth: true + height: Nheko.avatarSize Spinner { - height: parent.height anchors.centerIn: parent - - visible: running - running: login.loggingIn foreground: timelineRoot.palette.mid + height: parent.height + running: login.loggingIn + visible: running } } - MatrixText { - textFormat: Text.PlainText color: Nheko.theme.error text: loginPage.error + textFormat: Text.PlainText visible: text } - FlatButton { id: pwBtn - visible: login.passwordSupported - enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text - Layout.alignment: Qt.AlignHCenter - text: qsTr("LOGIN") function pwLogin() { - login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text) + login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text); } - onClicked: pwBtn.pwLogin() + + Keys.enabled: pwBtn.enabled && login.passwordSupported + Layout.alignment: Qt.AlignHCenter + enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text + text: qsTr("LOGIN") + visible: login.passwordSupported + Keys.onEnterPressed: pwBtn.pwLogin() Keys.onReturnPressed: pwBtn.pwLogin() - Keys.enabled: pwBtn.enabled && login.passwordSupported + onClicked: pwBtn.pwLogin() } - Repeater { id: ssoRepeater - model: login.identityProviders delegate: FlatButton { id: ssoBtn - visible: login.ssoSupported - enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text - Layout.alignment: Qt.AlignHCenter - text: modelData.name - iconImage: modelData.avatarUrl.replace("mxc://", "image://MxcImage/") function ssoLogin() { - login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, modelData.id, deviceNameLabel.text) + login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, modelData.id, deviceNameLabel.text); } - onClicked: ssoBtn.ssoLogin() + + Keys.enabled: ssoBtn.enabled && !login.passwordSupported + Layout.alignment: Qt.AlignHCenter + enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text + iconImage: modelData.avatarUrl.replace("mxc://", "image://MxcImage/") + text: modelData.name + visible: login.ssoSupported + Keys.onEnterPressed: ssoBtn.ssoLogin() Keys.onReturnPressed: ssoBtn.ssoLogin() - Keys.enabled: ssoBtn.enabled && !login.passwordSupported + onClicked: ssoBtn.ssoLogin() } } } } - ImageButton { - anchors.top: parent.top + ToolTip.text: qsTr("Back") + ToolTip.visible: hovered anchors.left: parent.left anchors.margins: Nheko.paddingMedium - width: Nheko.avatarSize + anchors.top: parent.top height: Nheko.avatarSize image: ":/icons/icons/ui/angle-arrow-left.svg" - ToolTip.visible: hovered - ToolTip.text: qsTr("Back") + width: Nheko.avatarSize + onClicked: mainWindow.pop() } } diff --git a/qml/pages/RegisterPage.qml b/qml/pages/RegisterPage.qml index 8054396e..c57671aa 100644 --- a/qml/pages/RegisterPage.qml +++ b/qml/pages/RegisterPage.qml @@ -1,215 +1,192 @@ // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.2 import QtQuick.Window 2.15 import im.nheko -import "../components/" -import "../ui/" +import "../components" +import "../ui" import "../" Item { id: registrationPage - property int maxExpansion: 400 property string error: regis.error + property int maxExpansion: 400 Registration { id: regis } - ScrollView { id: scroll - - clip: false - palette: timelineRoot.palette ScrollBar.horizontal.visible: false anchors.left: parent.left + anchors.margins: Nheko.paddingLarge anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - height: Math.min(registrationPage.height, col.implicitHeight) - anchors.margins: Nheko.paddingLarge - + clip: false contentWidth: availableWidth + height: Math.min(registrationPage.height, col.implicitHeight) + palette: timelineRoot.palette ColumnLayout { id: col - - spacing: Nheko.paddingMedium - anchors.horizontalCenter: parent.horizontalCenter - width: Math.min(registrationPage.maxExpansion, scroll.width- Nheko.paddingLarge*2) + spacing: Nheko.paddingMedium + width: Math.min(registrationPage.maxExpansion, scroll.width - Nheko.paddingLarge * 2) Image { Layout.alignment: Qt.AlignHCenter - source: "qrc:/logos/login.png" height: 128 + source: "qrc:/logos/login.png" width: 128 } - RowLayout { + Layout.fillWidth: true spacing: Nheko.paddingLarge - Layout.fillWidth: true MatrixTextField { id: hsLabel + ToolTip.text: qsTr("A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own.") label: qsTr("Homeserver") placeholderText: qsTr("your.server") + onEditingFinished: regis.setServer(text) - - ToolTip.text: qsTr("A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own.") } - - Spinner { - height: hsLabel.height/2 Layout.alignment: Qt.AlignBottom - - visible: running - running: regis.lookingUpHs foreground: timelineRoot.palette.mid + height: hsLabel.height / 2 + running: regis.lookingUpHs + visible: running } } - MatrixText { - textFormat: Text.PlainText color: Nheko.theme.error text: regis.hsError + textFormat: Text.PlainText visible: text } - RowLayout { + Layout.fillWidth: true spacing: Nheko.paddingLarge - visible: regis.supported - Layout.fillWidth: true MatrixTextField { id: usernameLabel Layout.fillWidth: true - label: qsTr("Username") ToolTip.text: qsTr("The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /.") + label: qsTr("Username") + onEditingFinished: regis.checkUsername(text) } Spinner { - height: usernameLabel.height/2 Layout.alignment: Qt.AlignBottom - - visible: running - running: regis.lookingUpUsername foreground: timelineRoot.palette.mid + height: usernameLabel.height / 2 + running: regis.lookingUpUsername + visible: running } - Image { - width: usernameLabel.height/2 - height: width - Layout.preferredHeight: usernameLabel.height/2 - Layout.preferredWidth: usernameLabel.height/2 Layout.alignment: Qt.AlignBottom - source: regis.usernameAvailable ? ("image://colorimage/:/icons/icons/ui/checkmark.svg?green") : ("image://colorimage/:/icons/icons/ui/dismiss.svg?"+Nheko.theme.error) - visible: regis.usernameAvailable || regis.usernameUnavailable - ToolTip.visible: ma.hovered + Layout.preferredHeight: usernameLabel.height / 2 + Layout.preferredWidth: usernameLabel.height / 2 ToolTip.text: qsTr("Back") + ToolTip.visible: ma.hovered + height: width + source: regis.usernameAvailable ? ("image://colorimage/:/icons/icons/ui/checkmark.svg?green") : ("image://colorimage/:/icons/icons/ui/dismiss.svg?" + Nheko.theme.error) sourceSize.height: height * Screen.devicePixelRatio sourceSize.width: width * Screen.devicePixelRatio + visible: regis.usernameAvailable || regis.usernameUnavailable + width: usernameLabel.height / 2 + HoverHandler { id: ma } } } - MatrixText { - textFormat: Text.PlainText color: Nheko.theme.error text: regis.usernameError + textFormat: Text.PlainText visible: text } - - MatrixTextField { - visible: regis.supported id: passwordLabel Layout.fillWidth: true - label: qsTr("Password") - echoMode: TextInput.Password ToolTip.text: qsTr("Please choose a secure password. The exact requirements for password strength may depend on your server.") - } - - MatrixTextField { + echoMode: TextInput.Password + label: qsTr("Password") visible: regis.supported + } + MatrixTextField { id: passwordConfirmationLabel Layout.fillWidth: true - label: qsTr("Password confirmation") echoMode: TextInput.Password - } - - MatrixText { + label: qsTr("Password confirmation") visible: regis.supported - textFormat: Text.PlainText + } + MatrixText { color: Nheko.theme.error text: passwordLabel.text != passwordConfirmationLabel.text ? qsTr("Your passwords do not match!") : "" - } - - MatrixTextField { + textFormat: Text.PlainText visible: regis.supported + } + MatrixTextField { id: deviceNameLabel Layout.fillWidth: true + ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.") label: qsTr("Device name") placeholderText: regis.initialDeviceName() - ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.") + visible: regis.supported } - Item { - height: Nheko.avatarSize Layout.fillWidth: true + height: Nheko.avatarSize Spinner { - height: parent.height anchors.centerIn: parent - - visible: running - running: regis.registering foreground: timelineRoot.palette.mid + height: parent.height + running: regis.registering + visible: running } } - MatrixText { - textFormat: Text.PlainText color: Nheko.theme.error text: registrationPage.error + textFormat: Text.PlainText visible: text } - FlatButton { id: regisBtn - visible: regis.supported - enabled: usernameLabel.text && passwordLabel.text && passwordLabel.text == passwordConfirmationLabel.text - Layout.alignment: Qt.AlignHCenter - text: qsTr("REGISTER") function register() { - regis.startRegistration(usernameLabel.text, passwordLabel.text, deviceNameLabel.text) + regis.startRegistration(usernameLabel.text, passwordLabel.text, deviceNameLabel.text); } - onClicked: regisBtn.register() + + Keys.enabled: regisBtn.enabled && regis.supported + Layout.alignment: Qt.AlignHCenter + enabled: usernameLabel.text && passwordLabel.text && passwordLabel.text == passwordConfirmationLabel.text + text: qsTr("REGISTER") + visible: regis.supported + Keys.onEnterPressed: regisBtn.register() Keys.onReturnPressed: regisBtn.register() - Keys.enabled: regisBtn.enabled && regis.supported + onClicked: regisBtn.register() } } } - ImageButton { - anchors.top: parent.top + ToolTip.text: qsTr("Back") + ToolTip.visible: hovered anchors.left: parent.left anchors.margins: Nheko.paddingMedium - width: Nheko.avatarSize + anchors.top: parent.top height: Nheko.avatarSize image: ":/icons/icons/ui/angle-arrow-left.svg" - ToolTip.visible: hovered - ToolTip.text: qsTr("Back") + width: Nheko.avatarSize + onClicked: mainWindow.pop() } } - diff --git a/qml/pages/UserSettingsPage.qml b/qml/pages/UserSettingsPage.qml index 3cf7d26e..36723b2a 100644 --- a/qml/pages/UserSettingsPage.qml +++ b/qml/pages/UserSettingsPage.qml @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import ".." +import "../" import "../ui" import Qt.labs.platform 1.1 as Platform import QtQuick 2.15 @@ -18,92 +16,94 @@ Rectangle { property int collapsePoint: 800 property bool collapsed: width < collapsePoint + color: timelineRoot.palette.window ScrollView { id: scroll - - palette: timelineRoot.palette ScrollBar.horizontal.visible: false anchors.fill: parent - anchors.topMargin: (collapsed? backButton.height : 0)+Nheko.paddingLarge - leftPadding: collapsed? Nheko.paddingMedium : Nheko.paddingLarge + anchors.topMargin: (collapsed ? backButton.height : 0) + Nheko.paddingLarge bottomPadding: Nheko.paddingLarge contentWidth: availableWidth + leftPadding: collapsed ? Nheko.paddingMedium : Nheko.paddingLarge + palette: timelineRoot.palette ColumnLayout { id: grid - + anchors.fill: parent + anchors.leftMargin: userSettingsDialog.collapsed ? 0 : (userSettingsDialog.width - userSettingsDialog.collapsePoint) * 0.4 + Nheko.paddingLarge + anchors.rightMargin: anchors.leftMargin spacing: Nheko.paddingMedium - anchors.fill: parent - anchors.leftMargin: userSettingsDialog.collapsed ? 0 : (userSettingsDialog.width-userSettingsDialog.collapsePoint) * 0.4 + Nheko.paddingLarge - anchors.rightMargin: anchors.leftMargin - Repeater { + Layout.fillWidth: true model: UserSettingsModel - Layout.fillWidth:true delegate: GridLayout { - columns: collapsed? 1 : 2 - rows: collapsed? 2: 1 - required property var model id: r + required property var model + + columns: collapsed ? 1 : 2 + rows: collapsed ? 2 : 1 + Label { Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - color: timelineRoot.palette.text - text: model.name //Layout.column: 0 Layout.columnSpan: (model.type == UserSettingsModel.SectionTitle && !userSettingsDialog.collapsed) ? 2 : 1 + Layout.fillWidth: true //Layout.row: model.index //Layout.minimumWidth: implicitWidth Layout.leftMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium Layout.topMargin: model.type == UserSettingsModel.SectionTitle ? Nheko.paddingLarge : 0 + ToolTip.delay: Nheko.tooltipDelay + ToolTip.text: model.description ?? "" + ToolTip.visible: hovered.hovered && model.description + color: timelineRoot.palette.text font.pointSize: 1.1 * fontMetrics.font.pointSize + text: model.name + wrapMode: Text.Wrap HoverHandler { id: hovered enabled: model.description ?? false } - ToolTip.visible: hovered.hovered && model.description - ToolTip.text: model.description ?? "" - ToolTip.delay: Nheko.tooltipDelay - wrapMode: Text.Wrap } - DelegateChooser { id: chooser - - roleValue: model.type Layout.alignment: Qt.AlignRight - Layout.columnSpan: (model.type == UserSettingsModel.SectionTitle && !userSettingsDialog.collapsed) ? 2 : 1 + Layout.fillWidth: model.type == UserSettingsModel.SectionTitle || model.type == UserSettingsModel.Options || model.type == UserSettingsModel.Number + Layout.maximumWidth: model.type == UserSettingsModel.SectionTitle ? Number.POSITIVE_INFINITY : 400 Layout.preferredHeight: child.height Layout.preferredWidth: Math.min(child.implicitWidth, child.width || 1000) - Layout.maximumWidth: model.type == UserSettingsModel.SectionTitle ? Number.POSITIVE_INFINITY : 400 - Layout.fillWidth: model.type == UserSettingsModel.SectionTitle || model.type == UserSettingsModel.Options || model.type == UserSettingsModel.Number Layout.rightMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium + roleValue: model.type DelegateChoice { roleValue: UserSettingsModel.Toggle + ToggleButton { checked: model.value - onCheckedChanged: model.value = checked enabled: model.enabled + + onCheckedChanged: model.value = checked } } DelegateChoice { roleValue: UserSettingsModel.Options + ComboBox { anchors.right: parent.right - width: Math.min(parent.width, implicitWidth) - model: r.model.values currentIndex: r.model.value + model: r.model.values + width: Math.min(parent.width, implicitWidth) + onCurrentIndexChanged: r.model.value = currentIndex - WheelHandler{} // suppress scrolling changing values + WheelHandler { + } // suppress scrolling changing values } } DelegateChoice { @@ -111,15 +111,17 @@ Rectangle { SpinBox { anchors.right: parent.right - width: Math.min(parent.width, implicitWidth) - from: model.valueLowerBound - to: model.valueUpperBound - stepSize: model.valueStep - value: model.value - onValueChanged: model.value = value editable: true + from: model.valueLowerBound + stepSize: model.valueStep + to: model.valueUpperBound + value: model.value + width: Math.min(parent.width, implicitWidth) - WheelHandler{} // suppress scrolling changing values + onValueChanged: model.value = value + + WheelHandler { + } // suppress scrolling changing values } } DelegateChoice { @@ -128,56 +130,58 @@ Rectangle { SpinBox { id: spinbox - readonly property double div: 100 readonly property int decimals: 2 - - anchors.right: parent.right - width: Math.min(parent.width, implicitWidth) - from: model.valueLowerBound * div - to: model.valueUpperBound * div - stepSize: model.valueStep * div - value: model.value * div - onValueChanged: model.value = value/div - editable: true - + readonly property double div: 100 property real realValue: value / div + anchors.right: parent.right + editable: true + from: model.valueLowerBound * div + stepSize: model.valueStep * div + textFromValue: function (value, locale) { + return Number(value / spinbox.div).toLocaleString(locale, 'f', spinbox.decimals); + } + to: model.valueUpperBound * div + value: model.value * div + valueFromText: function (text, locale) { + return Number.fromLocaleString(locale, text) * spinbox.div; + } + width: Math.min(parent.width, implicitWidth) + validator: DoubleValidator { - bottom: Math.min(spinbox.from/spinbox.div, spinbox.to/spinbox.div) - top: Math.max(spinbox.from/spinbox.div, spinbox.to/spinbox.div) + bottom: Math.min(spinbox.from / spinbox.div, spinbox.to / spinbox.div) + top: Math.max(spinbox.from / spinbox.div, spinbox.to / spinbox.div) } - textFromValue: function(value, locale) { - return Number(value / spinbox.div).toLocaleString(locale, 'f', spinbox.decimals) - } + onValueChanged: model.value = value / div - valueFromText: function(text, locale) { - return Number.fromLocaleString(locale, text) * spinbox.div - } - - WheelHandler{} // suppress scrolling changing values + WheelHandler { + } // suppress scrolling changing values } } DelegateChoice { roleValue: UserSettingsModel.ReadOnlyText + TextEdit { color: timelineRoot.palette.text - text: model.value readOnly: true selectByMouse: !Settings.mobileMode + text: model.value textFormat: Text.PlainText } } DelegateChoice { roleValue: UserSettingsModel.SectionTitle + Item { - width: grid.width height: fontMetrics.lineSpacing + width: grid.width + Rectangle { - anchors.topMargin: Nheko.paddingSmall - anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: Nheko.paddingSmall color: timelineRoot.palette.placeholderText height: 1 } @@ -185,6 +189,7 @@ Rectangle { } DelegateChoice { roleValue: UserSettingsModel.KeyStatus + Text { color: model.good ? "green" : Nheko.theme.error text: model.value ? qsTr("CACHED") : qsTr("NOT CACHED") @@ -192,26 +197,32 @@ Rectangle { } DelegateChoice { roleValue: UserSettingsModel.SessionKeyImportExport + RowLayout { Button { text: qsTr("IMPORT") + onClicked: UserSettingsModel.importSessionKeys() } Button { text: qsTr("EXPORT") + onClicked: UserSettingsModel.exportSessionKeys() } } } DelegateChoice { roleValue: UserSettingsModel.XSignKeysRequestDownload + RowLayout { Button { text: qsTr("DOWNLOAD") + onClicked: UserSettingsModel.downloadCrossSigningSecrets() } Button { text: qsTr("REQUEST") + onClicked: UserSettingsModel.requestCrossSigningSecrets() } } @@ -226,19 +237,17 @@ Rectangle { } } } - ImageButton { id: backButton - anchors.top: parent.top + ToolTip.text: qsTr("Back") + ToolTip.visible: hovered anchors.left: parent.left anchors.margins: Nheko.paddingMedium - width: Nheko.avatarSize + anchors.top: parent.top height: Nheko.avatarSize image: ":/icons/icons/ui/angle-arrow-left.svg" - ToolTip.visible: hovered - ToolTip.text: qsTr("Back") + width: Nheko.avatarSize + onClicked: mainWindow.pop() } - } - diff --git a/qml/pages/WelcomePage.qml b/qml/pages/WelcomePage.qml index 468e95e6..f7127fbc 100644 --- a/qml/pages/WelcomePage.qml +++ b/qml/pages/WelcomePage.qml @@ -1,64 +1,61 @@ // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.2 import QtQuick.Window 2.15 import im.nheko -import "../components/" +import "../components" ColumnLayout { Item { Layout.fillHeight: true } - Image { Layout.alignment: Qt.AlignHCenter - source: "qrc:/logos/splash.png" height: 256 + source: "qrc:/logos/splash.png" width: 256 } - Label { - Layout.margins: Nheko.paddingLarge + Layout.alignment: Qt.AlignHCenter Layout.bottomMargin: 0 - Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.") + Layout.margins: Nheko.paddingLarge color: timelineRoot.palette.text - font.pointSize: fontMetrics.font.pointSize*2 - wrapMode: Text.Wrap + font.pointSize: fontMetrics.font.pointSize * 2 horizontalAlignment: Text.AlignHCenter + text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.") + wrapMode: Text.Wrap } Label { - Layout.margins: Nheko.paddingLarge Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true - text: qsTr("Enjoy your stay!") + Layout.margins: Nheko.paddingLarge color: timelineRoot.palette.text - font.pointSize: fontMetrics.font.pointSize*1.5 - wrapMode: Text.Wrap + font.pointSize: fontMetrics.font.pointSize * 1.5 horizontalAlignment: Text.AlignHCenter + text: qsTr("Enjoy your stay!") + wrapMode: Text.Wrap } - RowLayout { Item { Layout.fillWidth: true } FlatButton { - Layout.margins: Nheko.paddingLarge Layout.alignment: Qt.AlignHCenter + Layout.margins: Nheko.paddingLarge text: qsTr("REGISTER") + onClicked: { mainWindow.push(registerPage); } } FlatButton { - Layout.margins: Nheko.paddingLarge Layout.alignment: Qt.AlignHCenter + Layout.margins: Nheko.paddingLarge text: qsTr("LOGIN") + onClicked: { mainWindow.push(loginPage); } diff --git a/qml/ui/NhekoSlider.qml b/qml/ui/NhekoSlider.qml index 285efadb..1f9103c6 100644 --- a/qml/ui/NhekoSlider.qml +++ b/qml/ui/NhekoSlider.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.15 import QtQuick.Controls 2.15 import im.nheko @@ -10,42 +8,39 @@ import im.nheko Slider { id: control - property color progressColor: timelineRoot.palette.highlight property bool alwaysShowSlider: true + property color progressColor: timelineRoot.palette.highlight property int sliderRadius: 16 - value: 0 implicitHeight: sliderRadius padding: 0 + value: 0 background: Rectangle { + color: timelineRoot.palette.placeholderText + height: implicitHeight + implicitHeight: control.sliderRadius / 4 + implicitWidth: 200 + radius: height / 2 + width: control.availableWidth - handle.width x: control.leftPadding + handle.width / 2 y: control.topPadding + control.availableHeight / 2 - height / 2 - implicitWidth: 200 - implicitHeight: control.sliderRadius / 4 - width: control.availableWidth - handle.width - height: implicitHeight - radius: height / 2 - color: timelineRoot.palette.placeholderText Rectangle { - width: control.visualPosition * parent.width - height: parent.height color: control.progressColor + height: parent.height radius: 2 + width: control.visualPosition * parent.width } - } - handle: Rectangle { + border.color: control.progressColor + color: control.progressColor + implicitHeight: control.sliderRadius + implicitWidth: control.sliderRadius + radius: control.sliderRadius / 2 + visible: Settings.mobileMode || control.alwaysShowSlider || control.hovered || control.pressed x: control.leftPadding + control.visualPosition * background.width y: control.topPadding + control.availableHeight / 2 - height / 2 - implicitWidth: control.sliderRadius - implicitHeight: control.sliderRadius - radius: control.sliderRadius / 2 - color: control.progressColor - visible: Settings.mobileMode || control.alwaysShowSlider || control.hovered || control.pressed - border.color: control.progressColor } - } diff --git a/qml/ui/Ripple.qml b/qml/ui/Ripple.qml index e0532702..418410fe 100644 --- a/qml/ui/Ripple.qml +++ b/qml/ui/Ripple.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.15 import QtQuick.Controls 2.15 @@ -11,28 +9,21 @@ Item { property color color: "#22000000" property real maxRadius: Math.max(width, height) + readonly property real opacityAnimationDuration: 300 readonly property real radiusAnimationRate: 0.05 readonly property real radiusTailAnimationRate: 0.5 - readonly property real opacityAnimationDuration: 300 property var rippleTarget: parent anchors.fill: parent PointHandler { id: ph - - onGrabChanged: { - circle.centerX = point.position.x - circle.centerY = point.position.y - } - target: Rectangle { id: backgroundLayer - parent: rippleTarget - anchors.fill: parent - color: "transparent" clip: true + color: "transparent" + parent: rippleTarget Rectangle { id: circle @@ -40,15 +31,14 @@ Item { property real centerX property real centerY + color: ripple.color + height: radius * 2 + radius: 0 + state: ph.active ? "ACTIVE" : "NORMAL" + width: radius * 2 x: centerX - radius y: centerY - radius - height: radius*2 - width: radius*2 - radius: 0 - color: ripple.color - - state: ph.active ? "ACTIVE" : "NORMAL" states: [ State { name: "NORMAL" @@ -65,26 +55,29 @@ Item { SequentialAnimation { //PropertyAction { target: circle; property: "centerX"; value: ph.point.position.x } //PropertyAction { target: circle; property: "centerY"; value: ph.point.position.y } - PropertyAction { target: circle; property: "visible"; value: true } - PropertyAction { target: circle; property: "opacity"; value: 1 } - + PropertyAction { + property: "visible" + target: circle + value: true + } + PropertyAction { + property: "opacity" + target: circle + value: 1 + } NumberAnimation { id: radius_animation - - target: circle - properties: "radius" - from: 0 - to: ripple.maxRadius duration: ripple.maxRadius / ripple.radiusAnimationRate + from: 0 + properties: "radius" + target: circle + to: ripple.maxRadius easing { type: Easing.OutQuad } - } - } - }, Transition { from: "ACTIVE" @@ -94,38 +87,41 @@ Item { ParallelAnimation { NumberAnimation { id: radius_tail_animation - - target: circle - properties: "radius" - to: ripple.maxRadius duration: ripple.maxRadius / ripple.radiusTailAnimationRate + properties: "radius" + target: circle + to: ripple.maxRadius easing { type: Easing.Linear } - } - NumberAnimation { id: opacity_animation - - target: circle - properties: "opacity" - to: 0 duration: ripple.opacityAnimationDuration + properties: "opacity" + target: circle + to: 0 easing { type: Easing.InQuad } - } - } - PropertyAction { target: circle; property: "visible"; value: false } + PropertyAction { + property: "visible" + target: circle + value: false + } } } ] } } + + onGrabChanged: { + circle.centerX = point.position.x; + circle.centerY = point.position.y; + } } } diff --git a/qml/ui/Snackbar.qml b/qml/ui/Snackbar.qml index 5480d4ea..3af80f3d 100644 --- a/qml/ui/Snackbar.qml +++ b/qml/ui/Snackbar.qml @@ -1,7 +1,5 @@ // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.15 import QtQuick.Controls 2.15 import im.nheko @@ -9,8 +7,8 @@ import im.nheko Popup { id: snackbar - property var messages: [] property string currentMessage: "" + property var messages: [] function showNotification(msg) { messages.push(msg); @@ -21,10 +19,58 @@ Popup { } } - Timer { - id: dismissTimer - interval: 10000 - onTriggered: snackbar.close() + opacity: 0 + padding: Nheko.paddingLarge + parent: Overlay.overlay + x: (parent.width - width) / 2 + y: -100 + + background: Rectangle { + color: timelineRoot.palette.dark + opacity: 0.8 + radius: Nheko.paddingLarge + } + contentItem: Label { + color: timelineRoot.palette.light + font.bold: true + text: snackbar.currentMessage + width: Math.max(Overlay.overlay ? Overlay.overlay.width / 2 : 0, 400) + } + enter: Transition { + NumberAnimation { + duration: 200 + easing.type: Easing.OutCubic + from: 0.0 + property: "opacity" + target: snackbar + to: 1.0 + } + NumberAnimation { + duration: 1000 + easing.type: Easing.OutCubic + from: -100 + properties: "y" + target: snackbar + to: 100 + } + } + exit: Transition { + NumberAnimation { + duration: 300 + easing.type: Easing.InCubic + from: 1.0 + property: "opacity" + target: snackbar + to: 0.0 + } + NumberAnimation { + duration: 300 + easing.type: Easing.InCubic + from: 100 + properties: "y" + target: snackbar + to: -100 + } } onAboutToHide: { @@ -38,61 +84,10 @@ Popup { } } - parent: Overlay.overlay - opacity: 0 - y: -100 - x: (parent.width - width)/2 - padding: Nheko.paddingLarge + Timer { + id: dismissTimer + interval: 10000 - contentItem: Label { - color: timelineRoot.palette.light - width: Math.max(Overlay.overlay? Overlay.overlay.width/2 : 0, 400) - text: snackbar.currentMessage - font.bold: true - } - - background: Rectangle { - radius: Nheko.paddingLarge - color: timelineRoot.palette.dark - opacity: 0.8 - } - - enter: Transition { - NumberAnimation { - target: snackbar - property: "opacity" - from: 0.0 - to: 1.0 - duration: 200 - easing.type: Easing.OutCubic - } - NumberAnimation { - target: snackbar - properties: "y" - from: -100 - to: 100 - duration: 1000 - easing.type: Easing.OutCubic - } - } - exit: Transition { - NumberAnimation { - target: snackbar - property: "opacity" - from: 1.0 - to: 0.0 - duration: 300 - easing.type: Easing.InCubic - } - NumberAnimation { - target: snackbar - properties: "y" - to: -100 - from: 100 - duration: 300 - easing.type: Easing.InCubic - } + onTriggered: snackbar.close() } } - - diff --git a/qml/ui/Spinner.qml b/qml/ui/Spinner.qml index 6d897d2b..11c8df41 100644 --- a/qml/ui/Spinner.qml +++ b/qml/ui/Spinner.qml @@ -1,143 +1,116 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - -import "./animations" +import "animations" //import QtGraphicalEffects 1.12 import QtQuick 2.12 Item { id: spinner - property int spacing: 0 - property bool running: true - property var foreground: "#333" - readonly property int barCount: 6 readonly property real a: Math.PI / 6 - readonly property var colors: ["#c0def5", "#87aade", "white"] readonly property var anims: [anim1, anim2, anim3, anim4, anim5, anim6] - readonly property int pauseDuration: barCount * 150 + readonly property int barCount: 6 + readonly property var colors: ["#c0def5", "#87aade", "white"] + property var foreground: "#333" readonly property int glowDuration: 300 + readonly property int pauseDuration: barCount * 150 + property bool running: true + property int spacing: 0 height: 40 width: barCount * (height * 0.375) Row { id: row - - Rectangle { - id: rect1 - - width: ((spinner.width / spinner.barCount) - (spinner.spacing)) * 1.5 - height: spinner.height / 3.5 - color: "white" - } - - Rectangle { - id: rect2 - - width: (spinner.width / spinner.barCount) - spinner.spacing - height: spinner.height - color: spinner.colors[0] - } - - Rectangle { - id: rect3 - - width: (spinner.width / spinner.barCount) - spinner.spacing - height: spinner.height - color: spinner.colors[1] - } - - Rectangle { - id: rect4 - - width: (spinner.width / spinner.barCount) - spinner.spacing - height: spinner.height - color: spinner.colors[2] - } - - Rectangle { - id: rect5 - - width: (spinner.width / (spinner.barCount + 1)) - spinner.spacing - height: spinner.height / 3.5 - color: "white" - } - - Rectangle { - id: rect6 - - width: (spinner.width / spinner.barCount) - spinner.spacing - height: spinner.height - color: "white" - } - - BlinkAnimation { - id: anim1 - - target: rect1 - running: spinner.running - pauseDuration: spinner.pauseDuration - glowDuration: spinner.glowDuration - offset: 0 / spinner.barCount - } - - BlinkAnimation { - id: anim2 - - target: rect2 - running: spinner.running - pauseDuration: spinner.pauseDuration - glowDuration: spinner.glowDuration - offset: 1 / spinner.barCount - } - - BlinkAnimation { - id: anim3 - - target: rect3 - running: spinner.running - pauseDuration: spinner.pauseDuration - glowDuration: spinner.glowDuration - offset: 2 / spinner.barCount - } - - BlinkAnimation { - id: anim4 - - target: rect4 - running: spinner.running - pauseDuration: spinner.pauseDuration - glowDuration: spinner.glowDuration - offset: 3 / spinner.barCount - } - - BlinkAnimation { - id: anim5 - - target: rect5 - running: spinner.running - pauseDuration: spinner.pauseDuration - glowDuration: spinner.glowDuration - offset: 4 / spinner.barCount - } - - BlinkAnimation { - id: anim6 - - target: rect6 - running: spinner.running - pauseDuration: spinner.pauseDuration - glowDuration: spinner.glowDuration - offset: 5 / spinner.barCount - } - transform: Matrix4x4 { matrix: Qt.matrix4x4(Math.cos(spinner.a), -Math.sin(spinner.a), 0, 0, 0, Math.cos(spinner.a), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) } + Rectangle { + id: rect1 + color: "white" + height: spinner.height / 3.5 + width: ((spinner.width / spinner.barCount) - (spinner.spacing)) * 1.5 + } + Rectangle { + id: rect2 + color: spinner.colors[0] + height: spinner.height + width: (spinner.width / spinner.barCount) - spinner.spacing + } + Rectangle { + id: rect3 + color: spinner.colors[1] + height: spinner.height + width: (spinner.width / spinner.barCount) - spinner.spacing + } + Rectangle { + id: rect4 + color: spinner.colors[2] + height: spinner.height + width: (spinner.width / spinner.barCount) - spinner.spacing + } + Rectangle { + id: rect5 + color: "white" + height: spinner.height / 3.5 + width: (spinner.width / (spinner.barCount + 1)) - spinner.spacing + } + Rectangle { + id: rect6 + color: "white" + height: spinner.height + width: (spinner.width / spinner.barCount) - spinner.spacing + } + BlinkAnimation { + id: anim1 + glowDuration: spinner.glowDuration + offset: 0 / spinner.barCount + pauseDuration: spinner.pauseDuration + running: spinner.running + target: rect1 + } + BlinkAnimation { + id: anim2 + glowDuration: spinner.glowDuration + offset: 1 / spinner.barCount + pauseDuration: spinner.pauseDuration + running: spinner.running + target: rect2 + } + BlinkAnimation { + id: anim3 + glowDuration: spinner.glowDuration + offset: 2 / spinner.barCount + pauseDuration: spinner.pauseDuration + running: spinner.running + target: rect3 + } + BlinkAnimation { + id: anim4 + glowDuration: spinner.glowDuration + offset: 3 / spinner.barCount + pauseDuration: spinner.pauseDuration + running: spinner.running + target: rect4 + } + BlinkAnimation { + id: anim5 + glowDuration: spinner.glowDuration + offset: 4 / spinner.barCount + pauseDuration: spinner.pauseDuration + running: spinner.running + target: rect5 + } + BlinkAnimation { + id: anim6 + glowDuration: spinner.glowDuration + offset: 5 / spinner.barCount + pauseDuration: spinner.pauseDuration + running: spinner.running + target: rect6 + } } //Glow { @@ -152,5 +125,4 @@ Item { // } //} - } diff --git a/qml/ui/animations/BlinkAnimation.qml b/qml/ui/animations/BlinkAnimation.qml index 04625481..635d3f03 100644 --- a/qml/ui/animations/BlinkAnimation.qml +++ b/qml/ui/animations/BlinkAnimation.qml @@ -1,32 +1,26 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.12 SequentialAnimation { - property alias target: numberAnimation.target property alias glowDuration: numberAnimation.duration - property int pauseDuration: 150 property double offset: 0 + property int pauseDuration: 150 + property alias target: numberAnimation.target loops: Animation.Infinite PauseAnimation { duration: pauseDuration * offset } - NumberAnimation { id: numberAnimation - - property: "opacity" from: 0 + property: "opacity" to: 1 } - PauseAnimation { duration: pauseDuration * (1 - offset) } - } diff --git a/qml/ui/media/MediaControls.qml b/qml/ui/media/MediaControls.qml index 0398b98f..bc9926d0 100644 --- a/qml/ui/media/MediaControls.qml +++ b/qml/ui/media/MediaControls.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import "../" import "../../" import QtMultimedia 5.15 @@ -15,27 +13,22 @@ Rectangle { id: control property alias desiredVolume: volumeSlider.desiredVolume + property var duration + property bool mediaLoaded: false + property var mediaState property bool muted: false property bool playingVideo: false - property var mediaState - property bool mediaLoaded: false - property var duration - property var positionValue: 0 property var position + property var positionValue: 0 property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.state == "shown" - signal playPauseActivated() - signal loadActivated() - - function showControls() { - controlHideTimer.restart(); - } + signal loadActivated + signal playPauseActivated function durationToString(duration) { function maybeZeroPrepend(time) { return (time < 10) ? "0" + time.toString() : time.toString(); } - var totalSeconds = Math.floor(duration / 1000); var seconds = totalSeconds % 60; var minutes = (Math.floor(totalSeconds / 60)) % 60; @@ -46,16 +39,25 @@ Rectangle { var hh = hours.toString(); if (hours < 1) return mm + ":" + ss; - return hh + ":" + mm + ":" + ss; } + function showControls() { + controlHideTimer.restart(); + } color: { var wc = timelineRoot.palette.alternateBase; return Qt.rgba(wc.r, wc.g, wc.b, 0.5); } - opacity: control.shouldShowControls ? 1 : 0 height: controlLayout.implicitHeight + opacity: control.shouldShowControls ? 1 : 0 + + // Fade controls in/out + Behavior on opacity { + OpacityAnimator { + duration: 100 + } + } HoverHandler { id: playerMouseArea @@ -64,41 +66,38 @@ Rectangle { onHoveredChanged: showControls() } - ColumnLayout { id: controlLayout - - enabled: control.shouldShowControls - spacing: 0 anchors.bottom: control.bottom anchors.left: control.left anchors.right: control.right + enabled: control.shouldShowControls + spacing: 0 NhekoSlider { Layout.fillWidth: true Layout.leftMargin: Nheko.paddingSmall Layout.rightMargin: Nheko.paddingSmall + alwaysShowSlider: false enabled: control.mediaLoaded - value: control.positionValue - onMoved: control.position = value from: 0 to: control.duration - alwaysShowSlider: false - } + value: control.positionValue + onMoved: control.position = value + } RowLayout { + Layout.fillWidth: true Layout.margins: Nheko.paddingSmall spacing: Nheko.paddingSmall - Layout.fillWidth: true // Cache/Play/pause button ImageButton { id: playbackStateImage - Layout.alignment: Qt.AlignLeft - buttonTextColor: timelineRoot.palette.text Layout.preferredHeight: 24 Layout.preferredWidth: 24 + buttonTextColor: timelineRoot.palette.text image: { if (control.mediaLoaded) { if (control.mediaState == MediaPlayer.PlayingState) @@ -109,38 +108,48 @@ Rectangle { return ":/icons/icons/ui/download.svg"; } } + onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated() } - ImageButton { id: volumeButton - Layout.alignment: Qt.AlignLeft - buttonTextColor: timelineRoot.palette.text Layout.preferredHeight: 24 Layout.preferredWidth: 24 + buttonTextColor: timelineRoot.palette.text image: { if (control.muted || control.desiredVolume <= 0) return ":/icons/icons/ui/volume-off-indicator.svg"; else return ":/icons/icons/ui/volume-up.svg"; } + onClicked: control.muted = !control.muted } - NhekoSlider { id: volumeSlider property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale) - state: "" Layout.alignment: Qt.AlignLeft Layout.preferredWidth: 0 opacity: 0 orientation: Qt.Horizontal + state: "" value: 1 - onDesiredVolumeChanged: { - control.muted = !(desiredVolume > 0); + + states: State { + name: "shown" + when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed + + PropertyChanges { + Layout.preferredWidth: 100 + target: volumeSlider + } + PropertyChanges { + opacity: 1 + target: volumeSlider + } } transitions: [ Transition { @@ -151,20 +160,16 @@ Rectangle { PauseAnimation { duration: 50 } - NumberAnimation { duration: 100 - properties: "opacity" easing.type: Easing.InQuad + properties: "opacity" } - } - NumberAnimation { - properties: "Layout.preferredWidth" duration: 150 + properties: "Layout.preferredWidth" } - }, Transition { from: "shown" @@ -174,72 +179,40 @@ Rectangle { PauseAnimation { duration: 100 } - ParallelAnimation { NumberAnimation { duration: 100 - properties: "opacity" easing.type: Easing.InQuad + properties: "opacity" } - NumberAnimation { - properties: "Layout.preferredWidth" duration: 150 + properties: "Layout.preferredWidth" } - } - } - } ] - states: State { - name: "shown" - when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed - - PropertyChanges { - target: volumeSlider - Layout.preferredWidth: 100 - } - - PropertyChanges { - target: volumeSlider - opacity: 1 - } - + onDesiredVolumeChanged: { + control.muted = !(desiredVolume > 0); } - } - Label { Layout.alignment: Qt.AlignRight - text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration) color: timelineRoot.palette.text + text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration) } - Item { Layout.fillWidth: true } - } - } // For hiding controls on stationary cursor Timer { id: controlHideTimer - interval: 1500 //ms repeat: false } - - // Fade controls in/out - Behavior on opacity { - OpacityAnimator { - duration: 100 - } - - } - } diff --git a/qml/voip/ActiveCallBar.qml b/qml/voip/ActiveCallBar.qml index 38f17d68..4a8862d9 100644 --- a/qml/voip/ActiveCallBar.qml +++ b/qml/voip/ActiveCallBar.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import "../" import QtQuick 2.9 import QtQuick.Controls 2.3 @@ -10,51 +8,46 @@ import QtQuick.Layouts 1.2 import im.nheko Rectangle { - visible: CallManager.isOnCall color: callInviteBar.color implicitHeight: visible ? rowLayout.height + 8 : 0 + visible: CallManager.isOnCall MouseArea { anchors.fill: parent + onClicked: { if (CallManager.callType != Voip.VOICE) stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1; - } } - RowLayout { id: rowLayout - anchors.left: parent.left + anchors.leftMargin: 8 anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 8 Avatar { - width: Nheko.avatarSize + displayName: CallManager.callPartyDisplayName height: Nheko.avatarSize url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") userid: CallManager.callParty - displayName: CallManager.callPartyDisplayName + width: Nheko.avatarSize + onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId) } - Label { Layout.leftMargin: 8 + color: "#000000" font.pointSize: fontMetrics.font.pointSize * 1.1 text: CallManager.callPartyDisplayName - color: "#000000" } - Image { id: callTypeIcon - Layout.leftMargin: 4 - Layout.preferredWidth: 24 Layout.preferredHeight: 24 + Layout.preferredWidth: 24 } - Item { states: [ State { @@ -62,41 +55,35 @@ Rectangle { when: CallManager.callType == Voip.VOICE PropertyChanges { - target: callTypeIcon source: "qrc:/icons/icons/ui/place-call.svg" + target: callTypeIcon } - }, State { name: "VIDEO" when: CallManager.callType == Voip.VIDEO PropertyChanges { - target: callTypeIcon source: "qrc:/icons/icons/ui/video.svg" + target: callTypeIcon } - }, State { name: "SCREEN" when: CallManager.callType == Voip.SCREEN PropertyChanges { - target: callTypeIcon source: "qrc:/icons/icons/ui/screen-share.svg" + target: callTypeIcon } - } ] } - Label { id: callStateLabel - - font.pointSize: fontMetrics.font.pointSize * 1.1 color: "#000000" + font.pointSize: fontMetrics.font.pointSize * 1.1 } - Item { states: [ State { @@ -107,7 +94,6 @@ Rectangle { target: callStateLabel text: qsTr("Calling...") } - }, State { name: "CONNECTING" @@ -117,7 +103,6 @@ Rectangle { target: callStateLabel text: qsTr("Connecting...") } - }, State { name: "ANSWERSENT" @@ -127,7 +112,6 @@ Rectangle { target: callStateLabel text: qsTr("Connecting...") } - }, State { name: "CONNECTED" @@ -137,17 +121,14 @@ Rectangle { target: callStateLabel text: "00:00" } - PropertyChanges { - target: callTimer startTime: Math.floor((new Date()).getTime() / 1000) + target: callTimer } - PropertyChanges { - target: stackLayout currentIndex: CallManager.callType != Voip.VOICE ? 1 : 0 + target: stackLayout } - }, State { name: "DISCONNECTED" @@ -157,16 +138,13 @@ Rectangle { target: callStateLabel text: "" } - PropertyChanges { - target: stackLayout currentIndex: 0 + target: stackLayout } - } ] } - Timer { id: callTimer @@ -177,8 +155,9 @@ Rectangle { } interval: 1000 - running: CallManager.callState == Voip.CONNECTED repeat: true + running: CallManager.callState == Voip.CONNECTED + onTriggered: { var d = new Date(); let seconds = Math.floor(d.getTime() / 1000 - startTime); @@ -188,44 +167,40 @@ Rectangle { callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s); } } - Label { Layout.leftMargin: 16 - visible: CallManager.callType == Voip.SCREEN && CallManager.callState == Voip.CONNECTED - text: qsTr("You are screen sharing") - font.pointSize: fontMetrics.font.pointSize * 1.1 color: "#000000" + font.pointSize: fontMetrics.font.pointSize * 1.1 + text: qsTr("You are screen sharing") + visible: CallManager.callType == Voip.SCREEN && CallManager.callState == Voip.CONNECTED } - Item { Layout.fillWidth: true } - ImageButton { + ToolTip.text: qsTr("Hide/Show Picture-in-Picture") + ToolTip.visible: hovered + buttonTextColor: "#000000" + height: 24 + hoverEnabled: true + image: ":/icons/icons/ui/picture-in-picture.svg" visible: CallManager.haveLocalPiP width: 24 - height: 24 - buttonTextColor: "#000000" - image: ":/icons/icons/ui/picture-in-picture.svg" - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Hide/Show Picture-in-Picture") + onClicked: CallManager.toggleLocalPiP() } - ImageButton { Layout.leftMargin: 8 Layout.rightMargin: 16 - width: 24 - height: 24 - buttonTextColor: "#000000" - image: CallManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.svg" : ":/icons/icons/ui/microphone-mute.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: CallManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") + ToolTip.visible: hovered + buttonTextColor: "#000000" + height: 24 + hoverEnabled: true + image: CallManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.svg" : ":/icons/icons/ui/microphone-mute.svg" + width: 24 + onClicked: CallManager.toggleMicMute() } - } - } diff --git a/qml/voip/CallDevices.qml b/qml/voip/CallDevices.qml index 9bad6127..a4175054 100644 --- a/qml/voip/CallDevices.qml +++ b/qml/voip/CallDevices.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 @@ -11,79 +9,68 @@ import im.nheko Popup { modal: true palette: timelineRoot.palette + + background: Rectangle { + border.color: timelineRoot.palette.windowText + color: timelineRoot.palette.window + } + // only set the anchors on Qt 5.12 or higher // see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop Component.onCompleted: { if (anchors) anchors.centerIn = parent; - } ColumnLayout { spacing: 16 ColumnLayout { - spacing: 8 - Layout.topMargin: 8 Layout.leftMargin: 8 Layout.rightMargin: 8 + Layout.topMargin: 8 + spacing: 8 RowLayout { Image { - Layout.preferredWidth: 22 Layout.preferredHeight: 22 + Layout.preferredWidth: 22 source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + timelineRoot.palette.windowText } - ComboBox { id: micCombo - Layout.fillWidth: true model: CallManager.mics } - } - RowLayout { visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 Image { - Layout.preferredWidth: 22 Layout.preferredHeight: 22 + Layout.preferredWidth: 22 source: "image://colorimage/:/icons/icons/ui/video-call.svg?" + timelineRoot.palette.windowText } - ComboBox { id: cameraCombo - Layout.fillWidth: true model: CallManager.cameras } - } - } - DialogButtonBox { Layout.leftMargin: 128 standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + onAccepted: { Settings.microphone = micCombo.currentText; if (cameraCombo.visible) Settings.camera = cameraCombo.currentText; - close(); } onRejected: { close(); } } - } - - background: Rectangle { - color: timelineRoot.palette.window - border.color: timelineRoot.palette.windowText - } - } diff --git a/qml/voip/CallInvite.qml b/qml/voip/CallInvite.qml index 4b811f74..13c00ca8 100644 --- a/qml/voip/CallInvite.qml +++ b/qml/voip/CallInvite.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import "../" import QtQuick 2.9 import QtQuick.Controls 2.3 @@ -11,54 +9,51 @@ import im.nheko Popup { id: callInv - closePolicy: Popup.NoAutoClose - width: parent.width height: parent.height palette: timelineRoot.palette + width: parent.width + + background: Rectangle { + border.color: timelineRoot.palette.windowText + color: timelineRoot.palette.window + } Component { id: deviceError - DeviceError { } - } - Connections { function onNewInviteState() { if (!CallManager.haveCallInvite) close(); - } target: CallManager } - ColumnLayout { - anchors.top: parent.top anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top Label { Layout.alignment: Qt.AlignCenter - Layout.topMargin: callInv.parent.height / 25 Layout.fillWidth: true - text: CallManager.callPartyDisplayName - font.pointSize: fontMetrics.font.pointSize * 2 + Layout.topMargin: callInv.parent.height / 25 color: timelineRoot.palette.windowText + font.pointSize: fontMetrics.font.pointSize * 2 horizontalAlignment: Text.AlignHCenter + text: CallManager.callPartyDisplayName } - Avatar { Layout.alignment: Qt.AlignCenter Layout.preferredHeight: callInv.height / 5 Layout.preferredWidth: callInv.height / 5 + displayName: CallManager.callPartyDisplayName url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") userid: CallManager.callParty - displayName: CallManager.callPartyDisplayName } - ColumnLayout { Layout.alignment: Qt.AlignCenter Layout.bottomMargin: callInv.height / 25 @@ -67,20 +62,17 @@ Popup { property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg" Layout.alignment: Qt.AlignCenter - Layout.preferredWidth: callInv.height / 10 Layout.preferredHeight: callInv.height / 10 + Layout.preferredWidth: callInv.height / 10 source: "image://colorimage/" + image + "?" + timelineRoot.palette.windowText } - Label { Layout.alignment: Qt.AlignCenter - text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call") - font.pointSize: fontMetrics.font.pointSize * 2 color: timelineRoot.palette.windowText + font.pointSize: fontMetrics.font.pointSize * 2 + text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call") } - } - ColumnLayout { id: deviceCombos @@ -93,41 +85,32 @@ Popup { Layout.alignment: Qt.AlignCenter Image { - Layout.preferredWidth: deviceCombos.imageSize Layout.preferredHeight: deviceCombos.imageSize + Layout.preferredWidth: deviceCombos.imageSize source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + timelineRoot.palette.windowText } - ComboBox { id: micCombo - Layout.fillWidth: true model: CallManager.mics } - } - RowLayout { - visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 Layout.alignment: Qt.AlignCenter + visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 Image { - Layout.preferredWidth: deviceCombos.imageSize Layout.preferredHeight: deviceCombos.imageSize + Layout.preferredWidth: deviceCombos.imageSize source: "image://colorimage/:/icons/icons/ui/video.svg?" + timelineRoot.palette.windowText } - ComboBox { id: cameraCombo - Layout.fillWidth: true model: CallManager.cameras } - } - } - RowLayout { id: buttonLayout @@ -136,9 +119,9 @@ Popup { function validateMic() { if (CallManager.mics.length == 0) { var dialog = deviceError.createObject(timelineRoot, { - "errorString": qsTr("No microphone found."), - "image": ":/icons/icons/ui/place-call.svg" - }); + "errorString": qsTr("No microphone found."), + "image": ":/icons/icons/ui/place-call.svg" + }); dialog.open(); timelineRoot.destroyOnClose(dialog); return false; @@ -150,60 +133,48 @@ Popup { spacing: callInv.height / 6 RoundButton { - implicitWidth: buttonLayout.buttonSize implicitHeight: buttonLayout.buttonSize - onClicked: { - CallManager.hangUp(); - close(); - } + implicitWidth: buttonLayout.buttonSize background: Rectangle { - radius: buttonLayout.buttonSize / 2 color: "#ff0000" + radius: buttonLayout.buttonSize / 2 } - contentItem: Image { source: "image://colorimage/:/icons/icons/ui/end-call.svg?#ffffff" } + onClicked: { + CallManager.hangUp(); + close(); + } } - RoundButton { id: acceptButton property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg" - implicitWidth: buttonLayout.buttonSize implicitHeight: buttonLayout.buttonSize + implicitWidth: buttonLayout.buttonSize + + background: Rectangle { + color: "#00ff00" + radius: buttonLayout.buttonSize / 2 + } + contentItem: Image { + source: "image://colorimage/" + acceptButton.image + "?#ffffff" + } + onClicked: { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; if (cameraCombo.visible) Settings.camera = cameraCombo.currentText; - CallManager.acceptInvite(); close(); } } - - background: Rectangle { - radius: buttonLayout.buttonSize / 2 - color: "#00ff00" - } - - contentItem: Image { - source: "image://colorimage/" + acceptButton.image + "?#ffffff" - } - } - } - } - - background: Rectangle { - color: timelineRoot.palette.window - border.color: timelineRoot.palette.windowText - } - } diff --git a/qml/voip/CallInviteBar.qml b/qml/voip/CallInviteBar.qml index a6a4d96d..61a11f6b 100644 --- a/qml/voip/CallInviteBar.qml +++ b/qml/voip/CallInviteBar.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import "../" import QtQuick 2.9 import QtQuick.Controls 2.3 @@ -10,129 +8,117 @@ import QtQuick.Layouts 1.2 import im.nheko Rectangle { - visible: CallManager.haveCallInvite && !Settings.mobileMode color: "#2ECC71" implicitHeight: visible ? rowLayout.height + 8 : 0 + visible: CallManager.haveCallInvite && !Settings.mobileMode Component { id: devicesDialog - CallDevices { } - } - Component { id: deviceError - DeviceError { } - } - RowLayout { id: rowLayout - anchors.left: parent.left + anchors.leftMargin: 8 anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 8 Avatar { - width: Nheko.avatarSize + displayName: CallManager.callPartyDisplayName height: Nheko.avatarSize url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") userid: CallManager.callParty - displayName: CallManager.callPartyDisplayName + width: Nheko.avatarSize + onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId) } - Label { Layout.leftMargin: 8 + color: "#000000" font.pointSize: fontMetrics.font.pointSize * 1.1 text: CallManager.callPartyDisplayName - color: "#000000" } - Image { Layout.leftMargin: 4 - Layout.preferredWidth: 24 Layout.preferredHeight: 24 + Layout.preferredWidth: 24 source: CallManager.callType == Voip.CallType.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg" } - Label { + color: "#000000" font.pointSize: fontMetrics.font.pointSize * 1.1 text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call") - color: "#000000" } - Item { Layout.fillWidth: true } - ImageButton { Layout.rightMargin: 16 - width: 20 - height: 20 - buttonTextColor: "#000000" - image: ":/icons/icons/ui/settings.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("Devices") + ToolTip.visible: hovered + buttonTextColor: "#000000" + height: 20 + hoverEnabled: true + image: ":/icons/icons/ui/settings.svg" + width: 20 + onClicked: { var dialog = devicesDialog.createObject(timelineRoot); dialog.open(); - timelineRoot.destroyOnClose(dialog); + timelineRoot.destroyOnClose(dialog); } } - Button { Layout.rightMargin: 4 icon.source: CallManager.callType == Voip.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg" - text: qsTr("Accept") palette: timelineRoot.palette + text: qsTr("Accept") + onClicked: { if (CallManager.mics.length == 0) { var dialog = deviceError.createObject(timelineRoot, { - "errorString": qsTr("No microphone found."), - "image": ":/icons/icons/ui/place-call.svg" - }); + "errorString": qsTr("No microphone found."), + "image": ":/icons/icons/ui/place-call.svg" + }); dialog.open(); - timelineRoot.destroyOnClose(dialog); - return ; + timelineRoot.destroyOnClose(dialog); + return; } else if (!CallManager.mics.includes(Settings.microphone)) { var dialog = deviceError.createObject(timelineRoot, { - "errorString": qsTr("Unknown microphone: %1").arg(Settings.microphone), - "image": ":/icons/icons/ui/place-call.svg" - }); + "errorString": qsTr("Unknown microphone: %1").arg(Settings.microphone), + "image": ":/icons/icons/ui/place-call.svg" + }); dialog.open(); - timelineRoot.destroyOnClose(dialog); - return ; + timelineRoot.destroyOnClose(dialog); + return; } if (CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) { var dialog = deviceError.createObject(timelineRoot, { - "errorString": qsTr("Unknown camera: %1").arg(Settings.camera), - "image": ":/icons/icons/ui/video.svg" - }); + "errorString": qsTr("Unknown camera: %1").arg(Settings.camera), + "image": ":/icons/icons/ui/video.svg" + }); dialog.open(); - timelineRoot.destroyOnClose(dialog); - return ; + timelineRoot.destroyOnClose(dialog); + return; } CallManager.acceptInvite(); } } - Button { Layout.rightMargin: 16 icon.source: "qrc:/icons/icons/ui/end-call.svg" - text: qsTr("Decline") palette: timelineRoot.palette + text: qsTr("Decline") + onClicked: { CallManager.hangUp(); } } - } - } diff --git a/qml/voip/DeviceError.qml b/qml/voip/DeviceError.qml index 751d1763..5a011877 100644 --- a/qml/voip/DeviceError.qml +++ b/qml/voip/DeviceError.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 @@ -13,31 +11,28 @@ Popup { property var image modal: true + + background: Rectangle { + border.color: timelineRoot.palette.windowText + color: timelineRoot.palette.window + } + // only set the anchors on Qt 5.12 or higher // see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop Component.onCompleted: { if (anchors) anchors.centerIn = parent; - } RowLayout { Image { - Layout.preferredWidth: 16 Layout.preferredHeight: 16 + Layout.preferredWidth: 16 source: "image://colorimage/" + image + "?" + timelineRoot.palette.windowText } - Label { - text: errorString color: timelineRoot.palette.windowText + text: errorString } - } - - background: Rectangle { - color: timelineRoot.palette.window - border.color: timelineRoot.palette.windowText - } - } diff --git a/qml/voip/PlaceCall.qml b/qml/voip/PlaceCall.qml index ddbc880e..d9b66dca 100644 --- a/qml/voip/PlaceCall.qml +++ b/qml/voip/PlaceCall.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import "../" import QtQuick 2.9 import QtQuick.Controls 2.3 @@ -11,52 +9,49 @@ import im.nheko Popup { modal: true + palette: timelineRoot.palette + + background: Rectangle { + border.color: timelineRoot.palette.windowText + color: timelineRoot.palette.window + } + // only set the anchors on Qt 5.12 or higher // see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop Component.onCompleted: { if (anchors) anchors.centerIn = parent; - } - palette: timelineRoot.palette Component { id: deviceError - DeviceError { } - } - ColumnLayout { id: columnLayout - spacing: 16 RowLayout { - Layout.topMargin: 8 Layout.leftMargin: 8 + Layout.topMargin: 8 Label { - text: qsTr("Place a call to %1?").arg(room.roomName) color: timelineRoot.palette.windowText + text: qsTr("Place a call to %1?").arg(room.roomName) } - Item { Layout.fillWidth: true } - } - RowLayout { id: buttonLayout - function validateMic() { if (CallManager.mics.length == 0) { var dialog = deviceError.createObject(timelineRoot, { - "errorString": qsTr("No microphone found."), - "image": ":/icons/icons/ui/place-call.svg" - }); + "errorString": qsTr("No microphone found."), + "image": ":/icons/icons/ui/place-call.svg" + }); dialog.open(); timelineRoot.destroyOnClose(dialog); return false; @@ -69,17 +64,18 @@ Popup { Avatar { Layout.rightMargin: cameraCombo.visible ? 16 : 64 - width: Nheko.avatarSize - height: Nheko.avatarSize - url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") displayName: room.roomName + height: Nheko.avatarSize roomid: room.roomId + url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") + width: Nheko.avatarSize + onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId) } - Button { - text: qsTr("Voice") icon.source: "qrc:/icons/icons/ui/place-call.svg" + text: qsTr("Voice") + onClicked: { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; @@ -88,11 +84,11 @@ Popup { } } } - Button { - visible: CallManager.cameras.length > 0 - text: qsTr("Video") icon.source: "qrc:/icons/icons/ui/video.svg" + text: qsTr("Video") + visible: CallManager.cameras.length > 0 + onClicked: { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; @@ -102,16 +98,15 @@ Popup { } } } - Button { - visible: CallManager.screenShareSupported - text: qsTr("Screen") icon.source: "qrc:/icons/icons/ui/screen-share.svg" + text: qsTr("Screen") + visible: CallManager.screenShareSupported + onClicked: { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; Settings.camera = cameraCombo.currentText; - var dialog = screenShareDialog.createObject(timelineRoot); dialog.open(); timelineRoot.destroyOnClose(dialog); @@ -119,67 +114,50 @@ Popup { } } } - Button { text: qsTr("Cancel") + onClicked: { close(); } } - } - ColumnLayout { spacing: 8 RowLayout { + Layout.bottomMargin: cameraCombo.visible ? 0 : 8 Layout.leftMargin: 8 Layout.rightMargin: 8 - Layout.bottomMargin: cameraCombo.visible ? 0 : 8 Image { - Layout.preferredWidth: 22 Layout.preferredHeight: 22 + Layout.preferredWidth: 22 source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + timelineRoot.palette.windowText } - ComboBox { id: micCombo - Layout.fillWidth: true model: CallManager.mics } - } - RowLayout { - visible: CallManager.cameras.length > 0 + Layout.bottomMargin: 8 Layout.leftMargin: 8 Layout.rightMargin: 8 - Layout.bottomMargin: 8 + visible: CallManager.cameras.length > 0 Image { - Layout.preferredWidth: 22 Layout.preferredHeight: 22 + Layout.preferredWidth: 22 source: "image://colorimage/:/icons/icons/ui/video.svg?" + timelineRoot.palette.windowText } - ComboBox { id: cameraCombo - Layout.fillWidth: true model: CallManager.cameras } - } - } - } - - background: Rectangle { - color: timelineRoot.palette.window - border.color: timelineRoot.palette.windowText - } - } diff --git a/qml/voip/ScreenShare.qml b/qml/voip/ScreenShare.qml index 7ece5ddd..ba214523 100644 --- a/qml/voip/ScreenShare.qml +++ b/qml/voip/ScreenShare.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import "../" import QtQuick 2.9 import QtQuick.Controls 2.3 @@ -10,154 +8,129 @@ import QtQuick.Layouts 1.2 import im.nheko Popup { + anchors.centerIn: parent modal: true + palette: timelineRoot.palette - anchors.centerIn: parent; + background: Rectangle { + border.color: timelineRoot.palette.windowText + color: timelineRoot.palette.window + } Component.onCompleted: { frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate); } - palette: timelineRoot.palette ColumnLayout { Label { - Layout.topMargin: 16 + Layout.alignment: Qt.AlignLeft Layout.bottomMargin: 16 Layout.leftMargin: 8 Layout.rightMargin: 8 - Layout.alignment: Qt.AlignLeft - text: qsTr("Share desktop with %1?").arg(room.roomName) + Layout.topMargin: 16 color: timelineRoot.palette.windowText + text: qsTr("Share desktop with %1?").arg(room.roomName) } - RowLayout { + Layout.bottomMargin: 8 Layout.leftMargin: 8 Layout.rightMargin: 8 - Layout.bottomMargin: 8 Label { Layout.alignment: Qt.AlignLeft - text: qsTr("Window:") color: timelineRoot.palette.windowText + text: qsTr("Window:") } - ComboBox { id: windowCombo - Layout.fillWidth: true model: CallManager.windowList() } - } - RowLayout { + Layout.bottomMargin: 8 Layout.leftMargin: 8 Layout.rightMargin: 8 - Layout.bottomMargin: 8 Label { Layout.alignment: Qt.AlignLeft - text: qsTr("Frame rate:") color: timelineRoot.palette.windowText + text: qsTr("Frame rate:") } - ComboBox { id: frameRateCombo - Layout.fillWidth: true model: ["25", "20", "15", "10", "5", "2", "1"] } - } - GridLayout { + Layout.margins: 8 columns: 2 rowSpacing: 10 - Layout.margins: 8 MatrixText { text: qsTr("Include your camera picture-in-picture") } - ToggleButton { id: pipCheckBox - - enabled: CallManager.cameras.length > 0 - checked: CallManager.cameras.length > 0 && Settings.screenSharePiP Layout.alignment: Qt.AlignRight + checked: CallManager.cameras.length > 0 && Settings.screenSharePiP + enabled: CallManager.cameras.length > 0 } - MatrixText { - text: qsTr("Request remote camera") ToolTip.text: qsTr("View your callee's camera like a regular video call") ToolTip.visible: hovered + text: qsTr("Request remote camera") } - ToggleButton { id: remoteVideoCheckBox - Layout.alignment: Qt.AlignRight - checked: Settings.screenShareRemoteVideo ToolTip.text: qsTr("View your callee's camera like a regular video call") ToolTip.visible: hovered + checked: Settings.screenShareRemoteVideo } - MatrixText { text: qsTr("Hide mouse cursor") } - ToggleButton { id: hideCursorCheckBox - Layout.alignment: Qt.AlignRight checked: Settings.screenShareHideCursor } - } - RowLayout { Layout.margins: 8 Item { Layout.fillWidth: true } - Button { - text: qsTr("Share") icon.source: "qrc:/icons/icons/ui/screen-share.svg" + text: qsTr("Share") onClicked: { Settings.screenShareFrameRate = frameRateCombo.currentText; Settings.screenSharePiP = pipCheckBox.checked; Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked; Settings.screenShareHideCursor = hideCursorCheckBox.checked; - CallManager.sendInvite(room.roomId, Voip.SCREEN, windowCombo.currentIndex); close(); } } - Button { text: qsTr("Preview") + onClicked: { CallManager.previewWindow(windowCombo.currentIndex); } } - Button { text: qsTr("Cancel") + onClicked: { close(); } } - } - } - - background: Rectangle { - color: timelineRoot.palette.window - border.color: timelineRoot.palette.windowText - } - } diff --git a/qml/voip/VideoCall.qml b/qml/voip/VideoCall.qml index 20bb0535..38c1c662 100644 --- a/qml/voip/VideoCall.qml +++ b/qml/voip/VideoCall.qml @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors -// // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick 2.9 import org.freedesktop.gstreamer.GLVideoItem 1.0