diff --git a/.ci/format.sh b/.ci/format.sh index 20c1d126..1a350163 100755 --- a/.ci/format.sh +++ b/.ci/format.sh @@ -15,6 +15,14 @@ do clang-format -i "$f" done; +if command -v /usr/lib64/qt6/bin/qmlformat &> /dev/null; then + /usr/lib64/qt6/bin/qmlformat -i $QML_FILES +elif command -v /usr/lib/qt6/bin/qmlformat &> /dev/null; then + /usr/lib/qt6/bin/qmlformat -i $QML_FILES +else + echo "No qmlformat found, skipping check!" +fi + git diff --exit-code if command -v /usr/lib64/qt6/bin/qmllint &> /dev/null; then diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml index c9f259e2..9336cd59 100644 --- a/resources/qml/CommunitiesList.qml +++ b/resources/qml/CommunitiesList.qml @@ -35,9 +35,9 @@ Page { anchors.left: parent.left anchors.right: parent.right + boundsBehavior: Flickable.StopAtBounds height: parent.height model: Communities.filtered() - boundsBehavior: Flickable.StopAtBounds ScrollBar.vertical: ScrollBar { id: scrollbar @@ -138,10 +138,11 @@ Page { id: avatar Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: avatarSize + Layout.preferredWidth: avatarSize color: communityItem.backgroundColor displayName: model.displayName enabled: false - Layout.preferredHeight: avatarSize roomid: model.id textColor: model.avatarUrl?.startsWith(":/") == true ? communityItem.unimportantText : communityItem.importantText url: { @@ -152,7 +153,6 @@ Page { else return ""; } - Layout.preferredWidth: avatarSize NotificationBubble { anchors.bottom: avatar.bottom diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml index d8205988..799bc3a4 100644 --- a/resources/qml/Completer.qml +++ b/resources/qml/Completer.qml @@ -149,10 +149,10 @@ Control { spacing: rowSpacing Avatar { - displayName: model.displayName - enabled: false Layout.preferredHeight: popup.avatarHeight Layout.preferredWidth: popup.avatarWidth + displayName: model.displayName + enabled: false url: model.avatarUrl.replace("mxc://", "image://MxcImage/") userid: model.userid } @@ -180,14 +180,14 @@ Control { visible: !!model.unicode } Avatar { + Layout.preferredHeight: popup.avatarHeight + Layout.preferredWidth: popup.avatarWidth crop: false displayName: model.shortcode enabled: false - Layout.preferredHeight: popup.avatarHeight //userid: model.shortcode url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/") visible: !model.unicode - Layout.preferredWidth: popup.avatarWidth } Label { Layout.leftMargin: Nheko.paddingSmall @@ -227,12 +227,12 @@ Control { spacing: rowSpacing Avatar { + Layout.preferredHeight: popup.avatarHeight + Layout.preferredWidth: popup.avatarWidth displayName: model.roomName enabled: false - Layout.preferredHeight: popup.avatarHeight roomid: model.roomid url: model.avatarUrl.replace("mxc://", "image://MxcImage/") - Layout.preferredWidth: popup.avatarWidth } Label { color: model.index == popup.currentIndex ? palette.highlightedText : palette.text @@ -251,12 +251,12 @@ Control { spacing: rowSpacing Avatar { + Layout.preferredHeight: popup.avatarHeight + Layout.preferredWidth: popup.avatarWidth displayName: model.roomName enabled: false - Layout.preferredHeight: popup.avatarHeight roomid: model.roomid url: model.avatarUrl.replace("mxc://", "image://MxcImage/") - Layout.preferredWidth: popup.avatarWidth } Label { color: model.index == popup.currentIndex ? palette.highlightedText : palette.text diff --git a/resources/qml/ForwardCompleter.qml b/resources/qml/ForwardCompleter.qml index 8ed6017c..73f8f702 100644 --- a/resources/qml/ForwardCompleter.qml +++ b/resources/qml/ForwardCompleter.qml @@ -55,8 +55,8 @@ Popup { id: replyPreview eventId: mid - userColor: TimelineManager.userColor(replyPreview.userId, palette.window) maxWidth: parent.width + userColor: TimelineManager.userColor(replyPreview.userId, palette.window) } MatrixTextField { id: roomTextInput @@ -64,7 +64,7 @@ Popup { color: palette.text width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2 - Keys.onPressed: (event) => { + Keys.onPressed: event => { if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) { event.accepted = true; completerPopup.up(); diff --git a/resources/qml/MatrixTextField.qml b/resources/qml/MatrixTextField.qml index 4ceadf6c..a08cded6 100644 --- a/resources/qml/MatrixTextField.qml +++ b/resources/qml/MatrixTextField.qml @@ -140,8 +140,8 @@ ColumnLayout { id: blueBar Layout.fillWidth: true - color: palette.highlight Layout.preferredHeight: 1 + color: palette.highlight Rectangle { id: blackBar diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 37793751..7b8eb646 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -44,14 +44,14 @@ Rectangle { ImageButton { Layout.alignment: Qt.AlignBottom Layout.margins: 8 + Layout.preferredHeight: 22 + Layout.preferredWidth: 22 ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : (CallManager.isOnCallOnOtherDevice ? qsTr("Already on a call") : qsTr("Place a call")) ToolTip.visible: hovered - Layout.preferredHeight: 22 hoverEnabled: true image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg" opacity: (CallManager.haveCallInvite || CallManager.isOnCallOnOtherDevice) ? 0.3 : 1 visible: CallManager.callsSupported && showAllButtons - Layout.preferredWidth: 22 onClicked: { if (room) { @@ -72,13 +72,13 @@ Rectangle { ImageButton { Layout.alignment: Qt.AlignBottom Layout.margins: 8 + Layout.preferredHeight: 22 + Layout.preferredWidth: 22 ToolTip.text: qsTr("Send a file") ToolTip.visible: hovered - Layout.preferredHeight: 22 hoverEnabled: true image: ":/icons/icons/ui/attach.svg" visible: showAllButtons - Layout.preferredWidth: 22 onClicked: room.input.openFileSelection() @@ -393,13 +393,13 @@ Rectangle { Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.margins: 8 + Layout.preferredHeight: 22 + Layout.preferredWidth: 22 ToolTip.text: qsTr("Stickers") ToolTip.visible: hovered - Layout.preferredHeight: 22 hoverEnabled: true image: ":/icons/icons/ui/sticky-note-solid.svg" visible: showAllButtons - Layout.preferredWidth: 22 onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function (row) { room.input.sticker(row); @@ -417,12 +417,12 @@ Rectangle { Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.margins: 8 + Layout.preferredHeight: 22 + Layout.preferredWidth: 22 ToolTip.text: qsTr("Emoji") ToolTip.visible: hovered - Layout.preferredHeight: 22 hoverEnabled: true image: ":/icons/icons/ui/smile.svg" - Layout.preferredWidth: 22 onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, room.roomId, function (plaintext, markdown) { messageInput.insert(messageInput.cursorPosition, markdown); @@ -438,13 +438,13 @@ Rectangle { ImageButton { Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.margins: 8 + Layout.preferredHeight: 22 + Layout.preferredWidth: 22 Layout.rightMargin: 8 ToolTip.text: qsTr("Send") ToolTip.visible: hovered - Layout.preferredHeight: 22 hoverEnabled: true image: ":/icons/icons/ui/send.svg" - Layout.preferredWidth: 22 onClicked: { room.input.send(); diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 97a121eb..5ad7c16e 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -16,8 +16,8 @@ Item { property int availableWidth: width property int padding: Nheko.paddingMedium - property string searchString: "" property Room roommodel: room + property string searchString: "" // HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu Connections { @@ -41,6 +41,7 @@ Item { property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - (scrollbar.interactive ? scrollbar.width : 0) readonly property alias filteringInProgress: filteredTimeline.filteringInProgress + property int lastScrollPos: 0 ScrollBar.vertical: scrollbar anchors.fill: parent @@ -49,6 +50,7 @@ Item { //onModelChanged: if (room) room.sendReset() //reuseItems: true boundsBehavior: Flickable.StopAtBounds + delegate: Settings.bubbles ? bubbleMessageStyle : defaultMessageStyle displayMarginBeginning: height / 4 displayMarginEnd: height / 4 model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room @@ -56,35 +58,6 @@ Item { spacing: 2 verticalLayoutDirection: ListView.BottomToTop - property int lastScrollPos: 0 - - // Fixup the scroll position when the height changes. Without this, the view is kept around the center of the currently visible content, while we usually want to stick to the bottom. - onMovementEnded: lastScrollPos = (contentY+height) - onModelChanged: lastScrollPos = (contentY+height) - onHeightChanged: contentY = (lastScrollPos-height) - - Component { - id: defaultMessageStyle - - TimelineDefaultMessageStyle { - messageActions: messageActionsC - messageContextMenu: messageContextMenuC - replyContextMenu: replyContextMenuC - scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) - } - } - Component { - id: bubbleMessageStyle - - TimelineBubbleMessageStyle { - messageActions: messageActionsC - messageContextMenu: messageContextMenuC - replyContextMenu: replyContextMenuC - scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) - } - } - - delegate: Settings.bubbles ? bubbleMessageStyle : defaultMessageStyle footer: Item { anchors.horizontalCenter: parent.horizontalCenter anchors.margins: Nheko.paddingLarge @@ -109,7 +82,32 @@ Item { if (atYEnd && room) model.currentIndex = 0; } + onHeightChanged: contentY = (lastScrollPos - height) + onModelChanged: lastScrollPos = (contentY + height) + + // Fixup the scroll position when the height changes. Without this, the view is kept around the center of the currently visible content, while we usually want to stick to the bottom. + onMovementEnded: lastScrollPos = (contentY + height) + Component { + id: defaultMessageStyle + + TimelineDefaultMessageStyle { + messageActions: messageActionsC + messageContextMenu: messageContextMenuC + replyContextMenu: replyContextMenuC + scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) + } + } + Component { + id: bubbleMessageStyle + + TimelineBubbleMessageStyle { + messageActions: messageActionsC + messageContextMenu: messageContextMenuC + replyContextMenu: replyContextMenuC + scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) + } + } TimelineFilter { id: filteredTimeline @@ -124,13 +122,13 @@ Item { // use comma to update on scroll property alias model: row.model + anchors.bottom: attached?.top + anchors.right: attached?.right hoverEnabled: true padding: Nheko.paddingSmall + parent: chat.contentItem visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered) z: 10 - parent: chat.contentItem - anchors.bottom: attached?.top - anchors.right: attached?.right background: Rectangle { border.color: palette.buttonText @@ -200,6 +198,7 @@ Item { } } ImageButton { + Layout.preferredWidth: 16 ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Edit") ToolTip.visible: hovered @@ -207,7 +206,6 @@ Item { hoverEnabled: true image: ":/icons/icons/ui/edit.svg" visible: !!row.model && row.model.isEditable - Layout.preferredWidth: 16 onClicked: { if (row.model.isEditable) @@ -217,13 +215,13 @@ Item { ImageButton { id: reactButton + Layout.preferredWidth: 16 ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("React") ToolTip.visible: hovered hoverEnabled: true image: ":/icons/icons/ui/smile-add.svg" visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false - Layout.preferredWidth: 16 onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, room.roomId, function (plaintext, markdown) { var event_id = row.model ? row.model.eventId : ""; @@ -232,28 +230,29 @@ Item { }) } ImageButton { + Layout.preferredWidth: 16 ToolTip.delay: Nheko.tooltipDelay ToolTip.text: (row.model && row.model.threadId) ? qsTr("Reply in thread") : qsTr("New thread") ToolTip.visible: hovered hoverEnabled: true image: (row.model && row.model.threadId) ? ":/icons/icons/ui/thread.svg" : ":/icons/icons/ui/new-thread.svg" visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false - Layout.preferredWidth: 16 onClicked: room.thread = (row.model.threadId || row.model.eventId) } ImageButton { + Layout.preferredWidth: 16 ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Reply") ToolTip.visible: hovered hoverEnabled: true image: ":/icons/icons/ui/reply.svg" visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false - Layout.preferredWidth: 16 onClicked: room.reply = row.model.eventId } ImageButton { + Layout.preferredWidth: 16 ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Go to message") ToolTip.visible: hovered @@ -261,7 +260,6 @@ Item { hoverEnabled: true image: ":/icons/icons/ui/go-to.svg" visible: !!row.model && filteredTimeline.filterByContent - Layout.preferredWidth: 16 onClicked: { topBar.searchString = ""; @@ -271,12 +269,12 @@ Item { ImageButton { id: optionsButton + Layout.preferredWidth: 16 ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Options") ToolTip.visible: hovered hoverEnabled: true image: ":/icons/icons/ui/options.svg" - Layout.preferredWidth: 16 onClicked: messageContextMenuC.show(row.model.eventId, row.model.threadId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton) } @@ -413,9 +411,9 @@ Item { Component { id: reportDialog - ReportMessage {} + ReportMessage { + } } - Platform.MenuItem { enabled: visible text: qsTr("Go to &message") @@ -523,10 +521,13 @@ Item { } } Platform.MenuItem { - text: qsTr("Report message") enabled: visible + text: qsTr("Report message") + onTriggered: function () { - var dialog = reportDialog.createObject(timelineRoot, {"eventId": messageContextMenu.eventId}); + var dialog = reportDialog.createObject(timelineRoot, { + "eventId": messageContextMenu.eventId + }); dialog.show(); dialog.forceActiveFocus(); timelineRoot.destroyOnClose(dialog); diff --git a/resources/qml/PrivacyScreen.qml b/resources/qml/PrivacyScreen.qml index 764bdf62..2a8b1f8f 100644 --- a/resources/qml/PrivacyScreen.qml +++ b/resources/qml/PrivacyScreen.qml @@ -50,8 +50,8 @@ Item { name: "Visible" PropertyChanges { - screenSaver.visible: true screenSaver.opacity: 1 + screenSaver.visible: true } }, State { diff --git a/resources/qml/ReplyPopup.qml b/resources/qml/ReplyPopup.qml index 17ce7ee4..0da6634d 100644 --- a/resources/qml/ReplyPopup.qml +++ b/resources/qml/ReplyPopup.qml @@ -30,9 +30,9 @@ Rectangle { anchors.top: parent.top anchors.topMargin: Nheko.paddingSmall eventId: room?.reply ?? "" + maxWidth: parent.width - anchors.leftMargin - anchors.rightMargin userColor: TimelineManager.userColor(modelData.userId, palette.window) visible: room && room.reply - maxWidth: parent.width - anchors.leftMargin - anchors.rightMargin } ImageButton { id: closeReplyButton diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 350b3846..17ef14eb 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -26,8 +26,8 @@ Page { Rectangle { Layout.fillWidth: true - color: Nheko.theme.separator Layout.preferredHeight: 1 + color: Nheko.theme.separator } Pane { Layout.alignment: Qt.AlignBottom @@ -45,11 +45,11 @@ Page { ImageButton { Layout.fillWidth: true Layout.margins: Nheko.paddingMedium + Layout.preferredHeight: 22 + Layout.preferredWidth: 22 ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Start a new chat") ToolTip.visible: hovered - Layout.preferredHeight: 22 - Layout.preferredWidth: 22 hoverEnabled: true image: ":/icons/icons/ui/add-square-button.svg" @@ -97,11 +97,11 @@ Page { ImageButton { Layout.fillWidth: true Layout.margins: Nheko.paddingMedium + Layout.preferredHeight: 22 + Layout.preferredWidth: 22 ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Room directory") ToolTip.visible: hovered - Layout.preferredHeight: 22 - Layout.preferredWidth: 22 hoverEnabled: true image: ":/icons/icons/ui/room-directory.svg" visible: !collapsed @@ -115,11 +115,11 @@ Page { ImageButton { Layout.fillWidth: true Layout.margins: Nheko.paddingMedium + Layout.preferredHeight: 22 + Layout.preferredWidth: 22 ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Search rooms (Ctrl+K)") ToolTip.visible: hovered - Layout.preferredHeight: 22 - Layout.preferredWidth: 22 hoverEnabled: true image: ":/icons/icons/ui/search.svg" ripple: false @@ -139,11 +139,11 @@ Page { ImageButton { Layout.fillWidth: true Layout.margins: Nheko.paddingMedium + Layout.preferredHeight: 22 + Layout.preferredWidth: 22 ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("User settings") ToolTip.visible: hovered - Layout.preferredHeight: 22 - Layout.preferredWidth: 22 hoverEnabled: true image: ":/icons/icons/ui/settings.svg" ripple: false @@ -268,37 +268,45 @@ Page { } Platform.MenuSeparator { } - Platform.MenuItemGroup { id: onlineStateGroup + } Platform.MenuItem { - text: qsTr("Automatic online status") - group: onlineStateGroup checkable: true checked: Settings.presence == Settings.AutomaticPresence - onTriggered: if (checked) Settings.presence = Settings.AutomaticPresence + group: onlineStateGroup + text: qsTr("Automatic online status") + + onTriggered: if (checked) + Settings.presence = Settings.AutomaticPresence } Platform.MenuItem { - text: qsTr("Online") - group: onlineStateGroup checkable: true checked: Settings.presence == Settings.Online - onTriggered: if (checked) Settings.presence = Settings.Online + group: onlineStateGroup + text: qsTr("Online") + + onTriggered: if (checked) + Settings.presence = Settings.Online } Platform.MenuItem { - text: qsTr("Unavailable") - group: onlineStateGroup checkable: true checked: Settings.presence == Settings.Unavailable - onTriggered: if (checked) Settings.presence = Settings.Unavailable + group: onlineStateGroup + text: qsTr("Unavailable") + + onTriggered: if (checked) + Settings.presence = Settings.Unavailable } Platform.MenuItem { - text: qsTr("Offline") - group: onlineStateGroup checkable: true checked: Settings.presence == Settings.Offline - onTriggered: if (checked) Settings.presence = Settings.Offline + group: onlineStateGroup + text: qsTr("Offline") + + onTriggered: if (checked) + Settings.presence = Settings.Offline } } TapHandler { @@ -319,8 +327,8 @@ Page { } Rectangle { Layout.fillWidth: true - color: Nheko.theme.separator Layout.preferredHeight: 2 + color: Nheko.theme.separator } Rectangle { id: unverifiedStuffBubble @@ -366,14 +374,14 @@ Page { id: closeUnverifiedBubble Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + Layout.preferredHeight: fontMetrics.font.pixelSize + Layout.preferredWidth: fontMetrics.font.pixelSize Layout.rightMargin: Nheko.paddingMedium ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Close") ToolTip.visible: closeUnverifiedBubble.hovered - Layout.preferredHeight: fontMetrics.font.pixelSize hoverEnabled: true image: ":/icons/icons/ui/dismiss.svg" - Layout.preferredWidth: fontMetrics.font.pixelSize onClicked: unverifiedStuffBubble.visible = false } @@ -398,8 +406,8 @@ Page { } Rectangle { Layout.fillWidth: true - color: Nheko.theme.separator Layout.preferredHeight: 1 + color: Nheko.theme.separator visible: unverifiedStuffBubble.visible } } @@ -436,9 +444,9 @@ Page { anchors.left: parent.left anchors.right: parent.right + boundsBehavior: Flickable.StopAtBounds height: parent.height model: Rooms - boundsBehavior: Flickable.StopAtBounds //reuseItems: true ScrollBar.vertical: ScrollBar { @@ -550,13 +558,13 @@ Page { id: avatar Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: avatarSize + Layout.preferredWidth: avatarSize displayName: roomName enabled: false roomid: roomId url: avatarUrl.replace("mxc://", "image://MxcImage/") userid: isDirect ? directChatOtherUserId : "" - Layout.preferredWidth: avatarSize - Layout.preferredHeight: avatarSize NotificationBubble { id: collapsedNotificationBubble @@ -576,8 +584,8 @@ Page { Layout.alignment: Qt.AlignLeft Layout.minimumWidth: 100 - Layout.preferredWidth: roomItem.width - avatar.width Layout.preferredHeight: avatar.height + Layout.preferredWidth: roomItem.width - avatar.width spacing: Nheko.paddingSmall visible: !collapsed diff --git a/resources/qml/TimelineBubbleMessageStyle.qml b/resources/qml/TimelineBubbleMessageStyle.qml index add701a1..37a50b8c 100644 --- a/resources/qml/TimelineBubbleMessageStyle.qml +++ b/resources/qml/TimelineBubbleMessageStyle.qml @@ -9,54 +9,53 @@ import im.nheko TimelineEvent { id: wrapper - ListView.delayRemove: true - width: chat.delegateMaxWidth - height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10) - anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter + + property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header + + property int bubbleMargin: 40 //room: chatRoot.roommodel required property var day - required property bool isSender + property alias hovered: messageHover.hovered required property int index + required property bool isEditable + required property bool isEdited + required property bool isEncrypted + required property bool isSender + required property Item messageActions + required property QtObject messageContextMenu + required property int notificationlevel property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day) property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent) property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId) - - required property date timestamp - required property string userId - required property string userName - required property string threadId - required property int userPowerlevel - required property bool isEdited - required property bool isEncrypted required property var reactions + required property QtObject replyContextMenu + property bool scrolledToThis: false required property int status + required property string threadId + required property date timestamp required property int trustlevel - required property int notificationlevel required property int type - required property bool isEditable - - required property QtObject messageContextMenu - required property QtObject replyContextMenu - required property Item messageActions - - property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header - - property alias hovered: messageHover.hovered - property bool scrolledToThis: false + required property string userId + required property string userName + required property int userPowerlevel + ListView.delayRemove: true + anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter + height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10) mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0) + 4 - replyInset: mainInset + 4 + Nheko.paddingSmall - - property int bubbleMargin: 40 - maxWidth: chat.delegateMaxWidth - avatarMargin - bubbleMargin + replyInset: mainInset + 4 + Nheko.paddingSmall + width: chat.delegateMaxWidth data: [ Loader { id: section active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent + visible: status == Loader.Ready + z: 4 + //asynchronous: true sourceComponent: TimelineSectionHeader { day: wrapper.day @@ -71,13 +70,12 @@ TimelineEvent { userName: wrapper.userName userPowerlevel: wrapper.userPowerlevel } - visible: status == Loader.Ready - z: 4 - }, + }, Rectangle { - anchors.fill: gridContainer - property color threadColor: TimelineManager.userColor(wrapper.threadId, palette.base) property color threadBackgroundColor: wrapper.threadId ? Qt.tint(palette.base, Qt.hsla(threadColor.hslHue, 0.7, threadColor.hslLightness, 0.1)) : "transparent" + property color threadColor: TimelineManager.userColor(wrapper.threadId, palette.base) + + anchors.fill: gridContainer color: (Settings.messageHoverHighlight && messageHover.hovered) ? palette.alternateBase : threadBackgroundColor // this looks better without margins @@ -91,8 +89,8 @@ TimelineEvent { }, Rectangle { id: scrollHighlight - anchors.fill: gridContainer + anchors.fill: gridContainer color: palette.highlight enabled: false opacity: 0 @@ -133,37 +131,43 @@ TimelineEvent { Item { id: gridContainer - width: wrapper.width - wrapper.avatarMargin implicitHeight: messageBubble.implicitHeight + width: wrapper.width - wrapper.avatarMargin x: wrapper.avatarMargin y: section.visible && section.active ? section.y + section.height : 0 HoverHandler { id: messageHover + blocking: false + onHoveredChanged: () => { if (!Settings.mobileMode && hovered) { if (!messageActions.hovered) { messageActions.model = wrapper; messageActions.attached = wrapper; - messageActions.anchors.bottomMargin = -gridContainer.y + messageActions.anchors.bottomMargin = -gridContainer.y; //messageActions.anchors.rightMargin = metadata.width } } } - } - - AbstractButton { id: messageBubble + property color userColor: TimelineManager.userColor(wrapper.main?.userId ?? '', palette.base) + + anchors.horizontalCenter: wrapper.isStateEvent ? parent.horizontalCenter : undefined anchors.left: (wrapper.isStateEvent || wrapper.isSender) ? undefined : parent.left // qmllint disable Quick.anchor-combinations anchors.right: (wrapper.isStateEvent || !wrapper.isSender) ? undefined : parent.right - anchors.horizontalCenter: wrapper.isStateEvent ? parent.horizontalCenter : undefined - - property color userColor: TimelineManager.userColor(wrapper.main?.userId ?? '', palette.base) + padding: wrapper.isStateEvent ? 0 : 4 + background: Rectangle { + border.color: Nheko.theme.red + border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0 + color: !wrapper.isStateEvent ? Qt.tint(palette.base, Qt.hsla(messageBubble.userColor.hslHue, wrapper.hovered ? 0.8 : 0.5, messageBubble.userColor.hslLightness, 0.2)) : "transparent" + radius: 4 + } contentItem: Item { id: contentPlacementContainer @@ -173,52 +177,47 @@ TimelineEvent { // property bool fitsMetadataInside: wrapper.main?.positionAt ? (wrapper.main.positionAt(wrapper.main.width, wrapper.main.height - 4) == wrapper.main.positionAt(wrapper.main.width - metadata.width, wrapper.main.height - 4)) : false property bool fitsMetadataInside: false - implicitWidth: Math.max((wrapper.reply?.width ?? 0) + wrapper.replyInset, (wrapper.main?.width ?? 0) + wrapper.mainInset + ((fitsMetadata && !fitsMetadataInside) ? metadata.width : 0)) implicitHeight: contentColumn.implicitHeight + ((fitsMetadata || fitsMetadataInside) ? 0 : metadata.height) + implicitWidth: Math.max((wrapper.reply?.width ?? 0) + wrapper.replyInset, (wrapper.main?.width ?? 0) + wrapper.mainInset + ((fitsMetadata && !fitsMetadataInside) ? metadata.width : 0)) TimelineMetadata { id: metadata - scaling: 0.75 - - anchors.right: parent.right anchors.bottom: parent.bottom - - visible: !wrapper.isStateEvent - + anchors.right: parent.right eventId: wrapper.eventId - status: wrapper.status - trustlevel: wrapper.trustlevel isEdited: wrapper.isEdited isEncrypted: wrapper.isEncrypted + room: wrapper.room + scaling: 0.75 + status: wrapper.status threadId: wrapper.threadId timestamp: wrapper.timestamp - room: wrapper.room + trustlevel: wrapper.trustlevel + visible: !wrapper.isStateEvent } - Column { id: contentColumn anchors.left: parent.left anchors.right: parent.right + data: [replyRow, wrapper.main] AbstractButton { id: replyRow - visible: wrapper.reply - - height: replyLine.height - anchors.left: parent.left - anchors.right: parent.right property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base) + anchors.left: parent.left + anchors.right: parent.right clip: true + height: replyLine.height + visible: wrapper.reply - NhekoCursorShape { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor + background: Rectangle { + //width: replyRow.implicitContentWidth + color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1)) } - contentItem: Row { id: replyRowLay @@ -226,94 +225,81 @@ TimelineEvent { Rectangle { id: replyLine - height: Math.min( wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height + color: replyRow.userColor + height: Math.min(wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height width: 4 } - Column { - spacing: 0 - id: replyCol + data: [replyUserButton, wrapper.reply,] + spacing: 0 + AbstractButton { id: replyUserButton contentItem: Label { id: userName_ - text: wrapper.reply?.userName ?? '' + color: replyRow.userColor + text: wrapper.reply?.userName ?? '' textFormat: Text.RichText width: wrapper.maxWidth //elideWidth: wrapper.maxWidth } + onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId) } - data: [ - replyUserButton, - wrapper.reply, - ] } } - background: Rectangle { - //width: replyRow.implicitContentWidth - color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1)) - } - onClicked: { - let link = wrapper.reply.hoveredLink + let link = wrapper.reply.hoveredLink; if (link) { - Nheko.openLink(link) + Nheko.openLink(link); } else { - console.log("Scrolling to "+wrapper.replyTo); - wrapper.room.showEvent(wrapper.replyTo) + console.log("Scrolling to " + wrapper.replyTo); + wrapper.room.showEvent(wrapper.replyTo); } } - onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX-replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo) + onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX - replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo) + + NhekoCursorShape { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + } TapHandler { acceptedButtons: Qt.RightButton - onSingleTapped: (eventPoint) => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x-replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo) - gesturePolicy: TapHandler.ReleaseWithinBounds acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad + gesturePolicy: TapHandler.ReleaseWithinBounds + + onSingleTapped: eventPoint => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x - replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo) } } - - data: [replyRow, wrapper.main] } - - - } - - padding: wrapper.isStateEvent ? 0 : 4 - background: Rectangle { - color: !wrapper.isStateEvent ? Qt.tint(palette.base, Qt.hsla(messageBubble.userColor.hslHue, wrapper.hovered ? 0.8 : 0.5, messageBubble.userColor.hslLightness, 0.2)) : "transparent" - radius: 4 - border.color: Nheko.theme.red - border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0 } } - DragHandler { id: replyDragHandler - yAxis.enabled: false + xAxis.enabled: true - xAxis.minimum: wrapper.avatarMargin - 100 xAxis.maximum: wrapper.avatarMargin + xAxis.minimum: wrapper.avatarMargin - 100 + yAxis.enabled: false + onActiveChanged: { if (!replyDragHandler.active) { if (replyDragHandler.xAxis.minimum <= replyDragHandler.xAxis.activeValue + 1) { - wrapper.room.reply = wrapper.eventId + wrapper.room.reply = wrapper.eventId; } gridContainer.x = wrapper.avatarMargin; } } } - TapHandler { onDoubleTapped: wrapper.room.reply = wrapper.eventId } - }, Reactions { id: reactionRow @@ -347,4 +333,3 @@ TimelineEvent { } ] } - diff --git a/resources/qml/TimelineDefaultMessageStyle.qml b/resources/qml/TimelineDefaultMessageStyle.qml index e9b52e93..27935109 100644 --- a/resources/qml/TimelineDefaultMessageStyle.qml +++ b/resources/qml/TimelineDefaultMessageStyle.qml @@ -9,52 +9,52 @@ import im.nheko TimelineEvent { id: wrapper - ListView.delayRemove: true - width: chat.delegateMaxWidth - height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10) - anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter + + property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header + //room: chatRoot.roommodel required property var day - required property bool isSender + property alias hovered: messageHover.hovered required property int index + required property bool isEditable + required property bool isEdited + required property bool isEncrypted + required property bool isSender + required property Item messageActions + required property QtObject messageContextMenu + required property int notificationlevel property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day) property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent) property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId) - - required property date timestamp - required property string userId - required property string userName - required property string threadId - required property int userPowerlevel - required property bool isEdited - required property bool isEncrypted required property var reactions + required property QtObject replyContextMenu + property bool scrolledToThis: false required property int status + required property string threadId + required property date timestamp required property int trustlevel - required property int notificationlevel required property int type - required property bool isEditable - - required property QtObject messageContextMenu - required property QtObject replyContextMenu - required property Item messageActions - - property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header - - property alias hovered: messageHover.hovered - property bool scrolledToThis: false + required property string userId + required property string userName + required property int userPowerlevel + ListView.delayRemove: true + anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter + height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10) mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0) - replyInset: mainInset + 4 + Nheko.paddingSmall - maxWidth: chat.delegateMaxWidth - avatarMargin - metadata.width + replyInset: mainInset + 4 + Nheko.paddingSmall + width: chat.delegateMaxWidth data: [ Loader { id: section active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent + visible: status == Loader.Ready + z: 4 + //asynchronous: true sourceComponent: TimelineSectionHeader { day: wrapper.day @@ -69,9 +69,7 @@ TimelineEvent { userName: wrapper.userName userPowerlevel: wrapper.userPowerlevel } - visible: status == Loader.Ready - z: 4 - }, + }, Rectangle { anchors.fill: gridContainer color: (Settings.messageHoverHighlight && messageHover.hovered) ? palette.alternateBase : "transparent" @@ -87,8 +85,8 @@ TimelineEvent { }, Rectangle { id: scrollHighlight - anchors.fill: gridContainer + anchors.fill: gridContainer color: palette.highlight enabled: false opacity: 0 @@ -127,41 +125,41 @@ TimelineEvent { } }, Rectangle { + anchors.left: gridContainer.left + anchors.leftMargin: -2 anchors.top: gridContainer.top - anchors.left: gridContainer.left anchors.topMargin: -2 - anchors.leftMargin: -2 - color: "transparent" border.color: Nheko.theme.red border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0 - radius: 4 + color: "transparent" height: contentColumn.implicitHeight + 4 + radius: 4 width: contentColumn.implicitWidth + 4 }, Row { id: gridContainer + spacing: Nheko.paddingSmall width: wrapper.width - wrapper.avatarMargin x: wrapper.avatarMargin y: section.visible && section.active ? section.y + section.height : 0 - spacing: Nheko.paddingSmall HoverHandler { id: messageHover + blocking: false + onHoveredChanged: () => { if (!Settings.mobileMode && hovered) { if (!messageActions.hovered) { messageActions.model = wrapper; messageActions.attached = wrapper; - messageActions.anchors.bottomMargin = -gridContainer.y - messageActions.anchors.rightMargin = metadata.width + messageActions.anchors.bottomMargin = -gridContainer.y; + messageActions.anchors.rightMargin = metadata.width; } } } - } - AbstractButton { ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Part of a thread") @@ -179,31 +177,29 @@ TimelineEvent { color: TimelineManager.userColor(wrapper.threadId, palette.base) } } - Item { + height: 1 visible: wrapper.isStateEvent width: (wrapper.maxWidth - (wrapper.main?.width ?? 0)) / 2 - height: 1 } - Column { id: contentColumn + data: [replyRow, wrapper.main,] + AbstractButton { id: replyRow - visible: wrapper.reply - - height: replyLine.height property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base) clip: true + height: replyLine.height + visible: wrapper.reply - NhekoCursorShape { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor + background: Rectangle { + //width: replyRow.implicitContentWidth + color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1)) } - contentItem: Row { id: replyRowLay @@ -211,103 +207,96 @@ TimelineEvent { Rectangle { id: replyLine - height: Math.min( wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height + color: replyRow.userColor + height: Math.min(wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height width: 4 } - Column { - spacing: 0 - id: replyCol + data: [replyUserButton, wrapper.reply,] + spacing: 0 + AbstractButton { id: replyUserButton contentItem: Label { id: userName_ - text: wrapper.reply?.userName ?? '' + color: replyRow.userColor + text: wrapper.reply?.userName ?? '' textFormat: Text.RichText width: wrapper.maxWidth //elideWidth: wrapper.maxWidth } + onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId) } - data: [ - replyUserButton, - wrapper.reply, - ] } } - background: Rectangle { - //width: replyRow.implicitContentWidth - color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1)) - } - onClicked: { - let link = wrapper.reply.hoveredLink + let link = wrapper.reply.hoveredLink; if (link) { - Nheko.openLink(link) + Nheko.openLink(link); } else { - console.log("Scrolling to "+wrapper.replyTo); - wrapper.room.showEvent(wrapper.replyTo) + console.log("Scrolling to " + wrapper.replyTo); + wrapper.room.showEvent(wrapper.replyTo); } } - onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX-replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo) + onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX - replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo) + + NhekoCursorShape { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + } TapHandler { acceptedButtons: Qt.RightButton - onSingleTapped: (eventPoint) => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x-replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo) - gesturePolicy: TapHandler.ReleaseWithinBounds acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad + gesturePolicy: TapHandler.ReleaseWithinBounds + + onSingleTapped: eventPoint => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x - replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo) } } - - data: [ - replyRow, wrapper.main, - ] } - DragHandler { id: replyDragHandler - yAxis.enabled: false + xAxis.enabled: true - xAxis.minimum: wrapper.avatarMargin - 100 xAxis.maximum: wrapper.avatarMargin + xAxis.minimum: wrapper.avatarMargin - 100 + yAxis.enabled: false + onActiveChanged: { if (!replyDragHandler.active) { if (replyDragHandler.xAxis.minimum <= replyDragHandler.xAxis.activeValue + 1) { - wrapper.room.reply = wrapper.eventId + wrapper.room.reply = wrapper.eventId; } gridContainer.x = wrapper.avatarMargin; } } } - TapHandler { onDoubleTapped: wrapper.room.reply = wrapper.eventId } }, - TimelineMetadata { - id: metadata + TimelineMetadata { + id: metadata - scaling: 1 - - anchors.right: parent.right - y: section.visible && section.active ? section.y + section.height : 0 - - visible: !wrapper.isStateEvent - - eventId: wrapper.eventId - status: wrapper.status - trustlevel: wrapper.trustlevel - isEdited: wrapper.isEdited - isEncrypted: wrapper.isEncrypted - threadId: wrapper.threadId - timestamp: wrapper.timestamp - room: wrapper.room - }, + anchors.right: parent.right + eventId: wrapper.eventId + isEdited: wrapper.isEdited + isEncrypted: wrapper.isEncrypted + room: wrapper.room + scaling: 1 + status: wrapper.status + threadId: wrapper.threadId + timestamp: wrapper.timestamp + trustlevel: wrapper.trustlevel + visible: !wrapper.isStateEvent + y: section.visible && section.active ? section.y + section.height : 0 + }, Reactions { id: reactionRow diff --git a/resources/qml/TimelineMetadata.qml b/resources/qml/TimelineMetadata.qml index 0085b950..5ac69f65 100644 --- a/resources/qml/TimelineMetadata.qml +++ b/resources/qml/TimelineMetadata.qml @@ -11,17 +11,16 @@ import im.nheko RowLayout { id: metadata - property int iconSize: Math.floor(fontMetrics.ascent * scaling) - required property double scaling - required property string eventId - required property int status - required property int trustlevel + property int iconSize: Math.floor(fontMetrics.ascent * scaling) required property bool isEdited required property bool isEncrypted + required property Room room + required property double scaling + required property int status required property string threadId required property date timestamp - required property Room room + required property int trustlevel spacing: 2 @@ -43,6 +42,7 @@ RowLayout { sourceSize.height: parent.iconSize * Screen.devicePixelRatio sourceSize.width: parent.iconSize * Screen.devicePixelRatio visible: metadata.isEdited || metadata.eventId == metadata.room.edit + HoverHandler { id: editHovered diff --git a/resources/qml/TimelineSectionHeader.qml b/resources/qml/TimelineSectionHeader.qml index 99fc1ad9..1be30ebe 100644 --- a/resources/qml/TimelineSectionHeader.qml +++ b/resources/qml/TimelineSectionHeader.qml @@ -6,11 +6,9 @@ import QtQuick import QtQuick.Controls import QtQuick.Window import im.nheko - import "./components" Column { - required property var day required property bool isSender required property bool isStateEvent @@ -79,31 +77,14 @@ Column { target: room } - AbstractButton { id: userNameButton - PowerlevelIndicator { - id: powerlevelIndicator - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - - powerlevel: userPowerlevel - height: fontMetrics.ascent - width: height - - sourceSize.width: fontMetrics.lineSpacing - sourceSize.height: fontMetrics.lineSpacing - - permissions: room ? room.permissions : null - visible: isAdmin || isModerator - } - ToolTip.delay: Nheko.tooltipDelay ToolTip.text: userId ToolTip.visible: hovered - leftPadding: powerlevelIndicator.visible ? 16 : 0 leftInset: 0 + leftPadding: powerlevelIndicator.visible ? 16 : 0 rightInset: 0 rightPadding: 0 @@ -117,6 +98,19 @@ Column { onClicked: room.openUserProfile(userId) + PowerlevelIndicator { + id: powerlevelIndicator + + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + height: fontMetrics.ascent + permissions: room ? room.permissions : null + powerlevel: userPowerlevel + sourceSize.height: fontMetrics.lineSpacing + sourceSize.width: fontMetrics.lineSpacing + visible: isAdmin || isModerator + width: height + } TextMetrics { id: userNameTextMetrics @@ -153,7 +147,7 @@ Column { Connections { function onPresenceChanged(id) { if (id == userId) - statusMsg.userStatus = Presence.userStatus(userId); + statusMsg.userStatus = Presence.userStatus(userId); } target: Presence @@ -161,4 +155,3 @@ Column { } } } - diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 006eb57f..c05bdef2 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -59,7 +59,7 @@ Item { visible: TimelineManager.isInitialSync z: 3 - Behavior on opacity { + Behavior on opacity { NumberAnimation { duration: 100 } @@ -188,9 +188,9 @@ Item { displayName: parent.roomName enabled: false implicitHeight: 130 + implicitWidth: 130 roomid: parent.roomId url: parent.avatarUrl.replace("mxc://", "image://MxcImage/") - implicitWidth: 130 } RowLayout { Layout.alignment: Qt.AlignHCenter @@ -293,10 +293,10 @@ Item { displayName: roomPreview?.inviterDisplayName ?? "" enabled: true implicitHeight: 48 + implicitWidth: 48 roomid: preview.roomId url: (roomPreview?.inviterAvatarUrl ?? "").replace("mxc://", "image://MxcImage/") userid: roomPreview?.inviterUserId ?? "" - implicitWidth: 48 onClicked: TimelineManager.openGlobalUserProfile(roomPreview.inviterUserId) } @@ -378,8 +378,8 @@ Item { running: false onTriggered: { - timelineEffects.removeParticles() - shouldEffectsRun = false + timelineEffects.removeParticles(); + shouldEffectsRun = false; } } Connections { diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 6d1cc445..7117e273 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -124,9 +124,9 @@ Pane { Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines Layout.row: 2 clip: true - enabled: false // don't use the disabled color color: topBar.palette.text + enabled: false selectByMouse: false text: roomTopic } @@ -287,9 +287,9 @@ Pane { property var e: room ? room.getDump(modelData, "pins") : {} - maxWidth: pinnedMessages.width //Layout.preferredHeight: height eventId: e.eventId ?? "" + maxWidth: pinnedMessages.width userColor: TimelineManager.userColor(e.userId, palette.window) Connections { diff --git a/resources/qml/components/AdaptiveLayout.qml b/resources/qml/components/AdaptiveLayout.qml index 86a0d4b6..50418f20 100644 --- a/resources/qml/components/AdaptiveLayout.qml +++ b/resources/qml/components/AdaptiveLayout.qml @@ -13,64 +13,16 @@ Container { 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; @@ -79,6 +31,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 @@ -87,49 +43,84 @@ 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 + grabPermissions: PointerHandler.CanTakeOverFromAnything | PointerHandler.ApprovesTakeOverByHandlersOfSameType + margin: container.splitterGrabMargin xAxis.enabled: true - yAxis.enabled: false - xAxis.minimum: splitter.minimumWidth - 1 xAxis.maximum: splitter.maximumWidth - margin: container.splitterGrabMargin - grabPermissions: PointerHandler.CanTakeOverFromAnything | PointerHandler.ApprovesTakeOverByHandlersOfSameType + 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 && !Settings.reducedMotion) ? 200 : 0 highlightRangeMode: ListView.StrictlyEnforceRange interactive: singlePageMode - highlightMoveDuration: (container.singlePageMode && !Settings.reducedMotion) ? 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/resources/qml/components/AdaptiveLayoutElement.qml b/resources/qml/components/AdaptiveLayoutElement.qml index 9c8d4622..9920650a 100644 --- a/resources/qml/components/AdaptiveLayoutElement.qml +++ b/resources/qml/components/AdaptiveLayoutElement.qml @@ -5,20 +5,20 @@ import QtQuick 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/resources/qml/components/AvatarListTile.qml b/resources/qml/components/AvatarListTile.qml index 75c69098..97815b0c 100644 --- a/resources/qml/components/AvatarListTile.qml +++ b/resources/qml/components/AvatarListTile.qml @@ -10,25 +10,26 @@ import im.nheko Rectangle { id: tile + property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) + required property string avatarUrl property color background: palette.window - property color importantText: palette.text - property color unimportantText: palette.buttonText property color bubbleBackground: palette.highlight property color bubbleText: 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 bool crop: true + property color importantText: 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: palette.buttonText 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" @@ -37,13 +38,12 @@ Rectangle { PropertyChanges { tile { background: palette.dark - importantText: palette.brightText - unimportantText: palette.brightText bubbleBackground: palette.highlight bubbleText: palette.highlightedText + importantText: palette.brightText + unimportantText: palette.brightText } } - }, State { name: "selected" @@ -52,37 +52,35 @@ Rectangle { PropertyChanges { tile { background: palette.highlight - importantText: palette.highlightedText - unimportantText: palette.highlightedText bubbleBackground: palette.highlightedText bubbleText: palette.highlight + importantText: palette.highlightedText + unimportantText: 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 + crop: tile.crop + displayName: title + enabled: false implicitHeight: avatarSize implicitWidth: avatarSize url: tile.avatarUrl.replace("mxc://", "image://MxcImage/") - displayName: title - crop: tile.crop } - ColumnLayout { id: textContent @@ -103,33 +101,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/resources/qml/components/FlatButton.qml b/resources/qml/components/FlatButton.qml index 0636bc04..debaa50a 100644 --- a/resources/qml/components/FlatButton.qml +++ b/resources/qml/components/FlatButton.qml @@ -12,53 +12,51 @@ 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: "" - - MultiEffect { - anchors.fill: control.background - shadowHorizontalOffset: 3 - shadowVerticalOffset: 3 - shadowBlur: 8 - shadowEnabled: true - shadowColor: "#80000000" - source: control.background + background: Rectangle { + color: Qt.lighter(palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1)) + //height: control.contentItem.implicitHeight * 2 + //width: control.contentItem.implicitWidth * 2 + radius: height / 8 } - contentItem: RowLayout { - spacing: 0 anchors.centerIn: parent + spacing: 0 + Image { - Layout.leftMargin: Nheko.paddingMedium Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Layout.leftMargin: Nheko.paddingMedium Layout.preferredHeight: fontMetrics.font.pixelSize * 1.5 - Layout.preferredWidth: fontMetrics.font.pixelSize * 1.5 - visible: !!iconImage + Layout.preferredWidth: fontMetrics.font.pixelSize * 1.5 source: iconImage + visible: !!iconImage } - Text { Layout.alignment: Qt.AlignHCenter - text: control.text + //font.capitalization: Font.AllUppercase + color: 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: 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(palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1)) + MultiEffect { + anchors.fill: control.background + shadowBlur: 8 + shadowColor: "#80000000" + shadowEnabled: true + shadowHorizontalOffset: 3 + shadowVerticalOffset: 3 + source: control.background } - } diff --git a/resources/qml/components/MainWindowDialog.qml b/resources/qml/components/MainWindowDialog.qml index 3372d429..7a1deba4 100644 --- a/resources/qml/components/MainWindowDialog.qml +++ b/resources/qml/components/MainWindowDialog.qml @@ -10,30 +10,29 @@ Dialog { default property alias inner: scroll.data property int useableWidth: scroll.width - scroll.ScrollBar.vertical.width - parent: Overlay.overlay anchors.centerIn: parent + closePolicy: Popup.NoAutoClose height: (Math.floor(parent.height / 2) - Nheko.paddingLarge) * 2 - width: (Math.floor(parent.width / 2) - Nheko.paddingLarge) * 2 - padding: 0 modal: true + padding: 0 + parent: Overlay.overlay standardButtons: Dialog.Ok | Dialog.Cancel - closePolicy: Popup.NoAutoClose + width: (Math.floor(parent.width / 2) - Nheko.paddingLarge) * 2 + + background: Rectangle { + border.color: Nheko.theme.separator + border.width: 1 + color: 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: palette.window - border.color: Nheko.theme.separator - border.width: 1 - radius: Nheko.paddingSmall - } - } diff --git a/resources/qml/components/NhekoTabButton.qml b/resources/qml/components/NhekoTabButton.qml index 796177e8..14508612 100644 --- a/resources/qml/components/NhekoTabButton.qml +++ b/resources/qml/components/NhekoTabButton.qml @@ -9,21 +9,19 @@ import im.nheko 1.0 TabButton { id: control - contentItem: Text { - text: control.text - font: control.font - opacity: enabled ? 1.0 : 0.3 - color: control.down ? palette.highlightedText : palette.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - background: Rectangle { border.color: control.down ? palette.highlight : Nheko.theme.separator - color: control.checked ? palette.highlight : palette.base border.width: 1 + color: control.checked ? palette.highlight : palette.base radius: 2 } + contentItem: Text { + color: control.down ? palette.highlightedText : palette.text + elide: Text.ElideRight + font: control.font + horizontalAlignment: Text.AlignHCenter + opacity: enabled ? 1.0 : 0.3 + text: control.text + verticalAlignment: Text.AlignVCenter + } } - diff --git a/resources/qml/components/NotificationBubble.qml b/resources/qml/components/NotificationBubble.qml index d4838e92..aa46d4e7 100644 --- a/resources/qml/components/NotificationBubble.qml +++ b/resources/qml/components/NotificationBubble.qml @@ -9,39 +9,38 @@ import im.nheko 1.0 Rectangle { id: bubbleRoot - required property int notificationCount - required property bool hasLoudNotification required property color bubbleBackgroundColor required property color bubbleTextColor - property bool mayBeVisible: true property alias font: notificationBubbleText.font - baselineOffset: notificationBubbleText.baseline - bubbleRoot.top + required property bool hasLoudNotification + property bool mayBeVisible: true + required property int notificationCount - visible: mayBeVisible && notificationCount > 0 + ToolTip.delay: Nheko.tooltipDelay + ToolTip.text: notificationCount + ToolTip.visible: notificationBubbleHover.hovered && (notificationCount > 9999) + baselineOffset: notificationBubbleText.baseline - bubbleRoot.top + color: hasLoudNotification ? Nheko.theme.red : bubbleBackgroundColor implicitHeight: notificationBubbleText.height + Nheko.paddingMedium implicitWidth: Math.max(notificationBubbleText.width, height) radius: height / 2 - color: hasLoudNotification ? Nheko.theme.red : bubbleBackgroundColor - ToolTip.text: notificationCount - ToolTip.delay: Nheko.tooltipDelay - ToolTip.visible: notificationBubbleHover.hovered && (notificationCount > 9999) + visible: mayBeVisible && notificationCount > 0 Label { id: notificationBubbleText anchors.centerIn: bubbleRoot - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - width: Math.max(implicitWidth + Nheko.paddingMedium, bubbleRoot.height) + color: bubbleRoot.hasLoudNotification ? "white" : bubbleRoot.bubbleTextColor font.bold: true font.pixelSize: fontMetrics.font.pixelSize * 0.8 - color: bubbleRoot.hasLoudNotification ? "white" : bubbleRoot.bubbleTextColor + horizontalAlignment: Text.AlignHCenter text: bubbleRoot.notificationCount > 9999 ? "9999+" : bubbleRoot.notificationCount + verticalAlignment: Text.AlignVCenter + width: Math.max(implicitWidth + Nheko.paddingMedium, bubbleRoot.height) HoverHandler { id: notificationBubbleHover - } + } } - } diff --git a/resources/qml/components/PowerlevelIndicator.qml b/resources/qml/components/PowerlevelIndicator.qml index 6a6d89af..022ad1bf 100644 --- a/resources/qml/components/PowerlevelIndicator.qml +++ b/resources/qml/components/PowerlevelIndicator.qml @@ -7,24 +7,20 @@ import QtQuick.Controls import im.nheko Image { - required property int powerlevel - required property var permissions - readonly property bool isAdmin: permissions ? permissions.changeLevel(MtxEvent.PowerLevels) <= powerlevel : false - readonly property bool isModerator: permissions ? permissions.redactLevel() <= powerlevel : false readonly property bool isDefault: permissions ? permissions.defaultLevel() <= powerlevel : false - + readonly property bool isModerator: permissions ? permissions.redactLevel() <= powerlevel : false + required property var permissions + required property int powerlevel readonly property string sourceUrl: { if (isAdmin) - return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?"; + return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?"; else if (isModerator) return "image://colorimage/:/icons/icons/ui/ribbon.svg?"; else return "image://colorimage/:/icons/icons/ui/person.svg?"; } - source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText) - ToolTip.visible: ma.hovered ToolTip.text: { if (isAdmin) return qsTr("Administrator: %1").arg(powerlevel); @@ -33,8 +29,11 @@ Image { else return qsTr("User: %1").arg(powerlevel); } + ToolTip.visible: ma.hovered + source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText) HoverHandler { id: ma + } } diff --git a/resources/qml/components/ReorderableListview.qml b/resources/qml/components/ReorderableListview.qml index b5d5f5e5..fac2b8b4 100644 --- a/resources/qml/components/ReorderableListview.qml +++ b/resources/qml/components/ReorderableListview.qml @@ -8,8 +8,8 @@ import QtQml.Models Item { id: root - property alias model: visualModel.model property Component delegate + property alias model: visualModel.model Component { id: dragDelegate @@ -17,104 +17,117 @@ Item { MouseArea { id: dragArea - required property var model + property bool held: false required property int index + required property var model + drag.axis: Drag.YAxis + drag.target: held ? content : undefined enabled: model.moveable == undefined || model.moveable - - property bool held: false - - anchors { left: parent.left; right: parent.right } height: content.height - drag.target: held ? content : undefined - drag.axis: Drag.YAxis - + onHeldChanged: if (held) + ListView.view.currentIndex = dragArea.index + else + ListView.view.currentIndex = -1 onPressAndHold: held = true - onPressed: if (mouse.source !== Qt.MouseEventNotSynthesized) { held = true } + onPressed: if (mouse.source !== Qt.MouseEventNotSynthesized) { + held = true; + } onReleased: held = false - onHeldChanged: if (held) ListView.view.currentIndex = dragArea.index; else ListView.view.currentIndex = -1 + anchors { + left: parent.left + right: parent.right + } Rectangle { id: content - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: parent.verticalCenter - } - width: dragArea.width; height: actualDelegate.implicitHeight + 4 - - border.width: dragArea.enabled ? 1 : 0 - border.color: palette.highlight - - color: dragArea.held ? palette.highlight : palette.base - Behavior on color { ColorAnimation { duration: 100 } } - - radius: 2 - Drag.active: dragArea.held - Drag.source: dragArea Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 + Drag.source: dragArea + border.color: palette.highlight + border.width: dragArea.enabled ? 1 : 0 + color: dragArea.held ? palette.highlight : palette.base + height: actualDelegate.implicitHeight + 4 + radius: 2 + width: dragArea.width + Behavior on color { + ColorAnimation { + duration: 100 + } + } states: State { when: dragArea.held - ParentChange { target: content; parent: root } + ParentChange { + parent: root + target: content + } AnchorChanges { target: content - anchors { horizontalCenter: undefined; verticalCenter: undefined } + + anchors { + horizontalCenter: undefined + verticalCenter: undefined + } } } + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } Loader { id: actualDelegate - sourceComponent: root.delegate - property var model: dragArea.model + property int index: dragArea.index + property var model: dragArea.model property int offset: -view.contentY + dragArea.y - anchors { fill: parent; margins: 2 } - } - } + sourceComponent: root.delegate + anchors { + fill: parent + margins: 2 + } + } + } DropArea { enabled: index != 0 || model.moveable == undefined || model.moveable - anchors { fill: parent; margins: 8 } - onEntered: (drag)=> { - visualModel.model.move(drag.source.index, dragArea.index) - } + onEntered: drag => { + visualModel.model.move(drag.source.index, dragArea.index); } + anchors { + fill: parent + margins: 8 + } } } + } + DelegateModel { + id: visualModel - - DelegateModel { - id: visualModel - - delegate: dragDelegate - } - - ListView { - id: view - - clip: true - - anchors { fill: parent; margins: 2 } - - model: visualModel - - highlightRangeMode: ListView.ApplyRange - preferredHighlightBegin: 0.2 * height - preferredHighlightEnd: 0.8 * height - - spacing: 4 - cacheBuffer: 50 + delegate: dragDelegate + } + ListView { + id: view + + cacheBuffer: 50 + clip: true + highlightRangeMode: ListView.ApplyRange + model: visualModel + preferredHighlightBegin: 0.2 * height + preferredHighlightEnd: 0.8 * height + spacing: 4 + + anchors { + fill: parent + margins: 2 } - - } - - +} diff --git a/resources/qml/components/SpaceMenuLevel.qml b/resources/qml/components/SpaceMenuLevel.qml index e35e5ddc..d2b771b5 100644 --- a/resources/qml/components/SpaceMenuLevel.qml +++ b/resources/qml/components/SpaceMenuLevel.qml @@ -9,76 +9,87 @@ import im.nheko 1.0 Platform.Menu { id: spacesMenu - property string roomid property Component childMenu - + property bool loadChildren: false property int position: modelData == undefined ? -2 : modelData.treeIndex + property string roomid + title: modelData != undefined ? modelData.name : qsTr("Add or remove from community") - property bool loadChildren: false onAboutToShow: loadChildren = true + //onAboutToHide: loadChildren = false Platform.MenuItemGroup { id: modificationGroup + visible: position != -1 } - Platform.MenuItem { - text: qsTr("Official community for this room") - group: modificationGroup checkable: true checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && modelData.canonical) enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent) - onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, true) + group: modificationGroup + text: qsTr("Official community for this room") + + onTriggered: if (checked) + Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, true) } Platform.MenuItem { - text: qsTr("Affiliated community for this room") - group: modificationGroup checkable: true checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && !modelData.canonical) enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent) - onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, false) + group: modificationGroup + text: qsTr("Affiliated community for this room") + + onTriggered: if (checked) + Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, false) } Platform.MenuItem { - text: qsTr("Listed only for community members") - group: modificationGroup checkable: true checked: spacesMenu.position >= 0 && (modelData.childValid && !modelData.parentValid) enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || modelData.childValid) && (!modelData.parentValid || modelData.canEditParent)) - onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, true, false) + group: modificationGroup + text: qsTr("Listed only for community members") + + onTriggered: if (checked) + Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, true, false) } Platform.MenuItem { - text: qsTr("Listed only for room members") - group: modificationGroup checkable: true checked: spacesMenu.position >= 0 && (!modelData.childValid && modelData.parentValid) enabled: spacesMenu.position >= 0 && ((modelData.canEditChild) && (modelData.parentValid || modelData.canEditParent)) - onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, false, false) + group: modificationGroup + text: qsTr("Listed only for room members") + + onTriggered: if (checked) + Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, false, false) } Platform.MenuItem { - text: qsTr("Not related") - group: modificationGroup checkable: true checked: spacesMenu.position >= 0 && (!modelData.childValid && !modelData.parentValid) enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || !modelData.childValid) && (!modelData.parentValid || modelData.canEditParent)) - onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, false, false) - } + group: modificationGroup + text: qsTr("Not related") + onTriggered: if (checked) + Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, false, false) + } Platform.MenuSeparator { - text: qsTr("Subcommunities") group: modificationGroup + text: qsTr("Subcommunities") visible: modificationGroup.visible && inst.model != undefined } - Instantiator { id: inst - model: spacesMenu.loadChildren ? Communities.spaceChildrenListFromIndex(spacesMenu.roomid, spacesMenu.position) : undefined - onObjectAdded: (idx, o) => { - spacesMenu.insertMenu(idx + (spacesMenu.position != -1 ? 6 : 0), o) - } + //onObjectRemoved: spacesMenu.removeMenu(object) delegate: childMenu + model: spacesMenu.loadChildren ? Communities.spaceChildrenListFromIndex(spacesMenu.roomid, spacesMenu.position) : undefined + + onObjectAdded: (idx, o) => { + spacesMenu.insertMenu(idx + (spacesMenu.position != -1 ? 6 : 0), o); + } } } diff --git a/resources/qml/components/TextButton.qml b/resources/qml/components/TextButton.qml index b6153f22..2ea312a8 100644 --- a/resources/qml/components/TextButton.qml +++ b/resources/qml/components/TextButton.qml @@ -10,37 +10,34 @@ import im.nheko 1.0 // for cursor shape AbstractButton { id: button + property color buttonTextColor: palette.buttonText property alias cursor: mouseArea.cursorShape property color highlightColor: palette.highlight - property color buttonTextColor: palette.buttonText 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/resources/qml/components/UserListRow.qml b/resources/qml/components/UserListRow.qml index 2047f700..9b8c49c1 100644 --- a/resources/qml/components/UserListRow.qml +++ b/resources/qml/components/UserListRow.qml @@ -9,43 +9,50 @@ import QtQuick.Layouts 1.12 import im.nheko 1.0 ItemDelegate { + property string avatarUrl property alias bgColor: background.color - property alias userid: avatar.userid property alias displayName: avatar.displayName - property string avatarUrl + property alias userid: avatar.userid + implicitHeight: layout.implicitHeight + Nheko.paddingSmall * 2 - background: Rectangle {id: background} + + background: Rectangle { + id: background + + } + GridLayout { id: layout + anchors.centerIn: parent - width: parent.width - Nheko.paddingSmall * 2 - rows: 2 + columnSpacing: Nheko.paddingMedium columns: 2 rowSpacing: Nheko.paddingSmall - columnSpacing: Nheko.paddingMedium + rows: 2 + width: parent.width - Nheko.paddingSmall * 2 Avatar { id: avatar - Layout.rowSpan: 2 - Layout.preferredWidth: Nheko.avatarSize - Layout.preferredHeight: Nheko.avatarSize + Layout.alignment: Qt.AlignLeft - url: avatarUrl.replace("mxc://", "image://MxcImage/") + Layout.preferredHeight: Nheko.avatarSize + Layout.preferredWidth: Nheko.avatarSize + Layout.rowSpan: 2 enabled: false + url: avatarUrl.replace("mxc://", "image://MxcImage/") } Label { Layout.fillWidth: true - text: displayName color: TimelineManager.userColor(userid, palette.window) font.pointSize: fontMetrics.font.pointSize + text: displayName } - Label { - Layout.fillWidth: true Layout.alignment: Qt.AlignTop - text: userid + Layout.fillWidth: true color: palette.buttonText font.pointSize: fontMetrics.font.pointSize * 0.9 + text: userid } } } diff --git a/resources/qml/delegates/Encrypted.qml b/resources/qml/delegates/Encrypted.qml index 42a61918..83aab05a 100644 --- a/resources/qml/delegates/Encrypted.qml +++ b/resources/qml/delegates/Encrypted.qml @@ -13,29 +13,37 @@ Control { required property int encryptionError required property string eventId - padding: Nheko.paddingMedium - implicitHeight: contents.implicitHeight + Nheko.paddingMedium * 2 - Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2 Layout.fillWidth: true + Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2 + implicitHeight: contents.implicitHeight + Nheko.paddingMedium * 2 + padding: Nheko.paddingMedium + background: Rectangle { + color: palette.alternateBase + radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingMedium + visible: !Settings.bubbles // the bubble in a bubble looks odd + } contentItem: RowLayout { id: contents spacing: Nheko.paddingMedium Image { - source: "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.error Layout.alignment: Qt.AlignVCenter - Layout.preferredWidth: 24 Layout.preferredHeight: 24 + Layout.preferredWidth: 24 + source: "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.error } - ColumnLayout { - spacing: Nheko.paddingSmall Layout.fillWidth: true + spacing: Nheko.paddingSmall Label { id: encryptedText + + Layout.fillWidth: true + Layout.maximumWidth: implicitWidth + 1 + color: palette.text text: { switch (r.encryptionError) { case Olm.MissingSession: @@ -56,24 +64,13 @@ Control { } textFormat: Text.PlainText wrapMode: Label.WordWrap - color: palette.text - Layout.fillWidth: true - Layout.maximumWidth: implicitWidth + 1 } - Button { - visible: r.encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex text: qsTr("Request key") + visible: r.encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex + onClicked: room.requestKeyForEvent(eventId) } - } - - } - - background: Rectangle { - color: palette.alternateBase - radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingMedium - visible: !Settings.bubbles // the bubble in a bubble looks odd } } diff --git a/resources/qml/delegates/EncryptionEnabled.qml b/resources/qml/delegates/EncryptionEnabled.qml index 40894543..2ed3bc10 100644 --- a/resources/qml/delegates/EncryptionEnabled.qml +++ b/resources/qml/delegates/EncryptionEnabled.qml @@ -13,53 +13,48 @@ Control { required property string userName - padding: Nheko.paddingMedium + Layout.fillWidth: true //implicitHeight: contents.implicitHeight + padd * 2 Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2 - Layout.fillWidth: true + padding: Nheko.paddingMedium + background: Rectangle { + border.color: Nheko.theme.green + border.width: 2 + color: palette.alternateBase + height: contents.implicitHeight + Nheko.paddingMedium * 2 + radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium + } contentItem: RowLayout { id: contents spacing: Nheko.paddingMedium Image { - source: "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green Layout.alignment: Qt.AlignVCenter - Layout.preferredWidth: 24 Layout.preferredHeight: 24 + Layout.preferredWidth: 24 + source: "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green } - ColumnLayout { - spacing: Nheko.paddingSmall Layout.fillWidth: true + spacing: Nheko.paddingSmall MatrixText { - text: qsTr("%1 enabled end-to-end encryption").arg(r.userName) - font.bold: true - font.pointSize: 14 - color: palette.text Layout.fillWidth: true Layout.maximumWidth: implicitWidth + 1 + color: palette.text + font.bold: true + font.pointSize: 14 + text: qsTr("%1 enabled end-to-end encryption").arg(r.userName) } - Label { + Layout.fillWidth: true + Layout.maximumWidth: implicitWidth + 1 text: qsTr("Encryption keeps your messages safe by only allowing the people you sent the message to to read it. For extra security, if you want to make sure you are talking to the right people, you can verify them in real life.") textFormat: Text.PlainText wrapMode: Label.WordWrap - Layout.fillWidth: true - Layout.maximumWidth: implicitWidth + 1 } - } - - } - - background: Rectangle { - radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium - height: contents.implicitHeight + Nheko.paddingMedium * 2 - color: palette.alternateBase - border.color: Nheko.theme.green - border.width: 2 } } diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml index 3f31e7ed..0f1f3445 100644 --- a/resources/qml/delegates/FileMessage.qml +++ b/resources/qml/delegates/FileMessage.qml @@ -13,15 +13,19 @@ Control { required property string eventId required property string filename required property string filesize - - padding: Settings.bubbles? 8 : 12 + property bool fitsMetadata: false //Layout.preferredHeight: rowa.implicitHeight + padding //Layout.maximumWidth: rowa.Layout.maximumWidth + metadataWidth + padding property int metadataWidth: 0 - property bool fitsMetadata: false Layout.maximumWidth: rowa.Layout.maximumWidth + padding * 2 + padding: Settings.bubbles ? 8 : 12 + background: Rectangle { + color: palette.alternateBase + radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall + visible: !Settings.bubbles // the bubble in a bubble looks odd + } contentItem: RowLayout { id: rowa @@ -30,36 +34,32 @@ Control { Rectangle { id: button - color: palette.light - radius: 22 Layout.preferredHeight: 44 Layout.preferredWidth: 44 + color: palette.light + radius: 22 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 @@ -68,31 +68,21 @@ Control { Layout.fillWidth: true Layout.maximumWidth: implicitWidth + 1 + color: palette.text + elide: Text.ElideRight text: evRoot.filename textFormat: Text.PlainText - elide: Text.ElideRight - color: palette.text } - Text { id: filesize_ Layout.fillWidth: true Layout.maximumWidth: implicitWidth + 1 + color: palette.text + elide: Text.ElideRight text: evRoot.filesize textFormat: Text.PlainText - elide: Text.ElideRight - color: palette.text } - } - } - - background: Rectangle { - color: palette.alternateBase - radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall - visible: !Settings.bubbles // the bubble in a bubble looks odd - } - } diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index 18ff11d2..b2a68772 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -8,27 +8,28 @@ import QtQuick.Controls import im.nheko AbstractButton { - required property int type - required property int originalWidth - required property int originalHeight - required property double proportionalHeight - required property string url required property string blurhash required property string body - required property string filename - required property string eventId required property int containerHeight property double divisor: EventDelegateChooser.isReply ? 10 : 4 + required property string eventId + required property string filename + property bool fitsMetadata: parent != null ? (parent.width - width) > metadataWidth + 4 : false + property int metadataWidth + required property int originalHeight + required property int originalWidth + required property double proportionalHeight + required property int type + required property string url + EventDelegateChooser.aspectRatio: proportionalHeight EventDelegateChooser.keepAspectRatio: true - EventDelegateChooser.maxWidth: originalWidth EventDelegateChooser.maxHeight: containerHeight / divisor - EventDelegateChooser.aspectRatio: proportionalHeight - - hoverEnabled: true + EventDelegateChooser.maxWidth: originalWidth enabled: !EventDelegateChooser.isReply - + hoverEnabled: true state: (img.status != Image.Ready || timeline.privacyScreen.active) ? "BlurhashVisible" : "ImageVisible" + states: [ State { name: "BlurhashVisible" @@ -39,11 +40,9 @@ AbstractButton { visible: (img.status != Image.Ready) || (timeline.privacyScreen.active && blurhash) } } - PropertyChanges { img.opacity: 0 } - PropertyChanges { mxcimage.opacity: 0 } @@ -57,11 +56,9 @@ AbstractButton { visible: false } } - PropertyChanges { img.opacity: 1 } - PropertyChanges { mxcimage.opacity: 1 } @@ -70,114 +67,98 @@ AbstractButton { transitions: [ Transition { from: "ImageVisible" - to: "BlurhashVisible" reversible: true + to: "BlurhashVisible" SequentialAnimation { PropertyAction { - target: blurhash_ property: "visible" + target: blurhash_ } - ParallelAnimation { NumberAnimation { - target: blurhash_ - property: "opacity" duration: 300 easing.type: Easing.Linear + property: "opacity" + target: blurhash_ } - NumberAnimation { - target: img - property: "opacity" duration: 300 easing.type: Easing.Linear + property: "opacity" + target: img } - NumberAnimation { - target: mxcimage - property: "opacity" duration: 300 easing.type: Easing.Linear + property: "opacity" + target: mxcimage } } } } ] - property int metadataWidth - property bool fitsMetadata: parent != null ? (parent.width - width) > metadataWidth+4 : false + onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId, originalWidth, proportionalHeight) Image { id: img - visible: !mxcimage.loaded anchors.fill: parent - source: url != "" ? (url.replace("mxc://", "image://MxcImage/") + "?scale") : "" asynchronous: true fillMode: Image.PreserveAspectFit horizontalAlignment: Image.AlignLeft - smooth: true mipmap: true - + smooth: true + source: url != "" ? (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 - roomm: room - play: !Settings.animateImagesOnHover || parent.hovered - eventId: parent.eventId - anchors.fill: parent + eventId: parent.eventId + play: !Settings.animateImagesOnHover || parent.hovered + roomm: room + visible: loaded } - Image { id: blurhash_ - source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + palette.buttonText) + anchors.fill: parent asynchronous: true fillMode: Image.PreserveAspectFit - sourceSize.width: parent.width * Screen.devicePixelRatio + source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + palette.buttonText) sourceSize.height: parent.height * Screen.devicePixelRatio - - anchors.fill: parent + sourceSize.width: parent.width * Screen.devicePixelRatio } - - onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId, originalWidth, proportionalHeight); - Item { id: overlay anchors.fill: parent - visible: parent.hovered Rectangle { id: container - width: parent.width - implicitHeight: imgcaption.implicitHeight anchors.bottom: overlay.bottom color: palette.window + implicitHeight: imgcaption.implicitHeight opacity: 0.75 + width: parent.width } - Text { id: imgcaption anchors.fill: container + color: 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: palette.text + verticalAlignment: Text.AlignVCenter } - } - } diff --git a/resources/qml/delegates/NoticeMessage.qml b/resources/qml/delegates/NoticeMessage.qml index 88efe7b7..37879396 100644 --- a/resources/qml/delegates/NoticeMessage.qml +++ b/resources/qml/delegates/NoticeMessage.qml @@ -5,11 +5,11 @@ import QtQuick 2.5 import im.nheko 1.0 - TextMessage { property bool isStateEvent - font.italic: true + color: palette.buttonText - 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/resources/qml/delegates/Pill.qml b/resources/qml/delegates/Pill.qml index c9ca1ed3..275f4400 100644 --- a/resources/qml/delegates/Pill.qml +++ b/resources/qml/delegates/Pill.qml @@ -7,14 +7,14 @@ import QtQuick.Controls 2.1 Label { property bool isStateEvent + color: 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: palette.alternateBase + radius: parent.height / 2 } - } diff --git a/resources/qml/delegates/Placeholder.qml b/resources/qml/delegates/Placeholder.qml index 66e28c03..de310445 100644 --- a/resources/qml/delegates/Placeholder.qml +++ b/resources/qml/delegates/Placeholder.qml @@ -8,7 +8,7 @@ import im.nheko 1.0 MatrixText { required property string typeString - text: qsTr("unimplemented event: ") + typeString -// width: parent.width + // width: parent.width color: palette.inactive.text + text: qsTr("unimplemented event: ") + typeString } diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml index 2a47d275..811f8939 100644 --- a/resources/qml/delegates/PlayableMediaMessage.qml +++ b/resources/qml/delegates/PlayableMediaMessage.qml @@ -11,80 +11,79 @@ import im.nheko Item { id: content - required property double proportionalHeight - required property int type - required property int originalWidth + required property string body + property double divisor: EventDelegateChooser.isReply ? 10 : 4 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: EventDelegateChooser.isReply ? 10 : 4 - 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, implicitWidth) - height: (type == MtxEvent.VideoMessage ? width*proportionalHeight : 80) + fileInfoLabel.height + property bool fitsMetadata: (parent.width - fileInfoLabel.width) > metadataWidth + 4 //implicitHeight: height 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 + implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth * Math.min((timelineView.height / divisor) / (tempWidth * proportionalHeight), 1)) : 500 + width: Math.min(parent?.width ?? implicitWidth, implicitWidth) MxcMedia { id: mxcmedia // TODO: Show error in overlay or so? roomm: room + videoOutput: videoOutput + audioOutput: AudioOutput { muted: mediaControls.muted volume: mediaControls.desiredVolume } - videoOutput: videoOutput } - Rectangle { id: videoContainer color: content.type == MtxEvent.VideoMessage ? 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: content.thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : "image://colorimage/:/icons/icons/ui/video-file.svg?" + palette.windowText asynchronous: true fillMode: Image.PreserveAspectFit + source: content.thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : "image://colorimage/:/icons/icons/ui/video-file.svg?" + palette.windowText VideoOutput { id: videoOutput - visible: content.type == MtxEvent.VideoMessage - clip: true anchors.fill: parent + clip: true fillMode: VideoOutput.PreserveAspectFit orientation: mxcmedia.orientation + visible: content.type == MtxEvent.VideoMessage } - } - MediaControls { id: mediaControls + anchors.bottom: videoContainer.bottom anchors.left: videoContainer.left anchors.right: videoContainer.right - anchors.bottom: videoContainer.bottom - playingVideo: content.type == MtxEvent.VideoMessage - positionValue: mxcmedia.position duration: mediaLoaded ? mxcmedia.duration : content.duration mediaLoaded: mxcmedia.loaded mediaState: mxcmedia.playbackState - onPositionChanged: mxcmedia.position = position - onPlayPauseActivated: mxcmedia.playbackState == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play() + playingVideo: content.type == MtxEvent.VideoMessage + positionValue: mxcmedia.position + onLoadActivated: mxcmedia.eventId = eventId + onPlayPauseActivated: mxcmedia.playbackState == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play() + onPositionChanged: mxcmedia.position = position } } @@ -93,15 +92,13 @@ Item { id: fileInfoLabel anchors.top: videoContainer.bottom + color: palette.text + elide: Text.ElideRight text: content.body + " [" + filesize + "]" textFormat: Text.RichText - elide: Text.ElideRight - color: palette.text background: Rectangle { color: palette.base } - } - } diff --git a/resources/qml/delegates/Redacted.qml b/resources/qml/delegates/Redacted.qml index 1bf87f91..ed38bb63 100644 --- a/resources/qml/delegates/Redacted.qml +++ b/resources/qml/delegates/Redacted.qml @@ -10,47 +10,50 @@ import im.nheko Control { id: msgRoot - property int metadataWidth: 0 + required property string eventId property bool fitsMetadata: false //parent.width - redactedLayout.width > metadataWidth + 4 - required property string eventId + property int metadataWidth: 0 required property Room room + Layout.maximumWidth: redactedLayout.Layout.maximumWidth + padding * 2 + padding: Nheko.paddingSmall + + background: Rectangle { + color: palette.alternateBase + radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall + } contentItem: RowLayout { id: redactedLayout + spacing: Nheko.paddingSmall 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?" + palette.text } Label { id: redactedLabel - Layout.margins: 0 + + property var redactedPair: msgRoot.room.formatRedactedEvent(msgRoot.eventId) + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.maximumWidth: implicitWidth + 1 Layout.fillWidth: true - property var redactedPair: msgRoot.room.formatRedactedEvent(msgRoot.eventId) + Layout.margins: 0 + Layout.maximumWidth: implicitWidth + 1 + ToolTip.text: redactedPair["second"] + ToolTip.visible: hh.hovered text: redactedPair["first"] wrapMode: Label.WordWrap - ToolTip.text: redactedPair["second"] - ToolTip.visible: hh.hovered HoverHandler { id: hh + } } } - - padding: Nheko.paddingSmall - - Layout.maximumWidth: redactedLayout.Layout.maximumWidth + padding * 2 - - background: Rectangle { - color: palette.alternateBase - radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall - } } diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml index ff46347f..3ee15535 100644 --- a/resources/qml/delegates/Reply.qml +++ b/resources/qml/delegates/Reply.qml @@ -11,43 +11,35 @@ import "../" AbstractButton { id: r - property color userColor: "red" - property bool keepFullText: false - required property string eventId - + property bool keepFullText: false + required property int maxWidth property var room_: room - + property color userColor: "red" property string userId: eventId ? room.dataById(eventId, Room.UserId, "") : "" property string userName: eventId ? room.dataById(eventId, Room.UserName, "") : "" + implicitHeight: replyContainer.implicitHeight implicitWidth: replyContainer.implicitWidth - required property int maxWidth - NhekoCursorShape { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - } + background: Rectangle { + id: backgroundItem - 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) + property color bgColor: palette.base + property color userColor: TimelineManager.userColor(r.userId, palette.base) + color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1)) + z: -1 + } contentItem: TimelineEvent { id: timelineEvent - isStateEvent: false - room: r.room_ eventId: r.eventId - replyTo: "" + isStateEvent: false mainInset: 4 + Nheko.paddingMedium maxWidth: r.maxWidth + replyTo: "" + room: r.room_ //height: replyContainer.implicitHeight data: Row { @@ -58,14 +50,14 @@ AbstractButton { Rectangle { id: colorline - width: 4 - height: content.height - color: TimelineManager.userColor(r.userId, palette.base) + height: content.height + width: 4 } - Column { id: content + + data: [usernameBtn, timelineEvent.main,] spacing: 0 AbstractButton { @@ -73,29 +65,31 @@ AbstractButton { contentItem: Label { id: userName_ - text: r.userName + color: r.userColor + text: r.userName textFormat: Text.RichText width: timelineEvent.main?.width } + onClicked: room.openUserProfile(r.userId) } - - data: [ - usernameBtn, timelineEvent.main, - ] } - } } - background: Rectangle { - id: backgroundItem - - z: -1 - property color userColor: TimelineManager.userColor(r.userId, palette.base) - property color bgColor: palette.base - color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1)) + 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 + } } diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index 3625aea1..5292f484 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -7,14 +7,17 @@ import im.nheko MatrixText { required property string body + property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body + property bool fitsMetadata: false //positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4) + + required property string formatted required property bool isOnlyEmoji property bool isReply: EventDelegateChooser.isReply required property bool keepFullText - required property string formatted - - property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body property int metadataWidth: 100 - property bool fitsMetadata: false //positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4) + + enabled: !isReply + font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize // table border-collapse doesn't seem to work text: ` @@ -30,7 +33,7 @@ MatrixText { } table th, table td { - padding: ` + Math.ceil(fontMetrics.lineSpacing/2) + `px; + padding: ` + Math.ceil(fontMetrics.lineSpacing / 2) + `px; } blockquote { margin-left: 1em; } ` + (!Settings.mobileMode ? `span[data-mx-spoiler] { @@ -40,13 +43,9 @@ MatrixText { ` ` + formatted.replace(//g, "").replace(/<\/del>/g, "").replace(//g, "").replace(/<\/strike>/g, "") - enabled: !isReply - 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/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index afc6fd0a..78ec7aa9 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -12,82 +12,69 @@ ApplicationWindow { property var flow - onClosing: VerificationManager.removeVerificationFlow(flow) - title: stack.currentItem ? (stack.currentItem.title_ || "") : "" - modality: Qt.NonModal color: palette.window + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: stack.currentItem.implicitHeight + 2 * Nheko.paddingMedium //height: stack.currentItem.implicitHeight minimumHeight: stack.currentItem.implicitHeight + 2 * Nheko.paddingLarge - height: stack.currentItem.implicitHeight + 2 * Nheko.paddingMedium minimumWidth: 400 + modality: Qt.NonModal + title: stack.currentItem ? (stack.currentItem.title_ || "") : "" width: 400 - flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint background: Rectangle { color: palette.window } + onClosing: VerificationManager.removeVerificationFlow(flow) StackView { id: stack anchors.centerIn: parent - + implicitHeight: dialog.height - 2 * Nheko.paddingMedium + implicitWidth: dialog.width - 2 * Nheko.paddingMedium initialItem: newVerificationRequest - implicitWidth: dialog.width - 2* Nheko.paddingMedium - implicitHeight: dialog.height - 2* Nheko.paddingMedium } - 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" @@ -95,7 +82,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(null, newVerificationRequest) } - }, State { name: "CompareEmoji" @@ -103,7 +89,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(null, emojiVerification) } - }, State { name: "CompareNumber" @@ -111,7 +96,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(null, digitVerification) } - }, State { name: "WaitingForKeys" @@ -119,7 +103,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(null, waiting) } - }, State { name: "WaitingForOtherToAccept" @@ -127,7 +110,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(null, waiting) } - }, State { name: "WaitingForMac" @@ -135,7 +117,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(null, waiting) } - }, State { name: "Success" @@ -143,7 +124,6 @@ ApplicationWindow { StateChangeScript { script: stack.replace(null, success) } - }, State { name: "Failed" @@ -151,9 +131,7 @@ ApplicationWindow { StateChangeScript { script: stack.replace(null, failed) } - } ] } - } diff --git a/resources/qml/device-verification/DigitVerification.qml b/resources/qml/device-verification/DigitVerification.qml index 148b3f65..ca254afe 100644 --- a/resources/qml/device-verification/DigitVerification.qml +++ b/resources/qml/device-verification/DigitVerification.qml @@ -12,59 +12,56 @@ ColumnLayout { 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: 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: palette.text font.pixelSize: Qt.application.font.pixelSize * 2 text: flow.sasList[0] - color: palette.text } - Label { + color: palette.text font.pixelSize: Qt.application.font.pixelSize * 2 text: flow.sasList[1] - color: palette.text } - Label { + color: palette.text font.pixelSize: Qt.application.font.pixelSize * 2 text: flow.sasList[2] - color: 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/resources/qml/device-verification/EmojiElement.qml b/resources/qml/device-verification/EmojiElement.qml index 519b4a8e..716a4b83 100644 --- a/resources/qml/device-verification/EmojiElement.qml +++ b/resources/qml/device-verification/EmojiElement.qml @@ -8,9 +8,9 @@ import QtQuick.Layouts 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 { @@ -21,17 +21,14 @@ Rectangle { anchors.bottom: parent.bottom Label { - Layout.preferredHeight: font.pixelSize * 2 Layout.alignment: Qt.AlignHCenter - text: col.emoji.emoji + Layout.preferredHeight: font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2 + text: col.emoji.emoji } - Label { Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom text: col.emoji.description } - } - } diff --git a/resources/qml/device-verification/EmojiVerification.qml b/resources/qml/device-verification/EmojiVerification.qml index 0ee279cd..0259b53e 100644 --- a/resources/qml/device-verification/EmojiVerification.qml +++ b/resources/qml/device-verification/EmojiVerification.qml @@ -13,339 +13,340 @@ ColumnLayout { 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: 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 @@ -370,58 +371,52 @@ ColumnLayout { 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: 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: palette.text + text: col.emoji.description } - } - } - } - } - Item { Layout.fillHeight: true; } - + Item { + Layout.fillHeight: true + } Label { - Layout.preferredWidth: 400 Layout.fillWidth: true - wrapMode: Text.Wrap - text: qsTr("The displayed emoji might look different in different clients if a different font is used. Similarly they might be translated into different languages. Nonetheless they should depict one of 64 different objects or animals. For example a lion and a cat are different, but a cat is the same even if one client just shows a cat face, while another client shows a full cat body.") + Layout.preferredWidth: 400 color: palette.text + text: qsTr("The displayed emoji might look different in different clients if a different font is used. Similarly they might be translated into different languages. Nonetheless they should depict one of 64 different objects or animals. For example a lion and a cat are different, but a cat is the same even if one client just shows a cat face, while another client shows a full cat body.") verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + } + 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/resources/qml/device-verification/Failed.qml b/resources/qml/device-verification/Failed.qml index 5847894b..b25645ef 100644 --- a/resources/qml/device-verification/Failed.qml +++ b/resources/qml/device-verification/Failed.qml @@ -9,49 +9,48 @@ import im.nheko 1.0 ColumnLayout { property string title: qsTr("Verification failed") + spacing: 16 Text { id: content - Layout.preferredWidth: 400 Layout.fillWidth: true - wrapMode: Text.Wrap + Layout.preferredWidth: 400 + color: palette.text text: { switch (flow.error) { - case DeviceVerificationFlow.UnknownMethod: + case DeviceVerificationFlow.UnknownMethod: return qsTr("Other client does not support our verification protocol."); - case DeviceVerificationFlow.MismatchedCommitment: - case DeviceVerificationFlow.MismatchedSAS: - case DeviceVerificationFlow.KeyMismatch: + case DeviceVerificationFlow.MismatchedCommitment: + case DeviceVerificationFlow.MismatchedSAS: + case DeviceVerificationFlow.KeyMismatch: return qsTr("Key mismatch detected!"); - case DeviceVerificationFlow.Timeout: + case DeviceVerificationFlow.Timeout: return qsTr("Device verification timed out."); - case DeviceVerificationFlow.User: + case DeviceVerificationFlow.User: return qsTr("Other party canceled the verification."); - case DeviceVerificationFlow.OutOfOrder: + case DeviceVerificationFlow.OutOfOrder: return qsTr("Verification messages received out of order!"); - default: + default: return qsTr("Unknown verification error."); } } - color: 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/resources/qml/device-verification/NewVerificationRequest.qml b/resources/qml/device-verification/NewVerificationRequest.qml index 6f6de8c7..0b8e434b 100644 --- a/resources/qml/device-verification/NewVerificationRequest.qml +++ b/resources/qml/device-verification/NewVerificationRequest.qml @@ -12,11 +12,11 @@ ColumnLayout { spacing: 16 Label { + Layout.fillWidth: true // Self verification Layout.preferredWidth: 400 - Layout.fillWidth: true - wrapMode: Text.Wrap + color: palette.text text: { if (flow.sender) { if (flow.isSelfVerification) @@ -35,32 +35,30 @@ ColumnLayout { return qsTr("Your device (%1) has requested to be verified.").arg(flow.deviceId); } } - color: 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/resources/qml/device-verification/Success.qml b/resources/qml/device-verification/Success.qml index 9fe69bdf..edbd4461 100644 --- a/resources/qml/device-verification/Success.qml +++ b/resources/qml/device-verification/Success.qml @@ -14,27 +14,25 @@ ColumnLayout { 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: 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/resources/qml/device-verification/Waiting.qml b/resources/qml/device-verification/Waiting.qml index 5b45fc7c..8e2cc8ab 100644 --- a/resources/qml/device-verification/Waiting.qml +++ b/resources/qml/device-verification/Waiting.qml @@ -10,52 +10,52 @@ import im.nheko 1.0 ColumnLayout { property string title: qsTr("Waiting for other party…") + spacing: 16 Label { id: content - Layout.preferredWidth: 400 Layout.fillWidth: true - wrapMode: Text.Wrap + Layout.preferredWidth: 400 + color: palette.text text: { switch (flow.state) { - case "WaitingForOtherToAccept": - return qsTr("Waiting for other side to accept the verification request."); - case "WaitingForKeys": - return qsTr("Waiting for other side to continue the verification process."); - case "WaitingForMac": - return qsTr("Waiting for other side to complete the verification process."); - default: - return ""; + case "WaitingForOtherToAccept": + return qsTr("Waiting for other side to accept the verification request."); + case "WaitingForKeys": + return qsTr("Waiting for other side to continue the verification process."); + case "WaitingForMac": + return qsTr("Waiting for other side to complete the verification process."); + default: + return ""; } } - color: palette.text verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + } + Item { + Layout.fillHeight: true } - - Item { Layout.fillHeight: true; } Spinner { Layout.alignment: Qt.AlignHCenter foreground: 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/resources/qml/dialogs/AliasEditor.qml b/resources/qml/dialogs/AliasEditor.qml index 82f12750..2e89c155 100644 --- a/resources/qml/dialogs/AliasEditor.qml +++ b/resources/qml/dialogs/AliasEditor.qml @@ -8,21 +8,31 @@ import QtQuick.Controls import QtQuick.Layouts import im.nheko - ApplicationWindow { id: aliasEditorW - property var roomSettings property var editingModel: Nheko.editAliases(roomSettings.roomId) + property var roomSettings - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - minimumWidth: 300 - minimumHeight: 400 height: 600 + minimumHeight: 400 + minimumWidth: 300 + modality: Qt.NonModal + title: qsTr("Aliases to %1").arg(roomSettings.roomName) width: 500 - title: qsTr("Aliases to %1").arg(roomSettings.roomName); + footer: DialogButtonBox { + id: dbb + + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + + onAccepted: { + editingModel.commit(); + aliasEditorW.close(); + } + onRejected: aliasEditorW.close() + } // Shortcut { // sequence: StandardKey.Cancel @@ -30,32 +40,27 @@ ApplicationWindow { // } ColumnLayout { - anchors.margins: Nheko.paddingMedium anchors.fill: parent + anchors.margins: Nheko.paddingMedium spacing: 0 - MatrixText { - text: qsTr("List of aliases to this room. Usually you can only add aliases on your server. You can have one canonical alias and many alternate aliases.") - font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) - Layout.fillWidth: true + Layout.bottomMargin: Nheko.paddingMedium Layout.fillHeight: false + Layout.fillWidth: true color: palette.text - Layout.bottomMargin: Nheko.paddingMedium + font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) + text: qsTr("List of aliases to this room. Usually you can only add aliases on your server. You can have one canonical alias and many alternate aliases.") } - ListView { - Layout.fillWidth: true - Layout.fillHeight: true - id: view + Layout.fillHeight: true + Layout.fillWidth: true + cacheBuffer: 50 clip: true - - model: editingModel spacing: 4 - cacheBuffer: 50 delegate: RowLayout { anchors.left: parent.left @@ -63,79 +68,70 @@ ApplicationWindow { Text { Layout.fillWidth: true - text: model.name color: model.isPublished ? palette.text : Nheko.theme.error + text: model.name textFormat: Text.PlainText } - ImageButton { Layout.alignment: Qt.AlignRight Layout.margins: 2 - image: ":/icons/icons/ui/star.svg" - hoverEnabled: true + ToolTip.text: model.isCanonical ? qsTr("Primary alias") : qsTr("Make primary alias") + ToolTip.visible: hovered buttonTextColor: model.isCanonical ? palette.highlight : palette.text highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor - - ToolTip.visible: hovered - ToolTip.text: model.isCanonical ? qsTr("Primary alias") : qsTr("Make primary alias") + hoverEnabled: true + image: ":/icons/icons/ui/star.svg" onClicked: editingModel.makeCanonical(model.index) } - ImageButton { Layout.alignment: Qt.AlignRight Layout.margins: 2 - image: ":/icons/icons/ui/building-shop.svg" - hoverEnabled: true + ToolTip.text: qsTr("Advertise as an alias in this room") + ToolTip.visible: hovered buttonTextColor: model.isAdvertized ? palette.highlight : palette.text highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor - - ToolTip.visible: hovered - ToolTip.text: qsTr("Advertise as an alias in this room") + hoverEnabled: true + image: ":/icons/icons/ui/building-shop.svg" onClicked: editingModel.toggleAdvertize(model.index) } - ImageButton { Layout.alignment: Qt.AlignRight Layout.margins: 2 - image: ":/icons/icons/ui/room-directory.svg" - hoverEnabled: true - buttonTextColor: model.isPublished ? palette.highlight : palette.text - - ToolTip.visible: hovered ToolTip.text: qsTr("Publish in room directory") + ToolTip.visible: hovered + buttonTextColor: model.isPublished ? palette.highlight : palette.text + hoverEnabled: true + image: ":/icons/icons/ui/room-directory.svg" onClicked: editingModel.togglePublish(model.index) } - ImageButton { Layout.alignment: Qt.AlignRight Layout.margins: 2 - image: ":/icons/icons/ui/dismiss.svg" - hoverEnabled: true - - ToolTip.visible: hovered ToolTip.text: qsTr("Remove this alias") + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/dismiss.svg" onClicked: editingModel.deleteAlias(model.index) } } } - RowLayout { - spacing: Nheko.paddingMedium Layout.fillWidth: true + spacing: Nheko.paddingMedium MatrixTextField { id: newAliasVal - focus: true Layout.fillWidth: true - selectByMouse: true - font.pixelSize: fontMetrics.font.pixelSize color: palette.text + focus: true + font.pixelSize: fontMetrics.font.pixelSize placeholderText: qsTr("#new-alias:server.tld") + selectByMouse: true Component.onCompleted: forceActiveFocus() Keys.onPressed: { @@ -145,10 +141,10 @@ ApplicationWindow { } } } - Button { - text: qsTr("Add") Layout.preferredWidth: 100 + text: qsTr("Add") + onClicked: { editingModel.addAlias(newAliasVal.text); newAliasVal.clear(); @@ -156,16 +152,4 @@ ApplicationWindow { } } } - - footer: DialogButtonBox { - id: dbb - - standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel - onAccepted: { - editingModel.commit(); - aliasEditorW.close(); - } - onRejected: aliasEditorW.close(); - } - } diff --git a/resources/qml/dialogs/AllowedRoomsSettingsDialog.qml b/resources/qml/dialogs/AllowedRoomsSettingsDialog.qml index 82b5506e..4f33fd3d 100644 --- a/resources/qml/dialogs/AllowedRoomsSettingsDialog.qml +++ b/resources/qml/dialogs/AllowedRoomsSettingsDialog.qml @@ -14,47 +14,54 @@ ApplicationWindow { property var roomSettings - minimumWidth: 340 - minimumHeight: 450 - width: 450 - height: 680 color: palette.window - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 680 + minimumHeight: 450 + minimumWidth: 340 + modality: Qt.NonModal title: qsTr("Allowed rooms settings") + width: 450 + + footer: DialogButtonBox { + id: dbb + + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + + onAccepted: { + roomSettings.applyAllowedFromModel(); + allowedDialog.close(); + } + onRejected: allowedDialog.close() + } Shortcut { sequence: StandardKey.Cancel + onActivated: roomSettingsDialog.close() } - ColumnLayout { - anchors.margins: Nheko.paddingMedium anchors.fill: parent + anchors.margins: Nheko.paddingMedium spacing: 0 - MatrixText { - text: qsTr("List of rooms that allow access to this room. Anyone who is in any of those rooms can join this room.") - font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) - Layout.fillWidth: true + Layout.bottomMargin: Nheko.paddingMedium Layout.fillHeight: false + Layout.fillWidth: true color: palette.text - Layout.bottomMargin: Nheko.paddingMedium + font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) + text: qsTr("List of rooms that allow access to this room. Anyone who is in any of those rooms can join this room.") } - ListView { - Layout.fillWidth: true - Layout.fillHeight: true - id: view + Layout.fillHeight: true + Layout.fillWidth: true + cacheBuffer: 50 clip: true - - model: roomSettings.allowedRoomsModel spacing: 4 - cacheBuffer: 50 delegate: RowLayout { anchors.left: parent.left @@ -62,63 +69,57 @@ ApplicationWindow { ColumnLayout { Layout.fillWidth: true + Text { Layout.fillWidth: true - text: model.name color: palette.text + text: model.name textFormat: Text.PlainText } - Text { Layout.fillWidth: true - text: model.isParent ? qsTr("Parent community") : qsTr("Other room") color: palette.buttonText + text: model.isParent ? qsTr("Parent community") : qsTr("Other room") textFormat: Text.PlainText } } - ToggleButton { - checked: model.allowed Layout.alignment: Qt.AlignRight + checked: model.allowed + onCheckedChanged: model.allowed = checked } } } - - Column{ + Column { id: roomEntryCompleter - Layout.fillWidth: true + Layout.fillWidth: true spacing: 1 z: 5 Completer { id: roomCompleter - visible: roomEntry.text.length > 0 - width: parent.width - roomId: allowedDialog.roomSettings.roomId - completerName: "room" - bottomToTop: true - fullWidth: true avatarHeight: Nheko.avatarSize / 2 avatarWidth: Nheko.avatarSize / 2 + bottomToTop: true centerRowContent: false + completerName: "room" + fullWidth: true + roomId: allowedDialog.roomSettings.roomId rowMargin: 2 rowSpacing: 2 + visible: roomEntry.text.length > 0 + width: parent.width } - MatrixTextField { id: roomEntry - width: parent.width - + color: palette.text placeholderText: qsTr("Enter additional rooms not in the list yet...") + width: parent.width - color: palette.text - onTextEdited: { - roomCompleter.completer.searchString = text; - } Keys.onPressed: { if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) { event.accepted = true; @@ -126,45 +127,31 @@ ApplicationWindow { } 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)) - roomCompleter.up(); + roomCompleter.up(); else - roomCompleter.down(); + roomCompleter.down(); } else if (event.matches(StandardKey.InsertParagraphSeparator)) { roomCompleter.finishCompletion(); event.accepted = true; } } + onTextEdited: { + roomCompleter.completer.searchString = text; + } } - } - Connections { function onCompletionSelected(id) { console.log("selected: " + id); roomSettings.allowedRoomsModel.addRoom(id); roomEntry.clear(); } - function onCountChanged() { if (roomCompleter.count > 0 && (roomCompleter.currentIndex < 0 || roomCompleter.currentIndex >= roomCompleter.count)) - roomCompleter.currentIndex = 0; - + roomCompleter.currentIndex = 0; } target: roomCompleter } - } - - footer: DialogButtonBox { - id: dbb - - standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel - onAccepted: { - roomSettings.applyAllowedFromModel(); - allowedDialog.close(); - } - onRejected: allowedDialog.close() - } - } diff --git a/resources/qml/dialogs/ConfirmJoinRoomDialog.qml b/resources/qml/dialogs/ConfirmJoinRoomDialog.qml index eb33cfc3..6da607dd 100644 --- a/resources/qml/dialogs/ConfirmJoinRoomDialog.qml +++ b/resources/qml/dialogs/ConfirmJoinRoomDialog.qml @@ -15,134 +15,124 @@ ApplicationWindow { required property RoomSummary summary - title: summary.isSpace ? qsTr("Confirm community join") : qsTr("Confirm room join") - modality: Qt.WindowModal - flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint color: palette.window - width: 350 + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint height: content.implicitHeight + Nheko.paddingLarge + footer.implicitHeight + modality: Qt.WindowModal + title: summary.isSpace ? qsTr("Confirm community join") : qsTr("Confirm room join") + width: 350 + + footer: DialogButtonBox { + id: dbb + + standardButtons: DialogButtonBox.Cancel + + onAccepted: { + summary.reason = reason.text; + summary.join(); + joinRoomRoot.close(); + } + onRejected: { + joinRoomRoot.close(); + } + + Button { + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + enabled: input.text.match("#.+?:.{3,}") + text: summary.isKnockOnly ? qsTr("Knock") : qsTr("Join") + } + } Shortcut { sequence: StandardKey.Cancel + onActivated: dbb.rejected() } - ColumnLayout { id: content - spacing: Nheko.paddingMedium - anchors.margins: Nheko.paddingMedium + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + spacing: Nheko.paddingMedium Avatar { - Layout.topMargin: Nheko.paddingMedium - url: summary.roomAvatarUrl.replace("mxc://", "image://MxcImage/") - roomid: summary.roomid - displayName: summary.roomName + Layout.alignment: Qt.AlignHCenter Layout.preferredHeight: 130 Layout.preferredWidth: 130 - Layout.alignment: Qt.AlignHCenter + Layout.topMargin: Nheko.paddingMedium + displayName: summary.roomName + roomid: summary.roomid + url: summary.roomAvatarUrl.replace("mxc://", "image://MxcImage/") } - Spinner { Layout.alignment: Qt.AlignHCenter - visible: !summary.isLoaded foreground: palette.mid running: !summary.isLoaded + visible: !summary.isLoaded } - TextEdit { - readOnly: true - textFormat: TextEdit.RichText - text: summary.roomName - font.pixelSize: fontMetrics.font.pixelSize * 2 - color: palette.text - Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true + color: palette.text + font.pixelSize: fontMetrics.font.pixelSize * 2 horizontalAlignment: TextEdit.AlignHCenter - wrapMode: TextEdit.Wrap + readOnly: true selectByMouse: true + text: summary.roomName + textFormat: TextEdit.RichText + wrapMode: TextEdit.Wrap } TextEdit { - readOnly: true - textFormat: TextEdit.RichText - text: summary.roomid - font.pixelSize: fontMetrics.font.pixelSize * 0.8 - color: palette.text - Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true + color: palette.text + font.pixelSize: fontMetrics.font.pixelSize * 0.8 horizontalAlignment: TextEdit.AlignHCenter - wrapMode: TextEdit.Wrap + readOnly: true selectByMouse: true + text: summary.roomid + textFormat: TextEdit.RichText + wrapMode: TextEdit.Wrap } RowLayout { - spacing: Nheko.paddingMedium Layout.alignment: Qt.AlignHCenter + spacing: Nheko.paddingMedium MatrixText { text: qsTr("%n member(s)", "", summary.memberCount) } - ImageButton { - image: ":/icons/icons/ui/people.svg" enabled: false + image: ":/icons/icons/ui/people.svg" } - } TextEdit { - readOnly: true - textFormat: TextEdit.RichText - text: summary.roomTopic - color: palette.text - Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true + color: palette.text horizontalAlignment: TextEdit.AlignHCenter - wrapMode: TextEdit.Wrap + readOnly: true selectByMouse: true + text: summary.roomTopic + textFormat: TextEdit.RichText + wrapMode: TextEdit.Wrap } - Label { id: promptLabel - text: summary.isKnockOnly ? qsTr("This room can't be joined directly. You can, however, knock on the room and room members can accept or decline this join request. You can additionally provide a reason for them to let you in below:") : qsTr("Do you want to join this room? You can optionally add a reason below:") - color: palette.text Layout.fillWidth: true + color: palette.text + font.bold: true horizontalAlignment: Text.AlignHCenter + text: summary.isKnockOnly ? qsTr("This room can't be joined directly. You can, however, knock on the room and room members can accept or decline this join request. You can additionally provide a reason for them to let you in below:") : qsTr("Do you want to join this room? You can optionally add a reason below:") wrapMode: Text.Wrap - font.bold: true } - MatrixTextField { id: reason - focus: true Layout.fillWidth: true + focus: true text: joinRoomRoot.summary.reason } - } - - footer: DialogButtonBox { - id: dbb - - standardButtons: DialogButtonBox.Cancel - onAccepted: { - summary.reason = reason.text; - summary.join(); - joinRoomRoot.close(); - } - onRejected: { - joinRoomRoot.close(); - } - - Button { - text: summary.isKnockOnly ? qsTr("Knock") : qsTr("Join") - enabled: input.text.match("#.+?:.{3,}") - DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole - } - - } - } diff --git a/resources/qml/dialogs/CreateDirect.qml b/resources/qml/dialogs/CreateDirect.qml index d411c5f2..cfa06199 100644 --- a/resources/qml/dialogs/CreateDirect.qml +++ b/resources/qml/dialogs/CreateDirect.qml @@ -11,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 && profile + text: "Start Direct Chat" + } + } onVisibilityChanged: { userID.forceActiveFocus(); @@ -25,91 +43,82 @@ 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, palette.window) font.pointSize: fontMetrics.font.pointSize + text: profile ? profile.displayName : "" } - Label { Layout.fillWidth: true - text: userID.text color: palette.buttonText 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: { // we can't use "isValidMxid" here, since the property might only be reevaluated after this change handler. - if(text.match("@.+?:.{3,}")) { + if (text.match("@.+?:.{3,}")) { 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: 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 && profile - } - onRejected: createDirectRoot.close(); - onAccepted: { - profile.startChat(encryption.checked) - createDirectRoot.close() + Item { + Layout.fillHeight: true } } } diff --git a/resources/qml/dialogs/CreateRoom.qml b/resources/qml/dialogs/CreateRoom.qml index 2164ba50..d8e937a3 100644 --- a/resources/qml/dialogs/CreateRoom.qml +++ b/resources/qml/dialogs/CreateRoom.qml @@ -14,11 +14,32 @@ ApplicationWindow { property bool space: false - title: space ? qsTr("New community") : qsTr("New 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: space ? qsTr("New community") : qsTr("New Room") + + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Cancel + + onAccepted: { + var preset = 0; + if (isPublic.checked) { + preset = 1; + } else { + preset = isTrusted.checked ? 2 : 0; + } + Nheko.createRoom(space, 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(); @@ -26,10 +47,12 @@ ApplicationWindow { Shortcut { sequence: StandardKey.Cancel + onActivated: createRoomRoot.close() } GridLayout { id: rootLayout + anchors.fill: parent anchors.margins: Nheko.paddingLarge columns: 2 @@ -37,127 +60,118 @@ ApplicationWindow { MatrixTextField { id: newRoomName + Layout.columnSpan: 2 Layout.fillWidth: true - focus: true label: qsTr("Name") placeholderText: qsTr("No name") } MatrixTextField { 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: "#" color: palette.text + text: "#" } MatrixTextField { id: newRoomAlias + focus: true placeholderText: qsTr("Alias") } Label { - Layout.preferredWidth: implicitWidth property string userName: userInfoGrid.profile.userid - text: userName.substring(userName.indexOf(":")) + + Layout.preferredWidth: implicitWidth color: 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: 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 { - visible: !space - 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: palette.text + text: qsTr("Trusted") + visible: !space + 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 { - visible: !space + id: isTrusted + Layout.alignment: Qt.AlignRight Layout.preferredWidth: implicitWidth - id: isTrusted checked: false enabled: !isPublic.checked + visible: !space } Label { - visible: !space - 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: palette.text + text: qsTr("Encryption") + visible: !space + HoverHandler { id: encryptionHover + } - ToolTip.visible: encryptionHover.hovered - ToolTip.text: qsTr("Caution: Encryption cannot be disabled") - ToolTip.delay: Nheko.tooltipDelay } ToggleButton { - visible: !space + id: isEncrypted + Layout.alignment: Qt.AlignRight Layout.preferredWidth: implicitWidth - id: isEncrypted checked: false + visible: !space } - - 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(space, newRoomName.text, newRoomTopic.text, newRoomAlias.text, isEncrypted.checked, preset) - createRoomRoot.close(); + Item { + Layout.fillHeight: true } } } diff --git a/resources/qml/dialogs/EventExpirationDialog.qml b/resources/qml/dialogs/EventExpirationDialog.qml index 37268ca7..7bee1956 100644 --- a/resources/qml/dialogs/EventExpirationDialog.qml +++ b/resources/qml/dialogs/EventExpirationDialog.qml @@ -11,157 +11,151 @@ import im.nheko ApplicationWindow { id: dialog - property string roomid: "" - property string roomName: "" property var onAccepted: undefined + property string roomName: "" + property string roomid: "" - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowTitleHint - width: 275 height: 330 - minimumWidth: 250 minimumHeight: 220 - - EventExpiry { - id: eventExpiry - - roomid: dialog.roomid - } - + minimumWidth: 250 + modality: Qt.NonModal title: { if (roomid) { return qsTr("Event expiration for %1").arg(roomName); - } - else { + } else { return qsTr("Event expiration"); } } + width: 275 + + footer: DialogButtonBox { + id: dbb + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + + onAccepted: { + eventExpiry.save(); + dialog.close(); + } + onRejected: dialog.close() + } + + EventExpiry { + id: eventExpiry + + roomid: dialog.roomid + } Shortcut { sequence: StandardKey.Cancel + onActivated: dbb.rejected() } - ColumnLayout { - spacing: Nheko.paddingMedium - anchors.margins: Nheko.paddingMedium 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("You can configure when your messages will be deleted in %1. This only happens when Nheko is open and has permissions to delete messages until Matrix servers support this feature natively. In general 0 means disable.").arg(roomName); - } - else { + } else { return qsTr("You can configure when your messages will be deleted in all rooms unless configured otherwise. This only happens when Nheko is open and has permissions to delete messages until Matrix servers support this feature natively. In general 0 means disable."); } } - font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2) - Layout.fillWidth: true - Layout.fillHeight: false } - GridLayout { + Layout.fillHeight: true + Layout.fillWidth: true columns: 2 rowSpacing: Nheko.paddingMedium - Layout.fillWidth: true - Layout.fillHeight: true MatrixText { - text: qsTr("Expire events after X days") + Layout.fillWidth: true ToolTip.text: qsTr("Automatically redacts messages after X days, unless otherwise protected. Set to 0 to disable.") ToolTip.visible: hh1.hovered - Layout.fillWidth: true + text: qsTr("Expire events after X days") HoverHandler { id: hh1 + } } - SpinBox { Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + editable: true from: 0 - to: 1000 stepSize: 1 + to: 1000 value: eventExpiry.expireEventsAfterDays + onValueChanged: eventExpiry.expireEventsAfterDays = value - editable: true } - MatrixText { - text: qsTr("Only keep latest X events") + Layout.fillWidth: true ToolTip.text: qsTr("Deletes your events in this room if there are more than X newer messages unless otherwise protected. Set to 0 to disable.") ToolTip.visible: hh2.hovered - Layout.fillWidth: true + text: qsTr("Only keep latest X events") HoverHandler { id: hh2 + } } - - SpinBox { Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + editable: true from: 0 - to: 1000000 stepSize: 1 + to: 1000000 value: eventExpiry.expireEventsAfterCount + onValueChanged: eventExpiry.expireEventsAfterCount = value - editable: true } - MatrixText { - text: qsTr("Always keep latest X events") + Layout.fillWidth: true ToolTip.text: qsTr("This prevents events to be deleted by the above 2 settings if they are the latest X messages from you in the room.") ToolTip.visible: hh3.hovered - Layout.fillWidth: true + text: qsTr("Always keep latest X events") HoverHandler { id: hh3 + } } - - SpinBox { Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + editable: true from: 0 - to: 1000000 stepSize: 1 + to: 1000000 value: eventExpiry.protectLatestEvents + onValueChanged: eventExpiry.protectLatestEvents = value - editable: true } - MatrixText { - text: qsTr("Include state events") + Layout.fillWidth: true ToolTip.text: qsTr("If this is turned on, old state events also get redacted. The latest state event of any type+key combination is excluded from redaction to not remove the room name and similar state by accident.") ToolTip.visible: hh4.hovered - Layout.fillWidth: true + text: qsTr("Include state events") HoverHandler { id: hh4 + } } - ToggleButton { Layout.alignment: Qt.AlignRight checked: eventExpiry.expireStateEvents + onToggled: eventExpiry.expireStateEvents = checked } } } - - footer: DialogButtonBox { - id: dbb - - standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel - onAccepted: { - eventExpiry.save(); - dialog.close(); - } - onRejected: dialog.close(); - } - } - diff --git a/resources/qml/dialogs/FallbackAuthDialog.qml b/resources/qml/dialogs/FallbackAuthDialog.qml index d9a7ac8f..eec43d4a 100644 --- a/resources/qml/dialogs/FallbackAuthDialog.qml +++ b/resources/qml/dialogs/FallbackAuthDialog.qml @@ -15,49 +15,46 @@ ApplicationWindow { fallback.confirm(); fallbackRoot.close(); } - function reject() { fallback.cancel(); fallbackRoot.close(); } color: palette.window - title: qsTr("Fallback authentication") flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint height: msg.implicitHeight + footer.implicitHeight + title: qsTr("Fallback authentication") width: Math.max(msg.implicitWidth, footer.implicitWidth) - Shortcut { - sequence: StandardKey.Cancel - onActivated: fallbackRoot.reject() - } - - Label { - id: msg - - anchors.fill: parent - padding: 8 - text: qsTr("Open the fallback, follow the steps, and confirm after completing them.") - } - footer: DialogButtonBox { onAccepted: fallbackRoot.accept() onRejected: fallbackRoot.reject() Button { text: qsTr("Open Fallback in Browser") + onClicked: fallback.openFallbackAuth() } - Button { - text: qsTr("Cancel") DialogButtonBox.buttonRole: DialogButtonBox.RejectRole + text: qsTr("Cancel") } - Button { - text: qsTr("Confirm") DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + text: qsTr("Confirm") } } + Shortcut { + sequence: StandardKey.Cancel + + onActivated: fallbackRoot.reject() + } + Label { + id: msg + + anchors.fill: parent + padding: 8 + text: qsTr("Open the fallback, follow the steps, and confirm after completing them.") + } } diff --git a/resources/qml/dialogs/HiddenEventsDialog.qml b/resources/qml/dialogs/HiddenEventsDialog.qml index a66a78f1..74f15aa3 100644 --- a/resources/qml/dialogs/HiddenEventsDialog.qml +++ b/resources/qml/dialogs/HiddenEventsDialog.qml @@ -11,119 +11,115 @@ import im.nheko 1.0 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 - width: 275 height: 220 - 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"); } } + width: 275 + + footer: DialogButtonBox { + id: dbb + + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + + onAccepted: { + hiddenEvents.save(); + hiddenEventsDialog.close(); + } + onRejected: hiddenEventsDialog.close() + } + + HiddenEvents { + id: hiddenEvents + roomid: hiddenEventsDialog.roomid + } Shortcut { sequence: StandardKey.Cancel + onActivated: dbb.rejected() } - ColumnLayout { - spacing: Nheko.paddingMedium - anchors.margins: Nheko.paddingMedium 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 { + } 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 { + Layout.fillHeight: true + Layout.fillWidth: true columns: 2 rowSpacing: Nheko.paddingMedium - Layout.fillWidth: true - Layout.fillHeight: true MatrixText { - text: qsTr("User events") + Layout.fillWidth: true ToolTip.text: qsTr("Joins, leaves, avatar and name changes, bans, …") ToolTip.visible: hh1.hovered - Layout.fillWidth: true + text: qsTr("User events") 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") + 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 - Layout.fillWidth: true + 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 { - text: qsTr("Stickers") Layout.fillWidth: true + text: qsTr("Stickers") } - 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(); - } - } diff --git a/resources/qml/dialogs/IgnoredUsers.qml b/resources/qml/dialogs/IgnoredUsers.qml index 6d6585f0..ada8b156 100644 --- a/resources/qml/dialogs/IgnoredUsers.qml +++ b/resources/qml/dialogs/IgnoredUsers.qml @@ -13,72 +13,74 @@ import "../" Window { id: ignoredUsers - title: qsTr("Ignored users") + color: palette.window flags: Qt.WindowCloseButtonHint | Qt.WindowTitleHint height: 650 - width: 420 minimumHeight: 420 - color: palette.window + title: qsTr("Ignored users") + width: 420 ListView { id: view + anchors.fill: parent - spacing: Nheko.paddingMedium footerPositioning: ListView.OverlayFooter - model: TimelineManager.ignoredUsers - header: ColumnLayout { - Text { - Layout.fillWidth: true - Layout.maximumWidth: view.width - wrapMode: Text.Wrap - color: palette.text - text: qsTr("Ignoring a user hides their messages (they can still see yours!).") - } + spacing: Nheko.paddingMedium - Item { Layout.preferredHeight: Nheko.paddingLarge } - } delegate: RowLayout { property var profile: TimelineManager.getGlobalUserProfile(modelData) width: view.width Avatar { - enabled: false displayName: profile.displayName - userid: profile.userid + enabled: false url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") + userid: profile.userid } - Text { - Layout.fillWidth: true Layout.alignment: Qt.AlignLeft - elide: Text.ElideRight + Layout.fillWidth: true color: palette.text + elide: Text.ElideRight text: modelData } - ImageButton { Layout.preferredHeight: 24 Layout.preferredWidth: 24 - image: ":/icons/icons/ui/dismiss.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("Stop Ignoring.") + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/dismiss.svg" + onClicked: profile.ignored = false } } footer: DialogButtonBox { - z: 2 - width: view.width alignment: Qt.AlignRight standardButtons: DialogButtonBox.Ok - onAccepted: ignoredUsers.close() + width: view.width + z: 2 background: Rectangle { anchors.fill: parent color: palette.window } + + onAccepted: ignoredUsers.close() + } + header: ColumnLayout { + Text { + Layout.fillWidth: true + Layout.maximumWidth: view.width + color: palette.text + text: qsTr("Ignoring a user hides their messages (they can still see yours!).") + wrapMode: Text.Wrap + } + Item { + Layout.preferredHeight: Nheko.paddingLarge + } } } } diff --git a/resources/qml/dialogs/ImageOverlay.qml b/resources/qml/dialogs/ImageOverlay.qml index b914829e..781aaa7e 100644 --- a/resources/qml/dialogs/ImageOverlay.qml +++ b/resources/qml/dialogs/ImageOverlay.qml @@ -4,33 +4,32 @@ import QtQuick 2.15 import QtQuick.Window 2.15 - import ".." - import im.nheko 1.0 Window { id: imageOverlay - required property string url required property string eventId - required property Room room required property int originalWidth required property double proportionalHeight + required property Room room + required property string url + //visibility: Window.FullScreen + color: Qt.rgba(0.2, 0.2, 0.2, 0.66) flags: Qt.FramelessWindowHint - //visibility: Window.FullScreen - color: Qt.rgba(0.2,0.2,0.2,0.66) Component.onCompleted: Nheko.setWindowRole(imageOverlay, "imageoverlay") Shortcut { sequences: [StandardKey.Cancel] + onActivated: imageOverlay.close() } - Shortcut { sequences: [StandardKey.Copy] + onActivated: { if (room) { room.copyMedia(eventId); @@ -39,94 +38,85 @@ Window { } } } - TapHandler { - onSingleTapped: imageOverlay.close(); + onSingleTapped: imageOverlay.close() } - - Item { id: imgContainer - property int imgSrcWidth: (imageOverlay.originalWidth && imageOverlay.originalWidth > 100) ? imageOverlay.originalWidth : Screen.width property int imgSrcHeight: imageOverlay.proportionalHeight ? imgSrcWidth * imageOverlay.proportionalHeight : Screen.height + property int imgSrcWidth: (imageOverlay.originalWidth && imageOverlay.originalWidth > 100) ? imageOverlay.originalWidth : Screen.width height: Math.min(parent.height || Screen.height, imgSrcHeight) width: Math.min(parent.width || Screen.width, imgSrcWidth) - x: (parent.width - width) / 2 y: (parent.height - height) / 2 + 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" // workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432: // Magic Mouse pretends to be a trackpad but doesn't work with PinchHandler // and we don't yet distinguish mice and trackpads on Wayland either acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + 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/copy.svg" + width: 48 //ToolTip.visible: hovered //ToolTip.delay: Nheko.tooltipDelay @@ -142,12 +132,11 @@ Window { imageOverlay.close(); } } - ImageButton { height: 48 - width: 48 hoverEnabled: true image: ":/icons/icons/ui/download.svg" + width: 48 //ToolTip.visible: hovered //ToolTip.delay: Nheko.tooltipDelay @@ -165,16 +154,15 @@ 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/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index 9416ec97..e0e73eb3 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -14,34 +14,46 @@ 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 color: palette.base - modality: Qt.WindowModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 600 + modality: Qt.WindowModal + title: qsTr("Editing image pack") + width: 600 + + footer: DialogButtonBox { + id: buttons + + standardButtons: DialogButtonBox.Save | DialogButtonBox.Cancel + + onAccepted: { + imagePack.save(); + win.close(); + } + onRejected: win.close() + } AdaptiveLayout { id: adaptiveView anchors.fill: parent - singlePageMode: false pageIndex: 0 + singlePageMode: false AdaptiveLayoutElement { id: packlistC - visible: Settings.groupView - minimumWidth: 200 + clip: true collapsedWidth: 200 - preferredWidth: 300 maximumWidth: 300 - clip: true + minimumWidth: 200 + preferredWidth: 300 + visible: Settings.groupView ListView { //required property bool isEmote @@ -49,75 +61,67 @@ ApplicationWindow { model: imagePack + delegate: AvatarListTile { + id: packItem - header: AvatarListTile { - title: imagePack.packname - avatarUrl: imagePack.avatarUrl - roomid: imagePack.statekey - subtitle: imagePack.statekey - index: -1 + property color background: palette.window + required property string body + property color bubbleBackground: palette.highlight + property color bubbleText: palette.highlightedText + property color importantText: palette.text + required property string shortCode + property color unimportantText: palette.buttonText + required property string url + + avatarUrl: url + crop: false selectedIndex: currentImageIndex + subtitle: body + title: shortCode TapHandler { - onSingleTapped: currentImageIndex = -1 - } - - Rectangle { - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - height: parent.height - Nheko.paddingSmall * 2 - width: 3 - color: palette.highlight + onSingleTapped: currentImageIndex = index } - } - footer: Button { - onClicked: addFilesDialog.open() - width: ListView.view.width text: qsTr("Add images") + width: ListView.view.width + + onClicked: addFilesDialog.open() FileDialog { id: addFilesDialog - folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) + 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") - acceptLabel: qsTr("Add to pack") + onAccepted: imagePack.addStickers(files) } - } - - delegate: AvatarListTile { - id: packItem - - property color background: palette.window - property color importantText: palette.text - property color unimportantText: palette.buttonText - property color bubbleBackground: palette.highlight - property color bubbleText: palette.highlightedText - required property string shortCode - required property string url - required property string body - - title: shortCode - subtitle: body - avatarUrl: url + header: AvatarListTile { + avatarUrl: imagePack.avatarUrl + index: -1 + roomid: imagePack.statekey selectedIndex: currentImageIndex - crop: false + subtitle: imagePack.statekey + title: imagePack.packname TapHandler { - onSingleTapped: currentImageIndex = index + onSingleTapped: currentImageIndex = -1 + } + Rectangle { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + color: palette.highlight + height: parent.height - Nheko.paddingSmall * 2 + width: 3 } - } - } - } - AdaptiveLayoutElement { id: packinfoC @@ -127,211 +131,189 @@ ApplicationWindow { GridLayout { anchors.fill: parent anchors.margins: Nheko.paddingMedium - visible: currentImageIndex == -1 - enabled: visible columns: 2 + enabled: visible rowSpacing: Nheko.paddingLarge + visible: currentImageIndex == -1 Avatar { + Layout.alignment: Qt.AlignHCenter Layout.columnSpan: 2 - url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/") - displayName: imagePack.packname - roomid: imagePack.statekey Layout.preferredHeight: 130 Layout.preferredWidth: 130 crop: false - Layout.alignment: Qt.AlignHCenter + displayName: imagePack.packname + roomid: imagePack.statekey + url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/") ImageButton { - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("Change the overview image for this pack") + ToolTip.visible: hovered anchors.left: parent.left - anchors.top: parent.top 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 - folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) 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 - visible: imagePack.roomid - Layout.fillWidth: true Layout.columnSpan: 2 + Layout.fillWidth: true label: qsTr("State key") text: imagePack.statekey + visible: imagePack.roomid + onTextEdited: imagePack.statekey = text } - MatrixTextField { - Layout.fillWidth: true Layout.columnSpan: 2 + Layout.fillWidth: true label: qsTr("Packname") text: imagePack.packname + onTextEdited: imagePack.packname = text } - MatrixTextField { - Layout.fillWidth: true Layout.columnSpan: 2 + Layout.fillWidth: true 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 + font.weight: Font.DemiBold text: qsTr("Use as Emoji") } - ToggleButton { + Layout.alignment: Qt.AlignRight 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 + font.weight: Font.DemiBold text: qsTr("Use as Sticker") } - ToggleButton { + Layout.alignment: Qt.AlignRight 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 + enabled: visible rowSpacing: Nheko.paddingLarge + visible: currentImageIndex >= 0 Avatar { + Layout.alignment: Qt.AlignHCenter 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 Layout.preferredHeight: 130 Layout.preferredWidth: 130 crop: false - Layout.alignment: Qt.AlignHCenter + displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) + roomid: displayName + url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/") + "?scale" } - MatrixTextField { - Layout.fillWidth: true + property int bindingCounter: 0 + Layout.columnSpan: 2 + Layout.fillWidth: true label: qsTr("Shortcode") - property int bindingCounter: 0 text: bindingCounter, imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) + onTextEdited: { imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode); // force text field to update in case the model disagreed with the new value. bindingCounter++; } } - MatrixTextField { id: bodyField - Layout.fillWidth: true 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.weight: Font.DemiBold 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) - Layout.alignment: Qt.AlignRight } - MatrixText { Layout.margins: bodyField.textPadding - font.weight: Font.DemiBold 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) - Layout.alignment: Qt.AlignRight } - MatrixText { Layout.margins: bodyField.textPadding - font.weight: Font.DemiBold 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); } - Layout.alignment: Qt.AlignRight } - Item { Layout.columnSpan: 2 Layout.fillHeight: true } - } - } - - } - - } - - footer: DialogButtonBox { - id: buttons - - standardButtons: DialogButtonBox.Save | DialogButtonBox.Cancel - onAccepted: { - imagePack.save(); - win.close(); } - onRejected: win.close() } - } diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml index 9f807e65..b730c264 100644 --- a/resources/qml/dialogs/ImagePackSettingsDialog.qml +++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml @@ -12,95 +12,74 @@ import im.nheko 1.0 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 color: palette.base - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 600 + modality: Qt.NonModal + 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 { - onClicked: { - var dialog = packEditor.createObject(timelineRoot, { - "imagePack": packlist.newPack(false) - }); - dialog.show(); - timelineRoot.destroyOnClose(dialog); - } - Layout.preferredWidth: packlistC.width - visible: !packlist.containsAccountPack - text: qsTr("Create account pack") - } - - Button { - onClicked: { - var dialog = packEditor.createObject(timelineRoot, { - "imagePack": packlist.newPack(true) - }); - dialog.show(); - timelineRoot.destroyOnClose(dialog); - } - Layout.preferredWidth: packlistC.width - visible: room.permissions.canChange(MtxEvent.ImagePackInRoom) - text: qsTr("New room pack") - } - - } + model: packlist delegate: AvatarListTile { id: packItem property color background: palette.window - property color importantText: palette.text - property color unimportantText: palette.buttonText property color bubbleBackground: palette.highlight property color bubbleText: palette.highlightedText required property string displayName required property bool fromAccountData required property bool fromCurrentRoom required property bool fromSpace + property color importantText: palette.text required property string statekey + property color unimportantText: palette.buttonText - title: displayName + roomid: statekey + selectedIndex: currentPackIndex subtitle: { if (fromAccountData) return qsTr("Private pack"); @@ -111,19 +90,42 @@ ApplicationWindow { else return qsTr("Globally enabled pack"); } - selectedIndex: currentPackIndex - roomid: statekey + title: displayName TapHandler { onSingleTapped: currentPackIndex = index } - } + footer: ColumnLayout { + Button { + Layout.preferredWidth: packlistC.width + text: qsTr("Create account pack") + visible: !packlist.containsAccountPack - } + onClicked: { + var dialog = packEditor.createObject(timelineRoot, { + "imagePack": packlist.newPack(false) + }); + dialog.show(); + timelineRoot.destroyOnClose(dialog); + } + } + Button { + Layout.preferredWidth: packlistC.width + text: qsTr("New room pack") + visible: room.permissions.canChange(MtxEvent.ImagePackInRoom) + onClicked: { + var dialog = packEditor.createObject(timelineRoot, { + "imagePack": packlist.newPack(true) + }); + dialog.show(); + timelineRoot.destroyOnClose(dialog); + } + } + } + } } - AdaptiveLayoutElement { id: packinfoC @@ -133,9 +135,9 @@ ApplicationWindow { 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,119 +145,94 @@ ApplicationWindow { spacing: Nheko.paddingLarge Avatar { - url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/") - displayName: packinfo.packName - roomid: packinfo.statekey + Layout.alignment: Qt.AlignHCenter Layout.preferredHeight: 100 Layout.preferredWidth: 100 - Layout.alignment: Qt.AlignHCenter + displayName: packinfo.packName enabled: false + roomid: packinfo.statekey + url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/") } - MatrixText { - text: packinfo.packName - font.pixelSize: Math.ceil(fontMetrics.pixelSize * 1.1) - horizontalAlignment: TextEdit.AlignHCenter 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 textFormat: TextEdit.PlainText } - MatrixText { - text: packinfo.attribution - wrapMode: TextEdit.Wrap - horizontalAlignment: TextEdit.AlignHCenter Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2 + horizontalAlignment: TextEdit.AlignHCenter + text: packinfo.attribution textFormat: TextEdit.PlainText + wrapMode: TextEdit.Wrap } - 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 ? 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/resources/qml/dialogs/InputDialog.qml b/resources/qml/dialogs/InputDialog.qml index 8bd95d09..6f3e30a5 100644 --- a/resources/qml/dialogs/InputDialog.qml +++ b/resources/qml/dialogs/InputDialog.qml @@ -11,57 +11,56 @@ import im.nheko 1.0 ApplicationWindow { id: inputDialog - property alias prompt: promptLabel.text property alias echoMode: statusInput.echoMode - signal accepted(text: string) + property alias prompt: promptLabel.text - modality: Qt.NonModal - flags: Qt.Dialog - width: 350 - height: fontMetrics.lineSpacing * 7 + signal accepted(string text) function forceActiveFocus() { statusInput.forceActiveFocus(); } + flags: Qt.Dialog + height: fontMetrics.lineSpacing * 7 + modality: Qt.NonModal + width: 350 + + footer: DialogButtonBox { + id: dbb + + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + + onAccepted: { + inputDialog.accepted(statusInput.text); + inputDialog.close(); + } + onRejected: { + inputDialog.close(); + } + } + Shortcut { sequence: StandardKey.Cancel + onActivated: dbb.rejected() } - ColumnLayout { - spacing: Nheko.paddingMedium - anchors.margins: Nheko.paddingMedium anchors.fill: parent + anchors.margins: Nheko.paddingMedium + spacing: Nheko.paddingMedium Label { id: promptLabel color: palette.text } - MatrixTextField { id: statusInput Layout.fillWidth: true - onAccepted: dbb.accepted() focus: true - } - - } - - footer: DialogButtonBox { - id: dbb - - standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel - onAccepted: { - inputDialog.accepted(statusInput.text); - inputDialog.close(); - } - onRejected: { - inputDialog.close(); + onAccepted: dbb.accepted() } } - } diff --git a/resources/qml/dialogs/InviteDialog.qml b/resources/qml/dialogs/InviteDialog.qml index 9fc165c7..740b6655 100644 --- a/resources/qml/dialogs/InviteDialog.qml +++ b/resources/qml/dialogs/InviteDialog.qml @@ -12,84 +12,106 @@ import im.nheko ApplicationWindow { id: inviteDialogRoot - property InviteesModel invitees property var friendsCompleter + property InviteesModel invitees property var profile - minimumWidth: 300 - - Component.onCompleted: { - friendsCompleter = TimelineManager.completerFor("user", "friends") - width = 600 - } function addInvite(mxid, displayName, avatarUrl) { if (mxid.match("@.+?:.{3,}")) { invitees.addUser(mxid, displayName, avatarUrl); } else - console.log("invalid mxid: " + mxid) + console.log("invalid mxid: " + mxid); } - function cleanUpAndClose() { if (inviteeEntry.isValidMxid) addInvite(inviteeEntry.text, "", ""); - invitees.accept(); close(); } - title: qsTr("Invite users to %1").arg(invitees.room.plainRoomName) - height: 380 - width: 340 color: palette.window flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 380 + minimumWidth: 300 + title: qsTr("Invite users to %1").arg(invitees.room.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() + } + } + + Component.onCompleted: { + friendsCompleter = TimelineManager.completerFor("user", "friends"); + width = 600; + } Shortcut { sequence: "Ctrl+Enter" + onActivated: cleanUpAndClose() } - Shortcut { sequence: StandardKey.Cancel + onActivated: inviteDialogRoot.close() } - ColumnLayout { anchors.fill: parent anchors.margins: Nheko.paddingMedium spacing: Nheko.paddingMedium + Flow { - layoutDirection: Qt.LeftToRight Layout.fillWidth: true Layout.preferredHeight: implicitHeight + layoutDirection: Qt.LeftToRight spacing: 4 visible: !inviteesList.visible + Repeater { id: inviteesRepeater + model: invitees + delegate: ItemDelegate { - onClicked: invitees.removeUser(model.mxid) id: inviteeButton - contentItem: Label { - anchors.centerIn: parent - id: inviteeUserid - text: model.displayName != "" ? model.displayName : model.userid - color: inviteeButton.hovered ? palette.highlightedText: palette.text - maximumLineCount: 1 - } + background: Rectangle { border.color: palette.text - color: inviteeButton.hovered ? palette.highlight : palette.window border.width: 1 + color: inviteeButton.hovered ? palette.highlight : palette.window radius: inviteeButton.height / 2 } + contentItem: Label { + id: inviteeUserid + + anchors.centerIn: parent + color: inviteeButton.hovered ? palette.highlightedText : palette.text + maximumLineCount: 1 + text: model.displayName != "" ? model.displayName : model.userid + } + + onClicked: invitees.removeUser(model.mxid) } } } - Label { - text: qsTr("Search user") Layout.fillWidth: true color: palette.text + text: qsTr("Search user") } RowLayout { spacing: Nheko.paddingMedium @@ -99,147 +121,136 @@ ApplicationWindow { property bool isValidMxid: text.match("@.+?:.{3,}") + Layout.fillWidth: true backgroundColor: palette.window placeholderText: qsTr("@user:yourserver.example.com", "Example user id. The name 'user' can be localized however you want.") - Layout.fillWidth: true - onAccepted: { - if (isValidMxid) { - addInvite(text, "", ""); - clear() - } - else if (userSearch.count > 0) { - addInvite(userSearch.itemAtIndex(0).userid, userSearch.itemAtIndex(0).displayName, userSearch.itemAtIndex(0).avatarUrl) - clear() - } - } + 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(text, "", ""); + clear(); + } else if (userSearch.count > 0) { + addInvite(userSearch.itemAtIndex(0).userid, userSearch.itemAtIndex(0).displayName, userSearch.itemAtIndex(0).avatarUrl); + clear(); + } } onTextChanged: { - searchTimer.restart() - if(isValidMxid) { + searchTimer.restart(); + if (isValidMxid) { profile = TimelineManager.getGlobalUserProfile(text); } else profile = null; } + Timer { id: searchTimer interval: 350 + onTriggered: { - userSearch.model.setSearchString(parent.text) + userSearch.model.setSearchString(parent.text); } } } - ToggleButton { id: searchOnServer + checked: false + onClicked: userSearch.model.setSearchString(inviteeEntry.text) } MatrixText { text: qsTr("Search on Server") } - } RowLayout { UserListRow { - visible: inviteeEntry.isValidMxid id: del3 - Layout.preferredWidth: inviteDialogRoot.width/2 + Layout.alignment: Qt.AlignTop Layout.preferredHeight: implicitHeight - displayName: profile? profile.displayName : "" - avatarUrl: profile? profile.avatarUrl : "" + Layout.preferredWidth: inviteDialogRoot.width / 2 + avatarUrl: profile ? profile.avatarUrl : "" + bgColor: del3.hovered ? palette.dark : inviteDialogRoot.color + displayName: profile ? profile.displayName : "" userid: inviteeEntry.text + visible: inviteeEntry.isValidMxid + onClicked: addInvite(inviteeEntry.text, displayName, avatarUrl) - bgColor: del3.hovered ? palette.dark : inviteDialogRoot.color } ListView { - visible: !inviteeEntry.isValidMxid id: userSearch - model: searchOnServer.checked? userDirectory : friendsCompleter - Layout.fillWidth: true + Layout.fillHeight: true + Layout.fillWidth: true clip: true + model: searchOnServer.checked ? userDirectory : friendsCompleter + visible: !inviteeEntry.isValidMxid + delegate: UserListRow { id: del2 - width: ListView.view.width - height: implicitHeight + + avatarUrl: model.avatarUrl + bgColor: del2.hovered ? palette.dark : inviteDialogRoot.color displayName: model.displayName + height: implicitHeight userid: model.userid - avatarUrl: model.avatarUrl + width: ListView.view.width + onClicked: addInvite(userid, displayName, avatarUrl) - bgColor: del2.hovered ? palette.dark : inviteDialogRoot.color } } Rectangle { Layout.fillHeight: true - visible: inviteesList.visible Layout.preferredWidth: 1 color: Nheko.theme.separator + visible: inviteesList.visible } ListView { id: inviteesList - Layout.fillWidth: true Layout.fillHeight: true - model: invitees + Layout.fillWidth: true clip: true + model: invitees visible: inviteDialogRoot.width >= 500 delegate: UserListRow { id: del + + avatarUrl: model.avatarUrl + bgColor: del.hovered ? palette.dark : inviteDialogRoot.color + displayName: model.displayName + height: implicitHeight hoverEnabled: true + userid: model.mxid width: ListView.view.width - height: implicitHeight + onClicked: TimelineManager.openGlobalUserProfile(model.mxid) - userid: model.mxid - avatarUrl: model.avatarUrl - displayName: model.displayName - bgColor: del.hovered ? palette.dark : inviteDialogRoot.color + ImageButton { + id: removeButton + anchors.right: parent.right anchors.rightMargin: Nheko.paddingSmall anchors.top: parent.top anchors.topMargin: Nheko.paddingSmall - id: removeButton 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/resources/qml/dialogs/JoinRoomDialog.qml b/resources/qml/dialogs/JoinRoomDialog.qml index 0974325a..2dc38c90 100644 --- a/resources/qml/dialogs/JoinRoomDialog.qml +++ b/resources/qml/dialogs/JoinRoomDialog.qml @@ -11,62 +11,59 @@ import im.nheko 1.0 ApplicationWindow { id: joinRoomRoot - title: qsTr("Join room") - modality: Qt.WindowModal - flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint color: palette.window - width: 350 + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint height: fontMetrics.lineSpacing * 7 + modality: Qt.WindowModal + title: qsTr("Join room") + width: 350 + + footer: DialogButtonBox { + id: dbb + + standardButtons: DialogButtonBox.Cancel + + onAccepted: { + Nheko.joinRoom(input.text); + joinRoomRoot.close(); + } + onRejected: { + joinRoomRoot.close(); + } + + Button { + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + enabled: input.text.match("#.+?:.{3,}") + text: qsTr("Join") + } + } Shortcut { sequence: StandardKey.Cancel + onActivated: dbb.rejected() } - ColumnLayout { - spacing: Nheko.paddingMedium - anchors.margins: Nheko.paddingMedium anchors.fill: parent + anchors.margins: Nheko.paddingMedium + spacing: Nheko.paddingMedium Label { id: promptLabel - text: qsTr("Room ID or alias") color: palette.text + text: qsTr("Room ID or alias") } - MatrixTextField { id: input - focus: true Layout.fillWidth: true + focus: true + onAccepted: { if (input.text.match("#.+?:.{3,}")) dbb.accepted(); - } } - } - - footer: DialogButtonBox { - id: dbb - - standardButtons: DialogButtonBox.Cancel - onAccepted: { - Nheko.joinRoom(input.text); - joinRoomRoot.close(); - } - onRejected: { - joinRoomRoot.close(); - } - - Button { - text: qsTr("Join") - enabled: input.text.match("#.+?:.{3,}") - DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole - } - - } - } diff --git a/resources/qml/dialogs/LeaveRoomDialog.qml b/resources/qml/dialogs/LeaveRoomDialog.qml index c70a4ac0..025f1d97 100644 --- a/resources/qml/dialogs/LeaveRoomDialog.qml +++ b/resources/qml/dialogs/LeaveRoomDialog.qml @@ -9,21 +9,20 @@ 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 - onAccepted: { + modality: Qt.ApplicationModal + text: qsTr("Are you sure you want to leave?") + title: qsTr("Leave room") + onAccepted: { if (CallManager.haveCallInvite) { callManager.rejectInvite(); } else if (CallManager.isOnCall) { CallManager.hangUp(); } - Rooms.leave(roomId, reason) + Rooms.leave(roomId, reason); } - } diff --git a/resources/qml/dialogs/LogoutDialog.qml b/resources/qml/dialogs/LogoutDialog.qml index e79a3e0d..db1bed1a 100644 --- a/resources/qml/dialogs/LogoutDialog.qml +++ b/resources/qml/dialogs/LogoutDialog.qml @@ -9,10 +9,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/resources/qml/dialogs/PhoneNumberInputDialog.qml b/resources/qml/dialogs/PhoneNumberInputDialog.qml index f9bff31c..f5c32776 100644 --- a/resources/qml/dialogs/PhoneNumberInputDialog.qml +++ b/resources/qml/dialogs/PhoneNumberInputDialog.qml @@ -11,21 +11,34 @@ import im.nheko 1.0 ApplicationWindow { id: inputDialog - property alias prompt: promptLabel.text property alias echoMode: statusInput.echoMode - signal accepted(countryCode: string, text: string) + property alias prompt: promptLabel.text + + signal accepted(string countryCode, string 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: { + inputDialog.accepted(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 @@ -33,7 +46,6 @@ ApplicationWindow { Layout.columnSpan: 2 color: palette.text } - ComboBox { id: numberPrefix @@ -47,255 +59,215 @@ 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 @@ -303,1439 +275,1197 @@ 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: { - inputDialog.accepted(numberPrefix.model.get(numberPrefix.currentIndex).i, statusInput.text); - - inputDialog.close(); - } - onRejected: { - inputDialog.close(); - } - } - } diff --git a/resources/qml/dialogs/PowerLevelEditor.qml b/resources/qml/dialogs/PowerLevelEditor.qml index 17b19c25..8962be20 100644 --- a/resources/qml/dialogs/PowerLevelEditor.qml +++ b/resources/qml/dialogs/PowerLevelEditor.qml @@ -9,21 +9,38 @@ import QtQuick.Controls 2.5 import QtQuick.Layouts 1.3 import im.nheko 1.0 - ApplicationWindow { id: plEditorW - property var roomSettings property var editingModel: Nheko.editPowerlevels(roomSettings.roomId) + property var roomSettings - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint - minimumWidth: 300 - minimumHeight: 400 height: 600 + minimumHeight: 400 + minimumWidth: 300 + modality: Qt.NonModal + title: qsTr("Permissions in %1").arg(roomSettings.roomName) width: 300 - title: qsTr("Permissions in %1").arg(roomSettings.roomName); + footer: DialogButtonBox { + id: dbb + + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + + onAccepted: { + if (editingModel.isSpace) { + // TODO(Nico): Replace with showing a list of spaces to apply to + editingModel.updateSpacesModel(); + plEditorW.close(); + timelineRoot.showSpacePLApplyPrompt(roomSettings, editingModel); + } else { + editingModel.commit(); + plEditorW.close(); + } + } + onRejected: plEditorW.close() + } // Shortcut { // sequence: StandardKey.Cancel @@ -31,22 +48,21 @@ ApplicationWindow { // } ColumnLayout { - anchors.margins: Nheko.paddingMedium anchors.fill: parent + anchors.margins: Nheko.paddingMedium spacing: 0 - MatrixText { - text: qsTr("Be careful when editing permissions. You can't lower the permissions of people with a same or higher level than you. Be careful when promoting others.") - font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) - Layout.fillWidth: true + Layout.bottomMargin: Nheko.paddingMedium Layout.fillHeight: false + Layout.fillWidth: true color: palette.text - Layout.bottomMargin: Nheko.paddingMedium + font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) + text: qsTr("Be careful when editing permissions. You can't lower the permissions of people with a same or higher level than you. Be careful when promoting others.") } - TabBar { id: bar + Layout.preferredWidth: parent.width NhekoTabButton { @@ -57,95 +73,95 @@ ApplicationWindow { } } Rectangle { - Layout.fillWidth: true Layout.fillHeight: true - color: palette.alternateBase - border.width: 1 + Layout.fillWidth: true border.color: Nheko.theme.separator + border.width: 1 + color: palette.alternateBase StackLayout { anchors.fill: parent anchors.margins: Nheko.paddingMedium currentIndex: bar.currentIndex - ColumnLayout { spacing: Nheko.paddingMedium MatrixText { - text: qsTr("Move permissions between roles to change them") - font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) - Layout.fillWidth: true Layout.fillHeight: false + Layout.fillWidth: true color: palette.text + font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) + text: qsTr("Move permissions between roles to change them") } - ReorderableListview { - Layout.fillWidth: true Layout.fillHeight: true - + Layout.fillWidth: true model: editingModel.types delegate: RowLayout { Column { Layout.fillWidth: true - Text { visible: model.isType; text: model.displayName; color: palette.text} Text { - visible: !model.isType; + color: palette.text + text: model.displayName + visible: model.isType + } + Text { + color: palette.text text: { if (editingModel.adminLevel == model.powerlevel) - return qsTr("Administrator (%1)").arg(model.powerlevel) + return qsTr("Administrator (%1)").arg(model.powerlevel); else if (editingModel.moderatorLevel == model.powerlevel) - return qsTr("Moderator (%1)").arg(model.powerlevel) + return qsTr("Moderator (%1)").arg(model.powerlevel); else if (editingModel.defaultUserLevel == model.powerlevel) - return qsTr("User (%1)").arg(model.powerlevel) + return qsTr("User (%1)").arg(model.powerlevel); else - return qsTr("Custom (%1)").arg(model.powerlevel) + return qsTr("Custom (%1)").arg(model.powerlevel); } - color: palette.text + visible: !model.isType } } - ImageButton { Layout.alignment: Qt.AlignRight Layout.rightMargin: 2 - image: model.isType ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg" - visible: !model.isType || model.removeable - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: model.isType ? qsTr("Remove event type") : qsTr("Add event type") + ToolTip.visible: hovered + hoverEnabled: true + image: model.isType ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg" + visible: !model.isType || model.removeable + onClicked: { if (model.isType) { editingModel.types.remove(index); } else { - typeEntry.y = offset - typeEntry.visible = true + typeEntry.y = offset; + typeEntry.visible = true; typeEntry.index = index; - typeEntry.forceActiveFocus() + typeEntry.forceActiveFocus(); } } } } + MatrixTextField { id: typeEntry property int index + color: palette.text + visible: false width: parent.width z: 5 - visible: false - - color: palette.text Keys.onPressed: { if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) { - editingModel.types.add(typeEntry.index, typeEntry.text) + editingModel.types.add(typeEntry.index, typeEntry.text); typeEntry.visible = false; typeEntry.clear(); event.accepted = true; - } - else if (event.matches(StandardKey.Cancel)) { + } else if (event.matches(StandardKey.Cancel)) { typeEntry.visible = false; typeEntry.clear(); event.accepted = true; @@ -153,7 +169,6 @@ ApplicationWindow { } } } - Button { Layout.fillWidth: true text: qsTr("Add new role") @@ -164,19 +179,18 @@ ApplicationWindow { id: newPLLay anchors.fill: parent - visible: false color: palette.alternateBase + visible: false RowLayout { - spacing: Nheko.paddingMedium anchors.fill: parent + spacing: Nheko.paddingMedium SpinBox { id: newPLVal - Layout.fillWidth: true Layout.fillHeight: true - + Layout.fillWidth: true editable: true //from: -9007199254740991 //to: 9007199254740991 @@ -192,10 +206,10 @@ ApplicationWindow { } } } - Button { - text: qsTr("Add") Layout.preferredWidth: 100 + text: qsTr("Add") + onClicked: { editingModel.addRole(newPLVal.value); newPLLay.visible = false; @@ -205,42 +219,109 @@ ApplicationWindow { } } } - ColumnLayout { spacing: Nheko.paddingMedium MatrixText { - text: qsTr("Move users up or down to change their permissions") - font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) - Layout.fillWidth: true Layout.fillHeight: false + Layout.fillWidth: true + font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) + text: qsTr("Move users up or down to change their permissions") } - ReorderableListview { - Layout.fillWidth: true Layout.fillHeight: true - + Layout.fillWidth: true model: editingModel.users - Column{ + delegate: RowLayout { + //anchors { fill: parent; margins: 2 } + id: row + + Avatar { + id: avatar + + Layout.leftMargin: 2 + Layout.preferredHeight: Nheko.avatarSize / 2 + Layout.preferredWidth: Nheko.avatarSize / 2 + displayName: model.displayName + enabled: false + url: { + if (model.isUser) + return model.avatarUrl.replace("mxc://", "image://MxcImage/"); + else if (editingModel.adminLevel >= model.powerlevel) + return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?" + palette.buttonText; + else if (editingModel.moderatorLevel >= model.powerlevel) + return "image://colorimage/:/icons/icons/ui/ribbon.svg?" + palette.buttonText; + else + return "image://colorimage/:/icons/icons/ui/person.svg?" + palette.buttonText; + } + userid: model.mxid + } + Column { + Layout.fillWidth: true + + Text { + color: palette.text + text: model.displayName + visible: model.isUser + } + Text { + color: palette.text + text: model.mxid + visible: model.isUser + } + Text { + color: palette.text + text: { + if (editingModel.adminLevel == model.powerlevel) + return qsTr("Administrator (%1)").arg(model.powerlevel); + else if (editingModel.moderatorLevel == model.powerlevel) + return qsTr("Moderator (%1)").arg(model.powerlevel); + else + return qsTr("Custom (%1)").arg(model.powerlevel); + } + visible: !model.isUser + } + } + ImageButton { + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 2 + ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user") + ToolTip.visible: hovered + hoverEnabled: true + image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg" + visible: !model.isUser || model.removeable + + onClicked: { + if (model.isUser) { + editingModel.users.remove(index); + } else { + userEntryCompleter.y = offset; + userEntryCompleter.visible = true; + userEntryCompleter.index = index; + userEntry.forceActiveFocus(); + } + } + } + } + + Column { id: userEntryCompleter property int index: 0 + spacing: 1 visible: false - width: parent.width - spacing: 1 z: 5 + MatrixTextField { id: userEntry - width: parent.width //font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6) color: palette.text - onTextEdited: { - userCompleter.completer.searchString = text; - } + width: parent.width + Keys.onPressed: { if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) { event.accepted = true; @@ -248,9 +329,9 @@ ApplicationWindow { } 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)) - userCompleter.up(); + userCompleter.up(); else - userCompleter.down(); + userCompleter.down(); } else if (event.matches(StandardKey.InsertParagraphSeparator)) { if (userCompleter.currentCompletion()) { userCompleter.finishCompletion(); @@ -264,130 +345,45 @@ ApplicationWindow { event.accepted = true; } } + onTextEdited: { + userCompleter.completer.searchString = text; + } } - - Completer { id: userCompleter - visible: userEntry.text.length > 0 - width: parent.width - roomId: plEditorW.roomSettings.roomId - completerName: "user" - bottomToTop: false - fullWidth: true avatarHeight: Nheko.avatarSize / 2 avatarWidth: Nheko.avatarSize / 2 + bottomToTop: false centerRowContent: false + completerName: "user" + fullWidth: true + roomId: plEditorW.roomSettings.roomId rowMargin: 2 rowSpacing: 2 + visible: userEntry.text.length > 0 + width: parent.width } } - Connections { + id: userCompletionConnections + function onCompletionSelected(id) { console.log("selected: " + id); editingModel.users.add(userEntryCompleter.index, id); userEntry.clear(); userEntryCompleter.visible = false; } - function onCountChanged() { if (userCompleter.count > 0 && (userCompleter.currentIndex < 0 || userCompleter.currentIndex >= userCompleter.count)) - userCompleter.currentIndex = 0; - + userCompleter.currentIndex = 0; } target: userCompleter - id: userCompletionConnections - } - - delegate: RowLayout { - //anchors { fill: parent; margins: 2 } - id: row - - Avatar { - id: avatar - - Layout.preferredHeight: Nheko.avatarSize / 2 - Layout.preferredWidth: Nheko.avatarSize / 2 - Layout.leftMargin: 2 - userid: model.mxid - url: { - if (model.isUser) - return model.avatarUrl.replace("mxc://", "image://MxcImage/") - else if (editingModel.adminLevel >= model.powerlevel) - return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?" + palette.buttonText; - else if (editingModel.moderatorLevel >= model.powerlevel) - return "image://colorimage/:/icons/icons/ui/ribbon.svg?" + palette.buttonText; - else - return "image://colorimage/:/icons/icons/ui/person.svg?" + palette.buttonText; - } - displayName: model.displayName - enabled: false - } - Column { - Layout.fillWidth: true - - Text { visible: model.isUser; text: model.displayName; color: palette.text} - Text { visible: model.isUser; text: model.mxid; color: palette.text} - Text { - visible: !model.isUser; - text: { - if (editingModel.adminLevel == model.powerlevel) - return qsTr("Administrator (%1)").arg(model.powerlevel) - else if (editingModel.moderatorLevel == model.powerlevel) - return qsTr("Moderator (%1)").arg(model.powerlevel) - else - return qsTr("Custom (%1)").arg(model.powerlevel) - } - color: palette.text - } - } - - ImageButton { - Layout.alignment: Qt.AlignRight - Layout.rightMargin: 2 - image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg" - visible: !model.isUser || model.removeable - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user") - onClicked: { - if (model.isUser) { - editingModel.users.remove(index); - } else { - userEntryCompleter.y = offset - userEntryCompleter.visible = true - userEntryCompleter.index = index; - userEntry.forceActiveFocus() - } - } - } } } - } } } } - - footer: DialogButtonBox { - id: dbb - - standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel - onAccepted: { - if (editingModel.isSpace) { - // TODO(Nico): Replace with showing a list of spaces to apply to - editingModel.updateSpacesModel(); - plEditorW.close(); - timelineRoot.showSpacePLApplyPrompt(roomSettings, editingModel) - } else { - editingModel.commit(); - plEditorW.close(); - } - } - onRejected: plEditorW.close(); - } - } diff --git a/resources/qml/dialogs/PowerLevelSpacesApplyDialog.qml b/resources/qml/dialogs/PowerLevelSpacesApplyDialog.qml index 6a2e74b2..6599d386 100644 --- a/resources/qml/dialogs/PowerLevelSpacesApplyDialog.qml +++ b/resources/qml/dialogs/PowerLevelSpacesApplyDialog.qml @@ -12,79 +12,85 @@ import im.nheko ApplicationWindow { id: applyDialog - property RoomSettings roomSettings property PowerlevelEditingModels editingModel + property RoomSettings roomSettings - minimumWidth: 340 - minimumHeight: 450 - width: 450 - height: 680 color: palette.window - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 680 + minimumHeight: 450 + minimumWidth: 340 + modality: Qt.NonModal title: qsTr("Apply permission changes") + width: 450 + + footer: DialogButtonBox { + id: dbb + + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + + onAccepted: { + editingModel.spaces.commit(); + applyDialog.close(); + } + onRejected: applyDialog.close() + } Shortcut { sequence: StandardKey.Cancel + onActivated: roomSettingsDialog.close() } - ColumnLayout { - anchors.margins: Nheko.paddingMedium anchors.fill: parent + anchors.margins: Nheko.paddingMedium spacing: Nheko.paddingLarge - MatrixText { - text: qsTr("Which of the subcommunities and rooms should these permissions be applied to?") - font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) - Layout.fillWidth: true + Layout.bottomMargin: Nheko.paddingMedium Layout.fillHeight: false + Layout.fillWidth: true color: palette.text - Layout.bottomMargin: Nheko.paddingMedium + font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) + text: qsTr("Which of the subcommunities and rooms should these permissions be applied to?") } - GridLayout { - Layout.fillWidth: true Layout.fillHeight: false + Layout.fillWidth: true columns: 2 - Label { - text: qsTr("Apply permissions recursively") - Layout.fillWidth: true - color: palette.text - } - - ToggleButton { - checked: editingModel.spaces.applyToChildren - Layout.alignment: Qt.AlignRight - onCheckedChanged: editingModel.spaces.applyToChildren = checked - } + Label { + Layout.fillWidth: true + color: palette.text + text: qsTr("Apply permissions recursively") + } + ToggleButton { + Layout.alignment: Qt.AlignRight + checked: editingModel.spaces.applyToChildren - Label { - text: qsTr("Overwrite exisiting modifications in rooms") - Layout.fillWidth: true - color: palette.text - } + onCheckedChanged: editingModel.spaces.applyToChildren = checked + } + Label { + Layout.fillWidth: true + color: palette.text + text: qsTr("Overwrite exisiting modifications in rooms") + } + ToggleButton { + Layout.alignment: Qt.AlignRight + checked: editingModel.spaces.overwriteDiverged - ToggleButton { - checked: editingModel.spaces.overwriteDiverged - Layout.alignment: Qt.AlignRight - onCheckedChanged: editingModel.spaces.overwriteDiverged = checked - } + onCheckedChanged: editingModel.spaces.overwriteDiverged = checked + } } - ListView { - Layout.fillWidth: true - Layout.fillHeight: true - id: view + Layout.fillHeight: true + Layout.fillWidth: true + cacheBuffer: 50 clip: true - model: editingModel.spaces spacing: 4 - cacheBuffer: 50 delegate: RowLayout { anchors.left: parent.left @@ -92,49 +98,38 @@ ApplicationWindow { ColumnLayout { Layout.fillWidth: true + Text { Layout.fillWidth: true - text: model.displayName color: palette.text - textFormat: Text.PlainText elide: Text.ElideRight + text: model.displayName + textFormat: Text.PlainText } - Text { Layout.fillWidth: true + color: palette.buttonText + elide: Text.ElideRight text: { - if (!model.isEditable) return qsTr("No permissions to apply the new permissions here"); - if (model.isAlreadyUpToDate) return qsTr("No changes needed"); - if (model.isDifferentFromBase) return qsTr("Existing modifications to the permissions in this room will be overwritten"); - return qsTr("Permissions synchronized with community") + if (!model.isEditable) + return qsTr("No permissions to apply the new permissions here"); + if (model.isAlreadyUpToDate) + return qsTr("No changes needed"); + if (model.isDifferentFromBase) + return qsTr("Existing modifications to the permissions in this room will be overwritten"); + return qsTr("Permissions synchronized with community"); } - elide: Text.ElideRight - color: palette.buttonText textFormat: Text.PlainText } } - ToggleButton { - checked: model.applyPermissions Layout.alignment: Qt.AlignRight - onCheckedChanged: model.applyPermissions = checked + checked: model.applyPermissions enabled: model.isEditable + + onCheckedChanged: model.applyPermissions = checked } } } - - } - - footer: DialogButtonBox { - id: dbb - - standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel - onAccepted: { - editingModel.spaces.commit(); - applyDialog.close(); - } - onRejected: applyDialog.close() - } - } diff --git a/resources/qml/dialogs/RawMessageDialog.qml b/resources/qml/dialogs/RawMessageDialog.qml index 653b136f..36a4595b 100644 --- a/resources/qml/dialogs/RawMessageDialog.qml +++ b/resources/qml/dialogs/RawMessageDialog.qml @@ -11,42 +11,39 @@ ApplicationWindow { property alias rawMessage: rawMessageView.text - height: 420 - width: 420 color: palette.window flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 420 + 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 + anchors.margins: Nheko.paddingMedium padding: Nheko.paddingMedium TextArea { id: rawMessageView - font: Nheko.monospaceFont() + anchors.fill: parent color: palette.text + font: Nheko.monospaceFont() readOnly: true textFormat: Text.PlainText - anchors.fill: parent - background: Rectangle { color: palette.base } - } - } - - footer: DialogButtonBox { - standardButtons: DialogButtonBox.Ok - onAccepted: rawMessageRoot.close() - } - } diff --git a/resources/qml/dialogs/ReCaptchaDialog.qml b/resources/qml/dialogs/ReCaptchaDialog.qml index 0da62cbc..73f1f7ac 100644 --- a/resources/qml/dialogs/ReCaptchaDialog.qml +++ b/resources/qml/dialogs/ReCaptchaDialog.qml @@ -15,49 +15,46 @@ ApplicationWindow { recaptcha.confirm(); recaptchaRoot.close(); } - function reject() { recaptcha.cancel(); recaptchaRoot.close(); } color: palette.window - title: recaptcha.context flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint height: msg.implicitHeight + footer.implicitHeight + title: recaptcha.context width: Math.max(msg.implicitWidth, footer.implicitWidth) - Shortcut { - sequence: StandardKey.Cancel - onActivated: recaptchaRoot.reject() - } - - Label { - id: msg - - anchors.fill: parent - padding: 8 - text: qsTr("Solve the reCAPTCHA and press the confirm button") - } - footer: DialogButtonBox { onAccepted: recaptchaRoot.accept() onRejected: recaptchaRoot.reject() Button { text: qsTr("Open reCAPTCHA") + onClicked: recaptcha.openReCaptcha() } - Button { - text: qsTr("Cancel") DialogButtonBox.buttonRole: DialogButtonBox.RejectRole + text: qsTr("Cancel") } - Button { - text: qsTr("Confirm") DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + text: qsTr("Confirm") } } + Shortcut { + sequence: StandardKey.Cancel + + onActivated: recaptchaRoot.reject() + } + Label { + id: msg + + anchors.fill: parent + padding: 8 + text: qsTr("Solve the reCAPTCHA and press the confirm button") + } } diff --git a/resources/qml/dialogs/ReadReceipts.qml b/resources/qml/dialogs/ReadReceipts.qml index 74a4d1d8..d30970ea 100644 --- a/resources/qml/dialogs/ReadReceipts.qml +++ b/resources/qml/dialogs/ReadReceipts.qml @@ -14,18 +14,24 @@ ApplicationWindow { property ReadReceiptsProxy readReceipts property Room room + color: palette.window + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint height: 380 - width: 340 minimumHeight: 380 minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium - color: 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 @@ -34,98 +40,84 @@ ApplicationWindow { Label { id: headerTitle - color: palette.text Layout.alignment: Qt.AlignCenter - text: qsTr("Read receipts") + color: palette.text font.pointSize: fontMetrics.font.pointSize * 1.5 + text: qsTr("Read receipts") } - ScrollView { - 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 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 ? 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 { id: avatar - Layout.preferredWidth: Nheko.avatarSize Layout.preferredHeight: Nheko.avatarSize - userid: model.mxid - url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + Layout.preferredWidth: Nheko.avatarSize displayName: model.displayName enabled: false + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + userid: model.mxid } - ColumnLayout { - spacing: Nheko.paddingSmall Layout.fillWidth: true + spacing: Nheko.paddingSmall ElidedLabel { - fullText: model.displayName + Layout.fillWidth: true color: TimelineManager.userColor(model ? model.mxid : "", palette.window) - font.pointSize: fontMetrics.font.pointSize elideWidth: del.width - Nheko.paddingMedium - avatar.width - Layout.fillWidth: true + font.pointSize: fontMetrics.font.pointSize + fullText: model.displayName } - ElidedLabel { - fullText: model.timestamp + Layout.fillWidth: true color: palette.buttonText - font.pointSize: fontMetrics.font.pointSize * 0.9 elideWidth: del.width - Nheko.paddingMedium - avatar.width - Layout.fillWidth: true + font.pointSize: fontMetrics.font.pointSize * 0.9 + fullText: model.timestamp } - } - } - NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } - } - } - } - } - - footer: DialogButtonBox { - standardButtons: DialogButtonBox.Ok - onAccepted: readReceiptsRoot.close() - } - } diff --git a/resources/qml/dialogs/ReportMessage.qml b/resources/qml/dialogs/ReportMessage.qml index a0b6325c..05fc5f89 100644 --- a/resources/qml/dialogs/ReportMessage.qml +++ b/resources/qml/dialogs/ReportMessage.qml @@ -10,71 +10,66 @@ import im.nheko ApplicationWindow { required property string eventId - width: 400 height: gl.implicitHeight + 2 * Nheko.paddingMedium title: qsTr("Report message") + width: 400 GridLayout { id: gl - columnSpacing: Nheko.paddingMedium - rowSpacing: Nheko.paddingMedium - columns: 2 anchors.fill: parent anchors.margins: Nheko.paddingMedium + columnSpacing: Nheko.paddingMedium + columns: 2 + rowSpacing: Nheko.paddingMedium Label { Layout.columnSpan: 2 Layout.fillWidth: true - wrapMode: Label.WordWrap text: qsTr("This message you are reporting will be sent to your server administrator for review. Please note that not all server administrators review reported content. You should also ask a room moderator to remove the content if necessary.") + wrapMode: Label.WordWrap } - Label { text: qsTr("Enter your reason for reporting:") } - TextField { id: reason Layout.fillWidth: true } - Label { text: qsTr("How bad is the message?") } - Slider { id: score + Layout.fillWidth: true from: 0 - to: -100 - stepSize: 25 snapMode: Slider.SnapAlways - Layout.fillWidth: true + stepSize: 25 + to: -100 + } + Item { } - - Item {} - Label { text: { if (score.value === 0) - return qsTr("Not bad") + return qsTr("Not bad"); else if (score.value === -25) - return qsTr("Mild") + return qsTr("Mild"); else if (score.value === -50) - return qsTr("Bad") + return qsTr("Bad"); else if (score.value === -75) - return qsTr("Serious") + return qsTr("Serious"); else if (score.value === -100) - return qsTr("Extremely serious") + return qsTr("Extremely serious"); } } - DialogButtonBox { - Layout.columnSpan: 2 Layout.alignment: Qt.AlignRight + Layout.columnSpan: 2 standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + onAccepted: { room.reportEvent(eventId, reason.text, score.value); close(); diff --git a/resources/qml/dialogs/RoomDirectory.qml b/resources/qml/dialogs/RoomDirectory.qml index dbf3b459..f19f94d2 100644 --- a/resources/qml/dialogs/RoomDirectory.qml +++ b/resources/qml/dialogs/RoomDirectory.qml @@ -13,21 +13,72 @@ import im.nheko 1.0 ApplicationWindow { id: roomDirectoryWindow - visible: true - minimumWidth: 340 - minimumHeight: 340 - height: 420 - width: 650 color: palette.window - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 420 + minimumHeight: 340 + minimumWidth: 340 + modality: Qt.NonModal title: qsTr("Explore Public Rooms") + visible: true + width: 650 + + footer: RowLayout { + spacing: Nheko.paddingMedium + width: parent.width + + Button { + Layout.alignment: Qt.AlignRight + Layout.margins: Nheko.paddingMedium + text: qsTr("Close") + + onClicked: roomDirectoryWindow.close() + } + } + header: RowLayout { + id: searchBarLayout + + implicitHeight: roomSearch.height + spacing: Nheko.paddingMedium + width: parent.width + + MatrixTextField { + id: roomSearch + + Layout.fillWidth: true + color: palette.text + focus: true + font.pixelSize: fontMetrics.font.pixelSize + placeholderText: qsTr("Search for public rooms") + selectByMouse: true + + Component.onCompleted: forceActiveFocus() + onTextChanged: searchTimer.restart() + } + MatrixTextField { + id: chooseServer + + Layout.maximumWidth: 0.3 * header.width + Layout.minimumWidth: 0.3 * header.width + color: 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 @@ -37,171 +88,108 @@ ApplicationWindow { delegate: Rectangle { id: roomDirDelegate + property int avatarSize: fontMetrics.height * 3.2 property color background: palette.window property color importantText: palette.text property color unimportantText: palette.buttonText - 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 - Layout.preferredWidth: roomDirDelegate.avatarSize Layout.preferredHeight: roomDirDelegate.avatarSize - - url: model.avatarUrl.replace("mxc://", "image://MxcImage/") - roomid: model.roomid + Layout.preferredWidth: roomDirDelegate.avatarSize + Layout.rightMargin: Nheko.paddingMedium displayName: model.name + roomid: model.roomid + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") } - GridLayout { id: textContent - rows: 2 - columns: 2 Layout.alignment: Qt.AlignLeft Layout.preferredWidth: parent.width - roomAvatar.width + columns: 2 + rows: 2 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 + Layout.fillWidth: true + Layout.row: 1 + color: roomDirDelegate.unimportantText elide: Text.ElideRight + font.pointSize: fontMetrics.font.pointSize * 0.9 maximumLineCount: 2 - Layout.fillWidth: true 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.roomid !== "" text: model.canJoin ? qsTr("Join") : qsTr("Open") + onClicked: { if (model.canJoin) publicRooms.joinRoom(model.index); - else - { + else { Rooms.setCurrentRoom(model.roomid); roomDirectoryWindow.close(); } } } - } - } - } - 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: palette.mid + running: visible } - } - } - - 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: 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: palette.text - placeholderText: qsTr("Choose custom homeserver") - onTextChanged: publicRooms.setMatrixServer(text) - } - - Timer { - id: searchTimer - - interval: 350 - onTriggered: roomDirView.model.setSearchTerm(roomSearch.text) - } - - } - - footer: RowLayout { - spacing: Nheko.paddingMedium - width: parent.width - - Button { - text: qsTr("Close") - onClicked: roomDirectoryWindow.close() - Layout.alignment: Qt.AlignRight - Layout.margins: Nheko.paddingMedium - } - } - } diff --git a/resources/qml/dialogs/RoomMembers.qml b/resources/qml/dialogs/RoomMembers.qml index afb76398..95d234f9 100644 --- a/resources/qml/dialogs/RoomMembers.qml +++ b/resources/qml/dialogs/RoomMembers.qml @@ -17,18 +17,24 @@ ApplicationWindow { property MemberList members property Room room - title: qsTr("Members of %1").arg(members.roomName) - height: 650 - width: 420 - minimumHeight: 420 color: palette.window flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 650 + minimumHeight: 420 + 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,148 +43,146 @@ ApplicationWindow { Avatar { id: roomAvatar + Layout.alignment: Qt.AlignHCenter Layout.preferredHeight: 130 Layout.preferredWidth: 130 - - roomid: members.roomId displayName: members.roomName - Layout.alignment: Qt.AlignHCenter + roomid: members.roomId url: members.avatarUrl.replace("mxc://", "image://MxcImage/") + 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: palette.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 { - 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 ListView { id: memberList - clip: true boundsBehavior: Flickable.StopAtBounds + clip: true model: members - delegate: ItemDelegate { id: del - onClicked: room.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 ? palette.dark : roomMembersRoot.color } + onClicked: room.openUserProfile(model.mxid) + RowLayout { id: memberLayout - spacing: Nheko.paddingMedium anchors.centerIn: parent + spacing: Nheko.paddingMedium width: parent.width - Nheko.paddingSmall * 2 Avatar { id: avatar - Layout.preferredWidth: Nheko.avatarSize Layout.preferredHeight: Nheko.avatarSize - userid: model.mxid - url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + Layout.preferredWidth: Nheko.avatarSize displayName: model.displayName enabled: false + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + userid: model.mxid } - ColumnLayout { - spacing: Nheko.paddingSmall Layout.fillWidth: true + spacing: Nheko.paddingSmall ElidedLabel { - fullText: model.displayName + Layout.fillWidth: true color: TimelineManager.userColor(model ? model.mxid : "", del.background.color) - font.pixelSize: fontMetrics.font.pixelSize elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width - Layout.fillWidth: true + font.pixelSize: fontMetrics.font.pixelSize + fullText: model.displayName } - ElidedLabel { - fullText: model.mxid + Layout.fillWidth: true color: del.hovered ? palette.brightText : palette.buttonText - font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9) elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width - Layout.fillWidth: true + font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9) + fullText: model.mxid } - } - PowerlevelIndicator { - powerlevel: model.powerlevel permissions: room.permissions + powerlevel: model.powerlevel } - EncryptionIndicator { id: encryptInd - Layout.preferredWidth: 16 - Layout.preferredHeight: 16 Layout.alignment: Qt.AlignRight - visible: room.isEncrypted - encrypted: room.isEncrypted - trust: encrypted ? model.trustlevel : Crypto.Unverified + Layout.preferredHeight: 16 + Layout.preferredWidth: 16 ToolTip.text: { if (!encrypted) return qsTr("This room is not encrypted!"); - switch (trust) { case Crypto.Verified: return qsTr("This user is verified."); @@ -188,23 +192,22 @@ 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 @@ -212,18 +215,8 @@ ApplicationWindow { anchors.centerIn: parent implicitHeight: parent.visible ? 35 : 0 } - } - } - } - - } - - footer: DialogButtonBox { - standardButtons: DialogButtonBox.Ok - onAccepted: roomMembersRoot.close() } - } diff --git a/resources/qml/dialogs/RoomSettingsDialog.qml b/resources/qml/dialogs/RoomSettingsDialog.qml index 9276a9d3..1c4a5442 100644 --- a/resources/qml/dialogs/RoomSettingsDialog.qml +++ b/resources/qml/dialogs/RoomSettingsDialog.qml @@ -16,80 +16,87 @@ ApplicationWindow { property var roomSettings - minimumWidth: 340 - minimumHeight: 450 - width: 450 - height: 680 color: palette.window - modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: 680 + minimumHeight: 450 + minimumWidth: 340 + modality: Qt.NonModal 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 { id: displayAvatar - Layout.topMargin: Nheko.paddingMedium - url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/") - roomid: roomSettings.roomId - displayName: roomSettings.roomName + Layout.alignment: Qt.AlignHCenter Layout.preferredHeight: 130 Layout.preferredWidth: 130 - Layout.alignment: Qt.AlignHCenter + Layout.topMargin: Nheko.paddingMedium + displayName: roomSettings.roomName + roomid: roomSettings.roomId + url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/") + onClicked: TimelineManager.openImageOverlay(null, roomSettings.roomAvatarUrl, "", 0, 0) ImageButton { - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("Change room avatar.") + ToolTip.visible: hovered anchors.left: displayAvatar.left - anchors.top: displayAvatar.top anchors.leftMargin: Nheko.paddingMedium + anchors.top: displayAvatar.top anchors.topMargin: Nheko.paddingMedium - visible: roomSettings.canChangeAvatar + hoverEnabled: true image: ":/icons/icons/ui/edit.svg" + visible: roomSettings.canChangeAvatar + onClicked: { roomSettings.updateAvatar(); } - } } - Spinner { Layout.alignment: Qt.AlignHCenter - visible: roomSettings.isLoading foreground: palette.mid running: roomSettings.isLoading + visible: roomSettings.isLoading } - Text { id: errorText + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true color: "red" - visible: opacity > 0 opacity: 0 - Layout.alignment: Qt.AlignHCenter + visible: opacity > 0 wrapMode: Text.Wrap // somehow still doesn't wrap - Layout.fillWidth: true } - SequentialAnimation { id: hideErrorAnimation @@ -98,43 +105,38 @@ ApplicationWindow { PauseAnimation { duration: 4000 } - NumberAnimation { - target: errorText + duration: 1000 property: 'opacity' + target: errorText to: 0 - duration: 1000 } - } - 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: palette.text - Layout.alignment: Qt.AlignHCenter Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - nameChangeButton.anchors.leftMargin - (nameChangeButton.width * 2) + color: 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); @@ -142,18 +144,21 @@ 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); @@ -165,69 +170,64 @@ ApplicationWindow { } } } - } - RowLayout { - spacing: Nheko.paddingMedium Layout.alignment: Qt.AlignHCenter + spacing: Nheko.paddingMedium Label { - text: qsTr("%n member(s)", "", roomSettings.memberCount) color: palette.text + text: qsTr("%n member(s)", "", roomSettings.memberCount) } - ImageButton { - image: ":/icons/icons/ui/people.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("View members of %1").arg(roomSettings.roomName) + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/people.svg" + onClicked: TimelineManager.openRoomMembers(Rooms.getRoomById(roomSettings.roomId)) } - } - 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.plainRoomTopic === "" ? ("" + qsTr("No topic set") + "") : roomSettings.roomTopic) - wrapMode: TextEdit.WordWrap background: null + clip: true color: palette.text horizontalAlignment: TextEdit.AlignHCenter + readOnly: !isTopicEditingAllowed + text: isTopicEditingAllowed ? roomSettings.plainRoomTopic : (roomSettings.plainRoomTopic === "" ? ("" + qsTr("No topic set") + "") : 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); @@ -240,234 +240,219 @@ 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("NOTIFICATIONS") - font.bold: true - color: palette.text Layout.columnSpan: 2 Layout.fillWidth: true Layout.topMargin: Nheko.paddingLarge + color: palette.text + font.bold: true + text: qsTr("NOTIFICATIONS") } - Label { - text: qsTr("Notifications") Layout.fillWidth: true color: 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("ENTRY PERMISSIONS") - font.bold: true - color: palette.text Layout.columnSpan: 2 Layout.fillWidth: true Layout.topMargin: Nheko.paddingLarge + color: palette.text + font.bold: true + text: qsTr("ENTRY PERMISSIONS") } - Label { - text: qsTr("Anyone can join") Layout.fillWidth: true color: palette.text + text: qsTr("Anyone can join") } - ToggleButton { id: publicRoomButton - enabled: roomSettings.canChangeJoinRules - checked: !roomSettings.privateAccess Layout.alignment: Qt.AlignRight + checked: !roomSettings.privateAccess + enabled: roomSettings.canChangeJoinRules } - Label { - text: qsTr("Allow knocking") Layout.fillWidth: true color: palette.text + text: qsTr("Allow knocking") visible: knockingButton.visible } - ToggleButton { id: knockingButton - visible: !publicRoomButton.checked - enabled: roomSettings.canChangeJoinRules && roomSettings.supportsKnocking + Layout.alignment: Qt.AlignRight checked: roomSettings.knockingEnabled + enabled: roomSettings.canChangeJoinRules && roomSettings.supportsKnocking + visible: !publicRoomButton.checked + onCheckedChanged: { - if (checked && !roomSettings.supportsKnockRestricted) restrictedButton.checked = false; + if (checked && !roomSettings.supportsKnockRestricted) + restrictedButton.checked = false; } - Layout.alignment: Qt.AlignRight } - Label { - text: qsTr("Allow joining via other rooms") Layout.fillWidth: true color: palette.text + text: qsTr("Allow joining via other rooms") visible: restrictedButton.visible } - ToggleButton { id: restrictedButton - visible: !publicRoomButton.checked - enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted + Layout.alignment: Qt.AlignRight checked: roomSettings.restrictedEnabled + enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted + visible: !publicRoomButton.checked + onCheckedChanged: { - if (checked && !roomSettings.supportsKnockRestricted) knockingButton.checked = false; + if (checked && !roomSettings.supportsKnockRestricted) + knockingButton.checked = false; } - Layout.alignment: Qt.AlignRight } - Label { - text: qsTr("Rooms to join via") Layout.fillWidth: true color: palette.text + text: qsTr("Rooms to join via") visible: allowedRoomsButton.visible } - Button { id: allowedRoomsButton - visible: restrictedButton.checked && restrictedButton.visible + Layout.alignment: Qt.AlignRight + ToolTip.text: qsTr("Change the list of rooms users can join this room via. Usually this is the official community of this room.") enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted - text: qsTr("Change") - ToolTip.text: qsTr("Change the list of rooms users can join this room via. Usually this is the official community of this room.") + visible: restrictedButton.checked && restrictedButton.visible + onClicked: timelineRoot.showAllowedRoomsEditor(roomSettings) - Layout.alignment: Qt.AlignRight } - Label { - text: qsTr("Allow guests to join") Layout.fillWidth: true color: palette.text + text: qsTr("Allow guests to join") } - ToggleButton { id: guestAccessButton - enabled: roomSettings.canChangeJoinRules - checked: roomSettings.guestAccess Layout.alignment: Qt.AlignRight + checked: roomSettings.guestAccess + enabled: roomSettings.canChangeJoinRules } - Button { - visible: publicRoomButton.checked == roomSettings.privateAccess || knockingButton.checked != roomSettings.knockingEnabled || restrictedButton.checked != roomSettings.restrictedEnabled || guestAccessButton.checked != roomSettings.guestAccess || roomSettings.allowedRoomsModified + Layout.columnSpan: 2 + Layout.fillWidth: true enabled: roomSettings.canChangeJoinRules - text: qsTr("Apply access rules") + visible: publicRoomButton.checked == roomSettings.privateAccess || knockingButton.checked != roomSettings.knockingEnabled || restrictedButton.checked != roomSettings.restrictedEnabled || guestAccessButton.checked != roomSettings.guestAccess || roomSettings.allowedRoomsModified + onClicked: roomSettings.changeAccessRules(!publicRoomButton.checked, guestAccessButton.checked, knockingButton.checked, restrictedButton.checked) - Layout.columnSpan: 2 - Layout.fillWidth: true } - Label { - text: qsTr("MESSAGE VISIBILITY") - font.bold: true - color: palette.text Layout.columnSpan: 2 Layout.fillWidth: true Layout.topMargin: Nheko.paddingLarge + color: palette.text + font.bold: true + text: qsTr("MESSAGE VISIBILITY") } - Label { - text: qsTr("Allow viewing history without joining") Layout.fillWidth: true - color: palette.text + ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("This is useful to see previews of the room or view it on public websites.") ToolTip.visible: publicHistoryHover.hovered - ToolTip.delay: Nheko.tooltipDelay + color: palette.text + text: qsTr("Allow viewing history without joining") HoverHandler { id: publicHistoryHover } } - ToggleButton { id: publicHistoryButton - enabled: roomSettings.canChangeHistoryVisibility - checked: roomSettings.historyVisibility == RoomSettings.WorldReadable Layout.alignment: Qt.AlignRight + checked: roomSettings.historyVisibility == RoomSettings.WorldReadable + enabled: roomSettings.canChangeHistoryVisibility } - Label { - visible: !publicHistoryButton.checked - text: qsTr("Members can see messages since") - Layout.fillWidth: true - color: palette.text Layout.alignment: Qt.AlignTop | Qt.AlignLeft + Layout.fillWidth: true + ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("How much of the history is visible to joined members. Changing this won't affect the visibility of already sent messages. It only applies to new messages.") ToolTip.visible: privateHistoryHover.hovered - ToolTip.delay: Nheko.tooltipDelay + color: palette.text + text: qsTr("Members can see messages since") + visible: !publicHistoryButton.checked HoverHandler { id: privateHistoryHover } } - ColumnLayout { + Layout.alignment: Qt.AlignTop | Qt.AlignRight Layout.fillWidth: true - visible: !publicHistoryButton.checked enabled: roomSettings.canChangeHistoryVisibility - Layout.alignment: Qt.AlignTop | Qt.AlignRight + visible: !publicHistoryButton.checked RadioButton { id: sharedHistory - checked: roomSettings.historyVisibility == RoomSettings.Shared - text: qsTr("Everything") + + ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("As long as the user joined, they can see all previous messages.") ToolTip.visible: hovered - ToolTip.delay: Nheko.tooltipDelay + checked: roomSettings.historyVisibility == RoomSettings.Shared + text: qsTr("Everything") } RadioButton { id: invitedHistory - checked: roomSettings.historyVisibility == RoomSettings.Invited - text: qsTr("They got invited") + + ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Members can only see messages from when they got invited going forward.") ToolTip.visible: hovered - ToolTip.delay: Nheko.tooltipDelay + checked: roomSettings.historyVisibility == RoomSettings.Invited + text: qsTr("They got invited") } RadioButton { id: joinedHistory - checked: roomSettings.historyVisibility == RoomSettings.Joined || roomSettings.historyVisibility == RoomSettings.WorldReadable - text: qsTr("They joined") + + ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Members can only see messages since after they joined.") ToolTip.visible: hovered - ToolTip.delay: Nheko.tooltipDelay + checked: roomSettings.historyVisibility == RoomSettings.Joined || roomSettings.historyVisibility == RoomSettings.WorldReadable + text: qsTr("They joined") } } - Button { - visible: roomSettings.historyVisibility != selectedVisibility - enabled: roomSettings.canChangeHistoryVisibility - - text: qsTr("Apply visibility changes") property int selectedVisibility: { if (publicHistoryButton.checked) return RoomSettings.WorldReadable; @@ -477,202 +462,200 @@ ApplicationWindow { return RoomSettings.Invited; return RoomSettings.Joined; } - onClicked: roomSettings.changeHistoryVisibility(selectedVisibility) + Layout.columnSpan: 2 Layout.fillWidth: true - } + enabled: roomSettings.canChangeHistoryVisibility + text: qsTr("Apply visibility changes") + visible: roomSettings.historyVisibility != selectedVisibility + onClicked: roomSettings.changeHistoryVisibility(selectedVisibility) + } Label { - text: qsTr("Locally hidden events") color: palette.text + text: qsTr("Locally hidden events") } - HiddenEventsDialog { id: hiddenEventsDialog - roomid: roomSettings.roomId + roomName: roomSettings.roomName + roomid: roomSettings.roomId } - Button { - text: qsTr("Configure") + Layout.alignment: Qt.AlignRight ToolTip.text: qsTr("Select events to hide in this room") + text: qsTr("Configure") + onClicked: hiddenEventsDialog.show() - Layout.alignment: Qt.AlignRight } - Label { - text: qsTr("Automatic event deletion") color: palette.text + text: qsTr("Automatic event deletion") } - EventExpirationDialog { id: eventExpirationDialog - roomid: roomSettings.roomId + roomName: roomSettings.roomName + roomid: roomSettings.roomId } - Button { - text: qsTr("Configure") + Layout.alignment: Qt.AlignRight ToolTip.text: qsTr("Select if your events get automatically deleted in this room.") + text: qsTr("Configure") + onClicked: eventExpirationDialog.show() - Layout.alignment: Qt.AlignRight } - Label { - text: qsTr("GENERAL SETTINGS") - font.bold: true - color: palette.text Layout.columnSpan: 2 Layout.fillWidth: true Layout.topMargin: Nheko.paddingLarge + color: palette.text + font.bold: true + text: qsTr("GENERAL SETTINGS") } - Label { - text: qsTr("Encryption") color: palette.text + text: qsTr("Encryption") } - ToggleButton { id: encryptionToggle + Layout.alignment: Qt.AlignRight checked: roomSettings.isEncryptionEnabled + onCheckedChanged: { if (roomSettings.isEncryptionEnabled) { checked = true; - return ; + return; } if (checked === true) 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 { - text: qsTr("Permission") color: palette.text + text: qsTr("Permission") } - Button { - text: qsTr("Configure") + Layout.alignment: Qt.AlignRight ToolTip.text: qsTr("View and change the permissions in this room") + text: qsTr("Configure") + onClicked: timelineRoot.showPLEditor(roomSettings) - Layout.alignment: Qt.AlignRight } - Label { - text: qsTr("Aliases") color: palette.text + text: qsTr("Aliases") } - Button { - text: qsTr("Configure") + Layout.alignment: Qt.AlignRight ToolTip.text: qsTr("View and change the addresses/aliases of this room") + text: qsTr("Configure") + onClicked: timelineRoot.showAliasEditor(roomSettings) - Layout.alignment: Qt.AlignRight } - Label { - text: qsTr("Sticker & Emote Settings") color: palette.text + text: qsTr("Sticker & Emote Settings") } - Button { - text: qsTr("Change") + 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) - Layout.alignment: Qt.AlignRight } - Label { - text: qsTr("INFO") - font.bold: true - color: palette.text Layout.columnSpan: 2 - Layout.topMargin: Nheko.paddingLarge Layout.fillWidth: true + Layout.topMargin: Nheko.paddingLarge + color: palette.text + font.bold: true + text: qsTr("INFO") } - Label { - text: qsTr("Internal ID") color: 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) + + ToolTip.text: qsTr("Copied to clipboard") + ToolTip.visible: toolTipTimer.running color: palette.text - width: parent.width + font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 0.8) horizontalAlignment: Text.AlignRight + text: roomSettings.roomId + width: parent.width wrapMode: Text.WrapAnywhere - ToolTip.text: qsTr("Copied to clipboard") - ToolTip.visible: toolTipTimer.running } - TextEdit{ // label does not allow selection + TextEdit { + // label does not allow selection id: textEdit - visible: false + text: roomSettings.roomId - } - onClicked: { - textEdit.selectAll() - textEdit.copy() - toolTipTimer.start() + visible: false } Timer { id: toolTipTimer + } } - Label { - text: qsTr("Room Version") color: palette.text + text: qsTr("Room Version") } - Label { - text: roomSettings.roomVersion - font.pixelSize: fontMetrics.font.pixelSize Layout.alignment: Qt.AlignRight color: 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/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml index 614ecb56..564fd36c 100644 --- a/resources/qml/dialogs/UserProfile.qml +++ b/resources/qml/dialogs/UserProfile.qml @@ -17,20 +17,20 @@ ApplicationWindow { property var profile + color: palette.window + flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint height: 650 - width: 420 - minimumWidth: 150 minimumHeight: 150 - color: palette.window - title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile") + minimumWidth: 150 modality: Qt.NonModal - flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile") + width: 420 Shortcut { sequence: StandardKey.Cancel + onActivated: userProfileDialog.close() } - ListView { id: devicelist @@ -38,61 +38,73 @@ ApplicationWindow { Layout.fillHeight: true Layout.fillWidth: true - clip: true - spacing: 8 - boundsBehavior: Flickable.StopAtBounds anchors.fill: parent anchors.margins: 10 + boundsBehavior: Flickable.StopAtBounds + clip: true footerPositioning: ListView.OverlayFooter + model: (selectedTab == 0) ? devicesModel : sharedRoomsModel + spacing: 8 + footer: DialogButtonBox { + alignment: Qt.AlignRight + standardButtons: DialogButtonBox.Ok + width: devicelist.width + z: 2 + + background: Rectangle { + anchors.fill: parent + color: palette.window + } + + onAccepted: userProfileDialog.close() + } header: ColumnLayout { id: contentL - width: devicelist.width spacing: Nheko.paddingMedium + width: devicelist.width Avatar { id: displayAvatar - url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") + Layout.alignment: Qt.AlignHCenter Layout.preferredHeight: 130 Layout.preferredWidth: 130 displayName: profile.displayName + url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") userid: profile.userid - Layout.alignment: Qt.AlignHCenter + onClicked: TimelineManager.openImageOverlay(null, profile.avatarUrl, "", 0, 0) 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: palette.mid running: profile.isLoading visible: profile.isLoading - foreground: palette.mid } - Text { id: errorText + Layout.alignment: Qt.AlignHCenter color: "red" - visible: opacity > 0 opacity: 0 - Layout.alignment: Qt.AlignHCenter + visible: opacity > 0 } - SequentialAnimation { id: hideErrorAnimation @@ -101,16 +113,13 @@ ApplicationWindow { PauseAnimation { duration: 4000 } - NumberAnimation { - target: errorText + duration: 1000 property: 'opacity' + target: errorText to: 0 - duration: 1000 } - } - Connections { function onDisplayError(errorMessage) { errorText.text = errorMessage; @@ -120,22 +129,22 @@ ApplicationWindow { target: profile } - TextInput { id: displayUsername property bool isUsernameEditingAllowed - readOnly: !isUsernameEditingAllowed - text: profile.displayName - font.pixelSize: 20 - color: TimelineManager.userColor(profile.userid, 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, 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; @@ -143,14 +152,16 @@ 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); @@ -162,82 +173,79 @@ ApplicationWindow { } } } - } - MatrixText { - text: profile.userid Layout.alignment: Qt.AlignHCenter + text: profile.userid } - MatrixText { id: statusMsg - text: qsTr("Status: %1").arg(userStatus) - visible: userStatus != "" + + property string userStatus: Presence.userStatus(profile.userid) + Layout.fillWidth: true - horizontalAlignment: TextEdit.AlignHCenter Layout.leftMargin: Nheko.paddingMedium Layout.rightMargin: Nheko.paddingMedium font.pointSize: Math.floor(fontMetrics.font.pointSize * 0.9) + horizontalAlignment: TextEdit.AlignHCenter + text: qsTr("Status: %1").arg(userStatus) + visible: userStatus != "" - property string userStatus: Presence.userStatus(profile.userid) Connections { - target: Presence function onPresenceChanged(id) { - if (id == profile.userid) statusMsg.userStatus = Presence.userStatus(profile.userid); + if (id == profile.userid) + statusMsg.userStatus = Presence.userStatus(profile.userid); } + + target: Presence } } - 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: 32 Layout.preferredWidth: 32 + 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" @@ -259,139 +267,135 @@ ApplicationWindow { ImageButton { Layout.preferredHeight: 24 Layout.preferredWidth: 24 - 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 { Layout.preferredHeight: 24 Layout.preferredWidth: 24 - 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 { Layout.preferredHeight: 24 Layout.preferredWidth: 24 - image: ":/icons/icons/ui/ban.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("Ban the user.") - onClicked: profile.banUser() + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/ban.svg" visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan() - } + onClicked: profile.banUser() + } ImageButton { Layout.preferredHeight: 24 Layout.preferredWidth: 24 - image: ":/icons/icons/ui/volume-off-indicator.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: profile.ignored ? qsTr("Unignore the user.") : qsTr("Ignore the user.") + ToolTip.visible: hovered buttonTextColor: profile.ignored ? Nheko.theme.red : palette.buttonText - onClicked: profile.ignored = !profile.ignored + hoverEnabled: true + image: ":/icons/icons/ui/volume-off-indicator.svg" visible: !profile.isSelf - } + onClicked: profile.ignored = !profile.ignored + } ImageButton { Layout.preferredHeight: 24 Layout.preferredWidth: 24 - image: ":/icons/icons/ui/refresh.svg" - hoverEnabled: true - ToolTip.visible: hovered ToolTip.text: qsTr("Refresh device list.") + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/refresh.svg" + onClicked: profile.refreshDevices() } } - TabBar { id: tabbar - visible: !profile.isSelf + + Layout.bottomMargin: Nheko.paddingMedium Layout.fillWidth: true + visible: !profile.isSelf onCurrentIndexChanged: devicelist.selectedTab = currentIndex - NhekoTabButton { text: qsTr("Devices") } NhekoTabButton { text: qsTr("Shared Rooms") } - - Layout.bottomMargin: Nheko.paddingMedium } } - model: (selectedTab == 0) ? devicesModel : sharedRoomsModel - DelegateModel { id: devicesModel + model: profile.deviceList + delegate: RowLayout { - required property int verificationStatus required property string deviceId required property string deviceName required property string lastIp required property var lastTs + required property int verificationStatus - width: devicelist.width spacing: 4 + width: devicelist.width ColumnLayout { - spacing: 0 - Layout.leftMargin: Nheko.paddingMedium Layout.rightMargin: Nheko.paddingMedium + spacing: 0 + RowLayout { Text { - Layout.fillWidth: true Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + color: palette.text elide: Text.ElideRight font.bold: true - color: 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: + case VerificationStatus.VERIFIED: return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green; - case VerificationStatus.UNVERIFIED: + case VerificationStatus.UNVERIFIED: return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?" + Nheko.theme.orange; - case VerificationStatus.SELF: + case VerificationStatus.SELF: return "image://colorimage/:/icons/icons/ui/checkmark.svg?" + Nheko.theme.green; - default: + default: return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.orange; } } + sourceSize.height: 16 * Screen.devicePixelRatio + sourceSize.width: 16 * Screen.devicePixelRatio + visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE } - 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) + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/power-off.svg" visible: profile.isSelf - } + onClicked: profile.signOutDevice(deviceId) + } } - RowLayout { id: deviceNameRow @@ -400,24 +404,25 @@ ApplicationWindow { TextInput { id: deviceNameField - readOnly: !deviceNameRow.isEditingAllowed - text: deviceName - color: palette.text Layout.alignment: Qt.AlignLeft Layout.fillWidth: true + color: palette.text + readOnly: !deviceNameRow.isEditingAllowed selectByMouse: true + text: deviceName + onAccepted: { profile.changeDeviceName(deviceId, deviceNameField.text); deviceNameRow.isEditingAllowed = false; } } - ImageButton { - visible: profile.isSelf - hoverEnabled: true - ToolTip.visible: hovered 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); @@ -429,111 +434,88 @@ ApplicationWindow { } } } - } - Text { - visible: profile.isSelf - Layout.fillWidth: true Layout.alignment: Qt.AlignLeft - elide: Text.ElideRight + Layout.fillWidth: true color: 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 - visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE source: { switch (verificationStatus) { - case VerificationStatus.VERIFIED: + case VerificationStatus.VERIFIED: return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green; - case VerificationStatus.UNVERIFIED: + case VerificationStatus.UNVERIFIED: return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?" + Nheko.theme.orange; - case VerificationStatus.SELF: + case VerificationStatus.SELF: return "image://colorimage/:/icons/icons/ui/checkmark.svg?" + Nheko.theme.green; - default: + default: return "image://colorimage/:/icons/icons/ui/shield-filled.svg?" + Nheko.theme.red; } } + visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE } - Button { id: verifyButton - visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled) text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify") + visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled) + onClicked: { if (verificationStatus == VerificationStatus.VERIFIED) - profile.unverify(deviceId); + profile.unverify(deviceId); else - profile.verify(deviceId); + profile.verify(deviceId); } } - } } - DelegateModel { id: sharedRoomsModel + model: profile.sharedRooms + delegate: RowLayout { + required property string avatarUrl required property string roomId required property string roomName - required property string avatarUrl - width: devicelist.width spacing: 4 - + width: devicelist.width Avatar { id: avatar - enabled: false + property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6) + Layout.alignment: Qt.AlignVCenter Layout.leftMargin: Nheko.paddingMedium - - property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6) Layout.preferredHeight: avatarSize Layout.preferredWidth: avatarSize - url: avatarUrl.replace("mxc://", "image://MxcImage/") - roomid: roomId displayName: roomName + enabled: false + roomid: roomId + url: avatarUrl.replace("mxc://", "image://MxcImage/") } - ElidedLabel { Layout.alignment: Qt.AlignVCenter - color: palette.text Layout.fillWidth: true + Layout.rightMargin: Nheko.paddingMedium + color: palette.text elideWidth: width fullText: roomName textFormat: Text.PlainText - Layout.rightMargin: Nheko.paddingMedium } - Item { Layout.fillWidth: true } } } - - footer: DialogButtonBox { - z: 2 - width: devicelist.width - alignment: Qt.AlignRight - standardButtons: DialogButtonBox.Ok - onAccepted: userProfileDialog.close() - - background: Rectangle { - anchors.fill: parent - color: palette.window - } - - } - } - } diff --git a/resources/qml/emoji/StickerPicker.qml b/resources/qml/emoji/StickerPicker.qml index b7721db6..15dc7132 100644 --- a/resources/qml/emoji/StickerPicker.qml +++ b/resources/qml/emoji/StickerPicker.qml @@ -12,17 +12,17 @@ Menu { id: stickerPopup property var callback - property string roomid - property alias model: gridView.model required property bool emoji - property var textArea property real highlightHue: palette.highlight.hslHue - property real highlightSat: palette.highlight.hslSaturation property real highlightLight: palette.highlight.hslLightness + property real highlightSat: palette.highlight.hslSaturation + property alias model: gridView.model + property string roomid + readonly property int sidebarAvatarSize: 24 readonly property int stickerDim: emoji ? 48 : 128 readonly property int stickerDimPad: stickerDim + Nheko.paddingSmall readonly property int stickersPerRow: emoji ? 7 : 3 - readonly property int sidebarAvatarSize: 24 + property var textArea function show(showAt, roomid_, callback) { console.debug("Showing sticker picker"); @@ -31,29 +31,29 @@ Menu { popup(showAt ? showAt : null); } - margins: 2 bottomPadding: 0 + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + focus: true leftPadding: 0 + margins: 2 + modal: true rightPadding: 0 topPadding: 0 - modal: true - focus: true - closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside width: sidebarAvatarSize + Nheko.paddingSmall + stickersPerRow * stickerDimPad + 20 Rectangle { color: palette.window - height: columnView.implicitHeight + Nheko.paddingSmall*2 + height: columnView.implicitHeight + Nheko.paddingSmall * 2 width: sidebarAvatarSize + Nheko.paddingSmall + stickersPerRow * stickerDimPad + 20 GridLayout { id: columnView - anchors.leftMargin: Nheko.paddingSmall - anchors.rightMargin: Nheko.paddingSmall anchors.bottom: parent.bottom anchors.left: parent.left + anchors.leftMargin: Nheko.paddingSmall anchors.right: parent.right + anchors.rightMargin: Nheko.paddingSmall columns: 2 rows: 2 @@ -61,14 +61,15 @@ Menu { TextField { id: emojiSearch + Layout.column: 1 Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall Layout.row: 0 - Layout.column: 1 background: null - placeholderTextColor: palette.buttonText placeholderText: qsTr("Search") - selectByMouse: true + placeholderTextColor: palette.buttonText rightPadding: clearSearch.width + selectByMouse: true + onTextChanged: searchTimer.restart() onVisibleChanged: { if (visible) @@ -81,23 +82,24 @@ Menu { id: searchTimer interval: 350 // tweak as needed? + onTriggered: stickerPopup.model.searchString = emojiSearch.text } - ImageButton { id: clearSearch + focusPolicy: Qt.NoFocus + hoverEnabled: true + image: ":/icons/icons/ui/round-remove-button.svg" visible: emojiSearch.text !== '' - image: ":/icons/icons/ui/round-remove-button.svg" - focusPolicy: Qt.NoFocus onClicked: emojiSearch.clear() - hoverEnabled: true + anchors { - top: parent.top bottom: parent.bottom right: parent.right rightMargin: Nheko.paddingSmall + top: parent.top } } } @@ -106,39 +108,30 @@ Menu { ListView { id: gridView - model: roomid ? TimelineManager.completerFor(stickerPopup.emoji ? "emojigrid" : "stickergrid", roomid) : null - Layout.row: 1 + property int cellHeight: stickerDimPad + Layout.column: 1 Layout.preferredHeight: cellHeight * (stickersPerRow + 0.5) Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall - property int cellHeight: stickerDimPad + Layout.row: 1 boundsBehavior: Flickable.StopAtBounds clip: true currentIndex: -1 // prevent sorting from stealing focus - section.property: "packname" + model: roomid ? TimelineManager.completerFor(stickerPopup.emoji ? "emojigrid" : "stickergrid", roomid) : null section.criteria: ViewSection.FullString - section.delegate: Rectangle { - width: gridView.width - height: childrenRect.height - color: palette.alternateBase + section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart + section.property: "packname" + spacing: Nheko.paddingSmall - required property string section + ScrollBar.vertical: ScrollBar { + id: emojiScroll - Text { - anchors.left: parent.left - anchors.right: parent.right - text: parent.section - font.bold: true - } } - section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart - - spacing: Nheko.paddingSmall // Individual emoji delegate: Row { - required property var row; + required property var row spacing: Nheko.paddingSmall @@ -150,27 +143,17 @@ Menu { required property var modelData - width: stickerDim - height: stickerDim - hoverEnabled: true ToolTip.text: ":" + modelData.shortcode + ": - " + (modelData.unicode ? modelData.unicodeName : modelData.body) ToolTip.visible: hovered - // TODO: maybe add favorites at some point? - onClicked: { - console.debug("Picked " + modelData); - stickerPopup.close(); - if (!stickerPopup.emoji) { - // return descriptor to calculate sticker to send - callback(modelData.descriptor); - } else if (modelData.unicode) { - // return the emoji unicode as both plain text and markdown - callback(modelData.unicode, modelData.unicode); - } else { - // return the emoji url as plain text and a markdown link as markdown - callback(modelData.url, modelData.markdown); - } - } + height: stickerDim + hoverEnabled: true + width: stickerDim + background: Rectangle { + anchors.fill: parent + color: hovered ? palette.highlight : 'transparent' + radius: 5 + } contentItem: DelegateChooser { roleValue: del.modelData.unicode != undefined @@ -178,87 +161,99 @@ Menu { roleValue: true Text { - width: stickerDim - height: stickerDim - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter font.family: Settings.emojiFont font.pixelSize: 36 + height: stickerDim + horizontalAlignment: Text.AlignHCenter text: del.modelData.unicode.replace('\ufe0f', '') + verticalAlignment: Text.AlignVCenter + width: stickerDim } } - DelegateChoice { roleValue: false + Image { + fillMode: Image.PreserveAspectFit height: stickerDim - width: stickerDim source: del.modelData.url.replace("mxc://", "image://MxcImage/") + "?scale" - fillMode: Image.PreserveAspectFit + width: stickerDim } } } - background: Rectangle { - anchors.fill: parent - color: hovered ? palette.highlight : 'transparent' - radius: 5 + // TODO: maybe add favorites at some point? + onClicked: { + console.debug("Picked " + modelData); + stickerPopup.close(); + if (!stickerPopup.emoji) { + // return descriptor to calculate sticker to send + callback(modelData.descriptor); + } else if (modelData.unicode) { + // return the emoji unicode as both plain text and markdown + callback(modelData.unicode, modelData.unicode); + } else { + // return the emoji url as plain text and a markdown link as markdown + callback(modelData.url, modelData.markdown); + } } - } } } + section.delegate: Rectangle { + required property string section - ScrollBar.vertical: ScrollBar { - id: emojiScroll - } + color: palette.alternateBase + height: childrenRect.height + width: gridView.width + Text { + anchors.left: parent.left + anchors.right: parent.right + font.bold: true + text: parent.section + } + } } - ListView { - Layout.row: 1 Layout.column: 0 - Layout.preferredWidth: sidebarAvatarSize Layout.fillHeight: true + Layout.preferredWidth: sidebarAvatarSize Layout.rightMargin: Nheko.paddingSmall - + Layout.row: 1 + clip: true model: gridView.model ? gridView.model.sections : null spacing: Nheko.paddingSmall - clip: true delegate: Avatar { - height: sidebarAvatarSize - width: sidebarAvatarSize - url: modelData.url.replace("mxc://", "image://MxcImage/") - textColor: modelData.url.startsWith("mxc://") ? palette.text : palette.buttonText + ToolTip.delay: Nheko.tooltipDelay + ToolTip.text: modelData.name + ToolTip.visible: hovered displayName: modelData.name + height: sidebarAvatarSize + hoverEnabled: true roomid: modelData.name + textColor: modelData.url.startsWith("mxc://") ? palette.text : palette.buttonText + url: modelData.url.replace("mxc://", "image://MxcImage/") + width: sidebarAvatarSize - hoverEnabled: true - ToolTip.visible: hovered - ToolTip.delay: Nheko.tooltipDelay - ToolTip.text: modelData.name onClicked: gridView.positionViewAtIndex(modelData.firstRowWith, ListView.Beginning) } } - ImageButton { - Layout.row: 0 Layout.column: 0 - Layout.preferredWidth: sidebarAvatarSize Layout.preferredHeight: sidebarAvatarSize + Layout.preferredWidth: sidebarAvatarSize Layout.rightMargin: Nheko.paddingSmall - - image: ":/icons/icons/ui/settings.svg" - - hoverEnabled: true - ToolTip.visible: hovered + Layout.row: 0 ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Change what packs are enabled, remove packs, or create new ones") + ToolTip.visible: hovered + hoverEnabled: true + image: ":/icons/icons/ui/settings.svg" + onClicked: TimelineManager.openImagePackSettings(stickerPopup.roomid) } } - } - } diff --git a/resources/qml/pages/LoginPage.qml b/resources/qml/pages/LoginPage.qml index 86188918..51c198da 100644 --- a/resources/qml/pages/LoginPage.qml +++ b/resources/qml/pages/LoginPage.qml @@ -13,147 +13,139 @@ 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 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) 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" Layout.preferredHeight: 128 Layout.preferredWidth: 128 + source: "qrc:/logos/login.png" } - 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:yourserver.example.com\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 @user:yourserver.example.com") - 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:yourserver.example.com\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.") - Keys.forwardTo: [pwBtn, ssoRepeater] + onEditingFinished: login.mxid = text } - - Spinner { - Layout.preferredHeight: matrixIdLabel.height/2 Layout.alignment: Qt.AlignBottom - - visible: running - running: login.lookingUpHs + Layout.preferredHeight: matrixIdLabel.height / 2 foreground: palette.mid + running: login.lookingUpHs + visible: running } } - MatrixText { Layout.fillWidth: true - textFormat: Text.PlainText color: Nheko.theme.error text: login.mxidError + textFormat: Text.PlainText visible: text wrapMode: TextEdit.Wrap } - MatrixTextField { id: passwordLabel + + Keys.forwardTo: [pwBtn, ssoRepeater] Layout.fillWidth: true - label: qsTr("Password") - echoMode: TextInput.Password ToolTip.text: qsTr("Your password.") + echoMode: TextInput.Password + label: qsTr("Password") visible: login.passwordSupported - Keys.forwardTo: [pwBtn, ssoRepeater] } - 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 nothing 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 nothing 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 your homeserver's client API.\nExample: https://yourserver.example.com:8787") + enabled: visible label: qsTr("Homeserver address") placeholderText: qsTr("yourserver.example.com:8787") text: login.homeserver + visible: login.homeserverNeeded + onEditingFinished: login.homeserver = text - ToolTip.text: qsTr("The address that can be used to contact your homeserver's client API.\nExample: https://yourserver.example.com:8787") - Keys.forwardTo: [pwBtn, ssoRepeater] } - Item { - Layout.preferredHeight: Nheko.avatarSize Layout.fillWidth: true + Layout.preferredHeight: Nheko.avatarSize Spinner { - height: parent.height anchors.centerIn: parent - - visible: running - running: login.loggingIn foreground: palette.mid + height: parent.height + running: login.loggingIn + visible: running } } - MatrixText { Layout.fillWidth: true - textFormat: Text.PlainText color: Nheko.theme.error text: loginPage.error + textFormat: Text.PlainText visible: text wrapMode: TextEdit.Wrap } - 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 @@ -161,32 +153,35 @@ Item { 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/resources/qml/pages/RegisterPage.qml b/resources/qml/pages/RegisterPage.qml index ad9143f7..bd86c313 100644 --- a/resources/qml/pages/RegisterPage.qml +++ b/resources/qml/pages/RegisterPage.qml @@ -13,208 +13,197 @@ 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 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) 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" Layout.preferredHeight: 128 Layout.preferredWidth: 128 + source: "qrc:/logos/login.png" } - 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.") + onEditingFinished: regis.setServer(text) } - - Spinner { - Layout.preferredHeight: hsLabel.height/2 Layout.alignment: Qt.AlignBottom - - visible: running - running: regis.lookingUpHs + Layout.preferredHeight: hsLabel.height / 2 foreground: palette.mid + running: regis.lookingUpHs + visible: running } } - MatrixText { Layout.fillWidth: true - textFormat: Text.PlainText color: Nheko.theme.error text: regis.hsError + textFormat: Text.PlainText visible: text wrapMode: TextEdit.Wrap } - 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 { - Layout.preferredHeight: usernameLabel.height/2 Layout.alignment: Qt.AlignBottom - - visible: running - running: regis.lookingUpUsername + Layout.preferredHeight: usernameLabel.height / 2 foreground: palette.mid + running: regis.lookingUpUsername + visible: running } - Image { - 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 + 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 + HoverHandler { id: ma + } } } - MatrixText { Layout.fillWidth: true - textFormat: Text.PlainText color: Nheko.theme.error text: regis.usernameError + textFormat: Text.PlainText visible: text && regis.supported wrapMode: TextEdit.Wrap } - - 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.") + echoMode: TextInput.Password + label: qsTr("Password") + visible: regis.supported } - MatrixTextField { - visible: regis.supported id: passwordConfirmationLabel + Layout.fillWidth: true - label: qsTr("Password confirmation") echoMode: TextInput.Password + label: qsTr("Password confirmation") + visible: regis.supported } - MatrixText { Layout.fillWidth: true - visible: regis.supported - textFormat: Text.PlainText color: Nheko.theme.error text: passwordLabel.text != passwordConfirmationLabel.text ? qsTr("Your passwords do not match!") : "" + textFormat: Text.PlainText + visible: regis.supported wrapMode: TextEdit.Wrap } - MatrixTextField { - visible: regis.supported id: deviceNameLabel + Layout.fillWidth: true + ToolTip.text: qsTr("A name for this device which will be shown to others when verifying your devices. If nothing 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 nothing is provided a default is used.") + visible: regis.supported } - Item { - Layout.preferredHeight: Nheko.avatarSize Layout.fillWidth: true + Layout.preferredHeight: Nheko.avatarSize Spinner { - height: parent.height anchors.centerIn: parent - - visible: running - running: regis.registering foreground: palette.mid + height: parent.height + running: regis.registering + visible: running } } - MatrixText { Layout.fillWidth: true - textFormat: Text.PlainText color: Nheko.theme.error text: registrationPage.error + textFormat: Text.PlainText visible: text wrapMode: TextEdit.Wrap } - 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/resources/qml/pages/UserSettingsPage.qml b/resources/qml/pages/UserSettingsPage.qml index 365366c7..7aa3c391 100644 --- a/resources/qml/pages/UserSettingsPage.qml +++ b/resources/qml/pages/UserSettingsPage.qml @@ -16,6 +16,7 @@ Rectangle { property int collapsePoint: 600 property bool collapsed: width < collapsePoint + color: palette.window ScrollView { @@ -23,87 +24,91 @@ Rectangle { 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 ColumnLayout { id: grid - spacing: Nheko.paddingMedium - - width: scroll.availableWidth anchors.fill: parent - anchors.leftMargin: userSettingsDialog.collapsed ? 0 : (userSettingsDialog.width-userSettingsDialog.collapsePoint) * 0.4 + Nheko.paddingLarge + anchors.leftMargin: userSettingsDialog.collapsed ? 0 : (userSettingsDialog.width - userSettingsDialog.collapsePoint) * 0.4 + Nheko.paddingLarge anchors.rightMargin: anchors.leftMargin - + spacing: Nheko.paddingMedium + width: scroll.availableWidth Repeater { model: UserSettingsModel delegate: GridLayout { - width: scroll.availableWidth - 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 + width: scroll.availableWidth + Label { Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - color: 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: 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: child.implicitWidth - 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 - model: r.model.values currentIndex: r.model.value + implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted + model: r.model.values width: Math.min(implicitWidth, scroll.availableWidth - Nheko.paddingMedium) + onCurrentIndexChanged: r.model.value = currentIndex - implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted - WheelHandler{} // suppress scrolling changing values + WheelHandler { + } // suppress scrolling changing values } } DelegateChoice { @@ -111,14 +116,16 @@ Rectangle { SpinBox { anchors.right: parent.right + editable: true from: model.valueLowerBound - to: model.valueUpperBound stepSize: model.valueStep + to: model.valueUpperBound value: model.value + onValueChanged: model.value = value - editable: true - WheelHandler{} // suppress scrolling changing values + WheelHandler { + } // suppress scrolling changing values } } DelegateChoice { @@ -127,54 +134,56 @@ Rectangle { SpinBox { id: spinbox - readonly property double div: 100 readonly property int decimals: 2 + readonly property double div: 100 + property real realValue: value / div anchors.right: parent.right + editable: true from: model.valueLowerBound * div - to: model.valueUpperBound * 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 - onValueChanged: model.value = value/div - editable: true - - property real realValue: value / div - - validator: DoubleValidator { - bottom: Math.min(spinbox.from/spinbox.div, spinbox.to/spinbox.div) - top: Math.max(spinbox.from/spinbox.div, spinbox.to/spinbox.div) + valueFromText: function (text, locale) { + return Number.fromLocaleString(locale, text) * spinbox.div; } - textFromValue: function(value, locale) { - return Number(value / spinbox.div).toLocaleString(locale, 'f', spinbox.decimals) + validator: DoubleValidator { + bottom: Math.min(spinbox.from / spinbox.div, spinbox.to / spinbox.div) + top: Math.max(spinbox.from / spinbox.div, spinbox.to / spinbox.div) } - valueFromText: function(text, locale) { - return Number.fromLocaleString(locale, text) * spinbox.div - } + onValueChanged: model.value = value / div - WheelHandler{} // suppress scrolling changing values + WheelHandler { + } // suppress scrolling changing values } } DelegateChoice { roleValue: UserSettingsModel.ReadOnlyText + TextEdit { color: palette.text - text: model.value readOnly: true + 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: palette.buttonText height: 1 } @@ -182,6 +191,7 @@ Rectangle { } DelegateChoice { roleValue: UserSettingsModel.KeyStatus + Text { color: model.good ? "green" : Nheko.theme.error text: model.value ? qsTr("CACHED") : qsTr("NOT CACHED") @@ -189,34 +199,42 @@ 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() } } } DelegateChoice { roleValue: UserSettingsModel.ConfigureHiddenEvents + Button { text: qsTr("CONFIGURE") + onClicked: { var dialog = hiddenEventsDialog.createObject(); dialog.show(); @@ -226,15 +244,17 @@ Rectangle { Component { id: hiddenEventsDialog - HiddenEventsDialog {} + HiddenEventsDialog { + } } } } - DelegateChoice { roleValue: UserSettingsModel.ManageIgnoredUsers + Button { text: qsTr("MANAGE") + onClicked: { var dialog = ignoredUsersDialog.createObject(); dialog.show(); @@ -244,11 +264,11 @@ Rectangle { Component { id: ignoredUsersDialog - IgnoredUsers {} + IgnoredUsers { + } } } } - DelegateChoice { Text { text: model.value @@ -259,19 +279,18 @@ 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/resources/qml/pages/WelcomePage.qml b/resources/qml/pages/WelcomePage.qml index 161785ac..88199165 100644 --- a/resources/qml/pages/WelcomePage.qml +++ b/resources/qml/pages/WelcomePage.qml @@ -14,86 +14,83 @@ ColumnLayout { Item { Layout.fillHeight: true } - Image { Layout.alignment: Qt.AlignHCenter - source: "qrc:/logos/splash.png" Layout.preferredHeight: 256 Layout.preferredWidth: 256 + source: "qrc:/logos/splash.png" } - Label { - Layout.margins: Nheko.paddingLarge - Layout.bottomMargin: 0 Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 0 Layout.fillWidth: true - text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.") + Layout.margins: Nheko.paddingLarge color: 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: 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); } } - Item { Layout.fillWidth: true } - } - RowLayout { Layout.alignment: Qt.AlignHCenter Layout.margins: Nheko.paddingLarge ToggleButton { - Layout.margins: Nheko.paddingLarge Layout.alignment: Qt.AlignRight + Layout.margins: Nheko.paddingLarge checked: Settings.reducedMotion + onCheckedChanged: Settings.reducedMotion = checked } - Label { Layout.alignment: Qt.AlignLeft Layout.margins: Nheko.paddingLarge - text: qsTr("Reduce animations") + ToolTip.delay: Nheko.tooltipDelay + ToolTip.text: qsTr("Nheko uses animations in several places to make stuff pretty. This allows you to turn those off if they make you feel unwell.") + ToolTip.visible: hovered.hovered color: palette.text + text: qsTr("Reduce animations") HoverHandler { id: hovered + } - ToolTip.visible: hovered.hovered - ToolTip.text: qsTr("Nheko uses animations in several places to make stuff pretty. This allows you to turn those off if they make you feel unwell.") - ToolTip.delay: Nheko.tooltipDelay } } Item { diff --git a/resources/qml/ui/NhekoSlider.qml b/resources/qml/ui/NhekoSlider.qml index 5e3a77d8..b340af46 100644 --- a/resources/qml/ui/NhekoSlider.qml +++ b/resources/qml/ui/NhekoSlider.qml @@ -9,42 +9,39 @@ import im.nheko 1.0 Slider { id: control - property color progressColor: palette.highlight property bool alwaysShowSlider: true + property color progressColor: palette.highlight property int sliderRadius: 16 - value: 0 implicitHeight: sliderRadius padding: 0 + value: 0 background: Rectangle { - 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 + color: palette.buttonText height: implicitHeight + implicitHeight: control.sliderRadius / 4 + implicitWidth: 200 radius: height / 2 - color: palette.buttonText + width: control.availableWidth - handle.width + x: control.leftPadding + handle.width / 2 + y: control.topPadding + control.availableHeight / 2 - height / 2 Rectangle { - width: control.visualPosition * parent.width - height: parent.height color: control.progressColor + height: parent.height radius: 2 + width: control.visualPosition * parent.width } - } - handle: Rectangle { - x: control.leftPadding + control.visualPosition * background.width - y: control.topPadding + control.availableHeight / 2 - height / 2 - implicitWidth: control.sliderRadius + border.color: control.progressColor + color: control.progressColor implicitHeight: control.sliderRadius + implicitWidth: control.sliderRadius radius: control.sliderRadius / 2 - color: control.progressColor visible: Settings.mobileMode || control.alwaysShowSlider || control.hovered || control.pressed - border.color: control.progressColor + x: control.leftPadding + control.visualPosition * background.width + y: control.topPadding + control.availableHeight / 2 - height / 2 } - } diff --git a/resources/qml/ui/Ripple.qml b/resources/qml/ui/Ripple.qml index 9d871419..82203db0 100644 --- a/resources/qml/ui/Ripple.qml +++ b/resources/qml/ui/Ripple.qml @@ -9,9 +9,9 @@ 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 @@ -19,18 +19,13 @@ Item { PointHandler { id: ph - onGrabChanged: (_, point) => { - 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 @@ -38,15 +33,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" @@ -63,26 +57,30 @@ 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" + duration: ripple.maxRadius / ripple.radiusAnimationRate from: 0 + properties: "radius" + target: circle to: ripple.maxRadius - duration: ripple.maxRadius / ripple.radiusAnimationRate easing { type: Easing.OutQuad } - } - } - }, Transition { from: "ACTIVE" @@ -93,37 +91,42 @@ Item { NumberAnimation { id: radius_tail_animation - target: circle + duration: ripple.maxRadius / ripple.radiusTailAnimationRate properties: "radius" + target: circle to: ripple.maxRadius - duration: ripple.maxRadius / ripple.radiusTailAnimationRate easing { type: Easing.Linear } - } - NumberAnimation { id: opacity_animation - target: circle + duration: ripple.opacityAnimationDuration properties: "opacity" + target: circle to: 0 - duration: ripple.opacityAnimationDuration easing { type: Easing.InQuad } - } - } - PropertyAction { target: circle; property: "visible"; value: false } + PropertyAction { + property: "visible" + target: circle + value: false + } } } ] } } + + onGrabChanged: (_, point) => { + circle.centerX = point.position.x; + circle.centerY = point.position.y; + } } } diff --git a/resources/qml/ui/Snackbar.qml b/resources/qml/ui/Snackbar.qml index 3c526d8d..2c9055c2 100644 --- a/resources/qml/ui/Snackbar.qml +++ b/resources/qml/ui/Snackbar.qml @@ -9,11 +9,8 @@ import im.nheko 1.0 Popup { id: snackbar - // Workaround palettes not inheriting for popups - palette: timelineRoot.palette - - property var messages: [] property string currentMessage: "" + property var messages: [] function showNotification(msg) { messages.push(msg); @@ -24,78 +21,79 @@ Popup { } } - Timer { - id: dismissTimer - interval: 10000 - onTriggered: snackbar.close() - } - - onAboutToHide: { - messages.shift(); - } - onClosed: { - if (messages.length > 0) { - currentMessage = messages[0]; - open(); - dismissTimer.restart(); - } - } - - parent: Overlay.overlay opacity: 0 - y: -100 - x: (parent.width - width)/2 padding: Nheko.paddingLarge - contentItem: Label { - color: palette.light - width: Math.max(snackbar.Overlay.overlay? snackbar.Overlay.overlay.width/2 : 0, 400) - text: snackbar.currentMessage - font.bold: true - } + // Workaround palettes not inheriting for popups + palette: timelineRoot.palette + parent: Overlay.overlay + x: (parent.width - width) / 2 + y: -100 background: Rectangle { - radius: Nheko.paddingLarge color: palette.dark opacity: 0.8 + radius: Nheko.paddingLarge + } + contentItem: Label { + color: palette.light + font.bold: true + text: snackbar.currentMessage + width: Math.max(snackbar.Overlay.overlay ? snackbar.Overlay.overlay.width / 2 : 0, 400) } - enter: Transition { NumberAnimation { - target: snackbar - property: "opacity" - from: 0.0 - to: 1.0 duration: 200 easing.type: Easing.OutCubic + from: 0.0 + property: "opacity" + target: snackbar + to: 1.0 } NumberAnimation { - target: snackbar - properties: "y" - from: -100 - to: 100 duration: 1000 easing.type: Easing.OutCubic + from: -100 + properties: "y" + target: snackbar + to: 100 } } exit: Transition { NumberAnimation { - target: snackbar - property: "opacity" - from: 1.0 - to: 0.0 duration: 300 easing.type: Easing.InCubic + from: 1.0 + property: "opacity" + target: snackbar + to: 0.0 } NumberAnimation { - target: snackbar - properties: "y" - to: -100 - from: 100 duration: 300 easing.type: Easing.InCubic + from: 100 + properties: "y" + target: snackbar + to: -100 } } -} + onAboutToHide: { + messages.shift(); + } + onClosed: { + if (messages.length > 0) { + currentMessage = messages[0]; + open(); + dismissTimer.restart(); + } + } + + Timer { + id: dismissTimer + interval: 10000 + + onTriggered: snackbar.close() + } +} diff --git a/resources/qml/ui/Spinner.qml b/resources/qml/ui/Spinner.qml index 9c0c8a31..9e247d48 100644 --- a/resources/qml/ui/Spinner.qml +++ b/resources/qml/ui/Spinner.qml @@ -9,15 +9,15 @@ import QtQuick.Effects 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) @@ -25,131 +25,116 @@ Item { Row { id: row + 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 - width: ((spinner.width / spinner.barCount) - (spinner.spacing)) * 1.5 - height: spinner.height / 3.5 color: "white" + height: spinner.height / 3.5 + width: ((spinner.width / spinner.barCount) - (spinner.spacing)) * 1.5 } - Rectangle { id: rect2 - width: (spinner.width / spinner.barCount) - spinner.spacing - height: spinner.height color: spinner.colors[0] + height: spinner.height + width: (spinner.width / spinner.barCount) - spinner.spacing } - Rectangle { id: rect3 - width: (spinner.width / spinner.barCount) - spinner.spacing - height: spinner.height color: spinner.colors[1] + height: spinner.height + width: (spinner.width / spinner.barCount) - spinner.spacing } - Rectangle { id: rect4 - width: (spinner.width / spinner.barCount) - spinner.spacing - height: spinner.height color: spinner.colors[2] + height: spinner.height + width: (spinner.width / spinner.barCount) - spinner.spacing } - Rectangle { id: rect5 - width: (spinner.width / (spinner.barCount + 1)) - spinner.spacing - height: spinner.height / 3.5 color: "white" + height: spinner.height / 3.5 + width: (spinner.width / (spinner.barCount + 1)) - spinner.spacing } - Rectangle { id: rect6 - width: (spinner.width / spinner.barCount) - spinner.spacing - height: spinner.height color: "white" + height: spinner.height + width: (spinner.width / spinner.barCount) - spinner.spacing } - BlinkAnimation { id: anim1 - target: rect1 - running: spinner.running - pauseDuration: spinner.pauseDuration glowDuration: spinner.glowDuration offset: 0 / spinner.barCount + pauseDuration: spinner.pauseDuration + running: spinner.running + target: rect1 } - BlinkAnimation { id: anim2 - target: rect2 - running: spinner.running - pauseDuration: spinner.pauseDuration glowDuration: spinner.glowDuration offset: 1 / spinner.barCount + pauseDuration: spinner.pauseDuration + running: spinner.running + target: rect2 } - BlinkAnimation { id: anim3 - target: rect3 - running: spinner.running - pauseDuration: spinner.pauseDuration glowDuration: spinner.glowDuration offset: 2 / spinner.barCount + pauseDuration: spinner.pauseDuration + running: spinner.running + target: rect3 } - BlinkAnimation { id: anim4 - target: rect4 - running: spinner.running - pauseDuration: spinner.pauseDuration glowDuration: spinner.glowDuration offset: 3 / spinner.barCount + pauseDuration: spinner.pauseDuration + running: spinner.running + target: rect4 } - BlinkAnimation { id: anim5 - target: rect5 - running: spinner.running - pauseDuration: spinner.pauseDuration glowDuration: spinner.glowDuration offset: 4 / spinner.barCount + pauseDuration: spinner.pauseDuration + running: spinner.running + target: rect5 } - BlinkAnimation { id: anim6 - target: rect6 - running: spinner.running - pauseDuration: spinner.pauseDuration glowDuration: spinner.glowDuration offset: 5 / spinner.barCount + pauseDuration: spinner.pauseDuration + running: spinner.running + target: rect6 } - - 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) - } - } - MultiEffect { anchors.fill: row shadowBlur: 14 - shadowEnabled: true shadowColor: spinner.foreground + shadowEnabled: true source: row 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) } - } - } diff --git a/resources/qml/ui/TimelineEffects.qml b/resources/qml/ui/TimelineEffects.qml index 4960ce32..551f8ea9 100644 --- a/resources/qml/ui/TimelineEffects.qml +++ b/resources/qml/ui/TimelineEffects.qml @@ -7,136 +7,134 @@ import QtQuick.Particles 2.15 Item { id: effectRoot + readonly property int maxLifespan: Math.max(confettiEmitter.lifeSpan, rainfallEmitter.lifeSpan) required property bool shouldEffectsRun - visible: effectRoot.shouldEffectsRun - function pulseConfetti() - { - confettiEmitter.pulse(effectRoot.height * 2) + function pulseConfetti() { + confettiEmitter.pulse(effectRoot.height * 2); } - - function pulseRainfall() - { - rainfallEmitter.pulse(effectRoot.height * 3.3) + function pulseRainfall() { + rainfallEmitter.pulse(effectRoot.height * 3.3); } - - function removeParticles() - { - particleSystem.reset() + function removeParticles() { + particleSystem.reset(); } + visible: effectRoot.shouldEffectsRun + ParticleSystem { id: particleSystem - Component.onCompleted: stop(); paused: !effectRoot.shouldEffectsRun running: effectRoot.shouldEffectsRun - } + Component.onCompleted: stop() + } Emitter { id: confettiEmitter - group: "confetti" - width: effectRoot.width * 3/4 - enabled: false anchors.horizontalCenter: effectRoot.horizontalCenter - y: effectRoot.height emitRate: Math.min(400 * Math.sqrt(effectRoot.width * effectRoot.height) / 870, 1000) + enabled: false + group: "confetti" lifeSpan: 15000 - system: particleSystem maximumEmitted: 500 - velocityFromMovement: 8 size: 16 sizeVariation: 4 + system: particleSystem + velocityFromMovement: 8 + width: effectRoot.width * 3 / 4 + y: effectRoot.height + velocity: PointDirection { x: 0 - y: -Math.min(450 * effectRoot.height / 700, 1000) xVariation: Math.min(4 * effectRoot.width / 7, 450) + y: -Math.min(450 * effectRoot.height / 700, 1000) yVariation: 250 } } - ImageParticle { - system: particleSystem + color: "white" + colorVariation: 1 + entryEffect: ImageParticle.None groups: ["confetti"] - source: "qrc:/confettiparticle.svg" rotationVelocity: 0 rotationVelocityVariation: 360 - colorVariation: 1 - color: "white" - entryEffect: ImageParticle.None + source: "qrc:/confettiparticle.svg" + system: particleSystem + xVector: PointDirection { x: 1 - y: 0 xVariation: 0.2 + y: 0 yVariation: 0.2 } yVector: PointDirection { x: 0 - y: 0.5 xVariation: 0.2 + y: 0.5 yVariation: 0.2 } } - Gravity { - system: particleSystem - groups: ["confetti"] anchors.fill: effectRoot - magnitude: 350 angle: 90 + groups: ["confetti"] + magnitude: 350 + system: particleSystem } - Emitter { id: rainfallEmitter - group: "rain" - width: effectRoot.width - enabled: false anchors.horizontalCenter: effectRoot.horizontalCenter - y: -60 emitRate: effectRoot.width / 30 + enabled: false + group: "rain" lifeSpan: 10000 system: particleSystem + width: effectRoot.width + y: -60 + velocity: PointDirection { x: 0 - y: 400 xVariation: 0 + y: 400 yVariation: 75 } // causes high CPU load, see: https://bugreports.qt.io/browse/QTBUG-117923 //ItemParticle { - // system: particleSystem - // groups: ["rain"] - // fade: false - // visible: effectRoot.shouldEffectsRun - // delegate: Rectangle { - // width: 2 - // height: 30 + 30 * Math.random() - // radius: 2 - // color: "#0099ff" - // } - //} - - ImageParticle { - system: particleSystem - groups: ["rain"] - source: "qrc:/confettiparticle.svg" - rotationVelocity: 0 - rotationVelocityVariation: 0 - colorVariation: 0 - color: "#0099ff" - entryEffect: ImageParticle.None - xVector: PointDirection { - x: 0.01 - y: 0 - } - yVector: PointDirection { - x: 0 - y: 5 - } + // system: particleSystem + // groups: ["rain"] + // fade: false + // visible: effectRoot.shouldEffectsRun + // delegate: Rectangle { + // width: 2 + // height: 30 + 30 * Math.random() + // radius: 2 + // color: "#0099ff" + // } + //} + + ImageParticle { + color: "#0099ff" + colorVariation: 0 + entryEffect: ImageParticle.None + groups: ["rain"] + rotationVelocity: 0 + rotationVelocityVariation: 0 + source: "qrc:/confettiparticle.svg" + system: particleSystem + + xVector: PointDirection { + x: 0.01 + y: 0 + } + yVector: PointDirection { + x: 0 + y: 5 } } } +} diff --git a/resources/qml/ui/animations/BlinkAnimation.qml b/resources/qml/ui/animations/BlinkAnimation.qml index de2a11d8..dcdf65ef 100644 --- a/resources/qml/ui/animations/BlinkAnimation.qml +++ b/resources/qml/ui/animations/BlinkAnimation.qml @@ -5,27 +5,24 @@ import QtQuick 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/resources/qml/ui/media/MediaControls.qml b/resources/qml/ui/media/MediaControls.qml index 0519a194..0f93845d 100644 --- a/resources/qml/ui/media/MediaControls.qml +++ b/resources/qml/ui/media/MediaControls.qml @@ -14,27 +14,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; @@ -45,16 +40,25 @@ Rectangle { var hh = hours.toString(); if (hours < 1) return mm + ":" + ss; - return hh + ":" + mm + ":" + ss; } + function showControls() { + controlHideTimer.restart(); + } color: { var wc = 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 @@ -63,41 +67,40 @@ 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: palette.text Layout.preferredHeight: 24 Layout.preferredWidth: 24 + buttonTextColor: palette.text image: { if (control.mediaLoaded) { if (control.mediaState == MediaPlayer.PlayingState) @@ -108,38 +111,47 @@ Rectangle { return ":/icons/icons/ui/download.svg"; } } + onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated() } - ImageButton { id: volumeButton Layout.alignment: Qt.AlignLeft - buttonTextColor: palette.text Layout.preferredHeight: 24 Layout.preferredWidth: 24 + buttonTextColor: 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: volumeSlider.value - 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 { + volumeSlider.implicitWidth: 100 + } + PropertyChanges { + volumeSlider.opacity: 1 + } } transitions: [ Transition { @@ -150,20 +162,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" @@ -173,54 +181,34 @@ 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 { - volumeSlider.implicitWidth: 100 - } - - PropertyChanges { - volumeSlider.opacity: 1 - } - + onDesiredVolumeChanged: { + control.muted = !(desiredVolume > 0); } - } - Label { Layout.alignment: Qt.AlignRight - text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration) color: palette.text + text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration) } - Item { Layout.fillWidth: true } - } - } // For hiding controls on stationary cursor @@ -230,13 +218,4 @@ Rectangle { interval: 1500 //ms repeat: false } - - // Fade controls in/out - Behavior on opacity { - OpacityAnimator { - duration: 100 - } - - } - } diff --git a/resources/qml/voip/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml index eb7fce76..448e36e3 100644 --- a/resources/qml/voip/ActiveCallBar.qml +++ b/resources/qml/voip/ActiveCallBar.qml @@ -9,51 +9,48 @@ import QtQuick.Layouts 1.2 import im.nheko 1.0 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 { - implicitWidth: Nheko.avatarSize + displayName: CallManager.callPartyDisplayName implicitHeight: Nheko.avatarSize + implicitWidth: Nheko.avatarSize url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") userid: CallManager.callParty - displayName: CallManager.callPartyDisplayName + 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 { @@ -63,7 +60,6 @@ Rectangle { PropertyChanges { callTypeIcon.source: "qrc:/icons/icons/ui/place-call.svg" } - }, State { name: "VIDEO" @@ -72,7 +68,6 @@ Rectangle { PropertyChanges { callTypeIcon.source: "qrc:/icons/icons/ui/video.svg" } - }, State { name: "SCREEN" @@ -81,18 +76,15 @@ Rectangle { PropertyChanges { callTypeIcon.source: "qrc:/icons/icons/ui/screen-share.svg" } - } ] } - Label { id: callStateLabel - font.pointSize: fontMetrics.font.pointSize * 1.1 color: "#000000" + font.pointSize: fontMetrics.font.pointSize * 1.1 } - Item { states: [ State { @@ -102,7 +94,6 @@ Rectangle { PropertyChanges { callStateLabel.text: qsTr("Calling...") } - }, State { name: "CONNECTING" @@ -111,7 +102,6 @@ Rectangle { PropertyChanges { callStateLabel.text: qsTr("Connecting...") } - }, State { name: "ANSWERSENT" @@ -120,7 +110,6 @@ Rectangle { PropertyChanges { callStateLabel.text: qsTr("Connecting...") } - }, State { name: "CONNECTED" @@ -129,15 +118,12 @@ Rectangle { PropertyChanges { callStateLabel.text: "00:00" } - PropertyChanges { callTimer.startTime: Math.floor((new Date()).getTime() / 1000) } - PropertyChanges { stackLayout.currentIndex: CallManager.callType != Voip.VOICE ? 1 : 0 } - }, State { name: "DISCONNECTED" @@ -152,14 +138,12 @@ Rectangle { // stackLayout.currentIndex: 0 //} PropertyChanges { - target: stackLayout currentIndex: 0 // qmllint disable Quick.property-changes-parsed + target: stackLayout } - } ] } - Timer { id: callTimer @@ -170,8 +154,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); @@ -181,44 +166,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 { - visible: CallManager.haveLocalPiP - Layout.preferredWidth: 24 Layout.preferredHeight: 24 + Layout.preferredWidth: 24 + ToolTip.text: qsTr("Hide/Show Picture-in-Picture") + ToolTip.visible: hovered buttonTextColor: "#000000" - image: ":/icons/icons/ui/picture-in-picture.svg" hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Hide/Show Picture-in-Picture") + image: ":/icons/icons/ui/picture-in-picture.svg" + visible: CallManager.haveLocalPiP + onClicked: CallManager.toggleLocalPiP() } - ImageButton { Layout.leftMargin: 8 - Layout.rightMargin: 16 - Layout.preferredWidth: 24 Layout.preferredHeight: 24 + Layout.preferredWidth: 24 + Layout.rightMargin: 16 + ToolTip.text: CallManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") + ToolTip.visible: hovered 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") + image: CallManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.svg" : ":/icons/icons/ui/microphone-mute.svg" + onClicked: CallManager.toggleMicMute() } - } - } diff --git a/resources/qml/voip/CallDevices.qml b/resources/qml/voip/CallDevices.qml index 46c8cde3..b5e762ba 100644 --- a/resources/qml/voip/CallDevices.qml +++ b/resources/qml/voip/CallDevices.qml @@ -9,79 +9,70 @@ import im.nheko 1.0 Popup { modal: true + + background: Rectangle { + border.color: palette.windowText + color: 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?" + 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?" + 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: palette.window - border.color: palette.windowText - } - } diff --git a/resources/qml/voip/CallInvite.qml b/resources/qml/voip/CallInvite.qml index 8a609c32..71ce4a1e 100644 --- a/resources/qml/voip/CallInvite.qml +++ b/resources/qml/voip/CallInvite.qml @@ -12,51 +12,50 @@ Popup { id: callInv closePolicy: Popup.NoAutoClose - width: parent.width height: parent.height + width: parent.width + + background: Rectangle { + border.color: palette.windowText + color: 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: 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 @@ -65,20 +64,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 + "?" + 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: palette.windowText + font.pointSize: fontMetrics.font.pointSize * 2 + text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call") } - } - ColumnLayout { id: deviceCombos @@ -91,41 +87,34 @@ 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?" + 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?" + palette.windowText } - ComboBox { id: cameraCombo Layout.fillWidth: true model: CallManager.cameras } - } - } - RowLayout { id: buttonLayout @@ -134,9 +123,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; @@ -148,60 +137,48 @@ Popup { spacing: callInv.height / 6 RoundButton { - implicitWidth: buttonLayout.buttonSize implicitHeight: buttonLayout.buttonSize - onClicked: { - CallManager.rejectInvite(); - 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.rejectInvite(); + 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: palette.window - border.color: palette.windowText - } - } diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml index c2ce8066..8f2a6642 100644 --- a/resources/qml/voip/CallInviteBar.qml +++ b/resources/qml/voip/CallInviteBar.qml @@ -9,127 +9,118 @@ import QtQuick.Layouts 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 { - implicitWidth: Nheko.avatarSize + displayName: CallManager.callPartyDisplayName implicitHeight: Nheko.avatarSize + implicitWidth: Nheko.avatarSize url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") userid: CallManager.callParty - displayName: CallManager.callPartyDisplayName + 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.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 - Layout.preferredWidth: 20 Layout.preferredHeight: 20 + Layout.preferredWidth: 20 + Layout.rightMargin: 16 + ToolTip.text: qsTr("Devices") + ToolTip.visible: hovered buttonTextColor: "#000000" - image: ":/icons/icons/ui/settings.svg" hoverEnabled: true - ToolTip.visible: hovered - ToolTip.text: qsTr("Devices") + image: ":/icons/icons/ui/settings.svg" + onClicked: { var dialog = devicesDialog.createObject(timelineRoot); dialog.open(); 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") + 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") + onClicked: { CallManager.rejectInvite(); } } - } - } diff --git a/resources/qml/voip/DeviceError.qml b/resources/qml/voip/DeviceError.qml index 4b9b4675..70af9225 100644 --- a/resources/qml/voip/DeviceError.qml +++ b/resources/qml/voip/DeviceError.qml @@ -8,35 +8,33 @@ import QtQuick.Layouts 1.2 Popup { id: r + property string errorString property var image modal: true + + background: Rectangle { + border.color: palette.windowText + color: 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/" + r.image + "?" + palette.windowText } - Label { - text: r.errorString color: palette.windowText + text: r.errorString } - - } - - background: Rectangle { - color: palette.window - border.color: palette.windowText } - } diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml index c0724828..abb0443f 100644 --- a/resources/qml/voip/PlaceCall.qml +++ b/resources/qml/voip/PlaceCall.qml @@ -10,12 +10,17 @@ import im.nheko 1.0 Popup { modal: true + + background: Rectangle { + border.color: palette.windowText + color: 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; - } Component { @@ -23,38 +28,33 @@ Popup { 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: 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; @@ -66,18 +66,19 @@ Popup { Layout.rightMargin: 8 Avatar { - Layout.rightMargin: cameraCombo.visible ? 16 : 64 - Layout.preferredWidth: Nheko.avatarSize Layout.preferredHeight: Nheko.avatarSize - url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") + Layout.preferredWidth: Nheko.avatarSize + Layout.rightMargin: cameraCombo.visible ? 16 : 64 displayName: room.roomName roomid: room.roomId + url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") + 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; @@ -86,11 +87,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; @@ -100,15 +101,14 @@ Popup { } } } - Button { - text: qsTr("Screen") icon.source: "qrc:/icons/icons/ui/screen-share.svg" + text: qsTr("Screen") + onClicked: { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; Settings.camera = cameraCombo.currentText; - var dialog = screenShareDialog.createObject(timelineRoot); dialog.open(); timelineRoot.destroyOnClose(dialog); @@ -116,67 +116,52 @@ 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?" + 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?" + palette.windowText } - ComboBox { id: cameraCombo Layout.fillWidth: true model: CallManager.cameras } - } - } - - } - - background: Rectangle { - color: palette.window - border.color: palette.windowText } - } diff --git a/resources/qml/voip/ScreenShare.qml b/resources/qml/voip/ScreenShare.qml index d3661933..719d8924 100644 --- a/resources/qml/voip/ScreenShare.qml +++ b/resources/qml/voip/ScreenShare.qml @@ -9,9 +9,13 @@ import QtQuick.Layouts import im.nheko Popup { + anchors.centerIn: parent modal: true - anchors.centerIn: parent; + background: Rectangle { + border.color: palette.windowText + color: palette.window + } Component.onCompleted: { frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate); @@ -22,176 +26,151 @@ Popup { 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: 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("Method:") - color: palette.windowText + Layout.alignment: Qt.AlignLeft + color: palette.windowText + text: qsTr("Method:") } + ComboBox { + id: screenshareType - ComboBox { - id: screenshareType + Layout.fillWidth: true + model: CallManager.screenShareTypeList() - Layout.fillWidth: true - model: CallManager.screenShareTypeList() - onCurrentIndexChanged: CallManager.setScreenShareType(currentIndex); - } + onCurrentIndexChanged: CallManager.setScreenShareType(currentIndex) + } } - RowLayout { + Layout.bottomMargin: 8 Layout.leftMargin: 8 Layout.rightMargin: 8 - Layout.bottomMargin: 8 Label { Layout.alignment: Qt.AlignLeft - text: qsTr("Window:") color: palette.windowText + text: qsTr("Window:") } - ComboBox { - visible: CallManager.screenShareType == Voip.X11 id: windowCombo Layout.fillWidth: true model: CallManager.windowList() + visible: CallManager.screenShareType == Voip.X11 } - Button { - visible: CallManager.screenShareType == Voip.XDP highlighted: !CallManager.screenShareReady text: qsTr("Request screencast") + visible: CallManager.screenShareType == Voip.XDP + onClicked: { - Settings.screenShareHideCursor = hideCursorCheckBox.checked; - CallManager.setupScreenShareXDP(); + Settings.screenShareHideCursor = hideCursorCheckBox.checked; + CallManager.setupScreenShareXDP(); } } - } - RowLayout { + Layout.bottomMargin: 8 Layout.leftMargin: 8 Layout.rightMargin: 8 - Layout.bottomMargin: 8 Label { Layout.alignment: Qt.AlignLeft - text: qsTr("Frame rate:") color: 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 { - visible: CallManager.screenShareReady - text: qsTr("Share") icon.source: "qrc:/icons/icons/ui/screen-share.svg" + text: qsTr("Share") + visible: CallManager.screenShareReady 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 { - visible: CallManager.screenShareReady text: qsTr("Preview") + visible: CallManager.screenShareReady + onClicked: { CallManager.previewWindow(windowCombo.currentIndex); } } - Button { text: qsTr("Cancel") + onClicked: { close(); } } - } - } - - background: Rectangle { - color: palette.window - border.color: palette.windowText - } - }