qt6
Nicolas Werner 3 years ago
parent a8bd8dddbf
commit da96558bb9
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
  1. 2
      .ci/licenses.sh
  2. 5
      .qmlformat.ini
  3. 23
      .qmllint.ini
  4. 68
      qml/Avatar.qml
  5. 82
      qml/ChatPage.qml
  6. 125
      qml/CommunitiesList.qml
  7. 190
      qml/Completer.qml
  8. 20
      qml/ElidedLabel.qml
  9. 40
      qml/EncryptionIndicator.qml
  10. 77
      qml/ForwardCompleter.qml
  11. 20
      qml/ImageButton.qml
  12. 20
      qml/MatrixText.qml
  13. 116
      qml/MatrixTextField.qml
  14. 272
      qml/MessageInput.qml
  15. 788
      qml/MessageView.qml
  16. 42
      qml/NhekoBusyIndicator.qml
  17. 16
      qml/NotificationWarning.qml
  18. 44
      qml/PrivacyScreen.qml
  19. 53
      qml/QuickSwitcher.qml
  20. 82
      qml/Reactions.qml
  21. 57
      qml/ReplyPopup.qml
  22. 248
      qml/Root.qml
  23. 178
      qml/SelfVerificationCheck.qml
  24. 26
      qml/StatusIndicator.qml
  25. 303
      qml/TimelineRow.qml
  26. 161
      qml/TimelineView.qml
  27. 23
      qml/ToggleButton.qml
  28. 260
      qml/TopBar.qml
  29. 14
      qml/TypingIndicator.qml
  30. 70
      qml/UploadBox.qml
  31. 130
      qml/components/AdaptiveLayout.qml
  32. 10
      qml/components/AdaptiveLayoutElement.qml
  33. 63
      qml/components/AvatarListTile.qml
  34. 37
      qml/components/FlatButton.qml
  35. 30
      qml/components/MainWindowDialog.qml
  36. 19
      qml/components/TextButton.qml
  37. 29
      qml/delegates/Encrypted.qml
  38. 53
      qml/delegates/FileMessage.qml
  39. 71
      qml/delegates/ImageMessage.qml
  40. 247
      qml/delegates/MessageDelegate.qml
  41. 10
      qml/delegates/NoticeMessage.qml
  42. 8
      qml/delegates/Pill.qml
  43. 8
      qml/delegates/Placeholder.qml
  44. 67
      qml/delegates/PlayableMediaMessage.qml
  45. 30
      qml/delegates/Redacted.qml
  46. 121
      qml/delegates/Reply.qml
  47. 24
      qml/delegates/TextMessage.qml
  48. 47
      qml/device-verification/DeviceVerification.qml
  49. 35
      qml/device-verification/DigitVerification.qml
  50. 11
      qml/device-verification/EmojiElement.qml
  51. 40
      qml/device-verification/EmojiVerification.qml
  52. 21
      qml/device-verification/Failed.qml
  53. 23
      qml/device-verification/NewVerificationRequest.qml
  54. 21
      qml/device-verification/Success.qml
  55. 25
      qml/device-verification/Waiting.qml
  56. 85
      qml/dialogs/CreateDirect.qml
  57. 108
      qml/dialogs/CreateRoom.qml
  58. 87
      qml/dialogs/HiddenEventsDialog.qml
  59. 72
      qml/dialogs/ImageOverlay.qml
  60. 236
      qml/dialogs/ImagePackEditorDialog.qml
  61. 193
      qml/dialogs/ImagePackSettingsDialog.qml
  62. 55
      qml/dialogs/InputDialog.qml
  63. 116
      qml/dialogs/InviteDialog.qml
  64. 69
      qml/dialogs/JoinRoomDialog.qml
  65. 11
      qml/dialogs/LeaveRoomDialog.qml
  66. 12
      qml/dialogs/LogoutDialog.qml
  67. 877
      qml/dialogs/PhoneNumberInputDialog.qml
  68. 34
      qml/dialogs/RawMessageDialog.qml
  69. 79
      qml/dialogs/ReadReceipts.qml
  70. 164
      qml/dialogs/RoomDirectory.qml
  71. 148
      qml/dialogs/RoomMembers.qml
  72. 286
      qml/dialogs/RoomSettings.qml
  73. 452
      qml/dialogs/UserProfile.qml
  74. 229
      qml/emoji/EmojiPicker.qml
  75. 107
      qml/emoji/StickerPicker.qml
  76. 131
      qml/pages/LoginPage.qml
  77. 143
      qml/pages/RegisterPage.qml
  78. 137
      qml/pages/UserSettingsPage.qml
  79. 33
      qml/pages/WelcomePage.qml
  80. 35
      qml/ui/NhekoSlider.qml
  81. 74
      qml/ui/Ripple.qml
  82. 93
      qml/ui/Snackbar.qml
  83. 108
      qml/ui/Spinner.qml
  84. 12
      qml/ui/animations/BlinkAnimation.qml
  85. 125
      qml/ui/media/MediaControls.qml
  86. 91
      qml/voip/ActiveCallBar.qml
  87. 35
      qml/voip/CallDevices.qml
  88. 95
      qml/voip/CallInvite.qml
  89. 56
      qml/voip/CallInviteBar.qml
  90. 21
      qml/voip/DeviceError.qml
  91. 76
      qml/voip/PlaceCall.qml
  92. 69
      qml/voip/ScreenShare.qml
  93. 2
      qml/voip/VideoCall.qml

@ -7,7 +7,7 @@
set -eu set -eu
FILES=$(find src resources/qml -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.qml" \)) FILES=$(find src qml -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.qml" \))
reuse addheader --copyright="Nheko Contributors" --license="GPL-3.0-or-later" $FILES reuse addheader --copyright="Nheko Contributors" --license="GPL-3.0-or-later" $FILES

@ -0,0 +1,5 @@
[General]
IndentWidth=4
NewlineType=native
NormalizeOrder=true
UseTabs=false

@ -0,0 +1,23 @@
[General]
AdditionalQmlImportPaths=
DisableDefaultImports=false
OverwriteImportTypes=
ResourcePath=
[Warnings]
AttachedPropertyReuse=disable
BadSignalHandler=warning
CompilerWarnings=disable
ControlsSanity=disable
DeferredPropertyId=warning
Deprecated=warning
ImportFailure=warning
InheritanceCycle=warning
MultilineStrings=info
PropertyAlias=warning
RequiredProperty=warning
TypeError=warning
UnknownProperty=warning
UnqualifiedAccess=disable
UnusedImports=info
WithStatement=warning

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "ui"
import "./ui"
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Window 2.15 import QtQuick.Window 2.15
@ -12,70 +10,54 @@ import im.nheko
AbstractButton { AbstractButton {
id: avatar id: avatar
property string url property alias color: bg.color
property string userid property bool crop: true
property string roomid
property string displayName property string displayName
property string roomid
property alias textColor: label.color property alias textColor: label.color
property bool crop: true property string url
property alias color: bg.color property string userid
width: 48
height: 48 height: 48
width: 48
background: Rectangle { background: Rectangle {
id: bg id: bg
radius: Settings.avatarCircles ? height / 2 : height / 8
color: timelineRoot.palette.alternateBase color: timelineRoot.palette.alternateBase
radius: Settings.avatarCircles ? height / 2 : height / 8
} }
Label { Label {
id: label id: label
enabled: false
anchors.fill: parent anchors.fill: parent
color: timelineRoot.palette.text
enabled: false
font.pixelSize: avatar.height / 2
horizontalAlignment: Text.AlignHCenter
text: TimelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "") text: TimelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "")
textFormat: Text.RichText textFormat: Text.RichText
font.pixelSize: avatar.height / 2
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
visible: img.status != Image.Ready && !Settings.useIdenticon visible: img.status != Image.Ready && !Settings.useIdenticon
color: timelineRoot.palette.text
} }
Image { Image {
id: identicon id: identicon
anchors.fill: parent anchors.fill: parent
visible: Settings.useIdenticon && img.status != Image.Ready
source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : "" source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : ""
visible: Settings.useIdenticon && img.status != Image.Ready
} }
Image { Image {
id: img id: img
anchors.fill: parent anchors.fill: parent
asynchronous: true asynchronous: true
fillMode: avatar.crop ? Image.PreserveAspectCrop : Image.PreserveAspectFit fillMode: avatar.crop ? Image.PreserveAspectCrop : Image.PreserveAspectFit
mipmap: true mipmap: true
smooth: true smooth: true
sourceSize.width: avatar.width * Screen.devicePixelRatio
sourceSize.height: avatar.height * Screen.devicePixelRatio
source: avatar.url ? (avatar.url + "?radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale")) : "" source: avatar.url ? (avatar.url + "?radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale")) : ""
sourceSize.height: avatar.height * Screen.devicePixelRatio
sourceSize.width: avatar.width * Screen.devicePixelRatio
} }
Rectangle { Rectangle {
id: onlineIndicator id: onlineIndicator
anchors.bottom: avatar.bottom
anchors.right: avatar.right
visible: !!userid
height: avatar.height / 6
width: height
radius: Settings.avatarCircles ? height / 2 : height / 8
color: updatePresence()
function updatePresence() { function updatePresence() {
switch (Presence.userPresence(userid)) { switch (Presence.userPresence(userid)) {
case "online": case "online":
@ -89,22 +71,28 @@ AbstractButton {
} }
} }
Connections { anchors.bottom: avatar.bottom
target: Presence anchors.right: avatar.right
color: updatePresence()
height: avatar.height / 6
radius: Settings.avatarCircles ? height / 2 : height / 8
visible: !!userid
width: height
Connections {
function onPresenceChanged(id) { function onPresenceChanged(id) {
if (id == userid) onlineIndicator.color = onlineIndicator.updatePresence(); if (id == userid)
onlineIndicator.color = onlineIndicator.updatePresence();
} }
target: Presence
} }
} }
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
Ripple { Ripple {
color: Qt.rgba(timelineRoot.palette.alternateBase.r, timelineRoot.palette.alternateBase.g, timelineRoot.palette.alternateBase.b, 0.5) color: Qt.rgba(timelineRoot.palette.alternateBase.r, timelineRoot.palette.alternateBase.g, timelineRoot.palette.alternateBase.b, 0.5)
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
@ -14,41 +12,28 @@ import QtQml 2.15
Rectangle { Rectangle {
id: chatPage id: chatPage
color: timelineRoot.palette.window color: timelineRoot.palette.window
ColumnLayout { ColumnLayout {
spacing: 0
anchors.fill: parent anchors.fill: parent
spacing: 0
Rectangle { Rectangle {
id: offlineIndicator id: offlineIndicator
Layout.fillWidth: true
Layout.preferredHeight: offlineLabel.height + Nheko.paddingMedium
color: Nheko.theme.error color: Nheko.theme.error
visible: !TimelineManager.isConnected visible: !TimelineManager.isConnected
Layout.preferredHeight: offlineLabel.height + Nheko.paddingMedium
Layout.fillWidth: true
z: 1 z: 1
Label { Label {
id: offlineLabel id: offlineLabel
anchors.centerIn: parent anchors.centerIn: parent
text: qsTr("No network connection") text: qsTr("No network connection")
} }
} }
AdaptiveLayout { AdaptiveLayout {
id: adaptiveView id: adaptiveView
Layout.fillWidth: true
Layout.fillHeight: true
singlePageMode: communityListC.preferredWidth + roomListC.preferredWidth + timlineViewC.minimumWidth > width
pageIndex: 1
Component.onCompleted: initializePageIndex()
onSinglePageModeChanged: initializePageIndex()
function initializePageIndex() { function initializePageIndex() {
if (!singlePageMode) if (!singlePageMode)
adaptiveView.pageIndex = 0; adaptiveView.pageIndex = 0;
@ -58,91 +43,80 @@ Rectangle {
adaptiveView.pageIndex = 1; adaptiveView.pageIndex = 1;
} }
Layout.fillHeight: true
Layout.fillWidth: true
pageIndex: 1
singlePageMode: communityListC.preferredWidth + roomListC.preferredWidth + timlineViewC.minimumWidth > width
Component.onCompleted: initializePageIndex()
onSinglePageModeChanged: initializePageIndex()
Connections { Connections {
target: Rooms
function onCurrentRoomChanged() { function onCurrentRoomChanged() {
adaptiveView.initializePageIndex(); adaptiveView.initializePageIndex();
} }
}
target: Rooms
}
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: communityListC id: communityListC
visible: Settings.groupView
minimumWidth: communitiesList.avatarSize * 4 + Nheko.paddingMedium * 2
collapsedWidth: communitiesList.avatarSize + 2 * Nheko.paddingMedium collapsedWidth: communitiesList.avatarSize + 2 * Nheko.paddingMedium
preferredWidth: Settings.communityListWidth >= minimumWidth ? Settings.communityListWidth : collapsedWidth
maximumWidth: communitiesList.avatarSize * 10 + 2 * Nheko.paddingMedium maximumWidth: communitiesList.avatarSize * 10 + 2 * Nheko.paddingMedium
minimumWidth: communitiesList.avatarSize * 4 + Nheko.paddingMedium * 2
preferredWidth: Settings.communityListWidth >= minimumWidth ? Settings.communityListWidth : collapsedWidth
visible: Settings.groupView
CommunitiesList { CommunitiesList {
id: communitiesList id: communitiesList
collapsed: parent.collapsed collapsed: parent.collapsed
} }
Binding { Binding {
target: Settings delayed: true
property: 'communityListWidth' property: 'communityListWidth'
restoreMode: Binding.RestoreBindingOrValue
target: Settings
value: communityListC.preferredWidth value: communityListC.preferredWidth
when: !adaptiveView.singlePageMode when: !adaptiveView.singlePageMode
delayed: true
restoreMode: Binding.RestoreBindingOrValue
} }
} }
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: roomListC id: roomListC
minimumWidth: roomlist.avatarSize * 4 + Nheko.paddingSmall * 2
preferredWidth: (Settings.roomListWidth == - 1)
? (roomlist.avatarSize * 5 + Nheko.paddingSmall * 2)
: (Settings.roomListWidth >= minimumWidth ? Settings.roomListWidth : collapsedWidth)
maximumWidth: roomlist.avatarSize * 10 + Nheko.paddingSmall * 2
collapsedWidth: roomlist.avatarSize + 2 * Nheko.paddingMedium collapsedWidth: roomlist.avatarSize + 2 * Nheko.paddingMedium
maximumWidth: roomlist.avatarSize * 10 + Nheko.paddingSmall * 2
minimumWidth: roomlist.avatarSize * 4 + Nheko.paddingSmall * 2
preferredWidth: (Settings.roomListWidth == -1) ? (roomlist.avatarSize * 5 + Nheko.paddingSmall * 2) : (Settings.roomListWidth >= minimumWidth ? Settings.roomListWidth : collapsedWidth)
RoomList { RoomList {
id: roomlist id: roomlist
height: adaptiveView.height
collapsed: parent.collapsed collapsed: parent.collapsed
height: adaptiveView.height
} }
Binding { Binding {
target: Settings delayed: true
property: 'roomListWidth' property: 'roomListWidth'
restoreMode: Binding.RestoreBindingOrValue
target: Settings
value: roomListC.preferredWidth value: roomListC.preferredWidth
when: !adaptiveView.singlePageMode when: !adaptiveView.singlePageMode
delayed: true
restoreMode: Binding.RestoreBindingOrValue
} }
} }
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: timlineViewC id: timlineViewC
minimumWidth: fontMetrics.averageCharacterWidth * 40 + Nheko.avatarSize + 2 * Nheko.paddingMedium minimumWidth: fontMetrics.averageCharacterWidth * 40 + Nheko.avatarSize + 2 * Nheko.paddingMedium
TimelineView { TimelineView {
id: timeline id: timeline
showBackButton: adaptiveView.singlePageMode
room: Rooms.currentRoom room: Rooms.currentRoom
roomPreview: Rooms.currentRoomPreview.roomid ? Rooms.currentRoomPreview : null roomPreview: Rooms.currentRoomPreview.roomid ? Rooms.currentRoomPreview : null
showBackButton: adaptiveView.singlePageMode
} }
} }
} }
} }
PrivacyScreen { PrivacyScreen {
anchors.fill: parent anchors.fill: parent
visible: Settings.privacyScreen
screenTimeout: Settings.privacyScreenTimeout screenTimeout: Settings.privacyScreenTimeout
timelineRoot: adaptiveView timelineRoot: adaptiveView
visible: Settings.privacyScreen
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "dialogs"
import "./dialogs"
import Qt.labs.platform 1.1 as Platform import Qt.labs.platform 1.1 as Platform
import QtQml 2.12 import QtQml 2.12
import QtQuick 2.12 import QtQuick 2.12
@ -13,168 +11,157 @@ import im.nheko
Page { Page {
id: communitySidebar id: communitySidebar
//leftPadding: Nheko.paddingSmall //leftPadding: Nheko.paddingSmall
//rightPadding: Nheko.paddingSmall //rightPadding: Nheko.paddingSmall
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6) property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
property bool collapsed: false property bool collapsed: false
background: Rectangle {
color: Nheko.theme.sidebarBackground
}
ListView { ListView {
id: communitiesList id: communitiesList
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
height: parent.height height: parent.height
model: Communities.filtered() model: Communities.filtered()
Platform.Menu {
id: communityContextMenu
property string tagId
function show(id_, tags_) {
tagId = id_;
open();
}
Platform.MenuItem {
text: qsTr("Hide rooms with this tag or from this space by default.")
onTriggered: Communities.toggleTagId(communityContextMenu.tagId)
}
}
delegate: ItemDelegate { delegate: ItemDelegate {
id: communityItem id: communityItem
property color backgroundColor: timelineRoot.palette.window property color backgroundColor: timelineRoot.palette.window
property color importantText: timelineRoot.palette.text
property color unimportantText: timelineRoot.palette.placeholderText
property color bubbleBackground: timelineRoot.palette.highlight property color bubbleBackground: timelineRoot.palette.highlight
property color bubbleText: timelineRoot.palette.highlightedText property color bubbleText: timelineRoot.palette.highlightedText
property color importantText: timelineRoot.palette.text
property color unimportantText: timelineRoot.palette.placeholderText
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: model.tooltip
ToolTip.visible: hovered && collapsed
height: avatarSize + 2 * Nheko.paddingMedium height: avatarSize + 2 * Nheko.paddingMedium
width: ListView.view.width
state: "normal" state: "normal"
ToolTip.visible: hovered && collapsed width: ListView.view.width
ToolTip.text: model.tooltip
ToolTip.delay: Nheko.tooltipDelay background: Rectangle {
onClicked: Communities.setCurrentTagId(model.id) color: backgroundColor
onPressAndHold: communityContextMenu.show(model.id) }
states: [ states: [
State { State {
name: "highlight" name: "highlight"
when: (communityItem.hovered || model.hidden) && !(Communities.currentTagId == model.id) when: (communityItem.hovered || model.hidden) && !(Communities.currentTagId == model.id)
PropertyChanges { PropertyChanges {
target: communityItem
backgroundColor: timelineRoot.palette.dark backgroundColor: timelineRoot.palette.dark
importantText: timelineRoot.palette.brightText
unimportantText: timelineRoot.palette.brightText
bubbleBackground: timelineRoot.palette.highlight bubbleBackground: timelineRoot.palette.highlight
bubbleText: timelineRoot.palette.highlightedText bubbleText: timelineRoot.palette.highlightedText
importantText: timelineRoot.palette.brightText
target: communityItem
unimportantText: timelineRoot.palette.brightText
} }
}, },
State { State {
name: "selected" name: "selected"
when: Communities.currentTagId == model.id when: Communities.currentTagId == model.id
PropertyChanges { PropertyChanges {
target: communityItem
backgroundColor: timelineRoot.palette.highlight backgroundColor: timelineRoot.palette.highlight
importantText: timelineRoot.palette.highlightedText
unimportantText: timelineRoot.palette.highlightedText
bubbleBackground: timelineRoot.palette.highlightedText bubbleBackground: timelineRoot.palette.highlightedText
bubbleText: timelineRoot.palette.highlight bubbleText: timelineRoot.palette.highlight
importantText: timelineRoot.palette.highlightedText
target: communityItem
unimportantText: timelineRoot.palette.highlightedText
} }
} }
] ]
onClicked: Communities.setCurrentTagId(model.id)
onPressAndHold: communityContextMenu.show(model.id)
Item { Item {
anchors.fill: parent anchors.fill: parent
TapHandler { TapHandler {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onSingleTapped: communityContextMenu.show(model.id)
gesturePolicy: TapHandler.ReleaseWithinBounds
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
} gesturePolicy: TapHandler.ReleaseWithinBounds
onSingleTapped: communityContextMenu.show(model.id)
}
} }
RowLayout { RowLayout {
id: r id: r
spacing: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
anchors.leftMargin: Nheko.paddingMedium + (communitySidebar.collapsed ? 0 : (fontMetrics.lineSpacing * model.depth)) anchors.leftMargin: Nheko.paddingMedium + (communitySidebar.collapsed ? 0 : (fontMetrics.lineSpacing * model.depth))
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
ImageButton { ImageButton {
visible: !communitySidebar.collapsed && model.collapsible Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: fontMetrics.lineSpacing Layout.preferredHeight: fontMetrics.lineSpacing
Layout.preferredWidth: fontMetrics.lineSpacing Layout.preferredWidth: fontMetrics.lineSpacing
Layout.alignment: Qt.AlignVCenter
height: fontMetrics.lineSpacing
width: fontMetrics.lineSpacing
image: model.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg"
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: model.collapsed ? qsTr("Expand") : qsTr("Collapse") ToolTip.text: model.collapsed ? qsTr("Expand") : qsTr("Collapse")
ToolTip.visible: hovered
height: fontMetrics.lineSpacing
hoverEnabled: true hoverEnabled: true
image: model.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg"
visible: !communitySidebar.collapsed && model.collapsible
width: fontMetrics.lineSpacing
onClicked: model.collapsed = !model.collapsed onClicked: model.collapsed = !model.collapsed
} }
Item { Item {
Layout.preferredWidth: fontMetrics.lineSpacing Layout.preferredWidth: fontMetrics.lineSpacing
visible: !communitySidebar.collapsed && !model.collapsible && Communities.containsSubspaces visible: !communitySidebar.collapsed && !model.collapsible && Communities.containsSubspaces
} }
Avatar { Avatar {
id: avatar id: avatar
enabled: false
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: communityItem.backgroundColor
displayName: model.displayName
enabled: false
height: avatarSize height: avatarSize
width: avatarSize roomid: model.id
url: { url: {
if (model.avatarUrl.startsWith("mxc://")) if (model.avatarUrl.startsWith("mxc://"))
return model.avatarUrl.replace("mxc://", "image://MxcImage/"); return model.avatarUrl.replace("mxc://", "image://MxcImage/");
else else
return "image://colorimage/" + model.avatarUrl + "?" + communityItem.unimportantText; return "image://colorimage/" + model.avatarUrl + "?" + communityItem.unimportantText;
} }
roomid: model.id width: avatarSize
displayName: model.displayName
color: communityItem.backgroundColor
} }
ElidedLabel { ElidedLabel {
visible: !communitySidebar.collapsed
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: communityItem.importantText
Layout.fillWidth: true Layout.fillWidth: true
color: communityItem.importantText
elideWidth: width elideWidth: width
fullText: model.displayName fullText: model.displayName
textFormat: Text.PlainText textFormat: Text.PlainText
visible: !communitySidebar.collapsed
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
background: Rectangle {
color: backgroundColor
} }
} Platform.Menu {
id: communityContextMenu
} property string tagId
background: Rectangle { function show(id_, tags_) {
color: Nheko.theme.sidebarBackground tagId = id_;
open();
} }
Platform.MenuItem {
text: qsTr("Hide rooms with this tag or from this space by default.")
onTriggered: Communities.toggleTagId(communityContextMenu.tagId)
}
}
}
} }

@ -1,115 +1,90 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import im.nheko import im.nheko
import im.nheko import im.nheko
import "ui"
import "./ui/"
Control { Control {
id: popup id: popup
property alias currentIndex: listView.currentIndex
property string completerName
property var completer
property bool bottomToTop: true
property bool fullWidth: false
property bool centerRowContent: true
property int avatarHeight: 24 property int avatarHeight: 24
property int avatarWidth: 24 property int avatarWidth: 24
property bool bottomToTop: true
property bool centerRowContent: true
property var completer
property string completerName
property alias count: listView.count
property alias currentIndex: listView.currentIndex
property bool fullWidth: false
property int rowMargin: 0 property int rowMargin: 0
property int rowSpacing: 5 property int rowSpacing: 5
property alias count: listView.count
signal completionClicked(string completion) signal completionClicked(string completion)
signal completionSelected(string id) signal completionSelected(string id)
function up() { function currentCompletion() {
if (bottomToTop) if (currentIndex > -1 && currentIndex < listView.count)
down_(); return completer.completionAt(currentIndex);
else else
up_(); return null;
} }
function down() { function down() {
if (bottomToTop) if (bottomToTop)
up_(); up_();
else else
down_(); down_();
} }
function up_() {
currentIndex = currentIndex - 1;
if (currentIndex == -2)
currentIndex = listView.count - 1;
}
function down_() { function down_() {
currentIndex = currentIndex + 1; currentIndex = currentIndex + 1;
if (currentIndex >= listView.count) if (currentIndex >= listView.count)
currentIndex = -1; currentIndex = -1;
} }
function currentCompletion() {
if (currentIndex > -1 && currentIndex < listView.count)
return completer.completionAt(currentIndex);
else
return null;
}
function finishCompletion() { function finishCompletion() {
if (popup.completerName == "room") if (popup.completerName == "room")
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid); popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid);
} }
function up() {
onCompleterNameChanged: { if (bottomToTop)
if (completerName) { down_();
completer = TimelineManager.completerFor(completerName, completerName == "room" ? "" : room.roomId); else
completer.setSearchString(""); up_();
} else {
completer = undefined;
} }
currentIndex = -1 function up_() {
currentIndex = currentIndex - 1;
if (currentIndex == -2)
currentIndex = listView.count - 1;
} }
bottomPadding: 1 bottomPadding: 1
leftPadding: 1 leftPadding: 1
topPadding: 1
rightPadding: 1 rightPadding: 1
topPadding: 1
background: Rectangle {
border.color: timelineRoot.palette.mid
color: timelineRoot.palette.base
}
contentItem: ListView { contentItem: ListView {
id: listView id: listView
clip: true
highlightFollowsCurrentItem: true
// If we have fewer than 7 items, just use the list view's content height. // If we have fewer than 7 items, just use the list view's content height.
// Otherwise, we want to show 7 items. Each item consists of row spacing between rows, row margins // Otherwise, we want to show 7 items. Each item consists of row spacing between rows, row margins
// on each side of a row, 1px of padding above the first item and below the last item, and nominally // on each side of a row, 1px of padding above the first item and below the last item, and nominally
// some kind of content height. avatarHeight is used for just about every delegate, so we're using // some kind of content height. avatarHeight is used for just about every delegate, so we're using
// that until we find something better. Put is all together and you have the formula below! // that until we find something better. Put is all together and you have the formula below!
implicitHeight: Math.min(contentHeight, 6*rowSpacing + 7*(popup.avatarHeight + 2*rowMargin)) implicitHeight: Math.min(contentHeight, 6 * rowSpacing + 7 * (popup.avatarHeight + 2 * rowMargin))
clip: true
Timer {
id: deadTimer
interval: 50
}
onContentYChanged: deadTimer.restart()
reuseItems: true
implicitWidth: listView.contentItem.childrenRect.width implicitWidth: listView.contentItem.childrenRect.width
model: completer model: completer
verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
spacing: rowSpacing
pixelAligned: true pixelAligned: true
highlightFollowsCurrentItem: true reuseItems: true
spacing: rowSpacing
verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
delegate: Rectangle { delegate: Rectangle {
property variant modelData: model property variant modelData: model
@ -120,193 +95,176 @@ Control {
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onPositionChanged: if (!listView.moving && !deadTimer.running) popup.currentIndex = model.index
onClicked: { onClicked: {
popup.completionClicked(completer.completionAt(model.index)); popup.completionClicked(completer.completionAt(model.index));
if (popup.completerName == "room") if (popup.completerName == "room")
popup.completionSelected(model.roomid); popup.completionSelected(model.roomid);
} }
onPositionChanged: if (!listView.moving && !deadTimer.running)
popup.currentIndex = model.index
} }
Ripple { Ripple {
color: Qt.rgba(timelineRoot.palette.base.r, timelineRoot.palette.base.g, timelineRoot.palette.base.b, 0.5) color: Qt.rgba(timelineRoot.palette.base.r, timelineRoot.palette.base.g, timelineRoot.palette.base.b, 0.5)
} }
DelegateChooser { DelegateChooser {
id: chooser id: chooser
roleValue: popup.completerName
anchors.fill: parent anchors.fill: parent
anchors.margins: popup.rowMargin anchors.margins: popup.rowMargin
enabled: false enabled: false
roleValue: popup.completerName
DelegateChoice { DelegateChoice {
roleValue: "user" roleValue: "user"
RowLayout { RowLayout {
id: del id: del
anchors.centerIn: parent anchors.centerIn: parent
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
height: popup.avatarHeight
width: popup.avatarWidth
displayName: model.displayName displayName: model.displayName
userid: model.userid height: popup.avatarHeight
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.userid
width: popup.avatarWidth
onClicked: popup.completionClicked(completer.completionAt(model.index)) onClicked: popup.completionClicked(completer.completionAt(model.index))
} }
Label { Label {
text: model.displayName
color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text
text: model.displayName
} }
Label { Label {
text: "(" + model.userid + ")"
color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.placeholderText color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.placeholderText
text: "(" + model.userid + ")"
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: "emoji" roleValue: "emoji"
RowLayout { RowLayout {
id: del id: del
anchors.centerIn: parent anchors.centerIn: parent
spacing: rowSpacing spacing: rowSpacing
Label { Label {
text: model.unicode
color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text
font: Settings.emojiFont font: Settings.emojiFont
text: model.unicode
} }
Label { Label {
text: model.shortName
color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text
text: model.shortName
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: "customEmoji" roleValue: "customEmoji"
RowLayout { RowLayout {
id: del id: del
anchors.centerIn: parent anchors.centerIn: parent
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
height: popup.avatarHeight crop: false
width: popup.avatarWidth
displayName: model.shortcode displayName: model.shortcode
height: popup.avatarHeight
//userid: model.shortcode //userid: model.shortcode
url: model.url.replace("mxc://", "image://MxcImage/") url: model.url.replace("mxc://", "image://MxcImage/")
width: popup.avatarWidth
onClicked: popup.completionClicked(completer.completionAt(model.index)) onClicked: popup.completionClicked(completer.completionAt(model.index))
crop: false
} }
Label { Label {
text: model.shortcode
color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text
text: model.shortcode
} }
Label { Label {
text: "(" + model.packname + ")"
color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.placeholderText color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.placeholderText
text: "(" + model.packname + ")"
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: "room" roleValue: "room"
RowLayout { RowLayout {
id: del id: del
anchors.centerIn: centerRowContent ? parent : undefined anchors.centerIn: centerRowContent ? parent : undefined
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
height: popup.avatarHeight
width: popup.avatarWidth
displayName: model.roomName displayName: model.roomName
height: popup.avatarHeight
roomid: model.roomid roomid: model.roomid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
width: popup.avatarWidth
onClicked: { onClicked: {
popup.completionClicked(completer.completionAt(model.index)); popup.completionClicked(completer.completionAt(model.index));
popup.completionSelected(model.roomid); popup.completionSelected(model.roomid);
} }
} }
Label { Label {
text: model.roomName
font.pixelSize: popup.avatarHeight * 0.5
color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text
font.pixelSize: popup.avatarHeight * 0.5
text: model.roomName
textFormat: Text.RichText textFormat: Text.RichText
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: "roomAliases" roleValue: "roomAliases"
RowLayout { RowLayout {
id: del id: del
anchors.centerIn: parent anchors.centerIn: parent
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
height: popup.avatarHeight
width: popup.avatarWidth
displayName: model.roomName displayName: model.roomName
height: popup.avatarHeight
roomid: model.roomid roomid: model.roomid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
width: popup.avatarWidth
onClicked: popup.completionClicked(completer.completionAt(model.index)) onClicked: popup.completionClicked(completer.completionAt(model.index))
} }
Label { Label {
text: model.roomName
color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.text
text: model.roomName
textFormat: Text.RichText textFormat: Text.RichText
} }
Label { Label {
text: "(" + model.roomAlias + ")"
color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.placeholderText color: model.index == popup.currentIndex ? timelineRoot.palette.highlightedText : timelineRoot.palette.placeholderText
text: "(" + model.roomAlias + ")"
textFormat: Text.RichText textFormat: Text.RichText
} }
} }
}
} }
} }
} }
onContentYChanged: deadTimer.restart()
background: Rectangle { Timer {
color: timelineRoot.palette.base id: deadTimer
border.color: timelineRoot.palette.mid interval: 50
}
} }
onCompleterNameChanged: {
if (completerName) {
completer = TimelineManager.completerFor(completerName, completerName == "room" ? "" : room.roomId);
completer.setSearchString("");
} else {
completer = undefined;
}
currentIndex = -1;
}
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.5 import QtQuick.Controls 2.5
import im.nheko import im.nheko
@ -10,21 +8,25 @@ import im.nheko
Label { Label {
id: root id: root
property alias fullText: metrics.text
property alias elideWidth: metrics.elideWidth property alias elideWidth: metrics.elideWidth
property int fullTextWidth: Math.ceil(metrics.advanceWidth) property alias fullText: metrics.text
property int fullTextWidth: Math.ceil(metrics2.advanceWidth + 4)
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: (textFormat == Text.PlainText) ? metrics.elidedText : TimelineManager.escapeEmoji(TimelineManager.htmlEscape(metrics.elidedText))
maximumLineCount: 1
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1
text: (textFormat == Text.PlainText) ? metrics.elidedText : TimelineManager.escapeEmoji(TimelineManager.htmlEscape(metrics.elidedText))
textFormat: Text.PlainText textFormat: Text.PlainText
TextMetrics { TextMetrics {
id: metrics id: metrics
font.pointSize: root.font.pointSize
elide: Text.ElideRight elide: Text.ElideRight
font: root.font
}
TextMetrics {
id: metrics2
//elide: Text.ElideRight
font: root.font
text: metrics.text
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import QtQuick.Window 2.15 import QtQuick.Window 2.15
@ -12,12 +10,9 @@ Image {
id: stateImg id: stateImg
property bool encrypted: false property bool encrypted: false
property int trust: Crypto.Unverified
property string sourceUrl: { property string sourceUrl: {
if (!encrypted) if (!encrypted)
return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?"; return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?";
switch (trust) { switch (trust) {
case Crypto.Verified: case Crypto.Verified:
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?"; return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?";
@ -29,11 +24,22 @@ Image {
return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?"; return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?";
} }
} }
property int trust: Crypto.Unverified
width: 16 ToolTip.text: {
if (!encrypted)
return qsTr("This message is not encrypted!");
switch (trust) {
case Crypto.Verified:
return qsTr("Encrypted by a verified device");
case Crypto.TOFU:
return qsTr("Encrypted by an unverified device, but you have trusted that user so far.");
default:
return qsTr("Encrypted by an unverified device or the key is from an untrusted source like the key backup.");
}
}
ToolTip.visible: ma.hovered
height: 16 height: 16
sourceSize.height: height * Screen.devicePixelRatio
sourceSize.width: width * Screen.devicePixelRatio
source: { source: {
if (encrypted) { if (encrypted) {
switch (trust) { switch (trust) {
@ -48,23 +54,11 @@ Image {
return sourceUrl + Nheko.theme.error; return sourceUrl + Nheko.theme.error;
} }
} }
ToolTip.visible: ma.hovered sourceSize.height: height * Screen.devicePixelRatio
ToolTip.text: { sourceSize.width: width * Screen.devicePixelRatio
if (!encrypted) width: 16
return qsTr("This message is not encrypted!");
switch (trust) {
case Crypto.Verified:
return qsTr("Encrypted by a verified device");
case Crypto.TOFU:
return qsTr("Encrypted by an unverified device, but you have trusted that user so far.");
default:
return qsTr("Encrypted by an unverified device or the key is from an untrusted source like the key backup.");
}
}
HoverHandler { HoverHandler {
id: ma id: ma
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "delegates"
import "./delegates/"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import im.nheko import im.nheko
@ -17,66 +15,65 @@ Popup {
mid = mid_in; mid = mid_in;
} }
x: Math.round(parent.width / 2 - width / 2) leftPadding: 10
y: Math.round(parent.height / 4)
modal: true modal: true
palette: timelineRoot.palette palette: timelineRoot.palette
parent: Overlay.overlay parent: Overlay.overlay
width: timelineRoot.width * 0.8
leftPadding: 10
rightPadding: 10 rightPadding: 10
width: timelineRoot.width * 0.8
x: Math.round(parent.width / 2 - width / 2)
y: Math.round(parent.height / 4)
Overlay.modal: Rectangle {
color: Qt.rgba(timelineRoot.palette.window.r, timelineRoot.palette.window.g, timelineRoot.palette.window.b, 0.7)
}
background: Rectangle {
color: timelineRoot.palette.window
}
onOpened: { onOpened: {
roomTextInput.forceActiveFocus(); roomTextInput.forceActiveFocus();
} }
Column { Column {
id: forwardColumn id: forwardColumn
spacing: 5 spacing: 5
Label { Label {
id: titleLabel id: titleLabel
text: qsTr("Forward Message")
font.bold: true
bottomPadding: 10 bottomPadding: 10
color: timelineRoot.palette.text color: timelineRoot.palette.text
font.bold: true
text: qsTr("Forward Message")
} }
Reply { Reply {
id: replyPreview id: replyPreview
property var modelData: room ? room.getDump(mid, "") : { property var modelData: room ? room.getDump(mid, "") : {}
}
width: parent.width
userColor: TimelineManager.userColor(modelData.userId, timelineRoot.palette.window)
blurhash: modelData.blurhash ?? "" blurhash: modelData.blurhash ?? ""
body: modelData.body ?? "" body: modelData.body ?? ""
formattedBody: modelData.formattedBody ?? "" encryptionError: modelData.encryptionError ?? ""
eventId: modelData.eventId ?? "" eventId: modelData.eventId ?? ""
filename: modelData.filename ?? "" filename: modelData.filename ?? ""
filesize: modelData.filesize ?? "" filesize: modelData.filesize ?? ""
formattedBody: modelData.formattedBody ?? ""
isOnlyEmoji: modelData.isOnlyEmoji ?? false
originalWidth: modelData.originalWidth ?? 0
proportionalHeight: modelData.proportionalHeight ?? 1 proportionalHeight: modelData.proportionalHeight ?? 1
type: modelData.type ?? MtxEvent.UnknownMessage type: modelData.type ?? MtxEvent.UnknownMessage
typeString: modelData.typeString ?? "" typeString: modelData.typeString ?? ""
url: modelData.url ?? "" url: modelData.url ?? ""
originalWidth: modelData.originalWidth ?? 0 userColor: TimelineManager.userColor(modelData.userId, timelineRoot.palette.window)
isOnlyEmoji: modelData.isOnlyEmoji ?? false
userId: modelData.userId ?? "" userId: modelData.userId ?? ""
userName: modelData.userName ?? "" userName: modelData.userName ?? ""
encryptionError: modelData.encryptionError ?? "" width: parent.width
} }
MatrixTextField { MatrixTextField {
id: roomTextInput id: roomTextInput
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
color: timelineRoot.palette.text color: timelineRoot.palette.text
onTextEdited: { width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
completerPopup.completer.searchString = text;
}
Keys.onPressed: { Keys.onPressed: {
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) { if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
event.accepted = true; event.accepted = true;
@ -92,43 +89,31 @@ Popup {
event.accepted = true; event.accepted = true;
} }
} }
onTextEdited: {
completerPopup.completer.searchString = text;
}
} }
Completer { Completer {
id: completerPopup id: completerPopup
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
completerName: "room"
fullWidth: true
centerRowContent: false
avatarHeight: 24 avatarHeight: 24
avatarWidth: 24 avatarWidth: 24
bottomToTop: false bottomToTop: false
centerRowContent: false
completerName: "room"
fullWidth: true
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
} }
} }
Connections { Connections {
function onCompletionSelected(id) { function onCompletionSelected(id) {
room.forwardMessage(messageContextMenu.eventId, id); room.forwardMessage(messageContextMenu.eventId, id);
forwardMessagePopup.close(); forwardMessagePopup.close();
} }
function onCountChanged() { function onCountChanged() {
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count)) if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
completerPopup.currentIndex = 0; completerPopup.currentIndex = 0;
} }
target: completerPopup target: completerPopup
} }
background: Rectangle {
color: timelineRoot.palette.window
}
Overlay.modal: Rectangle {
color: Qt.rgba(timelineRoot.palette.window.r, timelineRoot.palette.window.g, timelineRoot.palette.window.b, 0.7)
}
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "ui"
import "./ui"
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import im.nheko import im.nheko
@ -11,36 +9,32 @@ import im.nheko
AbstractButton { AbstractButton {
id: button id: button
property alias cursor: mouseArea.cursorShape
property string image: undefined
property color highlightColor: timelineRoot.palette.highlight
property color buttonTextColor: timelineRoot.palette.placeholderText property color buttonTextColor: timelineRoot.palette.placeholderText
property bool changeColorOnHover: true property bool changeColorOnHover: true
property alias cursor: mouseArea.cursorShape
property color highlightColor: timelineRoot.palette.highlight
property string image: undefined
property bool ripple: true property bool ripple: true
focusPolicy: Qt.NoFocus focusPolicy: Qt.NoFocus
width: 16
height: 16 height: 16
width: 16
Image { Image {
id: buttonImg id: buttonImg
// Workaround, can't get icon.source working for now... // Workaround, can't get icon.source working for now...
anchors.fill: parent anchors.fill: parent
source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : ""
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : ""
} }
NhekoCursorShape { NhekoCursorShape {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
Ripple { Ripple {
enabled: button.ripple
color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5) color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5)
enabled: button.ripple
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import im.nheko import im.nheko
@ -12,27 +10,27 @@ TextEdit {
property alias cursorShape: cs.cursorShape property alias cursorShape: cs.cursorShape
textFormat: TextEdit.RichText ToolTip.text: hoveredLink
readOnly: true ToolTip.visible: hoveredLink || false
focus: false
wrapMode: Text.Wrap
selectByMouse: !Settings.mobileMode
// this always has to be enabled, otherwise you can't click links anymore! // this always has to be enabled, otherwise you can't click links anymore!
//enabled: selectByMouse //enabled: selectByMouse
color: timelineRoot.palette.text color: timelineRoot.palette.text
onLinkActivated: Nheko.openLink(link) focus: false
ToolTip.visible: hoveredLink || false readOnly: true
ToolTip.text: hoveredLink selectByMouse: !Settings.mobileMode
textFormat: TextEdit.RichText
wrapMode: Text.Wrap
// Setting a tooltip delay makes the hover text empty .-. // Setting a tooltip delay makes the hover text empty .-.
//ToolTip.delay: Nheko.tooltipDelay //ToolTip.delay: Nheko.tooltipDelay
Component.onCompleted: { Component.onCompleted: {
TimelineManager.fixImageRendering(r.textDocument, r); TimelineManager.fixImageRendering(r.textDocument, r);
} }
onLinkActivated: Nheko.openLink(link)
NhekoCursorShape { NhekoCursorShape {
id: cs id: cs
anchors.fill: parent anchors.fill: parent
cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
} }

@ -1,74 +1,66 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.12 import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import im.nheko import im.nheko
ColumnLayout { ColumnLayout {
id: c id: c
property color backgroundColor: timelineRoot.palette.base property color backgroundColor: timelineRoot.palette.base
property alias color: labelC.color property alias color: labelC.color
property alias textPadding: input.padding property alias echoMode: input.echoMode
property alias text: input.text property alias font: input.font
property alias label: labelC.text property alias label: labelC.text
property alias placeholderText: input.placeholderText property alias placeholderText: input.placeholderText
property alias font: input.font
property alias echoMode: input.echoMode
property alias selectByMouse: input.selectByMouse property alias selectByMouse: input.selectByMouse
property alias text: input.text
property alias textPadding: input.padding
Timer {
id: timer
interval: 350
onTriggered: editingFinished()
}
onTextChanged: timer.restart()
signal textEdited
signal accepted signal accepted
signal editingFinished signal editingFinished
signal textEdited
function forceActiveFocus() {
input.forceActiveFocus();
}
function clear() { function clear() {
input.clear(); input.clear();
} }
function forceActiveFocus() {
input.forceActiveFocus();
}
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.visible: hover.hovered ToolTip.visible: hover.hovered
spacing: 0 spacing: 0
onTextChanged: timer.restart()
Timer {
id: timer
interval: 350
onTriggered: editingFinished()
}
Item { Item {
Layout.bottomMargin: Nheko.paddingSmall
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: labelC.contentHeight
Layout.margins: input.padding Layout.margins: input.padding
Layout.bottomMargin: Nheko.paddingSmall Layout.preferredHeight: labelC.contentHeight
visible: labelC.text visible: labelC.text
z: 1 z: 1
Label { Label {
id: labelC id: labelC
y: contentHeight + input.padding + Nheko.paddingSmall
enabled: false
palette: timelineRoot.palette
color: timelineRoot.palette.text color: timelineRoot.palette.text
enabled: false
font.letterSpacing: input.font.pixelSize * 0.02
font.pixelSize: input.font.pixelSize font.pixelSize: input.font.pixelSize
font.weight: Font.DemiBold font.weight: Font.DemiBold
font.letterSpacing: input.font.pixelSize * 0.02 palette: timelineRoot.palette
width: parent.width
state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : "" state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : ""
width: parent.width
y: contentHeight + input.padding + Nheko.paddingSmall
states: State { states: State {
name: "focused" name: "focused"
@ -77,76 +69,63 @@ ColumnLayout {
target: labelC target: labelC
y: 0 y: 0
} }
PropertyChanges { PropertyChanges {
target: input
opacity: 1 opacity: 1
target: input
} }
} }
transitions: Transition { transitions: Transition {
from: "" from: ""
to: "focused"
reversible: true reversible: true
to: "focused"
NumberAnimation { NumberAnimation {
target: labelC alwaysRunToEnd: true
properties: "y"
duration: 210 duration: 210
easing.type: Easing.InCubic easing.type: Easing.InCubic
alwaysRunToEnd: true properties: "y"
target: labelC
} }
NumberAnimation { NumberAnimation {
target: input alwaysRunToEnd: true
properties: "opacity"
duration: 210 duration: 210
easing.type: Easing.InCubic easing.type: Easing.InCubic
alwaysRunToEnd: true properties: "opacity"
target: input
} }
} }
} }
} }
TextField { TextField {
id: input id: input
Layout.fillWidth: true Layout.fillWidth: true
palette: timelineRoot.palette
color: labelC.color color: labelC.color
opacity: labelC.text ? 0 : 1
focus: true focus: true
opacity: labelC.text ? 0 : 1
onTextEdited: c.textEdited() palette: timelineRoot.palette
onAccepted: c.accepted()
onEditingFinished: c.editingFinished()
background: Rectangle { background: Rectangle {
id: backgroundRect id: backgroundRect
color: labelC.text ? "transparent" : backgroundColor color: labelC.text ? "transparent" : backgroundColor
} }
onAccepted: c.accepted()
onEditingFinished: c.editingFinished()
onTextEdited: c.textEdited()
} }
Rectangle { Rectangle {
id: blueBar id: blueBar
Layout.fillWidth: true Layout.fillWidth: true
color: timelineRoot.palette.highlight color: timelineRoot.palette.highlight
height: 1 height: 1
Rectangle { Rectangle {
id: blackBar id: blackBar
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
height: parent.height*2 anchors.top: parent.top
width: 0
color: timelineRoot.palette.text color: timelineRoot.palette.text
height: parent.height * 2
width: 0
states: State { states: State {
name: "focused" name: "focused"
@ -156,29 +135,22 @@ ColumnLayout {
target: blackBar target: blackBar
width: blueBar.width width: blueBar.width
} }
} }
transitions: Transition { transitions: Transition {
from: "" from: ""
to: "focused"
reversible: true reversible: true
to: "focused"
NumberAnimation { NumberAnimation {
target: blackBar alwaysRunToEnd: true
properties: "width"
duration: 310 duration: 310
easing.type: Easing.InCubic easing.type: Easing.InCubic
alwaysRunToEnd: true properties: "width"
target: blackBar
} }
} }
} }
} }
HoverHandler { HoverHandler {
id: hover id: hover
enabled: c.ToolTip.text enabled: c.ToolTip.text

@ -1,10 +1,8 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "emoji"
import "./emoji" import "voip"
import "./voip"
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
@ -14,51 +12,45 @@ import im.nheko
Rectangle { Rectangle {
id: inputBar id: inputBar
color: timelineRoot.palette.window
Layout.fillWidth: true
Layout.preferredHeight: row.implicitHeight
Layout.minimumHeight: 40
property bool showAllButtons: width > 450 || (messageInput.length == 0 && !messageInput.inputMethodComposing) property bool showAllButtons: width > 450 || (messageInput.length == 0 && !messageInput.inputMethodComposing)
Layout.fillWidth: true
Layout.minimumHeight: 40
Layout.preferredHeight: row.implicitHeight
color: timelineRoot.palette.window
Component { Component {
id: placeCallDialog id: placeCallDialog
PlaceCall { PlaceCall {
} }
} }
Component { Component {
id: screenShareDialog id: screenShareDialog
ScreenShare { ScreenShare {
} }
} }
RowLayout { RowLayout {
id: row id: row
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
ImageButton { ImageButton {
visible: CallManager.callsSupported && showAllButtons
opacity: CallManager.haveCallInvite ? 0.3 : 1
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
hoverEnabled: true Layout.margins: 8
width: 22 ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call")
ToolTip.visible: hovered
height: 22 height: 22
hoverEnabled: true
image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg" image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg"
ToolTip.visible: hovered opacity: CallManager.haveCallInvite ? 0.3 : 1
ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call") visible: CallManager.callsSupported && showAllButtons
Layout.margins: 8 width: 22
onClicked: { onClicked: {
if (room) { if (room) {
if (CallManager.haveCallInvite) { if (CallManager.haveCallInvite) {
return ; return;
} else if (CallManager.isOnCall) { } else if (CallManager.isOnCall) {
CallManager.hangUp(); CallManager.hangUp();
} else { } else {
@ -69,18 +61,18 @@ Rectangle {
} }
} }
} }
ImageButton { ImageButton {
visible: showAllButtons
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
hoverEnabled: true Layout.margins: 8
width: 22 ToolTip.text: qsTr("Send a file")
ToolTip.visible: hovered
height: 22 height: 22
hoverEnabled: true
image: ":/icons/icons/ui/attach.svg" image: ":/icons/icons/ui/attach.svg"
Layout.margins: 8 visible: showAllButtons
width: 22
onClicked: room.input.openFileSelection() onClicked: room.input.openFileSelection()
ToolTip.visible: hovered
ToolTip.text: qsTr("Send a file")
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@ -91,101 +83,57 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
running: parent.visible running: parent.visible
} }
} }
} }
ScrollView { ScrollView {
id: textInput id: textInput
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
Layout.maximumHeight: Window.height / 4 Layout.maximumHeight: Window.height / 4
Layout.minimumHeight: fontMetrics.lineSpacing Layout.minimumHeight: fontMetrics.lineSpacing
Layout.preferredHeight: contentHeight Layout.preferredHeight: contentHeight
Layout.fillWidth: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
contentWidth: availableWidth contentWidth: availableWidth
TextArea { TextArea {
id: messageInput id: messageInput
property int completerTriggeredAt: 0 property int completerTriggeredAt: 0
property string lastChar
function insertCompletion(completion) { function insertCompletion(completion) {
messageInput.remove(completerTriggeredAt, cursorPosition); messageInput.remove(completerTriggeredAt, cursorPosition);
messageInput.insert(cursorPosition, completion); messageInput.insert(cursorPosition, completion);
} }
function openCompleter(pos, type) { function openCompleter(pos, type) {
if (popup.opened) return; if (popup.opened)
return;
completerTriggeredAt = pos; completerTriggeredAt = pos;
completer.completerName = type; completer.completerName = type;
popup.open(); popup.open();
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)+messageInput.preeditText); completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText);
} }
function positionCursorAtEnd() { function positionCursorAtEnd() {
cursorPosition = messageInput.length; cursorPosition = messageInput.length;
} }
function positionCursorAtStart() { function positionCursorAtStart() {
cursorPosition = 0; cursorPosition = 0;
} }
selectByMouse: true background: null
bottomPadding: 8
color: timelineRoot.palette.text
focus: true
leftPadding: inputBar.showAllButtons ? 0 : 8
padding: 0
placeholderText: qsTr("Write a message...") placeholderText: qsTr("Write a message...")
placeholderTextColor: timelineRoot.palette.placeholderText placeholderTextColor: timelineRoot.palette.placeholderText
color: timelineRoot.palette.text selectByMouse: true
width: textInput.width topPadding: 8
verticalAlignment: TextEdit.AlignVCenter verticalAlignment: TextEdit.AlignVCenter
width: textInput.width
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
padding: 0
topPadding: 8
bottomPadding: 8
leftPadding: inputBar.showAllButtons? 0 : 8
focus: true
property string lastChar
onTextChanged: {
if (room)
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
forceActiveFocus();
if (cursorPosition > 0)
lastChar = text.charAt(cursorPosition-1)
else
lastChar = ''
if (lastChar == '@') {
messageInput.openCompleter(selectionStart-1, "user");
} else if (lastChar == ':') {
messageInput.openCompleter(selectionStart-1, "emoji");
} else if (lastChar == '#') {
messageInput.openCompleter(selectionStart-1, "roomAliases");
} else if (lastChar == "~") {
messageInput.openCompleter(selectionStart-1, "customEmoji");
}
}
onCursorPositionChanged: {
if (!room)
return ;
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
if (popup.opened && cursorPosition <= completerTriggeredAt)
popup.close();
if (popup.opened)
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)+messageInput.preeditText);
}
onPreeditTextChanged: {
if (popup.opened)
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)+messageInput.preeditText);
}
onSelectionStartChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
onSelectionEndChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
// Ensure that we get escape key press events first.
Keys.onShortcutOverride: event.accepted = (popup.opened && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter || event.key === Qt.Key_Space))
Keys.onPressed: { Keys.onPressed: {
if (event.matches(StandardKey.Paste)) { if (event.matches(StandardKey.Paste)) {
room.input.paste(false); room.input.paste(false);
@ -194,10 +142,8 @@ Rectangle {
// close popup if user enters space after colon // close popup if user enters space after colon
if (cursorPosition == completerTriggeredAt + 1) if (cursorPosition == completerTriggeredAt + 1)
popup.close(); popup.close();
if (popup.opened && completer.count <= 0) if (popup.opened && completer.count <= 0)
popup.close(); popup.close();
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) { } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) {
messageInput.clear(); messageInput.clear();
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) { } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) {
@ -212,7 +158,8 @@ Rectangle {
completer.completerName = ""; completer.completerName = "";
popup.close(); popup.close();
} else if (event.matches(StandardKey.InsertLineSeparator)) { } else if (event.matches(StandardKey.InsertLineSeparator)) {
if (popup.opened) popup.close(); if (popup.opened)
popup.close();
} else if (event.matches(StandardKey.InsertParagraphSeparator)) { } else if (event.matches(StandardKey.InsertParagraphSeparator)) {
if (popup.opened) { if (popup.opened) {
var currentCompletion = completer.currentCompletion(); var currentCompletion = completer.currentCompletion();
@ -242,16 +189,16 @@ Rectangle {
console.log('"' + t + '"'); console.log('"' + t + '"');
if (t == '@') { if (t == '@') {
messageInput.openCompleter(pos, "user"); messageInput.openCompleter(pos, "user");
return ; return;
} else if (t == ' ' || t == '\t') { } else if (t == ' ' || t == '\t') {
messageInput.openCompleter(pos + 1, "user"); messageInput.openCompleter(pos + 1, "user");
return ; return;
} else if (t == ':') { } else if (t == ':') {
messageInput.openCompleter(pos, "emoji"); messageInput.openCompleter(pos, "emoji");
return ; return;
} else if (t == '~') { } else if (t == '~') {
messageInput.openCompleter(pos, "customEmoji"); messageInput.openCompleter(pos, "customEmoji");
return ; return;
} }
pos = pos - 1; pos = pos - 1;
} }
@ -301,21 +248,53 @@ Rectangle {
} }
} }
} }
background: null // Ensure that we get escape key press events first.
Keys.onShortcutOverride: event.accepted = (popup.opened && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter || event.key === Qt.Key_Space))
onCursorPositionChanged: {
if (!room)
return;
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
if (popup.opened && cursorPosition <= completerTriggeredAt)
popup.close();
if (popup.opened)
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText);
}
onPreeditTextChanged: {
if (popup.opened)
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText);
}
onSelectionEndChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
onSelectionStartChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
onTextChanged: {
if (room)
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
forceActiveFocus();
if (cursorPosition > 0)
lastChar = text.charAt(cursorPosition - 1);
else
lastChar = '';
if (lastChar == '@') {
messageInput.openCompleter(selectionStart - 1, "user");
} else if (lastChar == ':') {
messageInput.openCompleter(selectionStart - 1, "emoji");
} else if (lastChar == '#') {
messageInput.openCompleter(selectionStart - 1, "roomAliases");
} else if (lastChar == "~") {
messageInput.openCompleter(selectionStart - 1, "customEmoji");
}
}
Connections { Connections {
function onRoomChanged() { function onRoomChanged() {
messageInput.clear(); messageInput.clear();
if (room) if (room)
messageInput.append(room.input.text); messageInput.append(room.input.text);
completer.completerName = ""; completer.completerName = "";
messageInput.forceActiveFocus(); messageInput.forceActiveFocus();
} }
target: timelineView target: timelineView
} }
Connections { Connections {
function onCompletionClicked(completion) { function onCompletionClicked(completion) {
messageInput.insertCompletion(completion); messageInput.insertCompletion(completion);
@ -323,49 +302,42 @@ Rectangle {
target: completer target: completer
} }
Popup { Popup {
id: popup id: popup
x: messageInput.positionToRectangle(messageInput.completerTriggeredAt).x
y: messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height
background: null background: null
padding: 0 padding: 0
x: messageInput.positionToRectangle(messageInput.completerTriggeredAt).x
Completer { y: messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height
anchors.fill: parent
id: completer
rowMargin: 2
rowSpacing: 0
}
enter: Transition { enter: Transition {
NumberAnimation { NumberAnimation {
property: "opacity" duration: 100
from: 0 from: 0
property: "opacity"
to: 1 to: 1
duration: 100
} }
} }
exit: Transition { exit: Transition {
NumberAnimation { NumberAnimation {
property: "opacity" duration: 100
from: 1 from: 1
property: "opacity"
to: 0 to: 0
duration: 100
}
} }
} }
Completer {
id: completer
anchors.fill: parent
rowMargin: 2
rowSpacing: 0
}
}
Connections { Connections {
function onInsertText(text) { function onInsertText(text) {
messageInput.remove(messageInput.selectionStart, messageInput.selectionEnd); messageInput.remove(messageInput.selectionStart, messageInput.selectionEnd);
messageInput.insert(messageInput.cursorPosition, text); messageInput.insert(messageInput.cursorPosition, text);
} }
function onTextChanged(newText) { function onTextChanged(newText) {
messageInput.text = newText; messageInput.text = newText;
messageInput.cursorPosition = newText.length; messageInput.cursorPosition = newText.length;
@ -374,20 +346,17 @@ Rectangle {
ignoreUnknownSignals: true ignoreUnknownSignals: true
target: room ? room.input : null target: room ? room.input : null
} }
Connections { Connections {
function onReplyChanged() { function onEditChanged() {
messageInput.forceActiveFocus(); messageInput.forceActiveFocus();
} }
function onReplyChanged() {
function onEditChanged() {
messageInput.forceActiveFocus(); messageInput.forceActiveFocus();
} }
ignoreUnknownSignals: true ignoreUnknownSignals: true
target: room target: room
} }
Connections { Connections {
function onFocusInput() { function onFocusInput() {
messageInput.forceActiveFocus(); messageInput.forceActiveFocus();
@ -395,83 +364,74 @@ Rectangle {
target: TimelineManager target: TimelineManager
} }
MouseArea { MouseArea {
acceptedButtons: Qt.MiddleButton
// workaround for wrong cursor shape on some platforms // workaround for wrong cursor shape on some platforms
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.MiddleButton
cursorShape: Qt.IBeamCursor cursorShape: Qt.IBeamCursor
onClicked: room.input.paste(true) onClicked: room.input.paste(true)
} }
} }
} }
ImageButton { ImageButton {
id: stickerButton id: stickerButton
visible: showAllButtons
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
hoverEnabled: true ToolTip.text: qsTr("Stickers")
width: 22 ToolTip.visible: hovered
height: 22 height: 22
hoverEnabled: true
image: ":/icons/icons/ui/sticky-note-solid.svg" image: ":/icons/icons/ui/sticky-note-solid.svg"
ToolTip.visible: hovered visible: showAllButtons
ToolTip.text: qsTr("Stickers") width: 22
onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function(row) {
onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function (row) {
room.input.sticker(stickerPopup.model.sourceModel, row); room.input.sticker(stickerPopup.model.sourceModel, row);
TimelineManager.focusMessageInput(); TimelineManager.focusMessageInput();
}) })
StickerPicker { StickerPicker {
id: stickerPopup id: stickerPopup
colors: timelineRoot.palette colors: timelineRoot.palette
} }
} }
ImageButton { ImageButton {
id: emojiButton id: emojiButton
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
hoverEnabled: true ToolTip.text: qsTr("Emoji")
width: 22 ToolTip.visible: hovered
height: 22 height: 22
hoverEnabled: true
image: ":/icons/icons/ui/smile.svg" image: ":/icons/icons/ui/smile.svg"
ToolTip.visible: hovered width: 22
ToolTip.text: qsTr("Emoji")
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) { onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function (emoji) {
messageInput.insert(messageInput.cursorPosition, emoji); messageInput.insert(messageInput.cursorPosition, emoji);
TimelineManager.focusMessageInput(); TimelineManager.focusMessageInput();
}) })
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
hoverEnabled: true
width: 22
height: 22
image: ":/icons/icons/ui/send.svg"
Layout.rightMargin: 8 Layout.rightMargin: 8
ToolTip.visible: hovered
ToolTip.text: qsTr("Send") ToolTip.text: qsTr("Send")
ToolTip.visible: hovered
height: 22
hoverEnabled: true
image: ":/icons/icons/ui/send.svg"
width: 22
onClicked: { onClicked: {
room.input.send(); room.input.send();
} }
} }
} }
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
visible: room ? (!room.permissions.canSend(MtxEvent.TextMessage)) : false
text: qsTr("You don't have permission to send messages in this room")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("You don't have permission to send messages in this room")
visible: room ? (!room.permissions.canSend(MtxEvent.TextMessage)) : false
} }
} }

File diff suppressed because it is too large Load Diff

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
@ -10,38 +8,41 @@ import im.nheko
BusyIndicator { BusyIndicator {
id: control id: control
contentItem: Item { contentItem: Item {
implicitWidth: 64
implicitHeight: 64 implicitHeight: 64
implicitWidth: 64
Item { Item {
id: item id: item
height: Math.min(parent.height, parent.width) height: Math.min(parent.height, parent.width)
width: height
opacity: control.running ? 1 : 0 opacity: control.running ? 1 : 0
width: height
Behavior on opacity {
OpacityAnimator {
duration: 250
}
}
RotationAnimator { RotationAnimator {
target: item duration: 2000
running: control.visible && control.running
from: 0 from: 0
to: 360
loops: Animation.Infinite loops: Animation.Infinite
duration: 2000 running: control.visible && control.running
target: item
to: 360
} }
Repeater { Repeater {
id: repeater id: repeater
model: 6 model: 6
Rectangle { Rectangle {
implicitWidth: radius * 2
implicitHeight: radius * 2
radius: item.height / 6
color: timelineRoot.palette.text color: timelineRoot.palette.text
implicitHeight: radius * 2
implicitWidth: radius * 2
opacity: (index + 2) / (repeater.count + 2) opacity: (index + 2) / (repeater.count + 2)
radius: item.height / 6
transform: [ transform: [
Translate { Translate {
y: -Math.min(item.width, item.height) * 0.5 + item.height / 6 y: -Math.min(item.width, item.height) * 0.5 + item.height / 6
@ -53,18 +54,7 @@ BusyIndicator {
} }
] ]
} }
}
Behavior on opacity {
OpacityAnimator {
duration: 250
}
} }
} }
} }
} }

@ -1,39 +1,33 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import im.nheko import im.nheko
Item { Item {
implicitHeight: warningRect.visible ? warningDisplay.implicitHeight : 0
height: implicitHeight
Layout.fillWidth: true Layout.fillWidth: true
height: implicitHeight
implicitHeight: warningRect.visible ? warningDisplay.implicitHeight : 0
Rectangle { Rectangle {
id: warningRect id: warningRect
visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom)
color: timelineRoot.palette.base
anchors.fill: parent anchors.fill: parent
color: timelineRoot.palette.base
visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom)
z: 3 z: 3
Label { Label {
id: warningDisplay id: warningDisplay
anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 10 anchors.rightMargin: 10
anchors.bottom: parent.bottom
color: Nheko.theme.red color: Nheko.theme.red
text: qsTr("You are about to notify the whole room") text: qsTr("You are about to notify the whole room")
textFormat: Text.PlainText textFormat: Text.PlainText
} }
} }
} }

@ -1,6 +1,5 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
//import QtGraphicalEffects 1.0 //import QtGraphicalEffects 1.0
@ -11,8 +10,8 @@ import im.nheko
Item { Item {
id: privacyScreen id: privacyScreen
property var timelineRoot
property int screenTimeout property int screenTimeout
property var timelineRoot
Connections { Connections {
function onFocusChanged() { function onFocusChanged() {
@ -22,29 +21,26 @@ Item {
} else { } else {
if (timelineRoot.visible) if (timelineRoot.visible)
screenSaverTimer.start(); screenSaverTimer.start();
} }
} }
target: TimelineManager target: TimelineManager
} }
Timer { Timer {
id: screenSaverTimer id: screenSaverTimer
interval: screenTimeout * 1000 interval: screenTimeout * 1000
running: !MainWindow.active running: !MainWindow.active
onTriggered: { onTriggered: {
screenSaver.state = "Visible"; screenSaver.state = "Visible";
} }
} }
Item { Item {
id: screenSaver id: screenSaver
state: "Invisible"
anchors.fill: parent anchors.fill: parent
state: "Invisible"
visible: false visible: false
states: [ states: [
State { State {
name: "Visible" name: "Visible"
@ -53,26 +49,22 @@ Item {
target: screenSaver target: screenSaver
visible: true visible: true
} }
PropertyChanges { PropertyChanges {
target: screenSaver
opacity: 1 opacity: 1
target: screenSaver
} }
}, },
State { State {
name: "Invisible" name: "Invisible"
PropertyChanges { PropertyChanges {
target: screenSaver
opacity: 0 opacity: 0
target: screenSaver
} }
PropertyChanges { PropertyChanges {
target: screenSaver target: screenSaver
visible: false visible: false
} }
} }
] ]
transitions: [ transitions: [
@ -82,20 +74,17 @@ Item {
SequentialAnimation { SequentialAnimation {
NumberAnimation { NumberAnimation {
target: screenSaver
property: "opacity"
duration: 250 duration: 250
easing.type: Easing.InQuad easing.type: Easing.InQuad
property: "opacity"
target: screenSaver
} }
NumberAnimation { NumberAnimation {
target: screenSaver
property: "visible"
duration: 0 duration: 0
property: "visible"
target: screenSaver
} }
} }
}, },
Transition { Transition {
from: "Invisible" from: "Invisible"
@ -103,20 +92,17 @@ Item {
SequentialAnimation { SequentialAnimation {
NumberAnimation { NumberAnimation {
target: screenSaver
property: "visible"
duration: 0 duration: 0
property: "visible"
target: screenSaver
} }
NumberAnimation { NumberAnimation {
target: screenSaver
property: "opacity"
duration: 500 duration: 500
easing.type: Easing.InQuad easing.type: Easing.InQuad
property: "opacity"
target: screenSaver
} }
} }
} }
] ]
@ -127,7 +113,5 @@ Item {
// source: timelineRoot // source: timelineRoot
// radius: 50 // radius: 50
//} //}
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
@ -12,33 +10,35 @@ Popup {
id: quickSwitcher id: quickSwitcher
property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4) property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
property int textMargin: Nheko.paddingSmall
background: null background: null
width: Math.min(Math.max(Math.round(parent.width / 2),450),parent.width) // limiting width to parent.width/2 can be a bit narrow
x: Math.round(parent.width / 2 - contentWidth / 2)
y: Math.round(parent.height / 4)
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
parent: Overlay.overlay modal: true
palette: timelineRoot.palette palette: timelineRoot.palette
parent: Overlay.overlay
width: Math.min(Math.max(Math.round(parent.width / 2), 450), parent.width) // limiting width to parent.width/2 can be a bit narrow
x: Math.round(parent.width / 2 - contentWidth / 2)
y: Math.round(parent.height / 4)
Overlay.modal: Rectangle {
color: "#aa1E1E1E"
}
onOpened: { onOpened: {
roomTextInput.forceActiveFocus(); roomTextInput.forceActiveFocus();
} }
property int textMargin: Nheko.paddingSmall
Column{ Column {
anchors.fill: parent anchors.fill: parent
spacing: 1 spacing: 1
MatrixTextField { MatrixTextField {
id: roomTextInput id: roomTextInput
width: parent.width
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
color: timelineRoot.palette.text color: timelineRoot.palette.text
onTextEdited: { font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
completerPopup.completer.searchString = text; width: parent.width
}
Keys.onPressed: { Keys.onPressed: {
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) { if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
event.accepted = true; event.accepted = true;
@ -54,41 +54,34 @@ Popup {
event.accepted = true; event.accepted = true;
} }
} }
onTextEdited: {
completerPopup.completer.searchString = text;
}
} }
Completer { Completer {
id: completerPopup id: completerPopup
visible: roomTextInput.text.length > 0
width: parent.width
completerName: "room"
bottomToTop: false
fullWidth: true
avatarHeight: quickSwitcher.textHeight avatarHeight: quickSwitcher.textHeight
avatarWidth: quickSwitcher.textHeight avatarWidth: quickSwitcher.textHeight
bottomToTop: false
centerRowContent: false centerRowContent: false
completerName: "room"
fullWidth: true
rowMargin: Math.round(quickSwitcher.textMargin / 2) rowMargin: Math.round(quickSwitcher.textMargin / 2)
rowSpacing: quickSwitcher.textMargin rowSpacing: quickSwitcher.textMargin
visible: roomTextInput.text.length > 0
width: parent.width
} }
} }
Connections { Connections {
function onCompletionSelected(id) { function onCompletionSelected(id) {
Rooms.setCurrentRoom(id); Rooms.setCurrentRoom(id);
quickSwitcher.close(); quickSwitcher.close();
} }
function onCountChanged() { function onCountChanged() {
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count)) if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
completerPopup.currentIndex = 0; completerPopup.currentIndex = 0;
} }
target: completerPopup target: completerPopup
} }
Overlay.modal: Rectangle {
color: "#aa1E1E1E"
}
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.6 import QtQuick 2.6
import QtQuick.Controls 2.2 import QtQuick.Controls 2.2
import im.nheko import im.nheko
@ -12,58 +10,54 @@ import im.nheko
Flow { Flow {
id: reactionFlow id: reactionFlow
property string eventId
// highlight colors for selfReactedEvent background // highlight colors for selfReactedEvent background
property real highlightHue: timelineRoot.palette.highlight.hslHue property real highlightHue: timelineRoot.palette.highlight.hslHue
property real highlightSat: timelineRoot.palette.highlight.hslSaturation
property real highlightLight: timelineRoot.palette.highlight.hslLightness property real highlightLight: timelineRoot.palette.highlight.hslLightness
property string eventId property real highlightSat: timelineRoot.palette.highlight.hslSaturation
property alias reactions: repeater.model property alias reactions: repeater.model
spacing: 4 spacing: 4
Repeater { Repeater {
id: repeater id: repeater
delegate: AbstractButton { delegate: AbstractButton {
id: reaction id: reaction
ToolTip.delay: Nheko.tooltipDelay
ToolTip.visible: hovered
hoverEnabled: true hoverEnabled: true
implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding * 2
implicitHeight: contentItem.childrenRect.height implicitHeight: contentItem.childrenRect.height
ToolTip.visible: hovered implicitWidth: contentItem.childrenRect.width + contentItem.leftPadding * 2
ToolTip.delay: Nheko.tooltipDelay
onClicked: {
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent);
room.input.reaction(reactionFlow.eventId, modelData.key);
}
Component.onCompleted: {
ToolTip.text = Qt.binding(function() {
if (textMetrics.elidedText === textMetrics.text) {
return modelData.users;
}
return modelData.displayKey + "\n" + modelData.users;
})
}
background: Rectangle {
anchors.centerIn: parent
border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? timelineRoot.palette.highlight : timelineRoot.palette.text
border.width: 1
color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : timelineRoot.palette.window
implicitHeight: reaction.implicitHeight
implicitWidth: reaction.implicitWidth
radius: reaction.height / 2
}
contentItem: Row { contentItem: Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: reactionText.implicitHeight / 4
leftPadding: reactionText.implicitHeight / 2 leftPadding: reactionText.implicitHeight / 2
rightPadding: reactionText.implicitHeight / 2 rightPadding: reactionText.implicitHeight / 2
spacing: reactionText.implicitHeight / 4
TextMetrics { TextMetrics {
id: textMetrics id: textMetrics
font.family: Settings.emojiFont
elide: Text.ElideRight elide: Text.ElideRight
elideWidth: 150 elideWidth: 150
font.family: Settings.emojiFont
text: modelData.displayKey text: modelData.displayKey
} }
Text { Text {
id: reactionText id: reactionText
anchors.baseline: reactionCounter.baseline anchors.baseline: reactionCounter.baseline
color: reaction.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.text
font.family: Settings.emojiFont
maximumLineCount: 1
text: { text: {
// When an emoji font is selected that doesn't have , it is dropped from elidedText. So we add it back. // When an emoji font is selected that doesn't have , it is dropped from elidedText. So we add it back.
if (textMetrics.elidedText !== modelData.displayKey) { if (textMetrics.elidedText !== modelData.displayKey) {
@ -73,42 +67,34 @@ Flow {
} }
return textMetrics.elidedText; return textMetrics.elidedText;
} }
font.family: Settings.emojiFont
color: reaction.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.text
maximumLineCount: 1
} }
Rectangle { Rectangle {
id: divider id: divider
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? timelineRoot.palette.highlight : timelineRoot.palette.text
height: Math.floor(reactionCounter.implicitHeight * 1.4) height: Math.floor(reactionCounter.implicitHeight * 1.4)
width: 1 width: 1
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? timelineRoot.palette.highlight : timelineRoot.palette.text
} }
Text { Text {
id: reactionCounter id: reactionCounter
anchors.verticalCenter: divider.verticalCenter anchors.verticalCenter: divider.verticalCenter
text: modelData.count
font: reaction.font
color: reaction.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.text color: reaction.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.text
font: reaction.font
text: modelData.count
} }
} }
background: Rectangle { Component.onCompleted: {
anchors.centerIn: parent ToolTip.text = Qt.binding(function () {
implicitWidth: reaction.implicitWidth if (textMetrics.elidedText === textMetrics.text) {
implicitHeight: reaction.implicitHeight return modelData.users;
border.color: (reaction.hovered || modelData.selfReactedEvent !== '') ? timelineRoot.palette.highlight : timelineRoot.palette.text }
color: modelData.selfReactedEvent !== '' ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : timelineRoot.palette.window return modelData.displayKey + "\n" + modelData.users;
border.width: 1 });
radius: reaction.height / 2 }
onClicked: {
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent);
room.input.reaction(reactionFlow.eventId, modelData.key);
} }
} }
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "delegates"
import "./delegates/"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
@ -11,76 +9,71 @@ import im.nheko
Rectangle { Rectangle {
id: replyPopup id: replyPopup
Layout.fillWidth: true Layout.fillWidth: true
visible: room && (room.reply || room.edit) color: timelineRoot.palette.window
// Height of child, plus margins, plus border // Height of child, plus margins, plus border
implicitHeight: (room && room.reply ? replyPreview.height : closeEditButton.height) + Nheko.paddingSmall implicitHeight: (room && room.reply ? replyPreview.height : closeEditButton.height) + Nheko.paddingSmall
color: timelineRoot.palette.window visible: room && (room.reply || room.edit)
z: 3 z: 3
Reply { Reply {
id: replyPreview id: replyPreview
property var modelData: room ? room.getDump(room.reply, room.id) : { property var modelData: room ? room.getDump(room.reply, room.id) : {}
}
visible: room && room.reply
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: replyPopup.width < 450? Nheko.paddingSmall : (CallManager.callsSupported? 2*(22+16) : 1*(22+16)) anchors.leftMargin: replyPopup.width < 450 ? Nheko.paddingSmall : (CallManager.callsSupported ? 2 * (22 + 16) : 1 * (22 + 16))
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: replyPopup.width < 450? 2*(22+16) : 3*(22+16) anchors.rightMargin: replyPopup.width < 450 ? 2 * (22 + 16) : 3 * (22 + 16)
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: Nheko.paddingSmall anchors.topMargin: Nheko.paddingSmall
userColor: TimelineManager.userColor(modelData.userId, timelineRoot.palette.window)
blurhash: modelData.blurhash ?? "" blurhash: modelData.blurhash ?? ""
body: modelData.body ?? "" body: modelData.body ?? ""
formattedBody: modelData.formattedBody ?? "" encryptionError: modelData.encryptionError ?? 0
eventId: modelData.eventId ?? "" eventId: modelData.eventId ?? ""
filename: modelData.filename ?? "" filename: modelData.filename ?? ""
filesize: modelData.filesize ?? "" filesize: modelData.filesize ?? ""
formattedBody: modelData.formattedBody ?? ""
isOnlyEmoji: modelData.isOnlyEmoji ?? false
originalWidth: modelData.originalWidth ?? 0
proportionalHeight: modelData.proportionalHeight ?? 1 proportionalHeight: modelData.proportionalHeight ?? 1
type: modelData.type ?? MtxEvent.UnknownMessage type: modelData.type ?? MtxEvent.UnknownMessage
typeString: modelData.typeString ?? "" typeString: modelData.typeString ?? ""
url: modelData.url ?? "" url: modelData.url ?? ""
originalWidth: modelData.originalWidth ?? 0 userColor: TimelineManager.userColor(modelData.userId, timelineRoot.palette.window)
isOnlyEmoji: modelData.isOnlyEmoji ?? false
userId: modelData.userId ?? "" userId: modelData.userId ?? ""
userName: modelData.userName ?? "" userName: modelData.userName ?? ""
encryptionError: modelData.encryptionError ?? 0 visible: room && room.reply
width: parent.width width: parent.width
} }
ImageButton { ImageButton {
id: closeReplyButton id: closeReplyButton
ToolTip.text: qsTr("Close")
visible: room && room.reply ToolTip.visible: closeReplyButton.hovered
anchors.margins: Nheko.paddingSmall
anchors.right: replyPreview.right anchors.right: replyPreview.right
anchors.top: replyPreview.top anchors.top: replyPreview.top
anchors.margins: Nheko.paddingSmall
hoverEnabled: true
width: 16
height: 16 height: 16
hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg" image: ":/icons/icons/ui/dismiss.svg"
ToolTip.visible: closeReplyButton.hovered visible: room && room.reply
ToolTip.text: qsTr("Close") width: 16
onClicked: room.reply = undefined onClicked: room.reply = undefined
} }
ImageButton { ImageButton {
id: closeEditButton id: closeEditButton
ToolTip.text: qsTr("Cancel Edit")
visible: room && room.edit ToolTip.visible: closeEditButton.hovered
anchors.right: parent.right
anchors.margins: 8 anchors.margins: 8
anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
height: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/dismiss_edit.svg" image: ":/icons/icons/ui/dismiss_edit.svg"
visible: room && room.edit
width: 22 width: 22
height: 22
ToolTip.visible: closeEditButton.hovered
ToolTip.text: qsTr("Cancel Edit")
onClicked: room.edit = undefined onClicked: room.edit = undefined
} }
} }

@ -1,15 +1,13 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "delegates"
import "./delegates" import "device-verification"
import "./device-verification" import "dialogs"
import "./dialogs" import "emoji"
import "./emoji" import "pages"
import "./pages" import "voip"
import "./voip" import "ui"
import "./ui"
import Qt.labs.platform 1.1 as Platform import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
@ -19,6 +17,15 @@ import im.nheko
Pane { Pane {
id: timelineRoot id: timelineRoot
function destroyOnClose(obj) {
if (obj.closing != undefined)
obj.closing.connect(() => obj.destroy(1000));
else if (obj.aboutToHide != undefined)
obj.aboutToHide.connect(() => obj.destroy(1000));
}
function destroyOnClosed(obj) {
obj.aboutToHide.connect(() => obj.destroy(1000));
}
background: null background: null
padding: 0 padding: 0
@ -33,180 +40,130 @@ Pane {
// running: true // running: true
// repeat: true // repeat: true
//} //}
EmojiPicker { EmojiPicker {
id: emojiPopup id: emojiPopup
colors: palette colors: palette
model: TimelineManager.completerFor("allemoji", "") model: TimelineManager.completerFor("allemoji", "")
} }
Component { Component {
id: userProfileComponent id: userProfileComponent
UserProfile { UserProfile {
} }
} }
Component { Component {
id: roomSettingsComponent id: roomSettingsComponent
RoomSettings { RoomSettings {
} }
} }
Component { Component {
id: roomMembersComponent id: roomMembersComponent
RoomMembers { RoomMembers {
} }
} }
Component { Component {
id: mobileCallInviteDialog id: mobileCallInviteDialog
CallInvite { CallInvite {
} }
} }
Component { Component {
id: quickSwitcherComponent id: quickSwitcherComponent
QuickSwitcher { QuickSwitcher {
} }
} }
Component { Component {
id: deviceVerificationDialog id: deviceVerificationDialog
DeviceVerification { DeviceVerification {
} }
} }
Component { Component {
id: inviteDialog id: inviteDialog
InviteDialog { InviteDialog {
} }
} }
Component { Component {
id: packSettingsComponent id: packSettingsComponent
ImagePackSettingsDialog { ImagePackSettingsDialog {
} }
} }
Component { Component {
id: readReceiptsDialog id: readReceiptsDialog
ReadReceipts { ReadReceipts {
} }
} }
Component { Component {
id: rawMessageDialog id: rawMessageDialog
RawMessageDialog { RawMessageDialog {
} }
} }
Component { Component {
id: logoutDialog id: logoutDialog
LogoutDialog { LogoutDialog {
} }
} }
Component { Component {
id: joinRoomDialog id: joinRoomDialog
JoinRoomDialog { JoinRoomDialog {
} }
} }
Component { Component {
id: leaveRoomComponent id: leaveRoomComponent
LeaveRoomDialog { LeaveRoomDialog {
} }
} }
Component { Component {
id: imageOverlay id: imageOverlay
ImageOverlay { ImageOverlay {
} }
} }
Component { Component {
id: userSettingsPage id: userSettingsPage
UserSettingsPage { UserSettingsPage {
} }
} }
Shortcut { Shortcut {
sequence: StandardKey.Quit sequence: StandardKey.Quit
onActivated: Qt.quit() onActivated: Qt.quit()
} }
Shortcut { Shortcut {
sequence: "Ctrl+K" sequence: "Ctrl+K"
onActivated: { onActivated: {
var quickSwitch = quickSwitcherComponent.createObject(timelineRoot); var quickSwitch = quickSwitcherComponent.createObject(timelineRoot);
quickSwitch.open(); quickSwitch.open();
destroyOnClosed(quickSwitch); destroyOnClosed(quickSwitch);
} }
} }
Shortcut { Shortcut {
// Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit // Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit
sequences: ["Alt+A", "Ctrl+Shift+A"] sequences: ["Alt+A", "Ctrl+Shift+A"]
onActivated: Rooms.nextRoomWithActivity() onActivated: Rooms.nextRoomWithActivity()
} }
Shortcut { Shortcut {
sequence: "Ctrl+Down" sequence: "Ctrl+Down"
onActivated: Rooms.nextRoom() onActivated: Rooms.nextRoom()
} }
Shortcut { Shortcut {
sequence: "Ctrl+Up" sequence: "Ctrl+Up"
onActivated: Rooms.previousRoom() onActivated: Rooms.previousRoom()
} }
Connections { Connections {
function onOpenLogoutDialog() {
var dialog = logoutDialog.createObject(timelineRoot);
dialog.open();
destroyOnClose(dialog);
}
function onOpenJoinRoomDialog() { function onOpenJoinRoomDialog() {
var dialog = joinRoomDialog.createObject(timelineRoot); var dialog = joinRoomDialog.createObject(timelineRoot);
dialog.show(); dialog.show();
destroyOnClose(dialog); destroyOnClose(dialog);
} }
function onOpenLogoutDialog() {
var dialog = logoutDialog.createObject(timelineRoot);
dialog.open();
destroyOnClose(dialog);
}
target: Nheko target: Nheko
} }
Connections { Connections {
function onNewDeviceVerificationRequest(flow) { function onNewDeviceVerificationRequest(flow) {
var dialog = deviceVerificationDialog.createObject(timelineRoot, { var dialog = deviceVerificationDialog.createObject(timelineRoot, {
@ -218,17 +175,24 @@ Pane {
target: VerificationManager target: VerificationManager
} }
Connections {
function destroyOnClose(obj) { function onOpenInviteUsersDialog(invitees) {
if (obj.closing != undefined) obj.closing.connect(() => obj.destroy(1000)); var dialog = inviteDialog.createObject(timelineRoot, {
else if (obj.aboutToHide != undefined) obj.aboutToHide.connect(() => obj.destroy(1000)); "roomId": Rooms.currentRoom.roomId,
"plainRoomName": Rooms.currentRoom.plainRoomName,
"invitees": invitees
});
dialog.show();
destroyOnClose(dialog);
} }
function onOpenLeaveRoomDialog(roomid, reason) {
function destroyOnClosed(obj) { var dialog = leaveRoomComponent.createObject(timelineRoot, {
obj.aboutToHide.connect(() => obj.destroy(1000)); "roomId": roomid,
"reason": reason
});
dialog.open();
destroyOnClose(dialog);
} }
Connections {
function onOpenProfile(profile) { function onOpenProfile(profile) {
var userProfile = userProfileComponent.createObject(timelineRoot, { var userProfile = userProfileComponent.createObject(timelineRoot, {
"profile": profile "profile": profile
@ -236,16 +200,6 @@ Pane {
userProfile.show(); userProfile.show();
destroyOnClose(userProfile); destroyOnClose(userProfile);
} }
function onShowImagePackSettings(room, packlist) {
var packSet = packSettingsComponent.createObject(timelineRoot, {
"room": room,
"packlist": packlist
});
packSet.show();
destroyOnClose(packSet);
}
function onOpenRoomMembersDialog(members, room) { function onOpenRoomMembersDialog(members, room) {
var membersDialog = roomMembersComponent.createObject(timelineRoot, { var membersDialog = roomMembersComponent.createObject(timelineRoot, {
"members": members, "members": members,
@ -254,7 +208,6 @@ Pane {
membersDialog.show(); membersDialog.show();
destroyOnClose(membersDialog); destroyOnClose(membersDialog);
} }
function onOpenRoomSettingsDialog(settings) { function onOpenRoomSettingsDialog(settings) {
var roomSettings = roomSettingsComponent.createObject(timelineRoot, { var roomSettings = roomSettingsComponent.createObject(timelineRoot, {
"roomSettings": settings "roomSettings": settings
@ -262,26 +215,6 @@ Pane {
roomSettings.show(); roomSettings.show();
destroyOnClose(roomSettings); destroyOnClose(roomSettings);
} }
function onOpenInviteUsersDialog(invitees) {
var dialog = inviteDialog.createObject(timelineRoot, {
"roomId": Rooms.currentRoom.roomId,
"plainRoomName": Rooms.currentRoom.plainRoomName,
"invitees": invitees
});
dialog.show();
destroyOnClose(dialog);
}
function onOpenLeaveRoomDialog(roomid, reason) {
var dialog = leaveRoomComponent.createObject(timelineRoot, {
"roomId": roomid,
"reason": reason
});
dialog.open();
destroyOnClose(dialog);
}
function onShowImageOverlay(room, eventId, url, proportionalHeight, originalWidth) { function onShowImageOverlay(room, eventId, url, proportionalHeight, originalWidth) {
var dialog = imageOverlay.createObject(timelineRoot, { var dialog = imageOverlay.createObject(timelineRoot, {
"room": room, "room": room,
@ -293,10 +226,17 @@ Pane {
dialog.showFullScreen(); dialog.showFullScreen();
destroyOnClose(dialog); destroyOnClose(dialog);
} }
function onShowImagePackSettings(room, packlist) {
var packSet = packSettingsComponent.createObject(timelineRoot, {
"room": room,
"packlist": packlist
});
packSet.show();
destroyOnClose(packSet);
}
target: TimelineManager target: TimelineManager
} }
Connections { Connections {
function onNewInviteState() { function onNewInviteState() {
if (CallManager.haveCallInvite && Settings.mobileMode) { if (CallManager.haveCallInvite && Settings.mobileMode) {
@ -308,144 +248,122 @@ Pane {
target: CallManager target: CallManager
} }
SelfVerificationCheck { SelfVerificationCheck {
} }
InputDialog { InputDialog {
id: uiaPassPrompt id: uiaPassPrompt
echoMode: TextInput.Password echoMode: TextInput.Password
title: UIA.title
prompt: qsTr("Please enter your login password to continue:") prompt: qsTr("Please enter your login password to continue:")
onAccepted: (t) => { title: UIA.title
onAccepted: t => {
return UIA.continuePassword(t); return UIA.continuePassword(t);
} }
} }
InputDialog { InputDialog {
id: uiaEmailPrompt id: uiaEmailPrompt
title: UIA.title
prompt: qsTr("Please enter a valid email address to continue:") prompt: qsTr("Please enter a valid email address to continue:")
onAccepted: (t) => { title: UIA.title
onAccepted: t => {
return UIA.continueEmail(t); return UIA.continueEmail(t);
} }
} }
PhoneNumberInputDialog { PhoneNumberInputDialog {
id: uiaPhoneNumberPrompt id: uiaPhoneNumberPrompt
title: UIA.title
prompt: qsTr("Please enter a valid phone number to continue:") prompt: qsTr("Please enter a valid phone number to continue:")
title: UIA.title
onAccepted: (p, t) => { onAccepted: (p, t) => {
return UIA.continuePhoneNumber(p, t); return UIA.continuePhoneNumber(p, t);
} }
} }
InputDialog { InputDialog {
id: uiaTokenPrompt id: uiaTokenPrompt
title: UIA.title
prompt: qsTr("Please enter the token, which has been sent to you:") prompt: qsTr("Please enter the token, which has been sent to you:")
onAccepted: (t) => { title: UIA.title
onAccepted: t => {
return UIA.submit3pidToken(t); return UIA.submit3pidToken(t);
} }
} }
Platform.MessageDialog { Platform.MessageDialog {
id: uiaErrorDialog id: uiaErrorDialog
buttons: Platform.MessageDialog.Ok buttons: Platform.MessageDialog.Ok
} }
Platform.MessageDialog { Platform.MessageDialog {
id: uiaConfirmationLinkDialog id: uiaConfirmationLinkDialog
buttons: Platform.MessageDialog.Ok buttons: Platform.MessageDialog.Ok
text: qsTr("Wait for the confirmation link to arrive, then continue.") text: qsTr("Wait for the confirmation link to arrive, then continue.")
onAccepted: UIA.continue3pidReceived() onAccepted: UIA.continue3pidReceived()
} }
Connections { Connections {
function onPassword() { function onConfirm3pidToken() {
console.log("UIA: password needed"); uiaConfirmationLinkDialog.open();
uiaPassPrompt.show();
} }
function onEmail() { function onEmail() {
uiaEmailPrompt.show(); uiaEmailPrompt.show();
} }
function onError(msg) {
uiaErrorDialog.text = msg;
uiaErrorDialog.open();
}
function onPassword() {
console.log("UIA: password needed");
uiaPassPrompt.show();
}
function onPhoneNumber() { function onPhoneNumber() {
uiaPhoneNumberPrompt.show(); uiaPhoneNumberPrompt.show();
} }
function onPrompt3pidToken() { function onPrompt3pidToken() {
uiaTokenPrompt.show(); uiaTokenPrompt.show();
} }
function onConfirm3pidToken() {
uiaConfirmationLinkDialog.open();
}
function onError(msg) {
uiaErrorDialog.text = msg;
uiaErrorDialog.open();
}
target: UIA target: UIA
} }
StackView { StackView {
id: mainWindow id: mainWindow
anchors.fill: parent anchors.fill: parent
initialItem: welcomePage initialItem: welcomePage
} }
Component { Component {
id: welcomePage id: welcomePage
WelcomePage { WelcomePage {
} }
} }
Component { Component {
id: chatPage id: chatPage
ChatPage { ChatPage {
} }
} }
Component { Component {
id: loginPage id: loginPage
LoginPage { LoginPage {
} }
} }
Component { Component {
id: registerPage id: registerPage
RegisterPage { RegisterPage {
} }
} }
Snackbar {
Snackbar { id: snackbar } id: snackbar
}
Connections { Connections {
function onShowNotification(msg) {
snackbar.showNotification(msg);
console.log("New snack: " + msg);
}
function onSwitchToChatPage() { function onSwitchToChatPage() {
mainWindow.replace(null, chatPage); mainWindow.replace(null, chatPage);
} }
function onSwitchToLoginPage(error) { function onSwitchToLoginPage(error) {
mainWindow.replace(welcomePage, {}, loginPage, {"error": error}, StackView.PopTransition); mainWindow.replace(welcomePage, {}, loginPage, {
} "error": error
function onShowNotification(msg) { }, StackView.PopTransition);
snackbar.showNotification(msg);
console.log("New snack: " + msg);
} }
target: MainWindow target: MainWindow
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "components"
import "./components/"
import Qt.labs.platform 1.1 as P import Qt.labs.platform 1.1 as P
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
@ -11,68 +9,61 @@ import QtQuick.Layouts 1.3
import im.nheko import im.nheko
Item { Item {
visible: false
enabled: false enabled: false
visible: false
Dialog { Dialog {
id: showRecoverKeyDialog id: showRecoverKeyDialog
property string recoveryKey: "" property string recoveryKey: ""
parent: Overlay.overlay
anchors.centerIn: parent anchors.centerIn: parent
closePolicy: Popup.NoAutoClose
height: content.height + implicitFooterHeight + implicitHeaderHeight height: content.height + implicitFooterHeight + implicitHeaderHeight
width: content.width
padding: 0
modal: true modal: true
padding: 0
parent: Overlay.overlay
standardButtons: Dialog.Ok standardButtons: Dialog.Ok
closePolicy: Popup.NoAutoClose width: content.width
background: Rectangle {
border.color: Nheko.theme.separator
border.width: 1
color: timelineRoot.palette.window
radius: Nheko.paddingSmall
}
ColumnLayout { ColumnLayout {
id: content id: content
spacing: 0 spacing: 0
Label { Label {
Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4 Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4
Layout.fillWidth: true
text: qsTr("This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200!")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200!")
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
TextEdit { TextEdit {
Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4
color: timelineRoot.palette.text
font.bold: true
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
verticalAlignment: TextEdit.AlignVCenter
readOnly: true readOnly: true
selectByMouse: true selectByMouse: true
text: showRecoverKeyDialog.recoveryKey text: showRecoverKeyDialog.recoveryKey
color: timelineRoot.palette.text verticalAlignment: TextEdit.AlignVCenter
font.bold: true
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
}
background: Rectangle {
color: timelineRoot.palette.window
border.color: Nheko.theme.separator
border.width: 1
radius: Nheko.paddingSmall
} }
} }
P.MessageDialog { P.MessageDialog {
id: successDialog id: successDialog
buttons: P.MessageDialog.Ok buttons: P.MessageDialog.Ok
text: qsTr("Encryption setup successfully") text: qsTr("Encryption setup successfully")
} }
P.MessageDialog { P.MessageDialog {
id: failureDialog id: failureDialog
@ -81,199 +72,185 @@ Item {
buttons: P.MessageDialog.Ok buttons: P.MessageDialog.Ok
text: qsTr("Failed to setup encryption: %1").arg(errorMessage) text: qsTr("Failed to setup encryption: %1").arg(errorMessage)
} }
MainWindowDialog { MainWindowDialog {
id: bootstrapCrosssigning id: bootstrapCrosssigning
background: Rectangle {
border.color: Nheko.theme.separator
border.width: 1
color: timelineRoot.palette.window
radius: Nheko.paddingSmall
}
onAccepted: SelfVerificationStatus.setupCrosssigning(storeSecretsOnline.checked, usePassword.checked ? passwordField.text : "", useOnlineKeyBackup.checked) onAccepted: SelfVerificationStatus.setupCrosssigning(storeSecretsOnline.checked, usePassword.checked ? passwordField.text : "", useOnlineKeyBackup.checked)
GridLayout { GridLayout {
id: grid id: grid
columnSpacing: 0
width: bootstrapCrosssigning.useableWidth
columns: 2 columns: 2
rowSpacing: 0 rowSpacing: 0
columnSpacing: 0 width: bootstrapCrosssigning.useableWidth
z: 1 z: 1
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.margins: Nheko.paddingMedium
color: timelineRoot.palette.text
font.pointSize: fontMetrics.font.pointSize * 2 font.pointSize: fontMetrics.font.pointSize * 2
text: qsTr("Setup Encryption") text: qsTr("Setup Encryption")
color: timelineRoot.palette.text
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2
text: qsTr("Hello and welcome to Matrix!\nIt seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Hello and welcome to Matrix!\nIt seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!")
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.columnSpan: 1 Layout.columnSpan: 1
Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
text: "Store secrets online.\nYou have a few secrets to make all the encryption magic work. While you can keep them stored only locally, we recommend storing them encrypted on the server. Otherwise it will be painful to recover them. Only disable this if you are paranoid and like losing your data!"
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: "Store secrets online.\nYou have a few secrets to make all the encryption magic work. While you can keep them stored only locally, we recommend storing them encrypted on the server. Otherwise it will be painful to recover them. Only disable this if you are paranoid and like losing your data!"
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
Item { Item {
Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: storeSecretsOnline.height
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: storeSecretsOnline.height
ToggleButton { ToggleButton {
id: storeSecretsOnline id: storeSecretsOnline
checked: true checked: true
onClicked: console.log("Store secrets toggled: " + checked) onClicked: console.log("Store secrets toggled: " + checked)
} }
} }
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.columnSpan: 1 Layout.columnSpan: 1
Layout.rowSpan: 2 Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
visible: storeSecretsOnline.checked Layout.rowSpan: 2
text: "Set an online backup password.\nWe recommend you DON'T set a password and instead only rely on the recovery key. You will get a recovery key in any case when storing the cross-signing secrets online, but passwords are usually not very random, so they are easier to attack than a completely random recovery key. If you choose to use a password, DON'T make it the same as your login password, otherwise your server can read all your encrypted messages. (You don't want that.)"
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: "Set an online backup password.\nWe recommend you DON'T set a password and instead only rely on the recovery key. You will get a recovery key in any case when storing the cross-signing secrets online, but passwords are usually not very random, so they are easier to attack than a completely random recovery key. If you choose to use a password, DON'T make it the same as your login password, otherwise your server can read all your encrypted messages. (You don't want that.)"
visible: storeSecretsOnline.checked
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
Item { Item {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium Layout.margins: Nheko.paddingMedium
Layout.topMargin: Nheko.paddingLarge
Layout.preferredHeight: storeSecretsOnline.height Layout.preferredHeight: storeSecretsOnline.height
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.rowSpan: usePassword.checked ? 1 : 2 Layout.rowSpan: usePassword.checked ? 1 : 2
Layout.fillWidth: true Layout.topMargin: Nheko.paddingLarge
visible: storeSecretsOnline.checked visible: storeSecretsOnline.checked
ToggleButton { ToggleButton {
id: usePassword id: usePassword
checked: false checked: false
} }
} }
MatrixTextField { MatrixTextField {
id: passwordField id: passwordField
Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.columnSpan: 1 Layout.columnSpan: 1
Layout.fillWidth: true Layout.fillWidth: true
visible: storeSecretsOnline.checked && usePassword.checked Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
echoMode: TextInput.Password echoMode: TextInput.Password
visible: storeSecretsOnline.checked && usePassword.checked
} }
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.columnSpan: 1 Layout.columnSpan: 1
Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
text: "Use online key backup.\nStore the keys for your messages securely encrypted online. In general you do want this, because it protects your messages from becoming unreadable, if you log out by accident. It does however carry a small security risk, if you ever share your recovery key by accident. Currently this also has some other weaknesses, that might allow the server to insert new keys into your backup. The server will however never be able to read your messages."
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: "Use online key backup.\nStore the keys for your messages securely encrypted online. In general you do want this, because it protects your messages from becoming unreadable, if you log out by accident. It does however carry a small security risk, if you ever share your recovery key by accident. Currently this also has some other weaknesses, that might allow the server to insert new keys into your backup. The server will however never be able to read your messages."
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
Item { Item {
Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: storeSecretsOnline.height
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: storeSecretsOnline.height
ToggleButton { ToggleButton {
id: useOnlineKeyBackup id: useOnlineKeyBackup
checked: true checked: true
onClicked: console.log("Online key backup toggled: " + checked)
}
onClicked: console.log("Online key backup toggled: " + checked)
} }
} }
background: Rectangle {
color: timelineRoot.palette.window
border.color: Nheko.theme.separator
border.width: 1
radius: Nheko.paddingSmall
} }
} }
MainWindowDialog { MainWindowDialog {
id: verifyMasterKey id: verifyMasterKey
standardButtons: Dialog.Cancel standardButtons: Dialog.Cancel
GridLayout { GridLayout {
id: masterGrid id: masterGrid
width: verifyMasterKey.useableWidth
columns: 1 columns: 1
width: verifyMasterKey.useableWidth
z: 1 z: 1
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingMedium
color: timelineRoot.palette.text
//Layout.columnSpan: 2 //Layout.columnSpan: 2
font.pointSize: fontMetrics.font.pointSize * 2 font.pointSize: fontMetrics.font.pointSize * 2
text: qsTr("Activate Encryption") text: qsTr("Activate Encryption")
color: timelineRoot.palette.text
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.margins: Nheko.paddingMedium
//Layout.columnSpan: 2 //Layout.columnSpan: 2
Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2
text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.")
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
FlatButton { FlatButton {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("verify") text: qsTr("verify")
onClicked: { onClicked: {
SelfVerificationStatus.verifyMasterKey(); SelfVerificationStatus.verifyMasterKey();
verifyMasterKey.close(); verifyMasterKey.close();
} }
} }
FlatButton { FlatButton {
visible: SelfVerificationStatus.hasSSSS
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("enter passphrase") text: qsTr("enter passphrase")
visible: SelfVerificationStatus.hasSSSS
onClicked: { onClicked: {
SelfVerificationStatus.verifyMasterKeyWithPassphrase(); SelfVerificationStatus.verifyMasterKeyWithPassphrase();
verifyMasterKey.close(); verifyMasterKey.close();
} }
} }
} }
} }
Connections { Connections {
function onSetupCompleted() {
successDialog.open();
}
function onSetupFailed(m) {
failureDialog.errorMessage = m;
failureDialog.open();
}
function onShowRecoveryKey(key) {
showRecoverKeyDialog.recoveryKey = key;
showRecoverKeyDialog.open();
}
function onStatusChanged() { function onStatusChanged() {
console.log("STATUS CHANGED: " + SelfVerificationStatus.status); console.log("STATUS CHANGED: " + SelfVerificationStatus.status);
if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) { if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) {
@ -286,21 +263,6 @@ Item {
} }
} }
function onShowRecoveryKey(key) {
showRecoverKeyDialog.recoveryKey = key;
showRecoverKeyDialog.open();
}
function onSetupCompleted() {
successDialog.open();
}
function onSetupFailed(m) {
failureDialog.errorMessage = m;
failureDialog.open();
}
target: SelfVerificationStatus target: SelfVerificationStatus
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import im.nheko import im.nheko
@ -10,15 +8,9 @@ import im.nheko
ImageButton { ImageButton {
id: indicator id: indicator
required property int status
required property string eventId required property string eventId
required property int status
width: 16
height: 16
hoverEnabled: true
changeColorOnHover: (status == MtxEvent.Read)
cursor: (status == MtxEvent.Read) ? Qt.PointingHandCursor : Qt.ArrowCursor
ToolTip.visible: hovered && status != MtxEvent.Empty
ToolTip.text: { ToolTip.text: {
switch (status) { switch (status) {
case MtxEvent.Failed: case MtxEvent.Failed:
@ -33,11 +25,11 @@ ImageButton {
return ""; return "";
} }
} }
onClicked: { ToolTip.visible: hovered && status != MtxEvent.Empty
if (status == MtxEvent.Read) changeColorOnHover: (status == MtxEvent.Read)
room.showReadReceipts(eventId); cursor: (status == MtxEvent.Read) ? Qt.PointingHandCursor : Qt.ArrowCursor
height: 16
} hoverEnabled: true
image: { image: {
switch (status) { switch (status) {
case MtxEvent.Failed: case MtxEvent.Failed:
@ -52,4 +44,10 @@ ImageButton {
return ""; return "";
} }
} }
width: 16
onClicked: {
if (status == MtxEvent.Read)
room.showReadReceipts(eventId);
}
} }

@ -1,10 +1,8 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "delegates"
import "./delegates" import "emoji"
import "./emoji"
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
@ -14,69 +12,42 @@ import im.nheko
AbstractButton { AbstractButton {
id: r id: r
required property double proportionalHeight
required property int type
required property string typeString
required property int originalWidth
required property string blurhash required property string blurhash
required property string body required property string body
required property string formattedBody required property string callType
required property int duration
required property int encryptionError
required property string eventId required property string eventId
required property string filename required property string filename
required property string filesize required property string filesize
required property string url required property string formattedBody
required property string thumbnailUrl
required property bool isOnlyEmoji
required property bool isSender
required property bool isEncrypted
required property bool isEditable required property bool isEditable
required property bool isEdited required property bool isEdited
required property bool isEncrypted
required property bool isOnlyEmoji
required property bool isSender
required property bool isStateEvent required property bool isStateEvent
required property int originalWidth
required property double proportionalHeight
required property var reactions
required property int relatedEventCacheBuster
required property string replyTo required property string replyTo
required property string userId
required property string userName
required property string roomTopic
required property string roomName required property string roomName
required property string callType required property string roomTopic
required property var reactions
required property int trustlevel
required property int encryptionError
required property int duration
required property var timestamp
required property int status required property int status
required property int relatedEventCacheBuster required property string thumbnailUrl
required property var timestamp
required property int trustlevel
required property int type
required property string typeString
required property string url
required property string userId
required property string userName
height: row.height + (reactionRow.height > 0 ? reactionRow.height - 2 : 0)
hoverEnabled: true hoverEnabled: true
width: parent.width width: parent.width
height: row.height+(reactionRow.height > 0 ? reactionRow.height-2 : 0 )
Rectangle {
color: (Settings.messageHoverHighlight && hovered) ? timelineRoot.palette.alternateBase : "transparent"
anchors.fill: parent
// this looks better without margins
TapHandler {
acceptedButtons: Qt.RightButton
onSingleTapped: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
gesturePolicy: TapHandler.ReleaseWithinBounds
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
}
}
onPressAndHold: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
onDoubleClicked: chat.model.reply = eventId
DragHandler {
id: draghandler
yAxis.enabled: false
xAxis.maximum: 100
xAxis.minimum: -100
onActiveChanged: {
if(!active && (x < -70 || x > 70))
chat.model.reply = eventId
}
}
states: State { states: State {
name: "dragging" name: "dragging"
when: draghandler.active when: draghandler.active
@ -84,212 +55,234 @@ AbstractButton {
transitions: Transition { transitions: Transition {
from: "dragging" from: "dragging"
to: "" to: ""
PropertyAnimation { PropertyAnimation {
target: r duration: 100
properties: "x"
easing.type: Easing.InOutQuad easing.type: Easing.InOutQuad
properties: "x"
target: r
to: 0 to: 0
duration: 100
} }
} }
onClicked: { onClicked: {
let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX-row.x-msg.x, pressY-row.y-msg.y-contentItem.y); let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX - row.x - msg.x, pressY - row.y - msg.y - contentItem.y);
if (link) { if (link) {
Nheko.openLink(link) Nheko.openLink(link);
}
}
onDoubleClicked: chat.model.reply = eventId
onPressAndHold: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
Rectangle {
anchors.fill: parent
color: (Settings.messageHoverHighlight && hovered) ? timelineRoot.palette.alternateBase : "transparent"
// this looks better without margins
TapHandler {
acceptedButtons: Qt.RightButton
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
gesturePolicy: TapHandler.ReleaseWithinBounds
onSingleTapped: messageContextMenu.show(eventId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
} }
} }
DragHandler {
id: draghandler
xAxis.maximum: 100
xAxis.minimum: -100
yAxis.enabled: false
onActiveChanged: {
if (!active && (x < -70 || x > 70))
chat.model.reply = eventId;
}
}
Rectangle { Rectangle {
id: row id: row
property bool bubbleOnRight : isSender && Settings.bubbles
anchors.leftMargin: isStateEvent || Settings.smallAvatars? 0 : Nheko.avatarSize+8 // align bubble with section header
anchors.left: isStateEvent? undefined : (bubbleOnRight? undefined : parent.left)
anchors.right: isStateEvent? undefined: (bubbleOnRight? parent.right : undefined)
anchors.horizontalCenter: isStateEvent? parent.horizontalCenter : undefined
property int maxWidth: (parent.width-(Settings.smallAvatars || isStateEvent? 0 : Nheko.avatarSize+8))*(Settings.bubbles && !isStateEvent? 0.9 : 1)
width: Settings.bubbles? Math.min(maxWidth,Math.max(reply.implicitWidth+8,contentItem.implicitWidth+metadata.width+20)) : maxWidth
height: msg.height+msg.anchors.margins*2
property color userColor: TimelineManager.userColor(userId, timelineRoot.palette.base)
property color bgColor: timelineRoot.palette.base property color bgColor: timelineRoot.palette.base
property bool bubbleOnRight: isSender && Settings.bubbles
property int maxWidth: (parent.width - (Settings.smallAvatars || isStateEvent ? 0 : Nheko.avatarSize + 8)) * (Settings.bubbles && !isStateEvent ? 0.9 : 1)
property color userColor: TimelineManager.userColor(userId, timelineRoot.palette.base)
anchors.horizontalCenter: isStateEvent ? parent.horizontalCenter : undefined
anchors.left: isStateEvent ? undefined : (bubbleOnRight ? undefined : parent.left)
anchors.leftMargin: isStateEvent || Settings.smallAvatars ? 0 : Nheko.avatarSize + 8 // align bubble with section header
anchors.right: isStateEvent ? undefined : (bubbleOnRight ? parent.right : undefined)
color: (Settings.bubbles && !isStateEvent) ? Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.2)) : "#00000000" color: (Settings.bubbles && !isStateEvent) ? Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.2)) : "#00000000"
height: msg.height + msg.anchors.margins * 2
radius: 4 radius: 4
width: Settings.bubbles ? Math.min(maxWidth, Math.max(reply.implicitWidth + 8, contentItem.implicitWidth + metadata.width + 20)) : maxWidth
GridLayout { GridLayout {
id: msg
columnSpacing: 2
columns: Settings.bubbles ? 1 : 2
rowSpacing: 0
rows: Settings.bubbles ? 3 : 2
anchors { anchors {
left: parent.left left: parent.left
top: parent.top
right: parent.right
margins: (Settings.bubbles && ! isStateEvent)? 4 : 2
leftMargin: 4 leftMargin: 4
margins: (Settings.bubbles && !isStateEvent) ? 4 : 2
right: parent.right
top: parent.top
} }
id: msg
rowSpacing: 0
columnSpacing: 2
columns: Settings.bubbles? 1 : 2
rows: Settings.bubbles? 3 : 2
// fancy reply, if this is a reply // fancy reply, if this is a reply
Reply { Reply {
Layout.row: 0
Layout.column: 0
Layout.fillWidth: true
Layout.maximumWidth: Settings.bubbles? Number.MAX_VALUE : implicitWidth
Layout.bottomMargin: visible? 2 : 0
Layout.preferredHeight: height
id: reply id: reply
function fromModel(role) { function fromModel(role) {
return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null; return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null;
} }
visible: replyTo
userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, timelineRoot.palette.base) Layout.bottomMargin: visible ? 2 : 0
Layout.column: 0
Layout.fillWidth: true
Layout.maximumWidth: Settings.bubbles ? Number.MAX_VALUE : implicitWidth
Layout.preferredHeight: height
Layout.row: 0
blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? "" blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? ""
body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? "" body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? ""
formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? "" callType: r.relatedEventCacheBuster, fromModel(Room.Voip) ?? ""
duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? 0
encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? 0
eventId: fromModel(Room.EventId) ?? "" eventId: fromModel(Room.EventId) ?? ""
filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? "" filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? ""
filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? "" filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? ""
formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? ""
isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false
isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false
originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0
proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1 proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1
relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? ""
type: r.relatedEventCacheBuster, fromModel(Room.Type) ?? MtxEvent.UnknownMessage type: r.relatedEventCacheBuster, fromModel(Room.Type) ?? MtxEvent.UnknownMessage
typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? "" typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? ""
url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? "" url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? ""
originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0 userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, timelineRoot.palette.base)
isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false
isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false
userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? "" userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? ""
userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? "" userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? ""
thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? "" visible: replyTo
duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? 0
roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
callType: r.relatedEventCacheBuster, fromModel(Room.Voip) ?? ""
encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? 0
relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
} }
// actual message content // actual message content
MessageDelegate { MessageDelegate {
Layout.row: 1 id: contentItem
Layout.column: 0 Layout.column: 0
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: height Layout.preferredHeight: height
id: contentItem Layout.row: 1
blurhash: r.blurhash blurhash: r.blurhash
body: r.body body: r.body
formattedBody: r.formattedBody callType: r.callType
duration: r.duration
encryptionError: r.encryptionError
eventId: r.eventId eventId: r.eventId
filename: r.filename filename: r.filename
filesize: r.filesize filesize: r.filesize
formattedBody: r.formattedBody
isOnlyEmoji: r.isOnlyEmoji
isReply: false
isStateEvent: r.isStateEvent
metadataWidth: metadata.width
originalWidth: r.originalWidth
proportionalHeight: r.proportionalHeight proportionalHeight: r.proportionalHeight
relatedEventCacheBuster: r.relatedEventCacheBuster
roomName: r.roomName
roomTopic: r.roomTopic
thumbnailUrl: r.thumbnailUrl
type: r.type type: r.type
typeString: r.typeString ?? "" typeString: r.typeString ?? ""
url: r.url url: r.url
thumbnailUrl: r.thumbnailUrl
duration: r.duration
originalWidth: r.originalWidth
isOnlyEmoji: r.isOnlyEmoji
isStateEvent: r.isStateEvent
userId: r.userId userId: r.userId
userName: r.userName userName: r.userName
roomTopic: r.roomTopic
roomName: r.roomName
callType: r.callType
encryptionError: r.encryptionError
relatedEventCacheBuster: r.relatedEventCacheBuster
isReply: false
metadataWidth: metadata.width
} }
Row { Row {
id: metadata id: metadata
Layout.column: Settings.bubbles? 0 : 1
Layout.row: Settings.bubbles? 2 : 0 property int iconSize: Math.floor(fontMetrics.ascent * scaling)
Layout.rowSpan: Settings.bubbles? 1 : 2 property double scaling: Settings.bubbles ? 0.75 : 1
Layout.bottomMargin: -2
Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles)? -height-Layout.bottomMargin : 0
Layout.alignment: Qt.AlignTop | Qt.AlignRight Layout.alignment: Qt.AlignTop | Qt.AlignRight
Layout.bottomMargin: -2
Layout.column: Settings.bubbles ? 0 : 1
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
visible: !isStateEvent Layout.row: Settings.bubbles ? 2 : 0
Layout.rowSpan: Settings.bubbles ? 1 : 2
Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles) ? -height - Layout.bottomMargin : 0
spacing: 2 spacing: 2
visible: !isStateEvent
property double scaling: Settings.bubbles? 0.75 : 1
property int iconSize: Math.floor(fontMetrics.ascent*scaling)
StatusIndicator { StatusIndicator {
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
anchors.verticalCenter: ts.verticalCenter
eventId: r.eventId
height: parent.iconSize height: parent.iconSize
width: parent.iconSize
status: r.status status: r.status
eventId: r.eventId width: parent.iconSize
anchors.verticalCenter: ts.verticalCenter
} }
Image { Image {
visible: isEdited || eventId == chat.model.edit
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
height: parent.iconSize
width: parent.iconSize
sourceSize.width: parent.iconSize * Screen.devicePixelRatio
sourceSize.height: parent.iconSize * Screen.devicePixelRatio
source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == chat.model.edit) ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText)
ToolTip.visible: editHovered.hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Edited") ToolTip.text: qsTr("Edited")
ToolTip.visible: editHovered.hovered
anchors.verticalCenter: ts.verticalCenter anchors.verticalCenter: ts.verticalCenter
height: parent.iconSize
source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == chat.model.edit) ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText)
sourceSize.height: parent.iconSize * Screen.devicePixelRatio
sourceSize.width: parent.iconSize * Screen.devicePixelRatio
visible: isEdited || eventId == chat.model.edit
width: parent.iconSize
HoverHandler { HoverHandler {
id: editHovered id: editHovered
} }
} }
EncryptionIndicator { EncryptionIndicator {
visible: room.isEncrypted
encrypted: isEncrypted
trust: trustlevel
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
anchors.verticalCenter: ts.verticalCenter
encrypted: isEncrypted
height: parent.iconSize height: parent.iconSize
width: parent.iconSize
sourceSize.width: parent.iconSize * Screen.devicePixelRatio
sourceSize.height: parent.iconSize * Screen.devicePixelRatio sourceSize.height: parent.iconSize * Screen.devicePixelRatio
anchors.verticalCenter: ts.verticalCenter sourceSize.width: parent.iconSize * Screen.devicePixelRatio
trust: trustlevel
visible: room.isEncrypted
width: parent.iconSize
} }
Label { Label {
id: ts id: ts
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
text: timestamp.toLocaleTimeString(Locale.ShortFormat)
color: timelineRoot.palette.inactive.text
ToolTip.visible: ma.hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate) ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
font.pointSize: fontMetrics.font.pointSize*parent.scaling ToolTip.visible: ma.hovered
color: timelineRoot.palette.inactive.text
font.pointSize: fontMetrics.font.pointSize * parent.scaling
text: timestamp.toLocaleTimeString(Locale.ShortFormat)
HoverHandler { HoverHandler {
id: ma id: ma
} }
} }
} }
} }
} }
Reactions { Reactions {
id: reactionRow
eventId: r.eventId
layoutDirection: row.bubbleOnRight ? Qt.RightToLeft : Qt.LeftToRight
reactions: r.reactions
width: row.maxWidth
anchors { anchors {
left: row.bubbleOnRight ? undefined : row.left
right: row.bubbleOnRight ? row.right : undefined
top: row.bottom top: row.bottom
topMargin: -2 topMargin: -2
left: row.bubbleOnRight? undefined : row.left
right: row.bubbleOnRight? row.right : undefined
} }
width: row.maxWidth
layoutDirection: row.bubbleOnRight? Qt.RightToLeft : Qt.LeftToRight
id: reactionRow
reactions: r.reactions
eventId: r.eventId
} }
} }

@ -1,14 +1,12 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "components"
import "./components" import "delegates"
import "./delegates" import "device-verification"
import "./device-verification" import "emoji"
import "./emoji" import "ui"
import "./ui" import "voip"
import "./voip"
import Qt.labs.platform 1.1 as Platform import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.5 import QtQuick.Controls 2.5
@ -23,55 +21,50 @@ Item {
property var room: null property var room: null
property var roomPreview: null property var roomPreview: null
property bool showBackButton: false property bool showBackButton: false
clip: true clip: true
Shortcut { Shortcut {
sequence: StandardKey.Close sequence: StandardKey.Close
onActivated: Rooms.resetCurrentRoom() onActivated: Rooms.resetCurrentRoom()
} }
Label { Label {
visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid)
anchors.centerIn: parent anchors.centerIn: parent
text: qsTr("No room open")
font.pointSize: 24
color: timelineRoot.palette.text color: timelineRoot.palette.text
font.pointSize: 24
text: qsTr("No room open")
visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid)
} }
Spinner { Spinner {
visible: TimelineManager.isInitialSync
anchors.centerIn: parent anchors.centerIn: parent
foreground: timelineRoot.palette.mid foreground: timelineRoot.palette.mid
running: TimelineManager.isInitialSync
// height is somewhat arbitrary here... don't set width because width scales w/ height // height is somewhat arbitrary here... don't set width because width scales w/ height
height: parent.height / 16 height: parent.height / 16
running: TimelineManager.isInitialSync
visible: TimelineManager.isInitialSync
z: 3 z: 3
} }
ColumnLayout { ColumnLayout {
id: timelineLayout id: timelineLayout
visible: room != null && !room.isSpace
enabled: visible
anchors.fill: parent anchors.fill: parent
enabled: visible
spacing: 0 spacing: 0
visible: room != null && !room.isSpace
TopBar { TopBar {
showBackButton: timelineView.showBackButton showBackButton: timelineView.showBackButton
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
color: Nheko.theme.separator
height: 1 height: 1
z: 3 z: 3
color: Nheko.theme.separator
} }
Rectangle { Rectangle {
id: msgView id: msgView
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
color: timelineRoot.palette.base color: timelineRoot.palette.base
ColumnLayout { ColumnLayout {
@ -80,7 +73,6 @@ Item {
StackLayout { StackLayout {
id: stackLayout id: stackLayout
currentIndex: 0 currentIndex: 0
Connections { Connections {
@ -90,129 +82,107 @@ Item {
target: timelineView target: timelineView
} }
MessageView { MessageView {
implicitHeight: msgView.height - typingIndicator.height
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: msgView.height - typingIndicator.height
} }
Loader { Loader {
source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? "voip/VideoCall.qml" : "" source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? "voip/VideoCall.qml" : ""
onLoaded: TimelineManager.setVideoCallItem() onLoaded: TimelineManager.setVideoCallItem()
} }
} }
TypingIndicator { TypingIndicator {
id: typingIndicator id: typingIndicator
} }
} }
} }
CallInviteBar { CallInviteBar {
id: callInviteBar id: callInviteBar
Layout.fillWidth: true Layout.fillWidth: true
z: 3 z: 3
} }
ActiveCallBar { ActiveCallBar {
Layout.fillWidth: true Layout.fillWidth: true
z: 3 z: 3
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
z: 3
height: 1
color: Nheko.theme.separator color: Nheko.theme.separator
height: 1
z: 3
} }
UploadBox { UploadBox {
} }
NotificationWarning { NotificationWarning {
} }
ReplyPopup { ReplyPopup {
} }
MessageInput { MessageInput {
} }
} }
ColumnLayout { ColumnLayout {
id: preview id: preview
property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "")
property string roomId: room ? room.roomId : (roomPreview ? roomPreview.roomid : "") property string roomId: room ? room.roomId : (roomPreview ? roomPreview.roomid : "")
property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "") property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "")
property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "") property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "")
property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "")
visible: room != null && room.isSpace || roomPreview != null
enabled: visible
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingLarge anchors.margins: Nheko.paddingLarge
enabled: visible
spacing: Nheko.paddingLarge spacing: Nheko.paddingLarge
visible: room != null && room.isSpace || roomPreview != null
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
} }
Avatar { Avatar {
url: parent.avatarUrl.replace("mxc://", "image://MxcImage/") Layout.alignment: Qt.AlignHCenter
roomid: parent.roomId
displayName: parent.roomName displayName: parent.roomName
enabled: false
height: 130 height: 130
roomid: parent.roomId
url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
width: 130 width: 130
Layout.alignment: Qt.AlignHCenter
enabled: false
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingMedium
MatrixText { MatrixText {
text: preview.roomName == "" ? qsTr("No preview available") : preview.roomName
font.pixelSize: 24 font.pixelSize: 24
text: preview.roomName == "" ? qsTr("No preview available") : preview.roomName
} }
ImageButton { ImageButton {
ToolTip.text: qsTr("Settings")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/settings.svg" image: ":/icons/icons/ui/settings.svg"
visible: !!room visible: !!room
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Settings")
onClicked: TimelineManager.openRoomSettings(room.roomId) onClicked: TimelineManager.openRoomSettings(room.roomId)
} }
} }
RowLayout { RowLayout {
visible: !!room
spacing: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingMedium
visible: !!room
MatrixText { MatrixText {
text: qsTr("%1 member(s)").arg(room ? room.roomMemberCount : 0)
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
text: qsTr("%1 member(s)").arg(room ? room.roomMemberCount : 0)
} }
ImageButton { ImageButton {
image: ":/icons/icons/ui/people.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("View members of %1").arg(room.roomName) ToolTip.text: qsTr("View members of %1").arg(room.roomName)
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/people.svg"
onClicked: TimelineManager.openRoomMembers(room) onClicked: TimelineManager.openRoomMembers(room)
} }
} }
ScrollView { ScrollView {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
@ -220,78 +190,71 @@ Item {
Layout.rightMargin: Nheko.paddingLarge Layout.rightMargin: Nheko.paddingLarge
TextArea { TextArea {
text: TimelineManager.escapeEmoji(preview.roomTopic)
wrapMode: TextEdit.WordWrap
textFormat: TextEdit.RichText
readOnly: true
background: null background: null
selectByMouse: true
color: timelineRoot.palette.text color: timelineRoot.palette.text
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
readOnly: true
selectByMouse: true
text: TimelineManager.escapeEmoji(preview.roomTopic)
textFormat: TextEdit.RichText
wrapMode: TextEdit.WordWrap
onLinkActivated: Nheko.openLink(link) onLinkActivated: Nheko.openLink(link)
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
} }
} }
FlatButton { FlatButton {
visible: roomPreview && !roomPreview.isInvite
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("join the conversation") text: qsTr("join the conversation")
visible: roomPreview && !roomPreview.isInvite
onClicked: Rooms.joinPreview(roomPreview.roomid) onClicked: Rooms.joinPreview(roomPreview.roomid)
} }
FlatButton { FlatButton {
visible: roomPreview && roomPreview.isInvite
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("accept invite") text: qsTr("accept invite")
visible: roomPreview && roomPreview.isInvite
onClicked: Rooms.acceptInvite(roomPreview.roomid) onClicked: Rooms.acceptInvite(roomPreview.roomid)
} }
FlatButton { FlatButton {
visible: roomPreview && roomPreview.isInvite
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("decline invite") text: qsTr("decline invite")
visible: roomPreview && roomPreview.isInvite
onClicked: Rooms.declineInvite(roomPreview.roomid) onClicked: Rooms.declineInvite(roomPreview.roomid)
} }
Item { Item {
visible: room != null
Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 2) Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 2)
visible: room != null
} }
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
} }
} }
ImageButton { ImageButton {
id: backToRoomsButton id: backToRoomsButton
ToolTip.text: qsTr("Back to room list")
anchors.top: parent.top ToolTip.visible: hovered
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
width: Nheko.avatarSize anchors.top: parent.top
height: Nheko.avatarSize
visible: (room == null || room.isSpace) && showBackButton
enabled: visible enabled: visible
height: Nheko.avatarSize
image: ":/icons/icons/ui/angle-arrow-left.svg" image: ":/icons/icons/ui/angle-arrow-left.svg"
ToolTip.visible: hovered visible: (room == null || room.isSpace) && showBackButton
ToolTip.text: qsTr("Back to room list") width: Nheko.avatarSize
onClicked: Rooms.resetCurrentRoom() onClicked: Rooms.resetCurrentRoom()
} }
NhekoDropArea { NhekoDropArea {
anchors.fill: parent anchors.fill: parent
roomid: room ? room.roomId : "" roomid: room ? room.roomId : ""
} }
Connections { Connections {
function onOpenReadReceiptsDialog(rr) { function onOpenReadReceiptsDialog(rr) {
var dialog = readReceiptsDialog.createObject(timelineRoot, { var dialog = readReceiptsDialog.createObject(timelineRoot, {
@ -301,7 +264,6 @@ Item {
dialog.show(); dialog.show();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
} }
function onShowRawMessageDialog(rawMessage) { function onShowRawMessageDialog(rawMessage) {
var dialog = rawMessageDialog.createObject(timelineRoot, { var dialog = rawMessageDialog.createObject(timelineRoot, {
"rawMessage": rawMessage "rawMessage": rawMessage
@ -312,5 +274,4 @@ Item {
target: room target: room
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.5 import QtQuick 2.5
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.12 import QtQuick.Controls 2.12
@ -10,36 +8,31 @@ import im.nheko
Switch { Switch {
id: toggleButton id: toggleButton
implicitWidth: indicatorItem.width implicitWidth: indicatorItem.width
indicator: Item { indicator: Item {
id: indicatorItem id: indicatorItem
implicitWidth: 48
implicitHeight: 24 implicitHeight: 24
implicitWidth: 48
y: parent.height / 2 - height / 2 y: parent.height / 2 - height / 2
Rectangle { Rectangle {
border.color: "#cccccc"
color: toggleButton.checked ? "skyblue" : "grey"
height: 3 * parent.height / 4 height: 3 * parent.height / 4
radius: height / 2 radius: height / 2
width: parent.width - height width: parent.width - height
x: radius x: radius
y: parent.height / 2 - height / 2 y: parent.height / 2 - height / 2
color: toggleButton.checked ? "skyblue" : "grey"
border.color: "#cccccc"
} }
Rectangle { Rectangle {
x: toggleButton.checked ? parent.width - width : 0 border.color: "#ebebeb"
y: parent.height / 2 - height / 2 color: toggleButton.enabled ? "whitesmoke" : "#cccccc"
width: parent.height
height: width height: width
radius: width / 2 radius: width / 2
color: toggleButton.enabled ? "whitesmoke" : "#cccccc" width: parent.height
border.color: "#ebebeb" x: toggleButton.checked ? parent.width - width : 0
y: parent.height / 2 - height / 2
} }
} }
} }

@ -1,161 +1,105 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import Qt.labs.platform 1.1 as Platform import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.15 import QtQuick.Window 2.15
import im.nheko import im.nheko
import "delegates"
import "./delegates"
Pane { Pane {
id: topBar id: topBar
property bool showBackButton: false
property string roomName: room ? room.roomName : qsTr("No room selected")
property string roomId: room ? room.roomId : ""
property string avatarUrl: room ? room.roomAvatarUrl : "" property string avatarUrl: room ? room.roomAvatarUrl : ""
property string roomTopic: room ? room.roomTopic : "" property string directChatOtherUserId: room ? room.directChatOtherUserId : ""
property bool isDirect: room ? room.isDirect : false
property bool isEncrypted: room ? room.isEncrypted : false property bool isEncrypted: room ? room.isEncrypted : false
property string roomId: room ? room.roomId : ""
property string roomName: room ? room.roomName : qsTr("No room selected")
property string roomTopic: room ? room.roomTopic : ""
property bool showBackButton: false
property int trustlevel: room ? room.trustlevel : Crypto.Unverified property int trustlevel: room ? room.trustlevel : Crypto.Unverified
property bool isDirect: room ? room.isDirect : false
property string directChatOtherUserId: room ? room.directChatOtherUserId : ""
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: topBarC.height + Nheko.paddingMedium * 2 implicitHeight: topBarC.height + Nheko.paddingMedium * 2
padding: 0
z: 3 z: 3
padding: 0
background: Rectangle { background: Rectangle {
color: timelineRoot.palette.window color: timelineRoot.palette.window
} }
TapHandler {
onSingleTapped: {
if (eventPoint.position.y > topBar.height - (pinnedMessages.visible ? pinnedMessages.height : 0) - (widgets.visible ? widgets.height : 0)) {
eventPoint.accepted = true
return;
}
if (showBackButton && eventPoint.position.x < Nheko.paddingMedium + backToRoomsButton.width) {
eventPoint.accepted = true
return;
}
if (eventPoint.position.x > topBar.width - Nheko.paddingMedium - roomOptionsButton.width) {
eventPoint.accepted = true
return;
}
if (room) {
let p = topBar.mapToItem(roomTopicC, eventPoint.position.x, eventPoint.position.y);
let link = roomTopicC.linkAt(p.x, p.y);
if (link) {
Nheko.openLink(link);
} else {
TimelineManager.openRoomSettings(room.roomId);
}
}
eventPoint.accepted = true;
}
gesturePolicy: TapHandler.ReleaseWithinBounds
}
HoverHandler {
grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything
}
contentItem: Item { contentItem: Item {
GridLayout { GridLayout {
id: topBarC id: topBarC
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
columnSpacing: Nheko.paddingSmall columnSpacing: Nheko.paddingSmall
rowSpacing: Nheko.paddingSmall rowSpacing: Nheko.paddingSmall
ImageButton { ImageButton {
id: backToRoomsButton id: backToRoomsButton
Layout.column: 0
Layout.row: 0
Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.column: 0
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
visible: showBackButton Layout.row: 0
image: ":/icons/icons/ui/angle-arrow-left.svg" Layout.rowSpan: 2
ToolTip.visible: hovered
ToolTip.text: qsTr("Back to room list") ToolTip.text: qsTr("Back to room list")
ToolTip.visible: hovered
image: ":/icons/icons/ui/angle-arrow-left.svg"
visible: showBackButton
onClicked: Rooms.resetCurrentRoom() onClicked: Rooms.resetCurrentRoom()
} }
Avatar { Avatar {
Layout.alignment: Qt.AlignVCenter
Layout.column: 1 Layout.column: 1
Layout.row: 0 Layout.row: 0
Layout.rowSpan: 2 Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter displayName: roomName
width: Nheko.avatarSize enabled: false
height: Nheko.avatarSize height: Nheko.avatarSize
url: avatarUrl.replace("mxc://", "image://MxcImage/")
roomid: roomId roomid: roomId
url: avatarUrl.replace("mxc://", "image://MxcImage/")
userid: isDirect ? directChatOtherUserId : "" userid: isDirect ? directChatOtherUserId : ""
displayName: roomName width: Nheko.avatarSize
enabled: false
} }
Label { Label {
Layout.fillWidth: true
Layout.column: 2 Layout.column: 2
Layout.fillWidth: true
Layout.row: 0 Layout.row: 0
color: timelineRoot.palette.text color: timelineRoot.palette.text
elide: Text.ElideRight
font.pointSize: fontMetrics.font.pointSize * 1.1 font.pointSize: fontMetrics.font.pointSize * 1.1
text: roomName
maximumLineCount: 1 maximumLineCount: 1
elide: Text.ElideRight text: roomName
textFormat: Text.RichText textFormat: Text.RichText
} }
MatrixText { MatrixText {
id: roomTopicC id: roomTopicC
Layout.fillWidth: true
Layout.column: 2 Layout.column: 2
Layout.row: 1 Layout.fillWidth: true
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
selectByMouse: false Layout.row: 1
enabled: false
clip: true clip: true
enabled: false
selectByMouse: false
text: roomTopic text: roomTopic
} }
AbstractButton { AbstractButton {
Layout.column: 3 Layout.column: 3
Layout.row: 0
Layout.rowSpan: 2
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
Layout.row: 0
contentItem: EncryptionIndicator { Layout.rowSpan: 2
sourceSize.height: parent.Layout.preferredHeight * Screen.devicePixelRatio
sourceSize.width: parent.Layout.preferredWidth * Screen.devicePixelRatio
visible: isEncrypted
encrypted: isEncrypted
trust: trustlevel
enabled: false
}
background: null
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: { ToolTip.text: {
if (!isEncrypted) if (!isEncrypted)
return qsTr("This room is not encrypted!"); return qsTr("This room is not encrypted!");
switch (trustlevel) { switch (trustlevel) {
case Crypto.Verified: case Crypto.Verified:
return qsTr("This room contains only verified devices."); return qsTr("This room contains only verified devices.");
@ -166,25 +110,35 @@ Pane {
} }
} }
ToolTip.visible: hovered ToolTip.visible: hovered
background: null
onClicked: TimelineManager.openRoomMembers(room) contentItem: EncryptionIndicator {
enabled: false
encrypted: isEncrypted
sourceSize.height: parent.Layout.preferredHeight * Screen.devicePixelRatio
sourceSize.width: parent.Layout.preferredWidth * Screen.devicePixelRatio
trust: trustlevel
visible: isEncrypted
} }
onClicked: TimelineManager.openRoomMembers(room)
}
ImageButton { ImageButton {
id: pinButton id: pinButton
property bool pinsShown: !Settings.hiddenPins.includes(roomId) property bool pinsShown: !Settings.hiddenPins.includes(roomId)
visible: !!room && room.pinnedMessages.length > 0
Layout.column: 4
Layout.row: 0
Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.column: 4
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg" Layout.row: 0
ToolTip.visible: hovered Layout.rowSpan: 2
ToolTip.text: qsTr("Show or hide pinned messages") ToolTip.text: qsTr("Show or hide pinned messages")
ToolTip.visible: hovered
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
visible: !!room && room.pinnedMessages.length > 0
onClicked: { onClicked: {
var ps = Settings.hiddenPins; var ps = Settings.hiddenPins;
if (pinsShown) { if (pinsShown) {
@ -197,156 +151,168 @@ Pane {
} }
Settings.hiddenPins = ps; Settings.hiddenPins = ps;
} }
} }
ImageButton { ImageButton {
id: roomOptionsButton id: roomOptionsButton
visible: !!room
Layout.column: 5
Layout.row: 0
Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.column: 5
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
image: ":/icons/icons/ui/options.svg" Layout.row: 0
ToolTip.visible: hovered Layout.rowSpan: 2
ToolTip.text: qsTr("Room options") ToolTip.text: qsTr("Room options")
ToolTip.visible: hovered
image: ":/icons/icons/ui/options.svg"
visible: !!room
onClicked: roomOptionsMenu.open(roomOptionsButton) onClicked: roomOptionsMenu.open(roomOptionsButton)
Platform.Menu { Platform.Menu {
id: roomOptionsMenu id: roomOptionsMenu
Platform.MenuItem { Platform.MenuItem {
visible: room ? room.permissions.canInvite() : false
text: qsTr("Invite users") text: qsTr("Invite users")
visible: room ? room.permissions.canInvite() : false
onTriggered: TimelineManager.openInviteUsers(roomId) onTriggered: TimelineManager.openInviteUsers(roomId)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Members") text: qsTr("Members")
onTriggered: TimelineManager.openRoomMembers(room) onTriggered: TimelineManager.openRoomMembers(room)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Leave room") text: qsTr("Leave room")
onTriggered: TimelineManager.openLeaveRoomDialog(roomId) onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Settings") text: qsTr("Settings")
onTriggered: TimelineManager.openRoomSettings(roomId) onTriggered: TimelineManager.openRoomSettings(roomId)
} }
} }
} }
ScrollView { ScrollView {
id: pinnedMessages id: pinnedMessages
Layout.row: 2
Layout.column: 2 Layout.column: 2
Layout.columnSpan: 3 Layout.columnSpan: 3
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4) Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
Layout.row: 2
visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId) ScrollBar.horizontal.visible: false
clip: true clip: true
palette: timelineRoot.palette palette: timelineRoot.palette
ScrollBar.horizontal.visible: false visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId)
ListView { ListView {
spacing: Nheko.paddingSmall
model: room ? room.pinnedMessages : undefined model: room ? room.pinnedMessages : undefined
spacing: Nheko.paddingSmall
delegate: RowLayout { delegate: RowLayout {
required property string modelData required property string modelData
width: ListView.view.width
height: implicitHeight height: implicitHeight
width: ListView.view.width
Reply { Reply {
property var e: room ? room.getDump(modelData, "") : {} property var e: room ? room.getDump(modelData, "") : {}
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: height Layout.preferredHeight: height
userColor: TimelineManager.userColor(e.userId, timelineRoot.palette.window)
blurhash: e.blurhash ?? "" blurhash: e.blurhash ?? ""
body: e.body ?? "" body: e.body ?? ""
formattedBody: e.formattedBody ?? "" encryptionError: e.encryptionError ?? ""
eventId: e.eventId ?? "" eventId: e.eventId ?? ""
filename: e.filename ?? "" filename: e.filename ?? ""
filesize: e.filesize ?? "" filesize: e.filesize ?? ""
formattedBody: e.formattedBody ?? ""
isOnlyEmoji: e.isOnlyEmoji ?? false
originalWidth: e.originalWidth ?? 0
proportionalHeight: e.proportionalHeight ?? 1 proportionalHeight: e.proportionalHeight ?? 1
type: e.type ?? MtxEvent.UnknownMessage type: e.type ?? MtxEvent.UnknownMessage
typeString: e.typeString ?? "" typeString: e.typeString ?? ""
url: e.url ?? "" url: e.url ?? ""
originalWidth: e.originalWidth ?? 0 userColor: TimelineManager.userColor(e.userId, timelineRoot.palette.window)
isOnlyEmoji: e.isOnlyEmoji ?? false
userId: e.userId ?? "" userId: e.userId ?? ""
userName: e.userName ?? "" userName: e.userName ?? ""
encryptionError: e.encryptionError ?? ""
} }
ImageButton { ImageButton {
id: deletePinButton id: deletePinButton
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16
Layout.alignment: Qt.AlignTop | Qt.AlignLeft ToolTip.text: qsTr("Unpin")
visible: room.permissions.canChange(MtxEvent.PinnedEvents) ToolTip.visible: hovered
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg" image: ":/icons/icons/ui/dismiss.svg"
ToolTip.visible: hovered visible: room.permissions.canChange(MtxEvent.PinnedEvents)
ToolTip.text: qsTr("Unpin")
onClicked: room.unpin(modelData) onClicked: room.unpin(modelData)
} }
} }
} }
} }
ScrollView { ScrollView {
id: widgets id: widgets
Layout.row: 3
Layout.column: 2 Layout.column: 2
Layout.columnSpan: 3 Layout.columnSpan: 3
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5) Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5)
Layout.row: 3
visible: !!room && room.widgetLinks.length > 0 && !Settings.hiddenWidgets.includes(roomId) ScrollBar.horizontal.visible: false
clip: true clip: true
palette: timelineRoot.palette palette: timelineRoot.palette
ScrollBar.horizontal.visible: false visible: !!room && room.widgetLinks.length > 0 && !Settings.hiddenWidgets.includes(roomId)
ListView { ListView {
spacing: Nheko.paddingSmall
model: room ? room.widgetLinks : undefined model: room ? room.widgetLinks : undefined
spacing: Nheko.paddingSmall
delegate: MatrixText { delegate: MatrixText {
required property var modelData required property var modelData
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: modelData text: modelData
} }
} }
} }
} }
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent
anchors.bottomMargin: (pinnedMessages.visible ? pinnedMessages.height : 0) + (widgets.visible ? widgets.height : 0) anchors.bottomMargin: (pinnedMessages.visible ? pinnedMessages.height : 0) + (widgets.visible ? widgets.height : 0)
anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
} }
TapHandler {
gesturePolicy: TapHandler.ReleaseWithinBounds
onSingleTapped: {
if (eventPoint.position.y > topBar.height - (pinnedMessages.visible ? pinnedMessages.height : 0) - (widgets.visible ? widgets.height : 0)) {
eventPoint.accepted = true;
return;
}
if (showBackButton && eventPoint.position.x < Nheko.paddingMedium + backToRoomsButton.width) {
eventPoint.accepted = true;
return;
}
if (eventPoint.position.x > topBar.width - Nheko.paddingMedium - roomOptionsButton.width) {
eventPoint.accepted = true;
return;
}
if (room) {
let p = topBar.mapToItem(roomTopicC, eventPoint.position.x, eventPoint.position.y);
let link = roomTopicC.linkAt(p.x, p.y);
if (link) {
Nheko.openLink(link);
} else {
TimelineManager.openRoomSettings(room.roomId);
}
}
eventPoint.accepted = true;
}
}
HoverHandler {
grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything
}
} }

@ -1,38 +1,32 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import im.nheko import im.nheko
Item { Item {
implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
Rectangle { Rectangle {
id: typingRect id: typingRect
visible: (room && room.typingUsers.length > 0)
color: timelineRoot.palette.base
anchors.fill: parent anchors.fill: parent
color: timelineRoot.palette.base
visible: (room && room.typingUsers.length > 0)
z: 3 z: 3
Label { Label {
id: typingDisplay id: typingDisplay
anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 10 anchors.rightMargin: 10
anchors.bottom: parent.bottom
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: room ? room.formatTypingUsers(room.typingUsers, timelineRoot.palette.base) : "" text: room ? room.formatTypingUsers(room.typingUsers, timelineRoot.palette.base) : ""
textFormat: Text.RichText textFormat: Text.RichText
} }
} }
} }

@ -1,10 +1,7 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "components"
import "./components" import "ui"
import "./ui"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.5 import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
@ -12,31 +9,30 @@ import im.nheko
Page { Page {
id: uploadPopup id: uploadPopup
visible: room && room.input.uploads.length > 0 Layout.fillWidth: true
Layout.preferredHeight: 200 Layout.preferredHeight: 200
clip: true clip: true
Layout.fillWidth: true
padding: Nheko.paddingMedium padding: Nheko.paddingMedium
visible: room && room.input.uploads.length > 0
background: Rectangle {
color: timelineRoot.palette.base
}
contentItem: ListView { contentItem: ListView {
id: uploadsList id: uploadsList
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
model: room ? room.input.uploads : undefined
orientation: ListView.Horizontal
spacing: Nheko.paddingMedium
width: Math.min(contentWidth, parent.availableWidth)
ScrollBar.horizontal: ScrollBar { ScrollBar.horizontal: ScrollBar {
id: scr id: scr
} }
orientation: ListView.Horizontal
width: Math.min(contentWidth, parent.availableWidth)
model: room ? room.input.uploads : undefined
spacing: Nheko.paddingMedium
delegate: Pane { delegate: Pane {
height: uploadPopup.availableHeight - buttons.height - (scr.visible ? scr.height : 0)
padding: Nheko.paddingSmall padding: Nheko.paddingSmall
height: uploadPopup.availableHeight - buttons.height - (scr.visible? scr.height : 0)
width: uploadPopup.availableHeight - buttons.height width: uploadPopup.availableHeight - buttons.height
background: Rectangle { background: Rectangle {
@ -45,45 +41,45 @@ Page {
} }
contentItem: ColumnLayout { contentItem: ColumnLayout {
Image { Image {
property string typeStr: switch (modelData.mediaType) {
case MediaUpload.Video:
return "video-file";
case MediaUpload.Audio:
return "music";
case MediaUpload.Image:
return "image";
default:
return "zip";
}
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
sourceSize.height: height
sourceSize.width: width
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
smooth: true
mipmap: true mipmap: true
smooth: true
property string typeStr: switch(modelData.mediaType) { source: (modelData.thumbnail != "") ? modelData.thumbnail : ("image://colorimage/:/icons/icons/ui/" + typeStr + ".svg?" + timelineRoot.palette.placeholderText)
case MediaUpload.Video: return "video-file"; sourceSize.height: height
case MediaUpload.Audio: return "music"; sourceSize.width: width
case MediaUpload.Image: return "image";
default: return "zip";
}
source: (modelData.thumbnail != "") ? modelData.thumbnail : ("image://colorimage/:/icons/icons/ui/"+typeStr+".svg?" + timelineRoot.palette.placeholderText)
} }
MatrixTextField { MatrixTextField {
Layout.fillWidth: true Layout.fillWidth: true
text: modelData.filename text: modelData.filename
onTextEdited: modelData.filename = text onTextEdited: modelData.filename = text
} }
} }
} }
} }
footer: DialogButtonBox { footer: DialogButtonBox {
id: buttons id: buttons
standardButtons: DialogButtonBox.Cancel standardButtons: DialogButtonBox.Cancel
Button {
text: qsTr("Upload %n file(s)", "", (room ? room.input.uploads.length : 0))
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
onAccepted: room.input.acceptUploads() onAccepted: room.input.acceptUploads()
onRejected: room.input.declineUploads() onRejected: room.input.declineUploads()
}
background: Rectangle { Button {
color: timelineRoot.palette.base DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Upload %n file(s)", "", (room ? room.input.uploads.length : 0))
}
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.5 import QtQuick.Controls 2.5
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
@ -12,67 +10,18 @@ Container {
//Component.onCompleted: { //Component.onCompleted: {
// parent.width = Qt.binding(function() { return calculatedWidth; }) // parent.width = Qt.binding(function() { return calculatedWidth; })
//} //}
id: container id: container
property bool singlePageMode: width < 800 property Component handle: Rectangle {
property int splitterGrabMargin: Nheko.paddingSmall anchors.right: parent.right
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
color: Nheko.theme.separator color: Nheko.theme.separator
height: container.height height: container.height
width: visible ? 1 : 0 width: visible ? 1 : 0
anchors.right: parent.right z: 3
} }
property Component handleToucharea: Item {
handleToucharea: Item {
id: splitter 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: { property int calculatedWidth: {
if (!visible) if (!visible)
return 0; return 0;
@ -81,6 +30,10 @@ Container {
else else
return (collapsible && x < minimumWidth) ? collapsedWidth : x; 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 enabled: !container.singlePageMode
height: container.height height: container.height
@ -89,49 +42,82 @@ Container {
z: 3 z: 3
NhekoCursorShape { NhekoCursorShape {
cursorShape: Qt.SizeHorCursor
height: parent.height height: parent.height
width: container.splitterGrabMargin * 2 width: container.splitterGrabMargin * 2
x: -container.splitterGrabMargin x: -container.splitterGrabMargin
cursorShape: Qt.SizeHorCursor
} }
DragHandler { DragHandler {
id: dragHandler id: dragHandler
enabled: !container.singlePageMode enabled: !container.singlePageMode
grabPermissions: PointerHandler.CanTakeOverFromAnything | PointerHandler.ApprovesTakeOverByHandlersOfSameType
margin: container.splitterGrabMargin
xAxis.enabled: true xAxis.enabled: true
yAxis.enabled: false
xAxis.minimum: splitter.minimumWidth - 1
xAxis.maximum: splitter.maximumWidth xAxis.maximum: splitter.maximumWidth
margin: container.splitterGrabMargin xAxis.minimum: splitter.minimumWidth - 1
grabPermissions: PointerHandler.CanTakeOverFromAnything | PointerHandler.ApprovesTakeOverByHandlersOfSameType yAxis.enabled: false
onActiveChanged: { onActiveChanged: {
if (!active) { if (!active) {
splitter.x = splitter.calculatedWidth; splitter.x = splitter.calculatedWidth;
splitter.parent.preferredWidth = splitter.calculatedWidth; splitter.parent.preferredWidth = splitter.calculatedWidth;
} }
} }
} }
HoverHandler { HoverHandler {
enabled: !container.singlePageMode enabled: !container.singlePageMode
margin: container.splitterGrabMargin margin: container.splitterGrabMargin
} }
} }
property alias pageIndex: view.currentIndex
property bool singlePageMode: width < 800
property int splitterGrabMargin: Nheko.paddingSmall
contentItem: ListView { contentItem: ListView {
id: view id: view
boundsBehavior: Flickable.StopAtBounds
model: container.contentModel currentIndex: container.singlePageMode ? container.pageIndex : 0
snapMode: ListView.SnapOneItem highlightMoveDuration: container.singlePageMode ? 200 : 0
orientation: ListView.Horizontal
highlightRangeMode: ListView.StrictlyEnforceRange highlightRangeMode: ListView.StrictlyEnforceRange
interactive: singlePageMode interactive: singlePageMode
highlightMoveDuration: container.singlePageMode ? 200 : 0 model: container.contentModel
currentIndex: container.singlePageMode ? container.pageIndex : 0 orientation: ListView.Horizontal
boundsBehavior: Flickable.StopAtBounds 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
} }

@ -1,20 +1,18 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.5 import QtQuick.Controls 2.5
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
Item { Item {
property int minimumWidth: 100 property bool collapsed: width < minimumWidth
property int maximumWidth: 400
property int collapsedWidth: 40 property int collapsedWidth: 40
property bool collapsible: true property bool collapsible: true
property bool collapsed: width < minimumWidth property int maximumWidth: 400
property int splitterWidth: 1 property int minimumWidth: 100
property int preferredWidth: 100 property int preferredWidth: 100
property int splitterWidth: 1
Component.onCompleted: { Component.onCompleted: {
children[0].width = Qt.binding(() => { children[0].width = Qt.binding(() => {

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
@ -12,86 +10,81 @@ import im.nheko
Rectangle { Rectangle {
id: tile id: tile
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
required property string avatarUrl
property color background: timelineRoot.palette.window property color background: timelineRoot.palette.window
property color importantText: timelineRoot.palette.text
property color unimportantText: timelineRoot.palette.placeholderText
property color bubbleBackground: timelineRoot.palette.highlight property color bubbleBackground: timelineRoot.palette.highlight
property color bubbleText: timelineRoot.palette.highlightedText property color bubbleText: timelineRoot.palette.highlightedText
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
required property string avatarUrl
required property string title
required property string subtitle
required property int index
required property int selectedIndex
property bool crop: true property bool crop: true
property color importantText: timelineRoot.palette.text
required property int index
property alias roomid: avatar.roomid property alias roomid: avatar.roomid
required property int selectedIndex
required property string subtitle
required property string title
property color unimportantText: timelineRoot.palette.placeholderText
property alias userid: avatar.userid property alias userid: avatar.userid
color: background color: background
height: avatarSize + 2 * Nheko.paddingMedium height: avatarSize + 2 * Nheko.paddingMedium
width: ListView.view.width
state: "normal" state: "normal"
width: ListView.view.width
states: [ states: [
State { State {
name: "highlight" name: "highlight"
when: hovered.hovered && !(index == selectedIndex) when: hovered.hovered && !(index == selectedIndex)
PropertyChanges { PropertyChanges {
target: tile
background: timelineRoot.palette.dark background: timelineRoot.palette.dark
importantText: timelineRoot.palette.brightText
unimportantText: timelineRoot.palette.brightText
bubbleBackground: timelineRoot.palette.highlight bubbleBackground: timelineRoot.palette.highlight
bubbleText: timelineRoot.palette.highlightedText bubbleText: timelineRoot.palette.highlightedText
importantText: timelineRoot.palette.brightText
target: tile
unimportantText: timelineRoot.palette.brightText
} }
}, },
State { State {
name: "selected" name: "selected"
when: index == selectedIndex when: index == selectedIndex
PropertyChanges { PropertyChanges {
target: tile
background: timelineRoot.palette.highlight background: timelineRoot.palette.highlight
importantText: timelineRoot.palette.highlightedText
unimportantText: timelineRoot.palette.highlightedText
bubbleBackground: timelineRoot.palette.highlightedText bubbleBackground: timelineRoot.palette.highlightedText
bubbleText: timelineRoot.palette.highlight bubbleText: timelineRoot.palette.highlight
importantText: timelineRoot.palette.highlightedText
target: tile
unimportantText: timelineRoot.palette.highlightedText
} }
} }
] ]
HoverHandler { HoverHandler {
id: hovered id: hovered
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Avatar { Avatar {
id: avatar id: avatar
enabled: false
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
crop: tile.crop
displayName: title
enabled: false
height: avatarSize height: avatarSize
width: avatarSize
url: tile.avatarUrl.replace("mxc://", "image://MxcImage/") url: tile.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: title width: avatarSize
crop: tile.crop
} }
ColumnLayout { ColumnLayout {
id: textContent id: textContent
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: 100 Layout.minimumWidth: 100
width: parent.width - avatar.width
Layout.preferredWidth: parent.width - avatar.width Layout.preferredWidth: parent.width - avatar.width
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
width: parent.width - avatar.width
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
@ -104,33 +97,25 @@ Rectangle {
fullText: title fullText: title
textFormat: Text.PlainText textFormat: Text.PlainText
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
spacing: 0 spacing: 0
ElidedLabel { ElidedLabel {
color: tile.unimportantText color: tile.unimportantText
font.pixelSize: fontMetrics.font.pixelSize * 0.9
elideWidth: textContent.width - Nheko.paddingSmall elideWidth: textContent.width - Nheko.paddingSmall
font.pixelSize: fontMetrics.font.pixelSize * 0.9
fullText: subtitle fullText: subtitle
textFormat: Text.PlainText textFormat: Text.PlainText
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
} }
} }
} }

@ -1,6 +1,5 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
//import QtGraphicalEffects 1.12 //import QtGraphicalEffects 1.12
@ -13,11 +12,18 @@ import im.nheko
Button { Button {
id: control id: control
property string iconImage: ""
hoverEnabled: true
implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.70) implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.70)
implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight) implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight)
hoverEnabled: true
property string iconImage: "" background: Rectangle {
color: Qt.lighter(timelineRoot.palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
//height: control.contentItem.implicitHeight * 2
//width: control.contentItem.implicitWidth * 2
radius: height / 8
}
//DropShadow { //DropShadow {
// anchors.fill: control.background // anchors.fill: control.background
@ -29,38 +35,29 @@ Button {
// color: "#80000000" // color: "#80000000"
// source: control.background // source: control.background
//} //}
contentItem: RowLayout { contentItem: RowLayout {
spacing: 0
anchors.centerIn: parent anchors.centerIn: parent
spacing: 0
Image { Image {
Layout.leftMargin: Nheko.paddingMedium
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.leftMargin: Nheko.paddingMedium
Layout.preferredHeight: fontMetrics.font.pixelSize * 1.5 Layout.preferredHeight: fontMetrics.font.pixelSize * 1.5
Layout.preferredWidth: fontMetrics.font.pixelSize * 1.5 Layout.preferredWidth: fontMetrics.font.pixelSize * 1.5
visible: !!iconImage
source: iconImage source: iconImage
visible: !!iconImage
} }
Text { Text {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: control.text //font.capitalization: Font.AllUppercase
color: timelineRoot.palette.light
elide: Text.ElideRight
//font: control.font //font: control.font
font.capitalization: Font.AllUppercase font.capitalization: Font.AllUppercase
font.pointSize: Math.ceil(fontMetrics.font.pointSize * 1.5) font.pointSize: Math.ceil(fontMetrics.font.pointSize * 1.5)
//font.capitalization: Font.AllUppercase
color: timelineRoot.palette.light
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: control.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
} }
} }
background: Rectangle {
//height: control.contentItem.implicitHeight * 2
//width: control.contentItem.implicitWidth * 2
radius: height / 8
color: Qt.lighter(timelineRoot.palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
}
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import Qt.labs.platform 1.1 as P import Qt.labs.platform 1.1 as P
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
@ -13,30 +11,28 @@ Dialog {
default property alias inner: scroll.data default property alias inner: scroll.data
property int useableWidth: scroll.width - scroll.ScrollBar.vertical.width property int useableWidth: scroll.width - scroll.ScrollBar.vertical.width
parent: Overlay.overlay
anchors.centerIn: parent anchors.centerIn: parent
closePolicy: Popup.NoAutoClose
height: (Math.floor(parent.height / 2) - Nheko.paddingLarge) * 2 height: (Math.floor(parent.height / 2) - Nheko.paddingLarge) * 2
width: (Math.floor(parent.width / 2) - Nheko.paddingLarge) * 2
padding: 0
modal: true modal: true
padding: 0
parent: Overlay.overlay
standardButtons: Dialog.Ok | Dialog.Cancel 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: timelineRoot.palette.window
radius: Nheko.paddingSmall
}
contentChildren: [ contentChildren: [
ScrollView { ScrollView {
id: scroll id: scroll
clip: true
anchors.fill: parent
ScrollBar.horizontal.visible: false ScrollBar.horizontal.visible: false
ScrollBar.vertical.visible: true ScrollBar.vertical.visible: true
anchors.fill: parent
clip: true
} }
] ]
background: Rectangle {
color: timelineRoot.palette.window
border.color: Nheko.theme.separator
border.width: 1
radius: Nheko.paddingSmall
}
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../ui" import "../ui"
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
@ -11,37 +9,32 @@ import im.nheko // for cursor shape
AbstractButton { AbstractButton {
id: button id: button
property color buttonTextColor: timelineRoot.palette.placeholderText
property alias cursor: mouseArea.cursorShape property alias cursor: mouseArea.cursorShape
property color highlightColor: timelineRoot.palette.highlight property color highlightColor: timelineRoot.palette.highlight
property color buttonTextColor: timelineRoot.palette.placeholderText
focusPolicy: Qt.NoFocus focusPolicy: Qt.NoFocus
width: buttonText.implicitWidth
height: buttonText.implicitHeight height: buttonText.implicitHeight
implicitWidth: buttonText.implicitWidth
implicitHeight: buttonText.implicitHeight implicitHeight: buttonText.implicitHeight
implicitWidth: buttonText.implicitWidth
width: buttonText.implicitWidth
Label { Label {
id: buttonText id: buttonText
anchors.centerIn: parent anchors.centerIn: parent
padding: 0
text: button.text
color: button.hovered ? highlightColor : buttonTextColor color: button.hovered ? highlightColor : buttonTextColor
font: button.font font: button.font
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
padding: 0
text: button.text
verticalAlignment: Text.AlignVCenter
} }
NhekoCursorShape { NhekoCursorShape {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
Ripple { Ripple {
color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5) color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5)
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
@ -15,32 +13,31 @@ Rectangle {
required property int encryptionError required property int encryptionError
required property string eventId required property string eventId
radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium
width: parent.width? parent.width : 0
implicitWidth: encryptedText.implicitWidth+24+Nheko.paddingMedium*3 // Column doesn't provide a useful implicitWidth, should be replaced by ColumnLayout
height: contents.implicitHeight + Nheko.paddingMedium * 2
color: timelineRoot.palette.alternateBase color: timelineRoot.palette.alternateBase
height: contents.implicitHeight + Nheko.paddingMedium * 2
implicitWidth: encryptedText.implicitWidth + 24 + Nheko.paddingMedium * 3 // Column doesn't provide a useful implicitWidth, should be replaced by ColumnLayout
radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium
width: parent.width ? parent.width : 0
RowLayout { RowLayout {
id: contents id: contents
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
Image { Image {
source: "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.error
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
width: 24
height: width height: width
source: "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.error
width: 24
} }
Column { Column {
spacing: Nheko.paddingSmall
Layout.fillWidth: true Layout.fillWidth: true
spacing: Nheko.paddingSmall
MatrixText { MatrixText {
id: encryptedText id: encryptedText
color: timelineRoot.palette.text
text: { text: {
switch (encryptionError) { switch (encryptionError) {
case Olm.MissingSession: case Olm.MissingSession:
@ -59,19 +56,15 @@ Rectangle {
return qsTr("Unknown decryption error"); return qsTr("Unknown decryption error");
} }
} }
color: timelineRoot.palette.text
width: parent.width width: parent.width
} }
Button { Button {
palette: timelineRoot.palette palette: timelineRoot.palette
visible: encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex
text: qsTr("Request key") text: qsTr("Request key")
visible: encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex
onClicked: room.requestKeyForEvent(eventId) onClicked: room.requestKeyForEvent(eventId)
} }
} }
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import im.nheko import im.nheko
@ -11,86 +9,71 @@ Item {
required property string eventId required property string eventId
required property string filename required property string filename
required property string filesize required property string filesize
property bool fitsMetadata: true
property int metadataWidth
height: row.height + (Settings.bubbles? 16: 24) height: row.height + (Settings.bubbles ? 16 : 24)
implicitWidth: row.implicitWidth + metadataWidth
width: parent.width width: parent.width
implicitWidth: row.implicitWidth+metadataWidth
property int metadataWidth
property bool fitsMetadata: true
RowLayout { RowLayout {
id: row id: row
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - (Settings.bubbles? 16 : 24)
spacing: 15 spacing: 15
width: parent.width - (Settings.bubbles ? 16 : 24)
Rectangle { Rectangle {
id: button id: button
color: timelineRoot.palette.light color: timelineRoot.palette.light
radius: 22
height: 44 height: 44
radius: 22
width: 44 width: 44
Image { Image {
id: img id: img
anchors.centerIn: parent
fillMode: Image.Pad
height: 40 height: 40
width: 40 source: "qrc:/icons/icons/ui/download.svg"
sourceSize.height: 40 sourceSize.height: 40
sourceSize.width: 40 sourceSize.width: 40
width: 40
anchors.centerIn: parent
source: "qrc:/icons/icons/ui/download.svg"
fillMode: Image.Pad
} }
TapHandler { TapHandler {
onSingleTapped: room.saveMedia(eventId)
gesturePolicy: TapHandler.ReleaseWithinBounds gesturePolicy: TapHandler.ReleaseWithinBounds
}
onSingleTapped: room.saveMedia(eventId)
}
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
} }
ColumnLayout { ColumnLayout {
id: col id: col
Text { Text {
id: filename_ id: filename_
Layout.fillWidth: true Layout.fillWidth: true
color: timelineRoot.palette.text
elide: Text.ElideRight
text: filename text: filename
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight
color: timelineRoot.palette.text
} }
Text { Text {
id: filesize_ id: filesize_
Layout.fillWidth: true Layout.fillWidth: true
color: timelineRoot.palette.text
elide: Text.ElideRight
text: filesize text: filesize
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight
color: timelineRoot.palette.text
} }
} }
} }
Rectangle { Rectangle {
anchors.fill: parent
color: timelineRoot.palette.alternateBase color: timelineRoot.palette.alternateBase
z: -1
radius: 10 radius: 10
anchors.fill: parent
visible: !Settings.bubbles // the bubble in a bubble looks odd visible: !Settings.bubbles // the bubble in a bubble looks odd
z: -1
} }
} }

@ -1,102 +1,85 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Window 2.15 import QtQuick.Window 2.15
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import im.nheko import im.nheko
AbstractButton { AbstractButton {
required property int type
required property int originalWidth
required property double proportionalHeight
required property string url
required property string blurhash required property string blurhash
required property string body required property string body
property double divisor: isReply ? 5 : 3
required property string eventId
required property string filename required property string filename
property bool fitsMetadata: (parent.width - width) > metadataWidth + 4
required property bool isReply required property bool isReply
required property string eventId property int metadataWidth
property double divisor: isReply ? 5 : 3 required property int originalWidth
required property double proportionalHeight
property int tempWidth: originalWidth < 1? 400: originalWidth property int tempWidth: originalWidth < 1 ? 400 : originalWidth
required property int type
required property string url
implicitWidth: Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1)) height: width * proportionalHeight
width: Math.min(parent?.width ?? implicitWidth,implicitWidth)
height: width*proportionalHeight
hoverEnabled: true hoverEnabled: true
implicitWidth: Math.round(tempWidth * Math.min((timelineView.height / divisor) / (tempWidth * proportionalHeight), 1))
width: Math.min(parent?.width ?? implicitWidth, implicitWidth)
property int metadataWidth onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId)
property bool fitsMetadata: (parent.width - width) > metadataWidth+4
Image { Image {
id: blurhash_ id: blurhash_
anchors.fill: parent anchors.fill: parent
visible: img.status != Image.Ready
source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + timelineRoot.palette.placeholderText)
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
sourceSize.width: parent.width * Screen.devicePixelRatio source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + timelineRoot.palette.placeholderText)
sourceSize.height: parent.height * Screen.devicePixelRatio sourceSize.height: parent.height * Screen.devicePixelRatio
sourceSize.width: parent.width * Screen.devicePixelRatio
visible: img.status != Image.Ready
} }
Image { Image {
id: img id: img
visible: !mxcimage.loaded
anchors.fill: parent anchors.fill: parent
source: url.replace("mxc://", "image://MxcImage/") + "?scale"
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
smooth: true
mipmap: true mipmap: true
smooth: true
source: url.replace("mxc://", "image://MxcImage/") + "?scale"
sourceSize.height: Math.min(Screen.desktopAvailableHeight, (originalWidth < 1 ? Screen.desktopAvailableHeight : originalWidth * proportionalHeight)) * Screen.devicePixelRatio
sourceSize.width: Math.min(Screen.desktopAvailableWidth, originalWidth < 1 ? Screen.desktopAvailableWidth : originalWidth) * Screen.devicePixelRatio sourceSize.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 { MxcAnimatedImage {
id: mxcimage id: mxcimage
visible: loaded
anchors.fill: parent anchors.fill: parent
roomm: room
play: !Settings.animateImagesOnHover || parent.hovered
eventId: parent.eventId eventId: parent.eventId
play: !Settings.animateImagesOnHover || parent.hovered
roomm: room
visible: loaded
} }
onClicked :Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId);
Item { Item {
id: overlay id: overlay
anchors.fill: parent anchors.fill: parent
visible: parent.hovered visible: parent.hovered
Rectangle { Rectangle {
id: container id: container
width: parent.width
implicitHeight: imgcaption.implicitHeight
anchors.bottom: overlay.bottom anchors.bottom: overlay.bottom
color: timelineRoot.palette.window color: timelineRoot.palette.window
implicitHeight: imgcaption.implicitHeight
opacity: 0.75 opacity: 0.75
width: parent.width
} }
Text { Text {
id: imgcaption id: imgcaption
anchors.fill: container anchors.fill: container
color: timelineRoot.palette.text
elide: Text.ElideMiddle elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
// See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530 // See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530
text: filename ? filename : body text: filename ? filename : body
color: timelineRoot.palette.text verticalAlignment: Text.AlignVCenter
} }
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.6 import QtQuick 2.6
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
@ -12,35 +10,35 @@ import im.nheko
Item { Item {
id: d id: d
required property bool isReply
property alias child: chooser.child
implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : 0
required property double proportionalHeight
required property int type
required property string typeString
required property int originalWidth
required property int duration
required property string blurhash required property string blurhash
required property string body required property string body
required property string formattedBody required property string callType
property alias child: chooser.child
required property int duration
required property int encryptionError
required property string eventId required property string eventId
required property string filename required property string filename
required property string filesize required property string filesize
required property string url property bool fitsMetadata: (chooser.child && chooser.child.fitsMetadata) ? chooser.child.fitsMetadata : false
required property string thumbnailUrl required property string formattedBody
required property bool isOnlyEmoji required property bool isOnlyEmoji
required property bool isReply
required property bool isStateEvent required property bool isStateEvent
property int metadataWidth
required property int originalWidth
required property double proportionalHeight
required property int relatedEventCacheBuster
required property string roomName
required property string roomTopic
required property string thumbnailUrl
required property int type
required property string typeString
required property string url
required property string userId required property string userId
required property string userName required property string userName
required property string roomTopic
required property string roomName
required property string callType
required property int encryptionError
required property int relatedEventCacheBuster
property bool fitsMetadata: (chooser.child && chooser.child.fitsMetadata) ? chooser.child.fitsMetadata : false
property int metadataWidth
height: chooser.child ? chooser.child.height : Nheko.paddingLarge height: chooser.child ? chooser.child.height : Nheko.paddingLarge
implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : 0
DelegateChooser { DelegateChooser {
id: chooser id: chooser
@ -48,97 +46,84 @@ Item {
//role: "type" //< not supported in our custom implementation, have to use roleValue //role: "type" //< not supported in our custom implementation, have to use roleValue
roleValue: type roleValue: type
//anchors.fill: parent //anchors.fill: parent
width: parent.width ? parent.width : 0 // this should get rid of "cannot read property 'width' of null"
width: parent.width? parent.width: 0 // this should get rid of "cannot read property 'width' of null"
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.UnknownMessage roleValue: MtxEvent.UnknownMessage
Placeholder { Placeholder {
typeString: d.typeString
text: "Unretrieved event" text: "Unretrieved event"
typeString: d.typeString
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.TextMessage roleValue: MtxEvent.TextMessage
TextMessage { TextMessage {
formatted: d.formattedBody
body: d.body body: d.body
formatted: d.formattedBody
isOnlyEmoji: d.isOnlyEmoji isOnlyEmoji: d.isOnlyEmoji
isReply: d.isReply isReply: d.isReply
metadataWidth: d.metadataWidth metadataWidth: d.metadataWidth
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.NoticeMessage roleValue: MtxEvent.NoticeMessage
NoticeMessage { NoticeMessage {
formatted: d.formattedBody
body: d.body body: d.body
formatted: d.formattedBody
isOnlyEmoji: d.isOnlyEmoji isOnlyEmoji: d.isOnlyEmoji
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
metadataWidth: d.metadataWidth metadataWidth: d.metadataWidth
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.EmoteMessage roleValue: MtxEvent.EmoteMessage
NoticeMessage { NoticeMessage {
formatted: TimelineManager.escapeEmoji(d.userName) + " " + d.formattedBody
color: TimelineManager.userColor(d.userId, timelineRoot.palette.base)
body: d.body body: d.body
color: TimelineManager.userColor(d.userId, timelineRoot.palette.base)
formatted: TimelineManager.escapeEmoji(d.userName) + " " + d.formattedBody
isOnlyEmoji: d.isOnlyEmoji isOnlyEmoji: d.isOnlyEmoji
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
metadataWidth: d.metadataWidth metadataWidth: d.metadataWidth
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.ImageMessage roleValue: MtxEvent.ImageMessage
ImageMessage { ImageMessage {
type: d.type
originalWidth: d.originalWidth
proportionalHeight: d.proportionalHeight
url: d.url
blurhash: d.blurhash blurhash: d.blurhash
body: d.body body: d.body
eventId: d.eventId
filename: d.filename filename: d.filename
isReply: d.isReply isReply: d.isReply
eventId: d.eventId
metadataWidth: d.metadataWidth metadataWidth: d.metadataWidth
originalWidth: d.originalWidth
proportionalHeight: d.proportionalHeight
type: d.type
url: d.url
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Sticker roleValue: MtxEvent.Sticker
ImageMessage { ImageMessage {
type: d.type
originalWidth: d.originalWidth
proportionalHeight: d.proportionalHeight
url: d.url
blurhash: d.blurhash blurhash: d.blurhash
body: d.body body: d.body
eventId: d.eventId
filename: d.filename filename: d.filename
isReply: d.isReply isReply: d.isReply
eventId: d.eventId
metadataWidth: d.metadataWidth metadataWidth: d.metadataWidth
originalWidth: d.originalWidth
proportionalHeight: d.proportionalHeight
type: d.type
url: d.url
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.FileMessage roleValue: MtxEvent.FileMessage
@ -148,45 +133,39 @@ Item {
filesize: d.filesize filesize: d.filesize
metadataWidth: d.metadataWidth metadataWidth: d.metadataWidth
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.VideoMessage roleValue: MtxEvent.VideoMessage
PlayableMediaMessage { PlayableMediaMessage {
proportionalHeight: d.proportionalHeight
type: d.type
originalWidth: d.originalWidth
thumbnailUrl: d.thumbnailUrl
eventId: d.eventId
url: d.url
body: d.body body: d.body
filesize: d.filesize
duration: d.duration duration: d.duration
eventId: d.eventId
filesize: d.filesize
metadataWidth: d.metadataWidth metadataWidth: d.metadataWidth
originalWidth: d.originalWidth
proportionalHeight: d.proportionalHeight
thumbnailUrl: d.thumbnailUrl
type: d.type
url: d.url
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.AudioMessage roleValue: MtxEvent.AudioMessage
PlayableMediaMessage { PlayableMediaMessage {
proportionalHeight: d.proportionalHeight
type: d.type
originalWidth: d.originalWidth
thumbnailUrl: d.thumbnailUrl
eventId: d.eventId
url: d.url
body: d.body body: d.body
filesize: d.filesize
duration: d.duration duration: d.duration
eventId: d.eventId
filesize: d.filesize
metadataWidth: d.metadataWidth metadataWidth: d.metadataWidth
originalWidth: d.originalWidth
proportionalHeight: d.proportionalHeight
thumbnailUrl: d.thumbnailUrl
type: d.type
url: d.url
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Redacted roleValue: MtxEvent.Redacted
@ -194,27 +173,22 @@ Item {
metadataWidth: d.metadataWidth metadataWidth: d.metadataWidth
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Redaction roleValue: MtxEvent.Redaction
Pill { Pill {
text: qsTr("%1 removed a message").arg(d.userName)
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
text: qsTr("%1 removed a message").arg(d.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Encryption roleValue: MtxEvent.Encryption
Pill { Pill {
text: qsTr("%1 enabled encryption").arg(d.userName)
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
text: qsTr("%1 enabled encryption").arg(d.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Encrypted roleValue: MtxEvent.Encrypted
@ -222,122 +196,100 @@ Item {
encryptionError: d.encryptionError encryptionError: d.encryptionError
eventId: d.eventId eventId: d.eventId
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Name roleValue: MtxEvent.Name
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: d.roomName ? qsTr("%2 changed the room name to: %1").arg(d.roomName).arg(d.userName) : qsTr("%1 removed the room name").arg(d.userName)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: d.roomName ? qsTr("%2 changed the room name to: %1").arg(d.roomName).arg(d.userName) : qsTr("%1 removed the room name").arg(d.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Topic roleValue: MtxEvent.Topic
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: d.roomTopic ? qsTr("%2 changed the topic to: %1").arg(d.roomTopic).arg(d.userName) : qsTr("%1 removed the topic").arg(d.userName)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: d.roomTopic ? qsTr("%2 changed the topic to: %1").arg(d.roomTopic).arg(d.userName): qsTr("%1 removed the topic").arg(d.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Avatar roleValue: MtxEvent.Avatar
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: qsTr("%1 changed the room avatar").arg(d.userName)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: qsTr("%1 changed the room avatar").arg(d.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.PinnedEvents roleValue: MtxEvent.PinnedEvents
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: qsTr("%1 changed the pinned messages.").arg(d.userName)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: qsTr("%1 changed the pinned messages.").arg(d.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.ImagePackInRoom roleValue: MtxEvent.ImagePackInRoom
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: d.relatedEventCacheBuster, room.formatImagePackEvent(d.eventId)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: d.relatedEventCacheBuster, room.formatImagePackEvent(d.eventId)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.CanonicalAlias roleValue: MtxEvent.CanonicalAlias
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: qsTr("%1 changed the addresses for this room.").arg(d.userName)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: qsTr("%1 changed the addresses for this room.").arg(d.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.SpaceParent roleValue: MtxEvent.SpaceParent
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: qsTr("%1 changed the parent spaces for this room.").arg(d.userName)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: qsTr("%1 changed the parent spaces for this room.").arg(d.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.RoomCreate roleValue: MtxEvent.RoomCreate
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.CallInvite roleValue: MtxEvent.CallInvite
NoticeMessage { NoticeMessage {
body: formatted body: formatted
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
formatted: { formatted: {
switch (d.callType) { switch (d.callType) {
case "voice": case "voice":
@ -348,101 +300,88 @@ Item {
return qsTr("%1 placed a call.").arg(d.userName); return qsTr("%1 placed a call.").arg(d.userName);
} }
} }
isOnlyEmoji: false
isReply: d.isReply
isStateEvent: d.isStateEvent
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.CallAnswer roleValue: MtxEvent.CallAnswer
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: qsTr("%1 answered the call.").arg(d.userName)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: qsTr("%1 answered the call.").arg(d.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.CallHangUp roleValue: MtxEvent.CallHangUp
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: qsTr("%1 ended the call.").arg(d.userName)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: qsTr("%1 ended the call.").arg(d.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.CallCandidates roleValue: MtxEvent.CallCandidates
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: qsTr("%1 is negotiating the call...").arg(d.userName)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: qsTr("%1 is negotiating the call...").arg(d.userName)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.PowerLevels roleValue: MtxEvent.PowerLevels
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: d.relatedEventCacheBuster, room.formatPowerLevelEvent(d.eventId)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: d.relatedEventCacheBuster, room.formatPowerLevelEvent(d.eventId)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.RoomJoinRules roleValue: MtxEvent.RoomJoinRules
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: d.relatedEventCacheBuster, room.formatJoinRuleEvent(d.eventId)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: d.relatedEventCacheBuster, room.formatJoinRuleEvent(d.eventId)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.RoomHistoryVisibility roleValue: MtxEvent.RoomHistoryVisibility
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: d.relatedEventCacheBuster, room.formatHistoryVisibilityEvent(d.eventId)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: d.relatedEventCacheBuster, room.formatHistoryVisibilityEvent(d.eventId)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.RoomGuestAccess roleValue: MtxEvent.RoomGuestAccess
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: d.relatedEventCacheBuster, room.formatGuestAccessEvent(d.eventId)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: d.relatedEventCacheBuster, room.formatGuestAccessEvent(d.eventId)
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Member roleValue: MtxEvent.Member
@ -450,149 +389,125 @@ Item {
width: parent?.width width: parent?.width
NoticeMessage { NoticeMessage {
Layout.fillWidth: true
body: formatted body: formatted
formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId)
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
Layout.fillWidth: true
formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId)
} }
Button { Button {
visible: d.relatedEventCacheBuster, room.showAcceptKnockButton(d.eventId)
palette: timelineRoot.palette palette: timelineRoot.palette
text: qsTr("Allow them in") text: qsTr("Allow them in")
visible: d.relatedEventCacheBuster, room.showAcceptKnockButton(d.eventId)
onClicked: room.acceptKnock(eventId) onClicked: room.acceptKnock(eventId)
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationRequest roleValue: MtxEvent.KeyVerificationRequest
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: "KeyVerificationRequest"
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: "KeyVerificationRequest"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationStart roleValue: MtxEvent.KeyVerificationStart
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: "KeyVerificationStart"
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: "KeyVerificationStart"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationReady roleValue: MtxEvent.KeyVerificationReady
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: "KeyVerificationReady"
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: "KeyVerificationReady"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationCancel roleValue: MtxEvent.KeyVerificationCancel
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: "KeyVerificationCancel"
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: "KeyVerificationCancel"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationKey roleValue: MtxEvent.KeyVerificationKey
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: "KeyVerificationKey"
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: "KeyVerificationKey"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationMac roleValue: MtxEvent.KeyVerificationMac
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: "KeyVerificationMac"
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: "KeyVerificationMac"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationDone roleValue: MtxEvent.KeyVerificationDone
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: "KeyVerificationDone"
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: "KeyVerificationDone"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationDone roleValue: MtxEvent.KeyVerificationDone
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: "KeyVerificationDone"
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: "KeyVerificationDone"
} }
} }
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.KeyVerificationAccept roleValue: MtxEvent.KeyVerificationAccept
NoticeMessage { NoticeMessage {
body: formatted body: formatted
formatted: "KeyVerificationAccept"
isOnlyEmoji: false isOnlyEmoji: false
isReply: d.isReply isReply: d.isReply
isStateEvent: d.isStateEvent isStateEvent: d.isStateEvent
formatted: "KeyVerificationAccept"
} }
} }
DelegateChoice { DelegateChoice {
Placeholder { Placeholder {
typeString: d.typeString typeString: d.typeString
} }
} }
} }
} }

@ -1,16 +1,14 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.5 import QtQuick 2.5
import im.nheko import im.nheko
TextMessage { TextMessage {
property bool isStateEvent property bool isStateEvent
font.italic: true
color: timelineRoot.palette.placeholderText color: timelineRoot.palette.placeholderText
font.pointSize: isStateEvent? 0.8*Settings.fontSize : Settings.fontSize font.italic: true
horizontalAlignment: isStateEvent? Text.AlignHCenter : undefined font.pointSize: isStateEvent ? 0.8 * Settings.fontSize : Settings.fontSize
horizontalAlignment: isStateEvent ? Text.AlignHCenter : undefined
} }

@ -1,22 +1,20 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import im.nheko import im.nheko
Label { Label {
property bool isStateEvent property bool isStateEvent
color: timelineRoot.palette.text color: timelineRoot.palette.text
horizontalAlignment: Text.AlignHCenter
height: Math.round(fontMetrics.height * 1.4) height: Math.round(fontMetrics.height * 1.4)
horizontalAlignment: Text.AlignHCenter
width: contentWidth * 1.2 width: contentWidth * 1.2
background: Rectangle { background: Rectangle {
radius: parent.height / 2
color: timelineRoot.palette.alternateBase color: timelineRoot.palette.alternateBase
radius: parent.height / 2
} }
} }

@ -1,15 +1,13 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import im.nheko import im.nheko
MatrixText { MatrixText {
required property string typeString required property string typeString
text: qsTr("unimplemented event: ") + typeString // width: parent.width
// width: parent.width
color: timelineRoot.palette.inactive.text color: timelineRoot.palette.inactive.text
text: qsTr("unimplemented event: ") + typeString
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import "../ui/media" import "../ui/media"
import QtMultimedia import QtMultimedia
@ -14,99 +12,88 @@ import im.nheko
Item { Item {
id: content id: content
required property double proportionalHeight required property string body
required property int type property double divisor: isReply ? 4 : 2
required property int originalWidth
required property int duration required property int duration
required property string thumbnailUrl
required property string eventId required property string eventId
required property string url
required property string body
required property string filesize required property string filesize
property double divisor: isReply ? 4 : 2 property bool fitsMetadata: (parent.width - fileInfoLabel.width) > metadataWidth + 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)
height: (type == MtxEvent.VideoMessage ? width*proportionalHeight : 80) + fileInfoLabel.height
implicitHeight: height
property int metadataWidth property int metadataWidth
property bool fitsMetadata: (parent.width - fileInfoLabel.width) > metadataWidth+4 required property int originalWidth
required property double proportionalHeight
property int tempWidth: originalWidth < 1 ? 400 : originalWidth
required property string thumbnailUrl
required property int type
required property string url
height: (type == MtxEvent.VideoMessage ? width * proportionalHeight : 80) + fileInfoLabel.height
implicitHeight: height
implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth * Math.min((timelineView.height / divisor) / (tempWidth * proportionalHeight), 1)) : 500
width: Math.min(parent.width, implicitWidth)
MxcMedia { MxcMedia {
id: mxcmedia id: mxcmedia
roomm: room roomm: room
videoOutput: videoOutput
audioOutput: AudioOutput { audioOutput: AudioOutput {
muted: mediaControls.muted muted: mediaControls.muted
volume: mediaControls.desiredVolume volume: mediaControls.desiredVolume
} }
videoOutput: videoOutput
} }
Rectangle { Rectangle {
id: videoContainer id: videoContainer
color: type == MtxEvent.VideoMessage ? timelineRoot.palette.window : "transparent" color: type == MtxEvent.VideoMessage ? timelineRoot.palette.window : "transparent"
width: parent.width
height: parent.height - fileInfoLabel.height height: parent.height - fileInfoLabel.height
width: parent.width
TapHandler { TapHandler {
onTapped: Settings.openVideoExternal ? room.openMedia(eventId) : mediaControls.showControls() onTapped: Settings.openVideoExternal ? room.openMedia(eventId) : mediaControls.showControls()
} }
Image { Image {
anchors.fill: parent anchors.fill: parent
source: thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : ""
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
source: thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : ""
VideoOutput { VideoOutput {
id: videoOutput id: videoOutput
visible: type == MtxEvent.VideoMessage
clip: true
anchors.fill: parent anchors.fill: parent
clip: true
fillMode: VideoOutput.PreserveAspectFit fillMode: VideoOutput.PreserveAspectFit
//flushMode: VideoOutput.FirstFrame //flushMode: VideoOutput.FirstFrame
orientation: mxcmedia.orientation orientation: mxcmedia.orientation
visible: type == MtxEvent.VideoMessage
} }
} }
} }
MediaControls { MediaControls {
id: mediaControls id: mediaControls
anchors.bottom: fileInfoLabel.top
anchors.left: content.left anchors.left: content.left
anchors.right: content.right anchors.right: content.right
anchors.bottom: fileInfoLabel.top
playingVideo: type == MtxEvent.VideoMessage
positionValue: mxcmedia.position
duration: mediaLoaded ? mxcmedia.duration : content.duration duration: mediaLoaded ? mxcmedia.duration : content.duration
mediaLoaded: mxcmedia.loaded mediaLoaded: mxcmedia.loaded
mediaState: mxcmedia.state mediaState: mxcmedia.state
onPositionChanged: mxcmedia.position = position playingVideo: type == MtxEvent.VideoMessage
onPlayPauseActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play() positionValue: mxcmedia.position
onLoadActivated: mxcmedia.eventId = eventId onLoadActivated: mxcmedia.eventId = eventId
onPlayPauseActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
onPositionChanged: mxcmedia.position = position
} }
// information about file name and file size // information about file name and file size
Label { Label {
id: fileInfoLabel id: fileInfoLabel
anchors.bottom: content.bottom anchors.bottom: content.bottom
color: timelineRoot.palette.text
elide: Text.ElideRight
text: body + " [" + filesize + "]" text: body + " [" + filesize + "]"
textFormat: Text.RichText textFormat: Text.RichText
elide: Text.ElideRight
color: timelineRoot.palette.text
background: Rectangle { background: Rectangle {
color: timelineRoot.palette.base color: timelineRoot.palette.base
} }
} }
} }

@ -1,49 +1,49 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import im.nheko import im.nheko
Rectangle{ Rectangle {
property bool fitsMetadata: parent.width - redactedLayout.width > metadataWidth + 4
property int metadataWidth
color: timelineRoot.palette.alternateBase
height: redactedLayout.implicitHeight + Nheko.paddingSmall height: redactedLayout.implicitHeight + Nheko.paddingSmall
implicitWidth: redactedLayout.implicitWidth + 2 * Nheko.paddingMedium implicitWidth: redactedLayout.implicitWidth + 2 * Nheko.paddingMedium
width: Math.min(parent.width,implicitWidth+1)
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
color: timelineRoot.palette.alternateBase width: Math.min(parent.width, implicitWidth + 1)
property int metadataWidth
property bool fitsMetadata: parent.width - redactedLayout.width > metadataWidth + 4
RowLayout { RowLayout {
id: redactedLayout id: redactedLayout
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - 2 * Nheko.paddingMedium
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
width: parent.width - 2 * Nheko.paddingMedium
Image { Image {
id: trashImg id: trashImg
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.preferredWidth: fontMetrics.font.pixelSize
Layout.preferredHeight: fontMetrics.font.pixelSize Layout.preferredHeight: fontMetrics.font.pixelSize
Layout.preferredWidth: fontMetrics.font.pixelSize
source: "image://colorimage/:/icons/icons/ui/delete.svg?" + timelineRoot.palette.text source: "image://colorimage/:/icons/icons/ui/delete.svg?" + timelineRoot.palette.text
} }
Label { Label {
id: redactedLabel id: redactedLabel
Layout.margins: 0
property var redactedPair: room.formatRedactedEvent(eventId)
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.preferredWidth: implicitWidth
Layout.fillWidth: true Layout.fillWidth: true
property var redactedPair: room.formatRedactedEvent(eventId) Layout.margins: 0
Layout.preferredWidth: implicitWidth
ToolTip.text: redactedPair["second"]
ToolTip.visible: hh.hovered
color: timelineRoot.palette.text
text: redactedPair["first"] text: redactedPair["first"]
wrapMode: Label.WordWrap wrapMode: Label.WordWrap
color: timelineRoot.palette.text
ToolTip.text: redactedPair["second"]
ToolTip.visible: hh.hovered
HoverHandler { HoverHandler {
id: hh id: hh
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import Qt.labs.platform 1.1 as Platform import Qt.labs.platform 1.1 as Platform
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -14,129 +12,124 @@ import "../"
AbstractButton { AbstractButton {
id: r id: r
property color userColor: "red"
property double proportionalHeight
property int type
property string typeString
property int originalWidth
property string blurhash property string blurhash
property string body property string body
property string formattedBody property string callType
property int duration
property int encryptionError
property string eventId property string eventId
property string filename property string filename
property string filesize property string filesize
property string url property string formattedBody
property bool isOnlyEmoji property bool isOnlyEmoji
property bool isStateEvent property bool isStateEvent
property int maxWidth
property int originalWidth
property double proportionalHeight
property int relatedEventCacheBuster
property string roomName
property string roomTopic
property string thumbnailUrl
property int type
property string typeString
property string url
property color userColor: "red"
property string userId property string userId
property string userName property string userName
property string thumbnailUrl
property string roomTopic
property string roomName
property string callType
property int duration
property int encryptionError
property int relatedEventCacheBuster
property int maxWidth
height: replyContainer.height height: replyContainer.height
implicitHeight: replyContainer.height implicitHeight: replyContainer.height
implicitWidth: visible? colorLine.width+Math.max(replyContainer.implicitWidth,userName_.fullTextWidth) : 0 // visible? seems to be causing issues implicitWidth: visible ? colorLine.width + Math.max(replyContainer.implicitWidth, userName_.fullTextWidth) : 0 // visible? seems to be causing issues
onClicked: {
let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX - colorLine.width, pressY - userName_.implicitHeight);
if (link) {
Nheko.openLink(link);
} else {
room.showEvent(r.eventId);
}
}
onPressAndHold: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(pressX - colorLine.width, pressY - userName_.implicitHeight), r.eventId)
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
Rectangle { Rectangle {
id: colorLine id: colorLine
anchors.top: replyContainer.top
anchors.bottom: replyContainer.bottom anchors.bottom: replyContainer.bottom
width: 4 anchors.top: replyContainer.top
color: TimelineManager.userColor(userId, timelineRoot.palette.base) color: TimelineManager.userColor(userId, timelineRoot.palette.base)
width: 4
} }
onClicked: {
let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX-colorLine.width, pressY - userName_.implicitHeight);
if (link) {
Nheko.openLink(link)
} else {
room.showEvent(r.eventId)
}
}
onPressAndHold: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(pressX-colorLine.width, pressY - userName_.implicitHeight), r.eventId)
ColumnLayout { ColumnLayout {
id: replyContainer id: replyContainer
anchors.left: colorLine.right anchors.left: colorLine.right
width: parent.width - 4
spacing: 0 spacing: 0
width: parent.width - 4
TapHandler { TapHandler {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onSingleTapped: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(eventPoint.position.x, eventPoint.position.y - userName_.implicitHeight), r.eventId)
gesturePolicy: TapHandler.ReleaseWithinBounds
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
} gesturePolicy: TapHandler.ReleaseWithinBounds
onSingleTapped: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(eventPoint.position.x, eventPoint.position.y - userName_.implicitHeight), r.eventId)
}
AbstractButton { AbstractButton {
Layout.leftMargin: 4
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 4
contentItem: ElidedLabel { contentItem: ElidedLabel {
id: userName_ id: userName_
fullText: userName
color: r.userColor color: r.userColor
elideWidth: width
fullText: userName
textFormat: Text.RichText textFormat: Text.RichText
width: parent.width width: parent.width
elideWidth: width
} }
onClicked: room.openUserProfile(userId) onClicked: room.openUserProfile(userId)
} }
MessageDelegate { MessageDelegate {
id: reply
Layout.fillWidth: true
Layout.leftMargin: 4 Layout.leftMargin: 4
Layout.preferredHeight: height Layout.preferredHeight: height
id: reply
blurhash: r.blurhash blurhash: r.blurhash
body: r.body body: r.body
formattedBody: r.formattedBody callType: r.callType
duration: r.duration
// This is disabled so that left clicking the reply goes to its location
enabled: false
encryptionError: r.encryptionError
eventId: r.eventId eventId: r.eventId
filename: r.filename filename: r.filename
filesize: r.filesize filesize: r.filesize
formattedBody: r.formattedBody
isOnlyEmoji: r.isOnlyEmoji
isReply: true
isStateEvent: r.isStateEvent
originalWidth: r.originalWidth
proportionalHeight: r.proportionalHeight proportionalHeight: r.proportionalHeight
relatedEventCacheBuster: r.relatedEventCacheBuster
roomName: r.roomName
roomTopic: r.roomTopic
thumbnailUrl: r.thumbnailUrl
type: r.type type: r.type
typeString: r.typeString ?? "" typeString: r.typeString ?? ""
url: r.url url: r.url
thumbnailUrl: r.thumbnailUrl
duration: r.duration
originalWidth: r.originalWidth
isOnlyEmoji: r.isOnlyEmoji
isStateEvent: r.isStateEvent
userId: r.userId userId: r.userId
userName: r.userName userName: r.userName
roomTopic: r.roomTopic
roomName: r.roomName
callType: r.callType
relatedEventCacheBuster: r.relatedEventCacheBuster
encryptionError: r.encryptionError
// This is disabled so that left clicking the reply goes to its location
enabled: false
Layout.fillWidth: true
isReply: true
} }
} }
Rectangle { Rectangle {
id: backgroundItem id: backgroundItem
z: -1
anchors.fill: replyContainer
property color userColor: TimelineManager.userColor(userId, timelineRoot.palette.base)
property color bgColor: timelineRoot.palette.base property color bgColor: timelineRoot.palette.base
property color userColor: TimelineManager.userColor(userId, timelineRoot.palette.base)
anchors.fill: replyContainer
color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1)) color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1))
z: -1
} }
} }

@ -1,21 +1,25 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import im.nheko import im.nheko
MatrixText { MatrixText {
required property string body required property string body
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body
property bool fitsMetadata: positionAt(width, height - 4) == positionAt(width - metadataWidth - 10, height - 4)
required property string formatted
required property bool isOnlyEmoji required property bool isOnlyEmoji
required property bool isReply required property bool isReply
required property string formatted
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body
property int metadataWidth property int metadataWidth
property bool fitsMetadata: positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4)
clip: isReply
enabled: !Settings.mobileMode
font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
height: isReply ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight
selectByMouse: !Settings.mobileMode && !isReply
// table border-collapse doesn't seem to work // table border-collapse doesn't seem to work
text: " text: "
@ -37,16 +41,10 @@ MatrixText {
</style> </style>
" + formatted.replace(/<pre>/g, "<pre style='white-space: pre-wrap; background-color: " + timelineRoot.palette.alternateBase + "'>").replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>") " + formatted.replace(/<pre>/g, "<pre style='white-space: pre-wrap; background-color: " + timelineRoot.palette.alternateBase + "'>").replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>")
width: parent?.width width: parent?.width
height: isReply ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight
clip: isReply
selectByMouse: !Settings.mobileMode && !isReply
enabled: !Settings.mobileMode
font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
NhekoCursorShape { NhekoCursorShape {
enabled: isReply
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: isReply
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.10 import QtQuick 2.10
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Window 2.13 import QtQuick.Window 2.13
@ -13,74 +11,56 @@ ApplicationWindow {
property var flow property var flow
onClosing: VerificationManager.removeVerificationFlow(flow)
title: stack.currentItem.title_
modality: Qt.NonModal
palette: timelineRoot.palette
color: timelineRoot.palette.window color: timelineRoot.palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
minimumHeight: stack.implicitHeight minimumHeight: stack.implicitHeight
modality: Qt.NonModal
palette: timelineRoot.palette
title: stack.currentItem.title_
width: stack.implicitWidth width: stack.implicitWidth
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
onClosing: VerificationManager.removeVerificationFlow(flow)
StackView { StackView {
id: stack id: stack
anchors.fill: parent anchors.fill: parent
initialItem: newVerificationRequest
implicitWidth: currentItem.implicitWidth
implicitHeight: currentItem.implicitHeight implicitHeight: currentItem.implicitHeight
implicitWidth: currentItem.implicitWidth
initialItem: newVerificationRequest
} }
Component { Component {
id: newVerificationRequest id: newVerificationRequest
NewVerificationRequest { NewVerificationRequest {
} }
} }
Component { Component {
id: waiting id: waiting
Waiting { Waiting {
} }
} }
Component { Component {
id: success id: success
Success { Success {
} }
} }
Component { Component {
id: failed id: failed
Failed { Failed {
} }
} }
Component { Component {
id: digitVerification id: digitVerification
DigitVerification { DigitVerification {
} }
} }
Component { Component {
id: emojiVerification id: emojiVerification
EmojiVerification { EmojiVerification {
} }
} }
Item { Item {
state: flow.state state: flow.state
states: [ states: [
State { State {
name: "PromptStartVerification" name: "PromptStartVerification"
@ -88,7 +68,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(newVerificationRequest) script: stack.replace(newVerificationRequest)
} }
}, },
State { State {
name: "CompareEmoji" name: "CompareEmoji"
@ -96,7 +75,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(emojiVerification) script: stack.replace(emojiVerification)
} }
}, },
State { State {
name: "CompareNumber" name: "CompareNumber"
@ -104,7 +82,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(digitVerification) script: stack.replace(digitVerification)
} }
}, },
State { State {
name: "WaitingForKeys" name: "WaitingForKeys"
@ -112,7 +89,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(waiting) script: stack.replace(waiting)
} }
}, },
State { State {
name: "WaitingForOtherToAccept" name: "WaitingForOtherToAccept"
@ -120,7 +96,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(waiting) script: stack.replace(waiting)
} }
}, },
State { State {
name: "WaitingForMac" name: "WaitingForMac"
@ -128,7 +103,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(waiting) script: stack.replace(waiting)
} }
}, },
State { State {
name: "Success" name: "Success"
@ -136,7 +110,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(success) script: stack.replace(success)
} }
}, },
State { State {
name: "Failed" name: "Failed"
@ -144,9 +117,7 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(failed) script: stack.replace(failed)
} }
} }
] ]
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
@ -10,6 +8,7 @@ import im.nheko
Pane { Pane {
property string title: qsTr("Verification Code") property string title: qsTr("Verification Code")
background: Rectangle { background: Rectangle {
color: timelineRoot.palette.window color: timelineRoot.palette.window
} }
@ -19,61 +18,57 @@ Pane {
spacing: 16 spacing: 16
Label { Label {
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
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!")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
RowLayout { RowLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Label { Label {
color: timelineRoot.palette.text
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[0] text: flow.sasList[0]
color: timelineRoot.palette.text
} }
Label { Label {
color: timelineRoot.palette.text
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[1] text: flow.sasList[1]
color: timelineRoot.palette.text
} }
Label { Label {
color: timelineRoot.palette.text
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[2] text: flow.sasList[2]
color: timelineRoot.palette.text
} }
} }
Item { Layout.fillHeight: true; } Item {
Layout.fillHeight: true
}
RowLayout { RowLayout {
Button { Button {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("They do not match!") text: qsTr("They do not match!")
onClicked: { onClicked: {
flow.cancel(); flow.cancel();
dialog.close(); dialog.close();
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: qsTr("They match!") text: qsTr("They match!")
onClicked: flow.next() onClicked: flow.next()
} }
} }
} }
} }

@ -1,17 +1,15 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
Rectangle { Rectangle {
color: "red" color: "red"
height: Qt.application.font.pixelSize * 4
implicitHeight: Qt.application.font.pixelSize * 4 implicitHeight: Qt.application.font.pixelSize * 4
implicitWidth: col.width implicitWidth: col.width
height: Qt.application.font.pixelSize * 4
width: col.width width: col.width
ColumnLayout { ColumnLayout {
@ -22,17 +20,14 @@ Rectangle {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
Label { Label {
height: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: col.emoji.emoji
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
height: font.pixelSize * 2
text: col.emoji.emoji
} }
Label { Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
text: col.emoji.description text: col.emoji.description
} }
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
@ -10,6 +8,7 @@ import im.nheko
Pane { Pane {
property string title: qsTr("Verification Code") property string title: qsTr("Verification Code")
background: Rectangle { background: Rectangle {
color: timelineRoot.palette.window color: timelineRoot.palette.window
} }
@ -19,15 +18,16 @@ Pane {
spacing: 16 spacing: 16
Label { Label {
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
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!")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
RowLayout { RowLayout {
id: emojis id: emojis
@ -357,7 +357,6 @@ Pane {
Repeater { Repeater {
id: repeater id: repeater
model: 7 model: 7
delegate: Rectangle { delegate: Rectangle {
@ -376,49 +375,42 @@ Pane {
Label { Label {
//height: font.pixelSize * 2 //height: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: col.emoji.emoji
font.pixelSize: Qt.application.font.pixelSize * 2
font.family: Settings.emojiFont
color: timelineRoot.palette.text color: timelineRoot.palette.text
font.family: Settings.emojiFont
font.pixelSize: Qt.application.font.pixelSize * 2
text: col.emoji.emoji
} }
Label { Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
text: col.emoji.description
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: col.emoji.description
} }
} }
} }
} }
} }
Item { Layout.fillHeight: true; } Item {
Layout.fillHeight: true
}
RowLayout { RowLayout {
Button { Button {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("They do not match!") text: qsTr("They do not match!")
onClicked: { onClicked: {
flow.cancel(); flow.cancel();
dialog.close(); dialog.close();
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: qsTr("They match!") text: qsTr("They match!")
onClicked: flow.next() onClicked: flow.next()
} }
} }
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
@ -10,6 +8,7 @@ import im.nheko
Pane { Pane {
property string title: qsTr("Verification failed") property string title: qsTr("Verification failed")
background: Rectangle { background: Rectangle {
color: timelineRoot.palette.window color: timelineRoot.palette.window
} }
@ -20,10 +19,9 @@ Pane {
Text { Text {
id: content id: content
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
color: timelineRoot.palette.text
text: { text: {
switch (flow.error) { switch (flow.error) {
case DeviceVerificationFlow.UnknownMethod: case DeviceVerificationFlow.UnknownMethod:
@ -42,25 +40,22 @@ Pane {
return qsTr("Unknown verification error."); return qsTr("Unknown verification error.");
} }
} }
color: timelineRoot.palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
RowLayout { RowLayout {
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: qsTr("Close") text: qsTr("Close")
onClicked: dialog.close() onClicked: dialog.close()
} }
} }
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
@ -10,6 +8,7 @@ import im.nheko
Pane { Pane {
property string title: flow.sender ? qsTr("Send Verification Request") : qsTr("Received Verification Request") property string title: flow.sender ? qsTr("Send Verification Request") : qsTr("Received Verification Request")
background: Rectangle { background: Rectangle {
color: timelineRoot.palette.window color: timelineRoot.palette.window
} }
@ -19,11 +18,10 @@ Pane {
spacing: 16 spacing: 16
Label { Label {
Layout.fillWidth: true
// Self verification // Self verification
Layout.preferredWidth: 400 Layout.preferredWidth: 400
Layout.fillWidth: true color: timelineRoot.palette.text
wrapMode: Text.Wrap
text: { text: {
if (flow.sender) { if (flow.sender) {
if (flow.isSelfVerification) if (flow.isSelfVerification)
@ -42,34 +40,31 @@ Pane {
return qsTr("Your device (%1) has requested to be verified.").arg(flow.deviceId); return qsTr("Your device (%1) has requested to be verified.").arg(flow.deviceId);
} }
} }
color: timelineRoot.palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
RowLayout { RowLayout {
Button { Button {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: flow.sender ? qsTr("Cancel") : qsTr("Deny") text: flow.sender ? qsTr("Cancel") : qsTr("Deny")
onClicked: { onClicked: {
flow.cancel(); flow.cancel();
dialog.close(); dialog.close();
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: flow.sender ? qsTr("Start verification") : qsTr("Accept") text: flow.sender ? qsTr("Start verification") : qsTr("Accept")
onClicked: flow.next() onClicked: flow.next()
} }
} }
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10
@ -10,6 +8,7 @@ import im.nheko
Pane { Pane {
property string title: qsTr("Successful Verification") property string title: qsTr("Successful Verification")
background: Rectangle { background: Rectangle {
color: timelineRoot.palette.window color: timelineRoot.palette.window
} }
@ -20,30 +19,26 @@ Pane {
Label { Label {
id: content id: content
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
text: qsTr("Verification successful! Both sides verified their devices!")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Verification successful! Both sides verified their devices!")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
RowLayout { RowLayout {
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: qsTr("Close") text: qsTr("Close")
onClicked: dialog.close() onClicked: dialog.close()
} }
} }
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../ui" import "../ui"
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -11,6 +9,7 @@ import im.nheko
Pane { Pane {
property string title: qsTr("Waiting for other party…") property string title: qsTr("Waiting for other party…")
background: Rectangle { background: Rectangle {
color: timelineRoot.palette.window color: timelineRoot.palette.window
} }
@ -21,10 +20,9 @@ Pane {
Label { Label {
id: content id: content
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
color: timelineRoot.palette.text
text: { text: {
switch (flow.state) { switch (flow.state) {
case "WaitingForOtherToAccept": case "WaitingForOtherToAccept":
@ -35,33 +33,32 @@ Pane {
return qsTr("Waiting for other side to complete the verification process."); return qsTr("Waiting for other side to complete the verification process.");
} }
} }
color: timelineRoot.palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
Spinner { Spinner {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
foreground: timelineRoot.palette.mid foreground: timelineRoot.palette.mid
} }
Item { Layout.fillHeight: true; } Item {
Layout.fillHeight: true
}
RowLayout { RowLayout {
Button { Button {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Cancel") text: qsTr("Cancel")
onClicked: { onClicked: {
flow.cancel(); flow.cancel();
dialog.close(); dialog.close();
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Window 2.13 import QtQuick.Window 2.13
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
@ -13,13 +11,31 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: createDirectRoot id: createDirectRoot
title: qsTr("Create Direct Chat")
property bool otherUserHasE2ee: profile ? profile.deviceList.rowCount() > 0 : true
property var profile 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) minimumWidth: Math.max(footer.implicitWidth, layout.implicitWidth)
modality: Qt.NonModal modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint title: qsTr("Create Direct Chat")
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
onAccepted: {
profile.startChat(encryption.checked);
createDirectRoot.close();
}
onRejected: createDirectRoot.close()
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: userID.isValidMxid
text: "Start Direct Chat"
}
}
onVisibilityChanged: { onVisibilityChanged: {
userID.forceActiveFocus(); userID.forceActiveFocus();
@ -27,90 +43,79 @@ ApplicationWindow {
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: createDirectRoot.close() onActivated: createDirectRoot.close()
} }
ColumnLayout { ColumnLayout {
id: layout id: layout
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingLarge anchors.margins: Nheko.paddingLarge
spacing: userID.height/4 spacing: userID.height / 4
GridLayout { GridLayout {
Layout.fillWidth: true Layout.fillWidth: true
rows: 2 columnSpacing: Nheko.paddingMedium
columns: 2 columns: 2
rowSpacing: Nheko.paddingSmall rowSpacing: Nheko.paddingSmall
columnSpacing: Nheko.paddingMedium rows: 2
Avatar { Avatar {
Layout.rowSpan: 2
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
userid: profile? profile.userid : "" Layout.preferredHeight: Nheko.avatarSize
url: profile? profile.avatarUrl.replace("mxc://", "image://MxcImage/") : null Layout.preferredWidth: Nheko.avatarSize
displayName: profile? profile.displayName : "" Layout.rowSpan: 2
displayName: profile ? profile.displayName : ""
enabled: false enabled: false
url: profile ? profile.avatarUrl.replace("mxc://", "image://MxcImage/") : null
userid: profile ? profile.userid : ""
} }
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: profile? profile.displayName : ""
color: TimelineManager.userColor(userID.text, timelineRoot.palette.window) color: TimelineManager.userColor(userID.text, timelineRoot.palette.window)
font.pointSize: fontMetrics.font.pointSize font.pointSize: fontMetrics.font.pointSize
text: profile ? profile.displayName : ""
} }
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: userID.text
color: timelineRoot.palette.placeholderText color: timelineRoot.palette.placeholderText
font.pointSize: fontMetrics.font.pointSize * 0.9 font.pointSize: fontMetrics.font.pointSize * 0.9
text: userID.text
} }
} }
MatrixTextField { MatrixTextField {
id: userID id: userID
property bool isValidMxid: text.match("@.+?:.{3,}") property bool isValidMxid: text.match("@.+?:.{3,}")
Layout.fillWidth: true Layout.fillWidth: true
focus: true focus: true
label: qsTr("User to invite") label: qsTr("User to invite")
placeholderText: qsTr("@user:server.tld") placeholderText: qsTr("@user:server.tld")
onTextChanged: { onTextChanged: {
if(isValidMxid) { if (isValidMxid) {
profile = TimelineManager.getGlobalUserProfile(text); profile = TimelineManager.getGlobalUserProfile(text);
} else } else
profile = null; profile = null;
} }
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Label { Label {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Encryption") Layout.fillWidth: true
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Encryption")
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight
id: encryption id: encryption
Layout.alignment: Qt.AlignRight
checked: otherUserHasE2ee checked: otherUserHasE2ee
} }
} }
Item {
Item {Layout.fillHeight: true} Layout.fillHeight: true
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
Button {
text: "Start Direct Chat"
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: userID.isValidMxid
}
onRejected: createDirectRoot.close();
onAccepted: {
profile.startChat(encryption.checked)
createDirectRoot.close()
} }
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Window 2.13 import QtQuick.Window 2.13
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
@ -12,11 +10,32 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: createRoomRoot id: createRoomRoot
title: qsTr("Create Room")
minimumWidth: Math.max(rootLayout.implicitWidth+2*rootLayout.anchors.margins, footer.implicitWidth + Nheko.paddingLarge)
minimumHeight: rootLayout.implicitHeight+footer.implicitHeight+2*rootLayout.anchors.margins
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
minimumHeight: rootLayout.implicitHeight + footer.implicitHeight + 2 * rootLayout.anchors.margins
minimumWidth: Math.max(rootLayout.implicitWidth + 2 * rootLayout.anchors.margins, footer.implicitWidth + Nheko.paddingLarge)
modality: Qt.NonModal
title: qsTr("Create Room")
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
onAccepted: {
var preset = 0;
if (isPublic.checked) {
preset = 1;
} else {
preset = isTrusted.checked ? 2 : 0;
}
Nheko.createRoom(newRoomName.text, newRoomTopic.text, newRoomAlias.text, isEncrypted.checked, preset);
createRoomRoot.close();
}
onRejected: createRoomRoot.close()
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Create Room")
}
}
onVisibilityChanged: { onVisibilityChanged: {
newRoomName.forceActiveFocus(); newRoomName.forceActiveFocus();
@ -24,6 +43,7 @@ ApplicationWindow {
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: createRoomRoot.close() onActivated: createRoomRoot.close()
} }
GridLayout { GridLayout {
@ -37,7 +57,6 @@ ApplicationWindow {
id: newRoomName id: newRoomName
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
focus: true focus: true
label: qsTr("Name") label: qsTr("Name")
placeholderText: qsTr("No name") placeholderText: qsTr("No name")
@ -46,23 +65,21 @@ ApplicationWindow {
id: newRoomTopic id: newRoomTopic
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
focus: true focus: true
label: qsTr("Topic") label: qsTr("Topic")
placeholderText: qsTr("No topic") placeholderText: qsTr("No topic")
} }
Item { Item {
Layout.preferredHeight: newRoomName.height / 2 Layout.preferredHeight: newRoomName.height / 2
} }
RowLayout { RowLayout {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
Label { Label {
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
text: qsTr("#")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("#")
} }
MatrixTextField { MatrixTextField {
id: newRoomAlias id: newRoomAlias
@ -70,88 +87,73 @@ ApplicationWindow {
placeholderText: qsTr("Alias") placeholderText: qsTr("Alias")
} }
Label { Label {
Layout.preferredWidth: implicitWidth
property string userName: userInfoGrid.profile.userid property string userName: userInfoGrid.profile.userid
text: userName.substring(userName.indexOf(":"))
Layout.preferredWidth: implicitWidth
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: userName.substring(userName.indexOf(":"))
} }
} }
Label { Label {
Layout.preferredWidth: implicitWidth
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Public") Layout.preferredWidth: implicitWidth
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Public rooms can be joined by anyone, private rooms need explicit invites.")
ToolTip.visible: privateHover.hovered
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Public")
HoverHandler { HoverHandler {
id: privateHover 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 { ToggleButton {
id: isPublic
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
id: isPublic
checked: false checked: false
} }
Label { Label {
Layout.preferredWidth: implicitWidth
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Trusted") Layout.preferredWidth: implicitWidth
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("All invitees are given the same power level as the creator")
ToolTip.visible: trustedHover.hovered
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Trusted")
HoverHandler { HoverHandler {
id: trustedHover 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 { ToggleButton {
id: isTrusted
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
id: isTrusted
checked: false checked: false
enabled: !isPublic.checked enabled: !isPublic.checked
} }
Label { Label {
Layout.preferredWidth: implicitWidth
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Encryption") Layout.preferredWidth: implicitWidth
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Caution: Encryption cannot be disabled")
ToolTip.visible: encryptionHover.hovered
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Encryption")
HoverHandler { HoverHandler {
id: encryptionHover id: encryptionHover
} }
ToolTip.visible: encryptionHover.hovered
ToolTip.text: qsTr("Caution: Encryption cannot be disabled")
ToolTip.delay: Nheko.tooltipDelay
} }
ToggleButton { ToggleButton {
id: isEncrypted
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
id: isEncrypted
checked: false checked: false
} }
Item {
Item {Layout.fillHeight: true} Layout.fillHeight: true
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
Button {
text: qsTr("Create Room")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
onRejected: createRoomRoot.close();
onAccepted: {
var preset = 0;
if (isPublic.checked) {
preset = 1;
}
else {
preset = isTrusted.checked ? 2 : 0;
}
Nheko.createRoom(newRoomName.text, newRoomTopic.text, newRoomAlias.text, isEncrypted.checked, preset)
createRoomRoot.close();
} }
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.5 import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
@ -11,117 +9,108 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: hiddenEventsDialog id: hiddenEventsDialog
property string roomid: ""
property string roomName: ""
property var onAccepted: undefined property var onAccepted: undefined
property string roomName: ""
property string roomid: ""
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowTitleHint
minimumWidth: 250
minimumHeight: 220 minimumHeight: 220
minimumWidth: 250
HiddenEvents { modality: Qt.NonModal
id: hiddenEvents
roomid: hiddenEventsDialog.roomid
}
title: { title: {
if (roomid) { if (roomid) {
return qsTr("Hidden events for %1").arg(roomName); return qsTr("Hidden events for %1").arg(roomName);
} } else {
else {
return qsTr("Hidden events"); return qsTr("Hidden events");
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
hiddenEvents.save();
hiddenEventsDialog.close();
}
onRejected: hiddenEventsDialog.close()
}
HiddenEvents {
id: hiddenEvents
roomid: hiddenEventsDialog.roomid
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: dbb.rejected() onActivated: dbb.rejected()
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingMedium
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
MatrixText { MatrixText {
id: promptLabel id: promptLabel
Layout.fillHeight: false
Layout.fillWidth: true
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2)
text: { text: {
if (roomid) { if (roomid) {
return qsTr("These events will be <b>shown</b> in %1:").arg(roomName); return qsTr("These events will be <b>shown</b> in %1:").arg(roomName);
} } else {
else {
return qsTr("These events will be <b>shown</b> in all rooms:"); return qsTr("These events will be <b>shown</b> in all rooms:");
} }
} }
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2)
Layout.fillWidth: true
Layout.fillHeight: false
} }
GridLayout { GridLayout {
Layout.fillHeight: true
Layout.fillWidth: true
columns: 2 columns: 2
rowSpacing: Nheko.paddingMedium rowSpacing: Nheko.paddingMedium
Layout.fillWidth: true
Layout.fillHeight: true
MatrixText { MatrixText {
text: qsTr("User events") Layout.fillWidth: true
ToolTip.text: qsTr("Joins, leaves, avatar and name changes, bans, …") ToolTip.text: qsTr("Joins, leaves, avatar and name changes, bans, …")
ToolTip.visible: hh1.hovered ToolTip.visible: hh1.hovered
Layout.fillWidth: true text: qsTr("User events")
HoverHandler { HoverHandler {
id: hh1 id: hh1
} }
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Member) checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Member)
onToggled: hiddenEvents.toggle(MtxEvent.Member) onToggled: hiddenEvents.toggle(MtxEvent.Member)
} }
MatrixText { 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.text: qsTr("Sent when a moderator is added/removed or the permissions of a room are changed.")
ToolTip.visible: hh2.hovered ToolTip.visible: hh2.hovered
Layout.fillWidth: true text: qsTr("Power level changes")
HoverHandler { HoverHandler {
id: hh2 id: hh2
} }
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.PowerLevels) checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.PowerLevels)
onToggled: hiddenEvents.toggle(MtxEvent.PowerLevels) onToggled: hiddenEvents.toggle(MtxEvent.PowerLevels)
} }
MatrixText { MatrixText {
text: qsTr("Stickers")
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Stickers")
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Sticker) checked: !hiddenEvents.hiddenEvents.includes(MtxEvent.Sticker)
onToggled: hiddenEvents.toggle(MtxEvent.Sticker) onToggled: hiddenEvents.toggle(MtxEvent.Sticker)
} }
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
hiddenEvents.save();
hiddenEventsDialog.close();
}
onRejected: hiddenEventsDialog.close();
}
} }

@ -1,113 +1,99 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Window 2.15 import QtQuick.Window 2.15
import "../"
import ".."
import im.nheko import im.nheko
Window { Window {
id: imageOverlay id: imageOverlay
required property string url
required property string eventId required property string eventId
required property Room room
required property int originalWidth required property int originalWidth
required property double proportionalHeight required property double proportionalHeight
required property Room room
flags: Qt.FramelessWindowHint required property string url
//visibility: Window.FullScreen //visibility: Window.FullScreen
color: Qt.rgba(0.2,0.2,0.2,0.66) color: Qt.rgba(0.2, 0.2, 0.2, 0.66)
flags: Qt.FramelessWindowHint
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: imageOverlay.close() onActivated: imageOverlay.close()
} }
Item { Item {
id: imgContainer id: imgContainer
property int imgSrcWidth: (originalWidth && originalWidth > 200) ? originalWidth : Screen.width
property int imgSrcHeight: proportionalHeight ? imgSrcWidth * proportionalHeight : Screen.height property int imgSrcHeight: proportionalHeight ? imgSrcWidth * proportionalHeight : Screen.height
property int imgSrcWidth: (originalWidth && originalWidth > 200) ? originalWidth : Screen.width
height: Math.min(parent.height, imgSrcHeight) height: Math.min(parent.height, imgSrcHeight)
width: Math.min(parent.width, imgSrcWidth) width: Math.min(parent.width, imgSrcWidth)
x: (parent.width - width) x: (parent.width - width)
y: (parent.height - height) y: (parent.height - height)
onScaleChanged: {
if (scale > 10)
scale = 10;
if (scale < 0.1)
scale = 0.1;
}
Image { Image {
id: img id: img
visible: !mxcimage.loaded property bool loaded: status == Image.Ready
anchors.fill: parent anchors.fill: parent
source: url.replace("mxc://", "image://MxcImage/")
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
smooth: true
mipmap: true mipmap: true
property bool loaded: status == Image.Ready smooth: true
source: url.replace("mxc://", "image://MxcImage/")
visible: !mxcimage.loaded
} }
MxcAnimatedImage { MxcAnimatedImage {
id: mxcimage id: mxcimage
visible: loaded
anchors.fill: parent anchors.fill: parent
roomm: imageOverlay.room
play: !Settings.animateImagesOnHover || mouseArea.hovered
eventId: imageOverlay.eventId eventId: imageOverlay.eventId
} play: !Settings.animateImagesOnHover || mouseArea.hovered
roomm: imageOverlay.room
onScaleChanged: { visible: loaded
if (scale > 10) scale = 10;
if (scale < 0.1) scale = 0.1
} }
} }
Item { Item {
anchors.fill: parent anchors.fill: parent
PinchHandler { PinchHandler {
target: imgContainer
maximumScale: 10 maximumScale: 10
minimumScale: 0.1 minimumScale: 0.1
target: imgContainer
} }
WheelHandler { WheelHandler {
property: "scale" property: "scale"
target: imgContainer target: imgContainer
} }
DragHandler { DragHandler {
target: imgContainer target: imgContainer
} }
HoverHandler { HoverHandler {
id: mouseArea id: mouseArea
} }
} }
Row { Row {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Nheko.paddingLarge anchors.margins: Nheko.paddingLarge
anchors.right: parent.right
anchors.top: parent.top
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
ImageButton { ImageButton {
height: 48 height: 48
width: 48
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/download.svg" image: ":/icons/icons/ui/download.svg"
width: 48
//ToolTip.visible: hovered //ToolTip.visible: hovered
//ToolTip.delay: Nheko.tooltipDelay //ToolTip.delay: Nheko.tooltipDelay
//ToolTip.text: qsTr("Download") //ToolTip.text: qsTr("Download")
@ -122,14 +108,14 @@ Window {
} }
ImageButton { ImageButton {
height: 48 height: 48
width: 48
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg" image: ":/icons/icons/ui/dismiss.svg"
width: 48
//ToolTip.visible: hovered //ToolTip.visible: hovered
//ToolTip.delay: Nheko.tooltipDelay //ToolTip.delay: Nheko.tooltipDelay
//ToolTip.text: qsTr("Close") //ToolTip.text: qsTr("Close")
onClicked: imageOverlay.close() onClicked: imageOverlay.close()
} }
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import "../components" import "../components"
import Qt.labs.platform 1.1 import Qt.labs.platform 1.1
import QtQuick 2.12 import QtQuick 2.12
@ -15,321 +13,293 @@ ApplicationWindow {
id: win id: win
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
property SingleImagePackModel imagePack
property int currentImageIndex: -1 property int currentImageIndex: -1
property SingleImagePackModel imagePack
readonly property int stickerDim: 128 readonly property int stickerDim: 128
readonly property int stickerDimPad: 128 + Nheko.paddingSmall readonly property int stickerDimPad: 128 + Nheko.paddingSmall
title: qsTr("Editing image pack")
height: 600
width: 600
palette: timelineRoot.palette
color: timelineRoot.palette.base color: timelineRoot.palette.base
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 600
modality: Qt.WindowModal
palette: timelineRoot.palette
title: qsTr("Editing image pack")
width: 600
footer: DialogButtonBox {
id: buttons
standardButtons: DialogButtonBox.Save | DialogButtonBox.Cancel
onAccepted: {
imagePack.save();
win.close();
}
onRejected: win.close()
}
AdaptiveLayout { AdaptiveLayout {
id: adaptiveView id: adaptiveView
anchors.fill: parent anchors.fill: parent
singlePageMode: false
pageIndex: 0 pageIndex: 0
singlePageMode: false
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: packlistC id: packlistC
clip: true
visible: Settings.groupView
minimumWidth: 200
collapsedWidth: 200 collapsedWidth: 200
preferredWidth: 300
maximumWidth: 300 maximumWidth: 300
clip: true minimumWidth: 200
preferredWidth: 300
visible: Settings.groupView
ListView { ListView {
//required property bool isEmote //required property bool isEmote
//required property bool isSticker //required property bool isSticker
model: imagePack model: imagePack
delegate: AvatarListTile {
id: packItem
header: AvatarListTile { property color background: timelineRoot.palette.window
title: imagePack.packname required property string body
avatarUrl: imagePack.avatarUrl property color bubbleBackground: timelineRoot.palette.highlight
roomid: imagePack.statekey property color bubbleText: timelineRoot.palette.highlightedText
subtitle: imagePack.statekey property color importantText: timelineRoot.palette.text
index: -1 required property string shortCode
property color unimportantText: timelineRoot.palette.placeholderText
required property string url
avatarUrl: url
crop: false
selectedIndex: currentImageIndex selectedIndex: currentImageIndex
subtitle: body
title: shortCode
TapHandler { TapHandler {
onSingleTapped: currentImageIndex = -1 onSingleTapped: currentImageIndex = index
}
Rectangle {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
height: parent.height - Nheko.paddingSmall * 2
width: 3
color: timelineRoot.palette.highlight
} }
} }
footer: Button { footer: Button {
palette: timelineRoot.palette palette: timelineRoot.palette
onClicked: addFilesDialog.open()
width: ListView.view.width
text: qsTr("Add images") text: qsTr("Add images")
width: ListView.view.width
onClicked: addFilesDialog.open()
FileDialog { FileDialog {
id: addFilesDialog id: addFilesDialog
acceptLabel: qsTr("Add to pack")
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
fileMode: FileDialog.OpenFiles fileMode: FileDialog.OpenFiles
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
nameFilters: [qsTr("Images (*.png *.webp *.gif *.jpg *.jpeg)")] nameFilters: [qsTr("Images (*.png *.webp *.gif *.jpg *.jpeg)")]
title: qsTr("Select images for pack") title: qsTr("Select images for pack")
acceptLabel: qsTr("Add to pack")
onAccepted: imagePack.addStickers(files) onAccepted: imagePack.addStickers(files)
} }
} }
header: AvatarListTile {
delegate: AvatarListTile { avatarUrl: imagePack.avatarUrl
id: packItem index: -1
roomid: imagePack.statekey
property color background: timelineRoot.palette.window
property color importantText: timelineRoot.palette.text
property color unimportantText: timelineRoot.palette.placeholderText
property color bubbleBackground: timelineRoot.palette.highlight
property color bubbleText: timelineRoot.palette.highlightedText
required property string shortCode
required property string url
required property string body
title: shortCode
subtitle: body
avatarUrl: url
selectedIndex: currentImageIndex selectedIndex: currentImageIndex
crop: false subtitle: imagePack.statekey
title: imagePack.packname
TapHandler { TapHandler {
onSingleTapped: currentImageIndex = index onSingleTapped: currentImageIndex = -1
}
Rectangle {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
color: timelineRoot.palette.highlight
height: parent.height - Nheko.paddingSmall * 2
width: 3
} }
} }
} }
} }
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: packinfoC id: packinfoC
Rectangle { Rectangle {
color: timelineRoot.palette.window color: timelineRoot.palette.window
GridLayout { GridLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
visible: currentImageIndex == -1
enabled: visible
columns: 2 columns: 2
enabled: visible
rowSpacing: Nheko.paddingLarge rowSpacing: Nheko.paddingLarge
visible: currentImageIndex == -1
Avatar { Avatar {
Layout.alignment: Qt.AlignHCenter
Layout.columnSpan: 2 Layout.columnSpan: 2
url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/") crop: false
displayName: imagePack.packname displayName: imagePack.packname
roomid: imagePack.statekey
height: 130 height: 130
roomid: imagePack.statekey
url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/")
width: 130 width: 130
crop: false
Layout.alignment: Qt.AlignHCenter
ImageButton { ImageButton {
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change the overview image for this pack") ToolTip.text: qsTr("Change the overview image for this pack")
ToolTip.visible: hovered
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top
anchors.leftMargin: Nheko.paddingMedium anchors.leftMargin: Nheko.paddingMedium
anchors.top: parent.top
anchors.topMargin: Nheko.paddingMedium anchors.topMargin: Nheko.paddingMedium
hoverEnabled: true
image: ":/icons/icons/ui/edit.svg" image: ":/icons/icons/ui/edit.svg"
onClicked: addAvatarDialog.open() onClicked: addAvatarDialog.open()
FileDialog { FileDialog {
id: addAvatarDialog id: addAvatarDialog
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
fileMode: FileDialog.OpenFile fileMode: FileDialog.OpenFile
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
nameFilters: [qsTr("Overview Image (*.png *.webp *.jpg *.jpeg)")] nameFilters: [qsTr("Overview Image (*.png *.webp *.jpg *.jpeg)")]
title: qsTr("Select overview image for pack") title: qsTr("Select overview image for pack")
onAccepted: imagePack.setAvatar(file) onAccepted: imagePack.setAvatar(file)
} }
} }
} }
MatrixTextField { MatrixTextField {
id: statekeyField id: statekeyField
visible: imagePack.roomid
Layout.fillWidth: true
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true
label: qsTr("State key") label: qsTr("State key")
text: imagePack.statekey text: imagePack.statekey
visible: imagePack.roomid
onTextEdited: imagePack.statekey = text onTextEdited: imagePack.statekey = text
} }
MatrixTextField { MatrixTextField {
Layout.fillWidth: true
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true
label: qsTr("Packname") label: qsTr("Packname")
text: imagePack.packname text: imagePack.packname
onTextEdited: imagePack.packname = text onTextEdited: imagePack.packname = text
} }
MatrixTextField { MatrixTextField {
Layout.fillWidth: true
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true
label: qsTr("Attribution") label: qsTr("Attribution")
text: imagePack.attribution text: imagePack.attribution
onTextEdited: imagePack.attribution = text onTextEdited: imagePack.attribution = text
} }
MatrixText { MatrixText {
Layout.margins: statekeyField.textPadding Layout.margins: statekeyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02 font.letterSpacing: font.pixelSize * 0.02
font.weight: Font.DemiBold
text: qsTr("Use as Emoji") text: qsTr("Use as Emoji")
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight
checked: imagePack.isEmotePack checked: imagePack.isEmotePack
onCheckedChanged: imagePack.isEmotePack = checked onCheckedChanged: imagePack.isEmotePack = checked
Layout.alignment: Qt.AlignRight
} }
MatrixText { MatrixText {
Layout.margins: statekeyField.textPadding Layout.margins: statekeyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02 font.letterSpacing: font.pixelSize * 0.02
font.weight: Font.DemiBold
text: qsTr("Use as Sticker") text: qsTr("Use as Sticker")
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight
checked: imagePack.isStickerPack checked: imagePack.isStickerPack
onCheckedChanged: imagePack.isStickerPack = checked onCheckedChanged: imagePack.isStickerPack = checked
Layout.alignment: Qt.AlignRight
} }
Item { Item {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillHeight: true Layout.fillHeight: true
} }
} }
GridLayout { GridLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
visible: currentImageIndex >= 0
enabled: visible
columns: 2 columns: 2
enabled: visible
rowSpacing: Nheko.paddingLarge rowSpacing: Nheko.paddingLarge
visible: currentImageIndex >= 0
Avatar { Avatar {
Layout.alignment: Qt.AlignHCenter
Layout.columnSpan: 2 Layout.columnSpan: 2
url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/") + "?scale" crop: false
displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
roomid: displayName
height: 130 height: 130
roomid: displayName
url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/") + "?scale"
width: 130 width: 130
crop: false
Layout.alignment: Qt.AlignHCenter
} }
MatrixTextField { MatrixTextField {
Layout.fillWidth: true
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true
label: qsTr("Shortcode") label: qsTr("Shortcode")
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode) onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode)
} }
MatrixTextField { MatrixTextField {
id: bodyField id: bodyField
Layout.fillWidth: true
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true
label: qsTr("Body") label: qsTr("Body")
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body) text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body)
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body) onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body)
} }
MatrixText { MatrixText {
Layout.margins: bodyField.textPadding Layout.margins: bodyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02 font.letterSpacing: font.pixelSize * 0.02
font.weight: Font.DemiBold
text: qsTr("Use as Emoji") text: qsTr("Use as Emoji")
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight
checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote) checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote)
onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsEmote) onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsEmote)
Layout.alignment: Qt.AlignRight
} }
MatrixText { MatrixText {
Layout.margins: bodyField.textPadding Layout.margins: bodyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02 font.letterSpacing: font.pixelSize * 0.02
font.weight: Font.DemiBold
text: qsTr("Use as Sticker") text: qsTr("Use as Sticker")
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight
checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker) checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker)
onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsSticker) onCheckedChanged: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsSticker)
Layout.alignment: Qt.AlignRight
} }
MatrixText { MatrixText {
Layout.margins: bodyField.textPadding Layout.margins: bodyField.textPadding
font.weight: Font.DemiBold
font.letterSpacing: font.pixelSize * 0.02 font.letterSpacing: font.pixelSize * 0.02
font.weight: Font.DemiBold
text: qsTr("Remove from pack") text: qsTr("Remove from pack")
} }
Button { Button {
Layout.alignment: Qt.AlignRight
text: qsTr("Remove") text: qsTr("Remove")
onClicked: { onClicked: {
let temp = currentImageIndex; let temp = currentImageIndex;
currentImageIndex = -1; currentImageIndex = -1;
imagePack.remove(temp); imagePack.remove(temp);
} }
Layout.alignment: Qt.AlignRight
} }
Item { Item {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillHeight: true Layout.fillHeight: true
} }
} }
} }
}
}
footer: DialogButtonBox {
id: buttons
standardButtons: DialogButtonBox.Save | DialogButtonBox.Cancel
onAccepted: {
imagePack.save();
win.close();
} }
onRejected: win.close()
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import "../components" import "../components"
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.12 import QtQuick.Controls 2.12
@ -13,96 +11,70 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: win id: win
property Room room
property ImagePackListModel packlist
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
property SingleImagePackModel currentPack: packlist.packAt(currentPackIndex) property SingleImagePackModel currentPack: packlist.packAt(currentPackIndex)
property int currentPackIndex: 0 property int currentPackIndex: 0
property ImagePackListModel packlist
property Room room
readonly property int stickerDim: 128 readonly property int stickerDim: 128
readonly property int stickerDimPad: 128 + Nheko.paddingSmall readonly property int stickerDimPad: 128 + Nheko.paddingSmall
title: qsTr("Image pack settings")
height: 600
width: 800
palette: timelineRoot.palette
color: timelineRoot.palette.base color: timelineRoot.palette.base
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 600
modality: Qt.NonModal
palette: timelineRoot.palette
title: qsTr("Image pack settings")
width: 800
footer: DialogButtonBox {
id: buttons
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Close")
onClicked: win.close()
}
}
Component { Component {
id: packEditor id: packEditor
ImagePackEditorDialog { ImagePackEditorDialog {
} }
} }
AdaptiveLayout { AdaptiveLayout {
id: adaptiveView id: adaptiveView
anchors.fill: parent anchors.fill: parent
singlePageMode: false
pageIndex: 0 pageIndex: 0
singlePageMode: false
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: packlistC id: packlistC
visible: Settings.groupView
minimumWidth: 200
collapsedWidth: 200 collapsedWidth: 200
preferredWidth: 300
maximumWidth: 300 maximumWidth: 300
minimumWidth: 200
preferredWidth: 300
visible: Settings.groupView
ListView { ListView {
model: packlist
clip: true clip: true
model: packlist
footer: ColumnLayout {
Button {
palette: timelineRoot.palette
onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": packlist.newPack(false)
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
}
width: packlistC.width
visible: !packlist.containsAccountPack
text: qsTr("Create account pack")
}
Button {
palette: timelineRoot.palette
onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": packlist.newPack(true)
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
}
width: packlistC.width
visible: room.permissions.canChange(MtxEvent.ImagePackInRoom)
text: qsTr("New room pack")
}
}
delegate: AvatarListTile { delegate: AvatarListTile {
id: packItem id: packItem
property color background: timelineRoot.palette.window property color background: timelineRoot.palette.window
property color importantText: timelineRoot.palette.text
property color unimportantText: timelineRoot.palette.placeholderText
property color bubbleBackground: timelineRoot.palette.highlight property color bubbleBackground: timelineRoot.palette.highlight
property color bubbleText: timelineRoot.palette.highlightedText property color bubbleText: timelineRoot.palette.highlightedText
required property string displayName required property string displayName
required property bool fromAccountData required property bool fromAccountData
required property bool fromCurrentRoom required property bool fromCurrentRoom
property color importantText: timelineRoot.palette.text
required property string statekey required property string statekey
property color unimportantText: timelineRoot.palette.placeholderText
title: displayName roomid: statekey
selectedIndex: currentPackIndex
subtitle: { subtitle: {
if (fromAccountData) if (fromAccountData)
return qsTr("Private pack"); return qsTr("Private pack");
@ -111,31 +83,55 @@ ApplicationWindow {
else else
return qsTr("Globally enabled pack"); return qsTr("Globally enabled pack");
} }
selectedIndex: currentPackIndex title: displayName
roomid: statekey
TapHandler { TapHandler {
onSingleTapped: currentPackIndex = index onSingleTapped: currentPackIndex = index
} }
} }
footer: ColumnLayout {
Button {
palette: timelineRoot.palette
text: qsTr("Create account pack")
visible: !packlist.containsAccountPack
width: packlistC.width
onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": packlist.newPack(false)
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
} }
} }
Button {
palette: timelineRoot.palette
text: qsTr("New room pack")
visible: room.permissions.canChange(MtxEvent.ImagePackInRoom)
width: packlistC.width
onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": packlist.newPack(true)
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
}
}
}
}
}
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: packinfoC id: packinfoC
Rectangle { Rectangle {
color: timelineRoot.palette.window color: timelineRoot.palette.window
ColumnLayout { ColumnLayout {
id: packinfo id: packinfo
property string packName: currentPack ? currentPack.packname : ""
property string attribution: currentPack ? currentPack.attribution : "" property string attribution: currentPack ? currentPack.attribution : ""
property string avatarUrl: currentPack ? currentPack.avatarUrl : "" property string avatarUrl: currentPack ? currentPack.avatarUrl : ""
property string packName: currentPack ? currentPack.packname : ""
property string statekey: currentPack ? currentPack.statekey : "" property string statekey: currentPack ? currentPack.statekey : ""
anchors.fill: parent anchors.fill: parent
@ -143,54 +139,50 @@ ApplicationWindow {
spacing: Nheko.paddingLarge spacing: Nheko.paddingLarge
Avatar { Avatar {
url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/") Layout.alignment: Qt.AlignHCenter
displayName: packinfo.packName displayName: packinfo.packName
roomid: packinfo.statekey enabled: false
height: 100 height: 100
roomid: packinfo.statekey
url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/")
width: 100 width: 100
Layout.alignment: Qt.AlignHCenter
enabled: false
} }
MatrixText { MatrixText {
text: packinfo.packName
font.pixelSize: Math.ceil(fontMetrics.pixelSize * 1.1)
horizontalAlignment: TextEdit.AlignHCenter
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2 Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2
font.pixelSize: Math.ceil(fontMetrics.pixelSize * 1.1)
horizontalAlignment: TextEdit.AlignHCenter
text: packinfo.packName
} }
MatrixText { MatrixText {
text: packinfo.attribution
wrapMode: TextEdit.Wrap
horizontalAlignment: TextEdit.AlignHCenter
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2 Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2
horizontalAlignment: TextEdit.AlignHCenter
text: packinfo.attribution
wrapMode: TextEdit.Wrap
} }
GridLayout { GridLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
visible: currentPack && currentPack.roomid != ""
columns: 2 columns: 2
rowSpacing: Nheko.paddingMedium rowSpacing: Nheko.paddingMedium
visible: currentPack && currentPack.roomid != ""
MatrixText { MatrixText {
text: qsTr("Enable globally") text: qsTr("Enable globally")
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("Enables this pack to be used in all rooms") ToolTip.text: qsTr("Enables this pack to be used in all rooms")
checked: currentPack ? currentPack.isGloballyEnabled : false checked: currentPack ? currentPack.isGloballyEnabled : false
onCheckedChanged: currentPack.isGloballyEnabled = checked onCheckedChanged: currentPack.isGloballyEnabled = checked
Layout.alignment: Qt.AlignRight
} }
} }
Button { Button {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("Edit")
enabled: currentPack.canEdit enabled: currentPack.canEdit
text: qsTr("Edit")
onClicked: { onClicked: {
var dialog = packEditor.createObject(timelineRoot, { var dialog = packEditor.createObject(timelineRoot, {
"imagePack": currentPack "imagePack": currentPack
@ -199,61 +191,40 @@ ApplicationWindow {
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
} }
} }
GridView { GridView {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
model: currentPack
cellWidth: stickerDimPad
cellHeight: stickerDimPad
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
cacheBuffer: 500
cellHeight: stickerDimPad
cellWidth: stickerDimPad
clip: true clip: true
currentIndex: -1 // prevent sorting from stealing focus currentIndex: -1 // prevent sorting from stealing focus
cacheBuffer: 500 model: currentPack
// Individual emoji // Individual emoji
delegate: AbstractButton { delegate: AbstractButton {
width: stickerDim
height: stickerDim
hoverEnabled: true
ToolTip.text: ":" + model.shortCode + ": - " + model.body ToolTip.text: ":" + model.shortCode + ": - " + model.body
ToolTip.visible: hovered ToolTip.visible: hovered
contentItem: Image {
height: stickerDim height: stickerDim
hoverEnabled: true
width: stickerDim width: stickerDim
source: model.url.replace("mxc://", "image://MxcImage/") + "?scale"
fillMode: Image.PreserveAspectFit
}
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: hovered ? timelineRoot.palette.highlight : 'transparent' color: hovered ? timelineRoot.palette.highlight : 'transparent'
radius: 5 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()
} }
}
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.5 import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
@ -12,58 +10,53 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: inputDialog id: inputDialog
property alias prompt: promptLabel.text
property alias echoMode: statusInput.echoMode property alias echoMode: statusInput.echoMode
property var onAccepted: undefined property var onAccepted: undefined
property alias prompt: promptLabel.text
function forceActiveFocus() {
statusInput.forceActiveFocus();
}
modality: Qt.NonModal
flags: Qt.Dialog flags: Qt.Dialog
width: 350
height: fontMetrics.lineSpacing * 7 height: fontMetrics.lineSpacing * 7
modality: Qt.NonModal
width: 350
function forceActiveFocus() { footer: DialogButtonBox {
statusInput.forceActiveFocus(); id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
if (inputDialog.onAccepted)
inputDialog.onAccepted(statusInput.text);
inputDialog.close();
}
onRejected: {
inputDialog.close();
}
} }
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: dbb.rejected() onActivated: dbb.rejected()
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingMedium
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Label { Label {
id: promptLabel id: promptLabel
color: timelineRoot.palette.text color: timelineRoot.palette.text
} }
MatrixTextField { MatrixTextField {
id: statusInput id: statusInput
Layout.fillWidth: true Layout.fillWidth: true
onAccepted: dbb.accepted()
focus: true focus: true
}
}
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
if (inputDialog.onAccepted)
inputDialog.onAccepted(statusInput.text);
inputDialog.close(); onAccepted: dbb.accepted()
}
onRejected: {
inputDialog.close();
} }
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.12 import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
@ -12,9 +10,9 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: inviteDialogRoot id: inviteDialogRoot
property string roomId
property string plainRoomName
property InviteesModel invitees property InviteesModel invitees
property string plainRoomName
property string roomId
function addInvite() { function addInvite() {
if (inviteeEntry.isValidMxid) { if (inviteeEntry.isValidMxid) {
@ -22,43 +20,57 @@ ApplicationWindow {
inviteeEntry.clear(); inviteeEntry.clear();
} }
} }
function cleanUpAndClose() { function cleanUpAndClose() {
if (inviteeEntry.isValidMxid) if (inviteeEntry.isValidMxid)
addInvite(); addInvite();
invitees.accept(); invitees.accept();
close(); close();
} }
title: qsTr("Invite users to %1").arg(plainRoomName)
height: 380
width: 340
palette: timelineRoot.palette
color: timelineRoot.palette.window color: timelineRoot.palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 380
palette: timelineRoot.palette
title: qsTr("Invite users to %1").arg(plainRoomName)
width: 340
footer: DialogButtonBox {
id: buttons
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: invitees.count > 0
text: qsTr("Invite")
onClicked: cleanUpAndClose()
}
Button {
DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole
text: qsTr("Cancel")
onClicked: inviteDialogRoot.close()
}
}
Shortcut { Shortcut {
sequence: "Ctrl+Enter" sequence: "Ctrl+Enter"
onActivated: cleanUpAndClose() onActivated: cleanUpAndClose()
} }
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: inviteDialogRoot.close() onActivated: inviteDialogRoot.close()
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
Label { Label {
text: qsTr("User ID to invite")
Layout.fillWidth: true Layout.fillWidth: true
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("User ID to invite")
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
@ -67,120 +79,88 @@ ApplicationWindow {
property bool isValidMxid: text.match("@.+?:.{3,}") property bool isValidMxid: text.match("@.+?:.{3,}")
Layout.fillWidth: true
backgroundColor: timelineRoot.palette.window backgroundColor: timelineRoot.palette.window
placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.") placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.")
Layout.fillWidth: true
onAccepted: {
if (isValidMxid)
addInvite();
}
Component.onCompleted: forceActiveFocus() Component.onCompleted: forceActiveFocus()
Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier))
Keys.onPressed: { Keys.onPressed: {
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier)) if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier))
cleanUpAndClose(); cleanUpAndClose();
}
Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier))
onAccepted: {
if (isValidMxid)
addInvite();
} }
} }
Button { Button {
text: qsTr("Add")
enabled: inviteeEntry.isValidMxid enabled: inviteeEntry.isValidMxid
text: qsTr("Add")
onClicked: addInvite() onClicked: addInvite()
} }
} }
ListView { ListView {
id: inviteesList id: inviteesList
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
model: invitees model: invitees
delegate: ItemDelegate { delegate: ItemDelegate {
id: del id: del
height: layout.implicitHeight + Nheko.paddingSmall * 2
hoverEnabled: true hoverEnabled: true
width: ListView.view.width width: ListView.view.width
height: layout.implicitHeight + Nheko.paddingSmall * 2
onClicked: TimelineManager.openGlobalUserProfile(model.mxid)
background: Rectangle { background: Rectangle {
color: del.hovered ? timelineRoot.palette.dark : inviteDialogRoot.color color: del.hovered ? timelineRoot.palette.dark : inviteDialogRoot.color
} }
onClicked: TimelineManager.openGlobalUserProfile(model.mxid)
RowLayout { RowLayout {
id: layout id: layout
spacing: Nheko.paddingMedium
anchors.centerIn: parent anchors.centerIn: parent
spacing: Nheko.paddingMedium
width: del.width - Nheko.paddingSmall * 2 width: del.width - Nheko.paddingSmall * 2
Avatar { Avatar {
width: Nheko.avatarSize
height: Nheko.avatarSize
userid: model.mxid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: model.displayName displayName: model.displayName
enabled: false enabled: false
height: Nheko.avatarSize
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.mxid
width: Nheko.avatarSize
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
Label { Label {
text: model.displayName
color: TimelineManager.userColor(model ? model.mxid : "", del.background.color) color: TimelineManager.userColor(model ? model.mxid : "", del.background.color)
font.pointSize: fontMetrics.font.pointSize font.pointSize: fontMetrics.font.pointSize
text: model.displayName
} }
Label { Label {
text: model.mxid
color: del.hovered ? timelineRoot.palette.brightText : timelineRoot.palette.placeholderText color: del.hovered ? timelineRoot.palette.brightText : timelineRoot.palette.placeholderText
font.pointSize: fontMetrics.font.pointSize * 0.9 font.pointSize: fontMetrics.font.pointSize * 0.9
text: model.mxid
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
ImageButton { ImageButton {
image: ":/icons/icons/ui/dismiss.svg" image: ":/icons/icons/ui/dismiss.svg"
onClicked: invitees.removeUser(model.mxid) onClicked: invitees.removeUser(model.mxid)
} }
} }
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor 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()
} }
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.5 import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
@ -11,64 +9,57 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: joinRoomRoot id: joinRoomRoot
color: timelineRoot.palette.window
title: qsTr("Join room")
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: fontMetrics.lineSpacing * 7
modality: Qt.WindowModal
palette: timelineRoot.palette palette: timelineRoot.palette
color: timelineRoot.palette.window title: qsTr("Join room")
width: 350 width: 350
height: fontMetrics.lineSpacing * 7
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: "Join"
}
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: dbb.rejected() onActivated: dbb.rejected()
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingMedium
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Label { Label {
id: promptLabel id: promptLabel
text: qsTr("Room ID or alias")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Room ID or alias")
} }
MatrixTextField { MatrixTextField {
id: input id: input
focus: true
Layout.fillWidth: true Layout.fillWidth: true
focus: true
onAccepted: { onAccepted: {
if (input.text.match("#.+?:.{3,}")) if (input.text.match("#.+?:.{3,}"))
dbb.accepted(); dbb.accepted();
} }
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Cancel
onAccepted: {
Nheko.joinRoom(input.text);
joinRoomRoot.close();
}
onRejected: {
joinRoomRoot.close();
}
Button {
text: "Join"
enabled: input.text.match("#.+?:.{3,}")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
}
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import Qt.labs.platform 1.1 as P import Qt.labs.platform 1.1 as P
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
@ -11,12 +9,13 @@ import im.nheko
P.MessageDialog { P.MessageDialog {
id: leaveRoomRoot id: leaveRoomRoot
required property string roomId
property string reason: "" 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 buttons: P.MessageDialog.Ok | P.MessageDialog.Cancel
modality: Qt.ApplicationModal
text: qsTr("Are you sure you want to leave?")
title: qsTr("Leave room")
onAccepted: Rooms.leave(roomId, reason) onAccepted: Rooms.leave(roomId, reason)
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import Qt.labs.platform 1.1 as P import Qt.labs.platform 1.1 as P
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
@ -10,11 +8,11 @@ import im.nheko
P.MessageDialog { P.MessageDialog {
id: logoutRoot 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 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() onAccepted: Nheko.logout()
} }

File diff suppressed because it is too large Load Diff

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import im.nheko import im.nheko
@ -12,44 +10,40 @@ ApplicationWindow {
property alias rawMessage: rawMessageView.text property alias rawMessage: rawMessageView.text
height: 420
width: 420
palette: timelineRoot.palette
color: timelineRoot.palette.window color: timelineRoot.palette.window
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 420
palette: timelineRoot.palette
width: 420
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: rawMessageRoot.close()
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: rawMessageRoot.close() onActivated: rawMessageRoot.close()
} }
ScrollView { ScrollView {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
palette: timelineRoot.palette anchors.margins: Nheko.paddingMedium
padding: Nheko.paddingMedium padding: Nheko.paddingMedium
palette: timelineRoot.palette
TextArea { TextArea {
id: rawMessageView id: rawMessageView
anchors.fill: parent
font: Nheko.monospaceFont()
color: timelineRoot.palette.text color: timelineRoot.palette.text
font: Nheko.monospaceFont()
readOnly: true readOnly: true
textFormat: Text.PlainText textFormat: Text.PlainText
anchors.fill: parent
background: Rectangle { background: Rectangle {
color: timelineRoot.palette.base color: timelineRoot.palette.base
} }
}
} }
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: rawMessageRoot.close()
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
@ -15,19 +13,25 @@ ApplicationWindow {
property ReadReceiptsProxy readReceipts property ReadReceiptsProxy readReceipts
property Room room property Room room
color: timelineRoot.palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 380 height: 380
width: 340
minimumHeight: 380 minimumHeight: 380
minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium
palette: timelineRoot.palette palette: timelineRoot.palette
color: timelineRoot.palette.window width: 340
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: readReceiptsRoot.close()
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: readReceiptsRoot.close() onActivated: readReceiptsRoot.close()
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
@ -35,97 +39,78 @@ ApplicationWindow {
Label { Label {
id: headerTitle id: headerTitle
color: timelineRoot.palette.text
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
text: qsTr("Read receipts") color: timelineRoot.palette.text
font.pointSize: fontMetrics.font.pointSize * 1.5 font.pointSize: fontMetrics.font.pointSize * 1.5
text: qsTr("Read receipts")
} }
ScrollView { ScrollView {
palette: timelineRoot.palette
padding: Nheko.paddingMedium
ScrollBar.horizontal.visible: false
Layout.fillHeight: true Layout.fillHeight: true
Layout.minimumHeight: 200
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: 200
ScrollBar.horizontal.visible: false
padding: Nheko.paddingMedium
palette: timelineRoot.palette
ListView { ListView {
id: readReceiptsList id: readReceiptsList
clip: true
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
clip: true
model: readReceipts model: readReceipts
delegate: ItemDelegate { delegate: ItemDelegate {
id: del id: del
ToolTip.text: model.mxid
onClicked: room.openUserProfile(model.mxid) ToolTip.visible: hovered
padding: Nheko.paddingMedium
width: ListView.view.width
height: receiptLayout.implicitHeight + Nheko.paddingSmall * 2 height: receiptLayout.implicitHeight + Nheko.paddingSmall * 2
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered padding: Nheko.paddingMedium
ToolTip.text: model.mxid width: ListView.view.width
background: Rectangle { background: Rectangle {
color: del.hovered ? timelineRoot.palette.dark : readReceiptsRoot.color color: del.hovered ? timelineRoot.palette.dark : readReceiptsRoot.color
} }
onClicked: room.openUserProfile(model.mxid)
RowLayout { RowLayout {
id: receiptLayout id: receiptLayout
spacing: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingSmall anchors.margins: Nheko.paddingSmall
spacing: Nheko.paddingMedium
Avatar { Avatar {
width: Nheko.avatarSize
height: Nheko.avatarSize
userid: model.mxid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: model.displayName displayName: model.displayName
enabled: false enabled: false
height: Nheko.avatarSize
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.mxid
width: Nheko.avatarSize
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
Label { Label {
text: model.displayName
color: TimelineManager.userColor(model ? model.mxid : "", timelineRoot.palette.window) color: TimelineManager.userColor(model ? model.mxid : "", timelineRoot.palette.window)
font.pointSize: fontMetrics.font.pointSize font.pointSize: fontMetrics.font.pointSize
text: model.displayName
} }
Label { Label {
text: model.timestamp
color: timelineRoot.palette.placeholderText color: timelineRoot.palette.placeholderText
font.pointSize: fontMetrics.font.pointSize * 0.9 font.pointSize: fontMetrics.font.pointSize * 0.9
text: model.timestamp
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
} }
} }
} }
} }
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: readReceiptsRoot.close()
}
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import "../ui" import "../ui"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -13,181 +11,157 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: roomDirectoryWindow id: roomDirectoryWindow
property RoomDirectoryModel publicRooms property RoomDirectoryModel publicRooms: RoomDirectoryModel {
}
visible: true
minimumWidth: 340
minimumHeight: 340
height: 420
width: 650
palette: timelineRoot.palette
color: timelineRoot.palette.window color: timelineRoot.palette.window
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 420
minimumHeight: 340
minimumWidth: 340
modality: Qt.WindowModal
palette: timelineRoot.palette
title: qsTr("Explore Public Rooms") title: qsTr("Explore Public Rooms")
visible: true
width: 650
header: RowLayout {
id: searchBarLayout
implicitHeight: roomSearch.height
spacing: Nheko.paddingMedium
width: parent.width
MatrixTextField {
id: roomSearch
Layout.fillWidth: true
color: timelineRoot.palette.text
focus: true
font.pixelSize: fontMetrics.font.pixelSize
placeholderText: qsTr("Search for public rooms")
selectByMouse: true
onTextChanged: searchTimer.restart()
}
MatrixTextField {
id: chooseServer
Layout.maximumWidth: 0.3 * header.width
Layout.minimumWidth: 0.3 * header.width
color: timelineRoot.palette.text
placeholderText: qsTr("Choose custom homeserver")
onTextChanged: publicRooms.setMatrixServer(text)
}
Timer {
id: searchTimer
interval: 350
onTriggered: roomDirView.model.setSearchTerm(roomSearch.text)
}
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: roomDirectoryWindow.close() onActivated: roomDirectoryWindow.close()
} }
ListView { ListView {
id: roomDirView id: roomDirView
anchors.fill: parent anchors.fill: parent
model: publicRooms model: publicRooms
delegate: Rectangle { delegate: Rectangle {
id: roomDirDelegate id: roomDirDelegate
property int avatarSize: fontMetrics.height * 3.2
property color background: timelineRoot.palette.window property color background: timelineRoot.palette.window
property color importantText: timelineRoot.palette.text property color importantText: timelineRoot.palette.text
property color unimportantText: timelineRoot.palette.placeholderText property color unimportantText: timelineRoot.palette.placeholderText
property int avatarSize: fontMetrics.height * 3.2
color: background color: background
height: avatarSize + Nheko.paddingLarge height: avatarSize + Nheko.paddingLarge
width: ListView.view.width width: ListView.view.width
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
implicitHeight: textContent.implicitHeight implicitHeight: textContent.implicitHeight
spacing: Nheko.paddingMedium
Avatar { Avatar {
id: roomAvatar id: roomAvatar
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: Nheko.paddingMedium Layout.rightMargin: Nheko.paddingMedium
width: avatarSize displayName: model.name
height: avatarSize height: avatarSize
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
roomid: model.roomid roomid: model.roomid
displayName: model.name url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
width: avatarSize
} }
GridLayout { GridLayout {
id: textContent id: textContent
rows: 2
columns: 2
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
width: parent.width - avatar.width
Layout.preferredWidth: parent.width - avatar.width Layout.preferredWidth: parent.width - avatar.width
columns: 2
rows: 2
width: parent.width - avatar.width
ElidedLabel { ElidedLabel {
Layout.row: 0
Layout.column: 0 Layout.column: 0
Layout.fillWidth:true Layout.fillWidth: true
Layout.row: 0
color: roomDirDelegate.importantText color: roomDirDelegate.importantText
elideWidth: width elideWidth: width
fullText: model.name fullText: model.name
} }
Label { Label {
id: roomTopic id: roomTopic
color: roomDirDelegate.unimportantText
Layout.row: 1
Layout.column: 0 Layout.column: 0
font.pointSize: fontMetrics.font.pointSize*0.9 Layout.fillWidth: true
Layout.row: 1
color: roomDirDelegate.unimportantText
elide: Text.ElideRight elide: Text.ElideRight
font.pointSize: fontMetrics.font.pointSize * 0.9
maximumLineCount: 2 maximumLineCount: 2
Layout.fillWidth: true
text: model.topic text: model.topic
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Label { Label {
id: roomCount
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.row: 0
Layout.column: 1 Layout.column: 1
id: roomCount Layout.row: 0
color: roomDirDelegate.unimportantText color: roomDirDelegate.unimportantText
font.pointSize: fontMetrics.font.pointSize*0.9 font.pointSize: fontMetrics.font.pointSize * 0.9
text: model.numMembers.toString() text: model.numMembers.toString()
} }
Button { Button {
Layout.row: 1
Layout.column: 1
id: joinRoomButton id: joinRoomButton
Layout.column: 1
Layout.row: 1
enabled: model.canJoin enabled: model.canJoin
text: "Join" text: "Join"
onClicked: publicRooms.joinRoom(model.index) onClicked: publicRooms.joinRoom(model.index)
} }
} }
} }
} }
footer: Item { footer: Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
width: parent.width anchors.margins: Nheko.paddingLarge
visible: !publicRooms.reachedEndOfPagination && publicRooms.loadingMoreRooms
// hacky but works // hacky but works
height: loadingSpinner.height + 2 * Nheko.paddingLarge height: loadingSpinner.height + 2 * Nheko.paddingLarge
anchors.margins: Nheko.paddingLarge visible: !publicRooms.reachedEndOfPagination && publicRooms.loadingMoreRooms
width: parent.width
Spinner { Spinner {
id: loadingSpinner id: loadingSpinner
anchors.centerIn: parent anchors.centerIn: parent
anchors.margins: Nheko.paddingLarge anchors.margins: Nheko.paddingLarge
running: visible
foreground: timelineRoot.palette.mid foreground: timelineRoot.palette.mid
running: visible
} }
}
}
publicRooms: RoomDirectoryModel {
}
header: RowLayout {
id: searchBarLayout
spacing: Nheko.paddingMedium
width: parent.width
implicitHeight: roomSearch.height
MatrixTextField {
id: roomSearch
focus: true
Layout.fillWidth: true
selectByMouse: true
font.pixelSize: fontMetrics.font.pixelSize
color: timelineRoot.palette.text
placeholderText: qsTr("Search for public rooms")
onTextChanged: searchTimer.restart()
Component.onCompleted: forceActiveFocus()
}
MatrixTextField {
id: chooseServer
Layout.minimumWidth: 0.3 * header.width
Layout.maximumWidth: 0.3 * header.width
color: timelineRoot.palette.text
placeholderText: qsTr("Choose custom homeserver")
onTextChanged: publicRooms.setMatrixServer(text)
}
Timer {
id: searchTimer
interval: 350
onTriggered: roomDirView.model.setSearchTerm(roomSearch.text)
} }
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import "../ui" import "../ui"
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.12 import QtQuick.Controls 2.12
@ -17,19 +15,25 @@ ApplicationWindow {
property MemberList members property MemberList members
property Room room property Room room
title: qsTr("Members of %1").arg(members.roomName) color: timelineRoot.palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 650 height: 650
width: 420
minimumHeight: 420 minimumHeight: 420
palette: timelineRoot.palette palette: timelineRoot.palette
color: timelineRoot.palette.window title: qsTr("Members of %1").arg(members.roomName)
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint width: 420
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: roomMembersRoot.close()
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: roomMembersRoot.close() onActivated: roomMembersRoot.close()
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
@ -37,143 +41,135 @@ ApplicationWindow {
Avatar { Avatar {
id: roomAvatar id: roomAvatar
Layout.alignment: Qt.AlignHCenter
width: 130 displayName: members.roomName
height: width height: width
roomid: members.roomId roomid: members.roomId
displayName: members.roomName
Layout.alignment: Qt.AlignHCenter
url: members.avatarUrl.replace("mxc://", "image://MxcImage/") url: members.avatarUrl.replace("mxc://", "image://MxcImage/")
width: 130
onClicked: TimelineManager.openRoomSettings(members.roomId) onClicked: TimelineManager.openRoomSettings(members.roomId)
} }
ElidedLabel { 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 Layout.alignment: Qt.AlignHCenter
elideWidth: parent.width - Nheko.paddingMedium 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 { ImageButton {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
image: ":/icons/icons/ui/add-square-button.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Invite more people") ToolTip.text: qsTr("Invite more people")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/add-square-button.svg"
onClicked: TimelineManager.openInviteUsers(members.roomId) onClicked: TimelineManager.openInviteUsers(members.roomId)
} }
MatrixTextField { MatrixTextField {
id: searchBar id: searchBar
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: qsTr("Search...") placeholderText: qsTr("Search...")
onTextChanged: members.setFilterString(text)
Component.onCompleted: forceActiveFocus() Component.onCompleted: forceActiveFocus()
onTextChanged: members.setFilterString(text)
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
Label { Label {
text: qsTr("Sort by: ")
color: Nheko.colors.text color: Nheko.colors.text
text: qsTr("Sort by: ")
} }
ComboBox { ComboBox {
model: ListModel { Layout.fillWidth: true
ListElement { data: MemberList.Mxid; text: qsTr("User ID") }
ListElement { data: MemberList.DisplayName; text: qsTr("Display name") }
ListElement { data: MemberList.Powerlevel; text: qsTr("Power level") }
}
textRole: "text" textRole: "text"
valueRole: "data" valueRole: "data"
onCurrentValueChanged: members.sortBy(currentValue)
Layout.fillWidth: true 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)
}
}
ScrollView { ScrollView {
palette: timelineRoot.palette
padding: Nheko.paddingMedium
ScrollBar.horizontal.visible: false
Layout.fillHeight: true Layout.fillHeight: true
Layout.minimumHeight: 200
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: 200
ScrollBar.horizontal.visible: false
padding: Nheko.paddingMedium
palette: timelineRoot.palette
ListView { ListView {
id: memberList id: memberList
clip: true
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
clip: true
model: members model: members
delegate: ItemDelegate { delegate: ItemDelegate {
id: del id: del
onClicked: Rooms.currentRoom.openUserProfile(model.mxid)
padding: Nheko.paddingMedium
width: ListView.view.width
height: memberLayout.implicitHeight + Nheko.paddingSmall * 2 height: memberLayout.implicitHeight + Nheko.paddingSmall * 2
hoverEnabled: true hoverEnabled: true
padding: Nheko.paddingMedium
width: ListView.view.width
background: Rectangle { background: Rectangle {
color: del.hovered ? timelineRoot.palette.dark : roomMembersRoot.color color: del.hovered ? timelineRoot.palette.dark : roomMembersRoot.color
} }
onClicked: Rooms.currentRoom.openUserProfile(model.mxid)
RowLayout { RowLayout {
id: memberLayout id: memberLayout
spacing: Nheko.paddingMedium
anchors.centerIn: parent anchors.centerIn: parent
spacing: Nheko.paddingMedium
width: parent.width - Nheko.paddingSmall * 2 width: parent.width - Nheko.paddingSmall * 2
Avatar { Avatar {
id: avatar id: avatar
width: Nheko.avatarSize
height: Nheko.avatarSize
userid: model.mxid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: model.displayName displayName: model.displayName
enabled: false enabled: false
height: Nheko.avatarSize
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.mxid
width: Nheko.avatarSize
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
ElidedLabel { ElidedLabel {
fullText: model.displayName
color: TimelineManager.userColor(model ? model.mxid : "", del.background.color) color: TimelineManager.userColor(model ? model.mxid : "", del.background.color)
font.pixelSize: fontMetrics.font.pixelSize
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
font.pixelSize: fontMetrics.font.pixelSize
fullText: model.displayName
} }
ElidedLabel { ElidedLabel {
fullText: model.mxid
color: del.hovered ? timelineRoot.palette.brightText : timelineRoot.palette.placeholderText color: del.hovered ? timelineRoot.palette.brightText : timelineRoot.palette.placeholderText
font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9)
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9)
fullText: model.mxid
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
EncryptionIndicator { EncryptionIndicator {
id: encryptInd id: encryptInd
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
visible: room.isEncrypted
encrypted: room.isEncrypted
trust: encrypted ? model.trustlevel : Crypto.Unverified
ToolTip.text: { ToolTip.text: {
if (!encrypted) if (!encrypted)
return qsTr("This room is not encrypted!"); return qsTr("This room is not encrypted!");
switch (trust) { switch (trust) {
case Crypto.Verified: case Crypto.Verified:
return qsTr("This user is verified."); return qsTr("This user is verified.");
@ -183,42 +179,30 @@ ApplicationWindow {
return qsTr("This user has unverified devices!"); return qsTr("This user has unverified devices!");
} }
} }
encrypted: room.isEncrypted
trust: encrypted ? model.trustlevel : Crypto.Unverified
visible: room.isEncrypted
} }
} }
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
} }
footer: Item { footer: Item {
width: parent.width anchors.margins: Nheko.paddingMedium
visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers
// use the default height if it's visible, otherwise no height at all // use the default height if it's visible, otherwise no height at all
height: membersLoadingSpinner.implicitHeight height: membersLoadingSpinner.implicitHeight
anchors.margins: Nheko.paddingMedium visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers
width: parent.width
Spinner { Spinner {
id: membersLoadingSpinner id: membersLoadingSpinner
anchors.centerIn: parent anchors.centerIn: parent
implicitHeight: parent.visible ? 35 : 0 implicitHeight: parent.visible ? 35 : 0
} }
}
} }
} }
} }
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: roomMembersRoot.close()
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import "../ui" import "../ui"
import Qt.labs.platform 1.1 as Platform import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15 import QtQuick 2.15
@ -18,112 +16,109 @@ ApplicationWindow {
property var roomSettings property var roomSettings
minimumWidth: 340
minimumHeight: 450
width: 450
height: 680
palette: timelineRoot.palette
color: timelineRoot.palette.window color: timelineRoot.palette.window
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 680
minimumHeight: 450
minimumWidth: 340
modality: Qt.NonModal
palette: timelineRoot.palette
title: qsTr("Room Settings") title: qsTr("Room Settings")
width: 450
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: close()
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: roomSettingsDialog.close() onActivated: roomSettingsDialog.close()
} }
Flickable { Flickable {
id: flickable id: flickable
boundsBehavior: Flickable.StopAtBounds
anchors.fill: parent anchors.fill: parent
boundsBehavior: Flickable.StopAtBounds
clip: true clip: true
flickableDirection: Flickable.VerticalFlick
contentWidth: roomSettingsDialog.width
contentHeight: contentLayout1.height contentHeight: contentLayout1.height
contentWidth: roomSettingsDialog.width
flickableDirection: Flickable.VerticalFlick
ColumnLayout { ColumnLayout {
id: contentLayout1 id: contentLayout1
width: parent.width
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
width: parent.width
Avatar { Avatar {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Nheko.paddingMedium Layout.topMargin: Nheko.paddingMedium
url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
roomid: roomSettings.roomId
displayName: roomSettings.roomName displayName: roomSettings.roomName
height: 130 height: 130
roomid: roomSettings.roomId
url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
width: 130 width: 130
Layout.alignment: Qt.AlignHCenter
onClicked: { onClicked: {
if (roomSettings.canChangeAvatar) if (roomSettings.canChangeAvatar)
roomSettings.updateAvatar(); roomSettings.updateAvatar();
} }
} }
Spinner { Spinner {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
visible: roomSettings.isLoading
foreground: timelineRoot.palette.mid foreground: timelineRoot.palette.mid
running: roomSettings.isLoading running: roomSettings.isLoading
visible: roomSettings.isLoading
} }
Text { Text {
id: errorText id: errorText
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: "red" color: "red"
visible: opacity > 0
opacity: 0 opacity: 0
Layout.alignment: Qt.AlignHCenter visible: opacity > 0
wrapMode: Text.Wrap // somehow still doesn't wrap wrapMode: Text.Wrap // somehow still doesn't wrap
Layout.fillWidth: true
} }
SequentialAnimation { SequentialAnimation {
id: hideErrorAnimation id: hideErrorAnimation
running: false running: false
PauseAnimation { PauseAnimation {
duration: 4000 duration: 4000
} }
NumberAnimation { NumberAnimation {
target: errorText duration: 1000
property: 'opacity' property: 'opacity'
target: errorText
to: 0 to: 0
duration: 1000
} }
} }
Connections { Connections {
target: roomSettings
function onDisplayError(errorMessage) { function onDisplayError(errorMessage) {
errorText.text = errorMessage; errorText.text = errorMessage;
errorText.opacity = 1; errorText.opacity = 1;
hideErrorAnimation.restart(); hideErrorAnimation.restart();
} }
}
target: roomSettings
}
TextEdit { TextEdit {
id: roomName id: roomName
property bool isNameEditingAllowed: false property bool isNameEditingAllowed: false
readOnly: !isNameEditingAllowed
textFormat: isNameEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
text: isNameEditingAllowed ? roomSettings.plainRoomName : roomSettings.roomName
font.pixelSize: fontMetrics.font.pixelSize * 2
color: timelineRoot.palette.text
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - nameChangeButton.anchors.leftMargin - (nameChangeButton.width * 2) Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - nameChangeButton.anchors.leftMargin - (nameChangeButton.width * 2)
color: timelineRoot.palette.text
font.pixelSize: fontMetrics.font.pixelSize * 2
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap readOnly: !isNameEditingAllowed
selectByMouse: true 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: { Keys.onPressed: {
if (event.matches(StandardKey.InsertLineSeparator) || event.matches(StandardKey.InsertParagraphSeparator)) { if (event.matches(StandardKey.InsertLineSeparator) || event.matches(StandardKey.InsertParagraphSeparator)) {
roomSettings.changeName(roomName.text); roomSettings.changeName(roomName.text);
@ -131,18 +126,20 @@ ApplicationWindow {
event.accepted = true; event.accepted = true;
} }
} }
Keys.onShortcutOverride: event.key === Qt.Key_Enter
ImageButton { ImageButton {
id: nameChangeButton id: nameChangeButton
visible: roomSettings.canChangeName ToolTip.delay: Nheko.tooltipDelay
anchors.leftMargin: Nheko.paddingSmall ToolTip.text: qsTr("Change name of this room")
ToolTip.visible: hovered
anchors.left: roomName.right anchors.left: roomName.right
anchors.leftMargin: Nheko.paddingSmall
anchors.verticalCenter: roomName.verticalCenter anchors.verticalCenter: roomName.verticalCenter
hoverEnabled: true 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" image: roomName.isNameEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
visible: roomSettings.canChangeName
onClicked: { onClicked: {
if (roomName.isNameEditingAllowed) { if (roomName.isNameEditingAllowed) {
roomSettings.changeName(roomName.text); roomSettings.changeName(roomName.text);
@ -154,65 +151,60 @@ ApplicationWindow {
} }
} }
} }
} }
Label { Label {
text: qsTr("%n member(s)", "", roomSettings.memberCount)
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("%n member(s)", "", roomSettings.memberCount)
TapHandler { TapHandler {
onSingleTapped: TimelineManager.openRoomMembers(Rooms.getRoomById(roomSettings.roomId)) onSingleTapped: TimelineManager.openRoomMembers(Rooms.getRoomById(roomSettings.roomId))
} }
NhekoCursorShape { NhekoCursorShape {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor
} }
} }
TextArea { TextArea {
id: roomTopic id: roomTopic
property bool cut: implicitHeight > 100 property bool cut: implicitHeight > 100
property bool isTopicEditingAllowed: false
property bool showMore: false property bool showMore: false
clip: true
Layout.maximumHeight: showMore? Number.POSITIVE_INFINITY : 100
Layout.preferredHeight: implicitHeight
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: Nheko.paddingLarge Layout.leftMargin: Nheko.paddingLarge
Layout.maximumHeight: showMore ? Number.POSITIVE_INFINITY : 100
Layout.preferredHeight: implicitHeight
Layout.rightMargin: Nheko.paddingLarge Layout.rightMargin: Nheko.paddingLarge
property bool isTopicEditingAllowed: false
readOnly: !isTopicEditingAllowed
textFormat: isTopicEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
text: isTopicEditingAllowed ? roomSettings.plainRoomTopic : roomSettings.roomTopic
wrapMode: TextEdit.WordWrap
background: null background: null
selectByMouse: !Settings.mobileMode clip: true
color: timelineRoot.palette.text color: timelineRoot.palette.text
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
readOnly: !isTopicEditingAllowed
selectByMouse: !Settings.mobileMode
text: isTopicEditingAllowed ? roomSettings.plainRoomTopic : roomSettings.roomTopic
textFormat: isTopicEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
wrapMode: TextEdit.WordWrap
onLinkActivated: Nheko.openLink(link) onLinkActivated: Nheko.openLink(link)
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
} }
ImageButton { ImageButton {
id: topicChangeButton id: topicChangeButton
Layout.alignment: Qt.AlignHCenter 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.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" image: roomTopic.isTopicEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
visible: roomSettings.canChangeTopic
onClicked: { onClicked: {
if (roomTopic.isTopicEditingAllowed) { if (roomTopic.isTopicEditingAllowed) {
roomSettings.changeTopic(roomTopic.text); roomSettings.changeTopic(roomTopic.text);
@ -225,223 +217,207 @@ ApplicationWindow {
} }
} }
} }
Item { Item {
Layout.alignment: Qt.AlignHCenter
id: showMorePlaceholder id: showMorePlaceholder
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: showMoreButton.height Layout.preferredHeight: showMoreButton.height
Layout.preferredWidth: showMoreButton.width Layout.preferredWidth: showMoreButton.width
visible: roomTopic.cut visible: roomTopic.cut
} }
GridLayout { GridLayout {
Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium
columns: 2 columns: 2
rowSpacing: Nheko.paddingMedium rowSpacing: Nheko.paddingMedium
Layout.margins: Nheko.paddingMedium
Layout.fillWidth: true
Label { Label {
text: qsTr("SETTINGS")
font.bold: true
color: timelineRoot.palette.text color: timelineRoot.palette.text
font.bold: true
text: qsTr("SETTINGS")
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Label { Label {
text: qsTr("Notifications")
Layout.fillWidth: true Layout.fillWidth: true
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Notifications")
} }
ComboBox { ComboBox {
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")] Layout.fillWidth: true
currentIndex: roomSettings.notifications currentIndex: roomSettings.notifications
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")]
onActivated: { onActivated: {
roomSettings.changeNotifications(index); roomSettings.changeNotifications(index);
} }
Layout.fillWidth: true
WheelHandler{} // suppress scrolling changing values
}
WheelHandler {
} // suppress scrolling changing values
}
Label { Label {
text: qsTr("Room access")
Layout.fillWidth: true Layout.fillWidth: true
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Room access")
} }
ComboBox { ComboBox {
Layout.fillWidth: true
currentIndex: roomSettings.accessJoinRules
enabled: roomSettings.canChangeJoinRules enabled: roomSettings.canChangeJoinRules
model: { model: {
let opts = [qsTr("Anyone and guests"), qsTr("Anyone"), qsTr("Invited users")]; let opts = [qsTr("Anyone and guests"), qsTr("Anyone"), qsTr("Invited users")];
if (roomSettings.supportsKnocking) if (roomSettings.supportsKnocking)
opts.push(qsTr("By knocking")); opts.push(qsTr("By knocking"));
if (roomSettings.supportsRestricted) if (roomSettings.supportsRestricted)
opts.push(qsTr("Restricted by membership in other rooms")); opts.push(qsTr("Restricted by membership in other rooms"));
return opts; return opts;
} }
currentIndex: roomSettings.accessJoinRules
onActivated: { onActivated: {
roomSettings.changeAccessRules(index); roomSettings.changeAccessRules(index);
} }
Layout.fillWidth: true
WheelHandler{} // suppress scrolling changing values
}
WheelHandler {
} // suppress scrolling changing values
}
Label { Label {
text: qsTr("Encryption")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Encryption")
} }
ToggleButton { ToggleButton {
id: encryptionToggle id: encryptionToggle
Layout.alignment: Qt.AlignRight
checked: roomSettings.isEncryptionEnabled checked: roomSettings.isEncryptionEnabled
onCheckedChanged: { onCheckedChanged: {
if (roomSettings.isEncryptionEnabled) { if (roomSettings.isEncryptionEnabled) {
checked = true; checked = true;
return ; return;
} }
confirmEncryptionDialog.open(); confirmEncryptionDialog.open();
} }
Layout.alignment: Qt.AlignRight
} }
Platform.MessageDialog { Platform.MessageDialog {
id: confirmEncryptionDialog id: confirmEncryptionDialog
buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel
title: qsTr("End-to-End Encryption") modality: Qt.NonModal
text: qsTr("Encryption is currently experimental and things might break unexpectedly. <br> text: qsTr("Encryption is currently experimental and things might break unexpectedly. <br>
Please take note that it can't be disabled afterwards.") Please take note that it can't be disabled afterwards.")
modality: Qt.NonModal title: qsTr("End-to-End Encryption")
onAccepted: { onAccepted: {
if (roomSettings.isEncryptionEnabled) if (roomSettings.isEncryptionEnabled)
return ; return;
roomSettings.enableEncryption(); roomSettings.enableEncryption();
} }
onRejected: { onRejected: {
encryptionToggle.checked = false; encryptionToggle.checked = false;
} }
buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel
} }
Label { Label {
text: qsTr("Sticker & Emote Settings")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Sticker & Emote Settings")
} }
Button { Button {
text: qsTr("Change") Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("Change what packs are enabled, remove packs or create new ones") ToolTip.text: qsTr("Change what packs are enabled, remove packs or create new ones")
text: qsTr("Change")
onClicked: TimelineManager.openImagePackSettings(roomSettings.roomId) onClicked: TimelineManager.openImagePackSettings(roomSettings.roomId)
Layout.alignment: Qt.AlignRight
} }
Label { Label {
text: qsTr("Hidden events")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Hidden events")
} }
HiddenEventsDialog { HiddenEventsDialog {
id: hiddenEventsDialog id: hiddenEventsDialog
roomid: roomSettings.roomId
roomName: roomSettings.roomName roomName: roomSettings.roomName
roomid: roomSettings.roomId
} }
Button { Button {
text: qsTr("Configure") Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("Select events to hide in this room") ToolTip.text: qsTr("Select events to hide in this room")
text: qsTr("Configure")
onClicked: hiddenEventsDialog.show() onClicked: hiddenEventsDialog.show()
Layout.alignment: Qt.AlignRight
} }
Item { Item {
// for adding extra space between sections // for adding extra space between sections
Layout.fillWidth: true Layout.fillWidth: true
} }
Item { Item {
// for adding extra space between sections // for adding extra space between sections
Layout.fillWidth: true Layout.fillWidth: true
} }
Label { Label {
text: qsTr("INFO")
font.bold: true
color: timelineRoot.palette.text color: timelineRoot.palette.text
font.bold: true
text: qsTr("INFO")
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Label { Label {
text: qsTr("Internal ID")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Internal ID")
} }
AbstractButton {
AbstractButton { // AbstractButton does not allow setting text color // AbstractButton does not allow setting text color
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: idLabel.height 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 id: idLabel
text: roomSettings.roomId ToolTip.text: qsTr("Copied to clipboard")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 0.8) ToolTip.visible: toolTipTimer.running
color: timelineRoot.palette.text color: timelineRoot.palette.text
width: parent.width font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 0.8)
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
text: roomSettings.roomId
width: parent.width
wrapMode: Text.WrapAnywhere 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 id: textEdit
visible: false
text: roomSettings.roomId text: roomSettings.roomId
} visible: false
onClicked: {
textEdit.selectAll()
textEdit.copy()
toolTipTimer.start()
} }
Timer { Timer {
id: toolTipTimer id: toolTipTimer
} }
} }
Label { Label {
text: qsTr("Room Version")
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: qsTr("Room Version")
} }
Label { Label {
text: roomSettings.roomVersion
font.pixelSize: fontMetrics.font.pixelSize
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
color: timelineRoot.palette.text color: timelineRoot.palette.text
font.pixelSize: fontMetrics.font.pixelSize
text: roomSettings.roomVersion
} }
} }
} }
} }
Button { Button {
id: showMoreButton id: showMoreButton
anchors.horizontalCenter: flickable.horizontalCenter 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 visible: roomTopic.cut
text: roomTopic.showMore? qsTr("show less") : qsTr("show more") y: Math.min(showMorePlaceholder.y + contentLayout1.y - flickable.contentY, flickable.height - height)
onClicked: {roomTopic.showMore = !roomTopic.showMore
console.log(flickable.visibleArea) onClicked: {
} roomTopic.showMore = !roomTopic.showMore;
console.log(flickable.visibleArea);
} }
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: close()
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import "../device-verification" import "../device-verification"
import "../ui" import "../ui"
import QtQuick 2.15 import QtQuick 2.15
@ -18,101 +16,232 @@ ApplicationWindow {
property var profile property var profile
color: timelineRoot.palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 650 height: 650
width: 420
minimumWidth: 150
minimumHeight: 150 minimumHeight: 150
minimumWidth: 150
modality: Qt.NonModal
palette: timelineRoot.palette palette: timelineRoot.palette
color: timelineRoot.palette.window
title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile") title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
modality: Qt.NonModal width: 420
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: userProfileDialog.close() onActivated: userProfileDialog.close()
} }
ListView { ListView {
id: devicelist id: devicelist
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
clip: true
spacing: 8
boundsBehavior: Flickable.StopAtBounds
model: profile.deviceList
anchors.fill: parent anchors.fill: parent
anchors.margins: 10 anchors.margins: 10
boundsBehavior: Flickable.StopAtBounds
clip: true
footerPositioning: ListView.OverlayFooter footerPositioning: ListView.OverlayFooter
model: profile.deviceList
spacing: 8
delegate: RowLayout {
required property string deviceId
required property string deviceName
required property string lastIp
required property var lastTs
required property int verificationStatus
header: ColumnLayout { spacing: 4
id: contentL width: devicelist.width
ColumnLayout {
spacing: 0
RowLayout {
Text {
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
color: timelineRoot.palette.text
elide: Text.ElideRight
font.bold: true
text: deviceId
}
Image {
Layout.preferredHeight: 16
Layout.preferredWidth: 16
source: {
switch (verificationStatus) {
case VerificationStatus.VERIFIED:
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?green";
case VerificationStatus.UNVERIFIED:
return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?#d6c020";
case VerificationStatus.SELF:
return "image://colorimage/:/icons/icons/ui/checkmark.svg?green";
default:
return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?#d6c020";
}
}
sourceSize.height: 16 * Screen.devicePixelRatio
sourceSize.width: 16 * Screen.devicePixelRatio
visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
}
ImageButton {
Layout.alignment: Qt.AlignTop
ToolTip.text: qsTr("Sign out this device.")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/power-off.svg"
visible: profile.isSelf
onClicked: profile.signOutDevice(deviceId)
}
}
RowLayout {
id: deviceNameRow
property bool isEditingAllowed
TextInput {
id: deviceNameField
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
color: timelineRoot.palette.text
readOnly: !deviceNameRow.isEditingAllowed
selectByMouse: true
text: deviceName
onAccepted: {
profile.changeDeviceName(deviceId, deviceNameField.text);
deviceNameRow.isEditingAllowed = false;
}
}
ImageButton {
ToolTip.text: qsTr("Change device name.")
ToolTip.visible: hovered
hoverEnabled: true
image: deviceNameRow.isEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
visible: profile.isSelf
onClicked: {
if (deviceNameRow.isEditingAllowed) {
profile.changeDeviceName(deviceId, deviceNameField.text);
deviceNameRow.isEditingAllowed = false;
} else {
deviceNameRow.isEditingAllowed = true;
deviceNameField.focus = true;
deviceNameField.selectAll();
}
}
}
}
Text {
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
color: timelineRoot.palette.text
elide: Text.ElideRight
text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp ? lastIp : "???")
visible: profile.isSelf
}
}
Image {
Layout.preferredHeight: 16
Layout.preferredWidth: 16
source: {
switch (verificationStatus) {
case VerificationStatus.VERIFIED:
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?green";
case VerificationStatus.UNVERIFIED:
return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?#d6c020";
case VerificationStatus.SELF:
return "image://colorimage/:/icons/icons/ui/checkmark.svg?green";
default:
return "image://colorimage/:/icons/icons/ui/shield-filled.svg?red";
}
}
visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
}
Button {
id: verifyButton
text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify")
visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled)
onClicked: {
if (verificationStatus == VerificationStatus.VERIFIED)
profile.unverify(deviceId);
else
profile.verify(deviceId);
}
}
}
footer: DialogButtonBox {
alignment: Qt.AlignRight
standardButtons: DialogButtonBox.Ok
width: devicelist.width width: devicelist.width
z: 2
background: Rectangle {
anchors.fill: parent
color: timelineRoot.palette.window
}
onAccepted: userProfileDialog.close()
}
header: ColumnLayout {
id: contentL
spacing: 10 spacing: 10
width: devicelist.width
Avatar { Avatar {
id: displayAvatar id: displayAvatar
Layout.alignment: Qt.AlignHCenter
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
height: 130
width: 130
displayName: profile.displayName displayName: profile.displayName
height: 130
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: profile.userid userid: profile.userid
Layout.alignment: Qt.AlignHCenter width: 130
onClicked: TimelineManager.openImageOverlay(null, profile.avatarUrl, "") onClicked: TimelineManager.openImageOverlay(null, profile.avatarUrl, "")
ImageButton { ImageButton {
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change avatar globally.") : qsTr("Change avatar. Will only apply to this room.") 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.left: displayAvatar.left
anchors.top: displayAvatar.top
anchors.leftMargin: Nheko.paddingMedium anchors.leftMargin: Nheko.paddingMedium
anchors.top: displayAvatar.top
anchors.topMargin: Nheko.paddingMedium anchors.topMargin: Nheko.paddingMedium
visible: profile.isSelf hoverEnabled: true
image: ":/icons/icons/ui/edit.svg" image: ":/icons/icons/ui/edit.svg"
visible: profile.isSelf
onClicked: profile.changeAvatar() onClicked: profile.changeAvatar()
} }
} }
Spinner { Spinner {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
foreground: timelineRoot.palette.mid
running: profile.isLoading running: profile.isLoading
visible: profile.isLoading visible: profile.isLoading
foreground: timelineRoot.palette.mid
} }
Text { Text {
id: errorText id: errorText
Layout.alignment: Qt.AlignHCenter
color: "red" color: "red"
visible: opacity > 0
opacity: 0 opacity: 0
Layout.alignment: Qt.AlignHCenter visible: opacity > 0
} }
SequentialAnimation { SequentialAnimation {
id: hideErrorAnimation id: hideErrorAnimation
running: false running: false
PauseAnimation { PauseAnimation {
duration: 4000 duration: 4000
} }
NumberAnimation { NumberAnimation {
target: errorText duration: 1000
property: 'opacity' property: 'opacity'
target: errorText
to: 0 to: 0
duration: 1000
} }
} }
Connections { Connections {
function onDisplayError(errorMessage) { function onDisplayError(errorMessage) {
errorText.text = errorMessage; errorText.text = errorMessage;
@ -122,22 +251,22 @@ ApplicationWindow {
target: profile target: profile
} }
TextInput { TextInput {
id: displayUsername id: displayUsername
property bool isUsernameEditingAllowed property bool isUsernameEditingAllowed
readOnly: !isUsernameEditingAllowed
text: profile.displayName
font.pixelSize: 20
color: TimelineManager.userColor(profile.userid, timelineRoot.palette.window)
font.bold: true
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - usernameChangeButton.anchors.leftMargin - (usernameChangeButton.width * 2) Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - usernameChangeButton.anchors.leftMargin - (usernameChangeButton.width * 2)
color: TimelineManager.userColor(profile.userid, timelineRoot.palette.window)
font.bold: true
font.pixelSize: 20
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignHCenter
wrapMode: TextInput.Wrap readOnly: !isUsernameEditingAllowed
selectByMouse: true selectByMouse: true
text: profile.displayName
wrapMode: TextInput.Wrap
onAccepted: { onAccepted: {
profile.changeUsername(displayUsername.text); profile.changeUsername(displayUsername.text);
displayUsername.isUsernameEditingAllowed = false; displayUsername.isUsernameEditingAllowed = false;
@ -145,14 +274,15 @@ ApplicationWindow {
ImageButton { ImageButton {
id: usernameChangeButton id: usernameChangeButton
visible: profile.isSelf ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change display name globally.") : qsTr("Change display name. Will only apply to this room.")
anchors.leftMargin: Nheko.paddingSmall ToolTip.visible: hovered
anchors.left: displayUsername.right anchors.left: displayUsername.right
anchors.leftMargin: Nheko.paddingSmall
anchors.verticalCenter: displayUsername.verticalCenter anchors.verticalCenter: displayUsername.verticalCenter
hoverEnabled: true 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" image: displayUsername.isUsernameEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
visible: profile.isSelf
onClicked: { onClicked: {
if (displayUsername.isUsernameEditingAllowed) { if (displayUsername.isUsernameEditingAllowed) {
profile.changeUsername(displayUsername.text); profile.changeUsername(displayUsername.text);
@ -164,63 +294,54 @@ ApplicationWindow {
} }
} }
} }
} }
MatrixText { MatrixText {
text: profile.userid
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: profile.userid
} }
RowLayout { RowLayout {
visible: !profile.isGlobalUserProfile
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
visible: !profile.isGlobalUserProfile
MatrixText { MatrixText {
id: displayRoomname id: displayRoomname
Layout.maximumWidth: parent.parent.width - (parent.spacing * 3) - 16
text: qsTr("Room: %1").arg(profile.room ? profile.room.roomName : "")
ToolTip.text: qsTr("This is a room-specific profile. The user's name and avatar may be different from their global versions.") 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 ToolTip.visible: ma.hovered
Layout.maximumWidth: parent.parent.width - (parent.spacing * 3) - 16
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
text: qsTr("Room: %1").arg(profile.room ? profile.room.roomName : "")
HoverHandler { HoverHandler {
id: ma id: ma
} }
} }
ImageButton { ImageButton {
image: ":/icons/icons/ui/world.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Open the global profile for this user.") ToolTip.text: qsTr("Open the global profile for this user.")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/world.svg"
onClicked: profile.openGlobalProfile() onClicked: profile.openGlobalProfile()
} }
} }
Button { Button {
id: verifyUserButton id: verifyUserButton
text: qsTr("Verify")
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
enabled: profile.userVerified != Crypto.Verified enabled: profile.userVerified != Crypto.Verified
text: qsTr("Verify")
visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled
onClicked: profile.verify() onClicked: profile.verify()
} }
EncryptionIndicator { EncryptionIndicator {
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16
ToolTip.visible: false
encrypted: profile.userVerificationEnabled encrypted: profile.userVerificationEnabled
trust: profile.userVerified trust: profile.userVerified
Layout.alignment: Qt.AlignHCenter
ToolTip.visible: false
} }
RowLayout { RowLayout {
// ImageButton{ // ImageButton{
// image:":/icons/icons/ui/volume-off-indicator.svg" // image:":/icons/icons/ui/volume-off-indicator.svg"
@ -234,202 +355,45 @@ ApplicationWindow {
// profile.ignoreUser() // profile.ignoreUser()
// } // }
// } // }
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 10 Layout.bottomMargin: 10
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
ImageButton { ImageButton {
image: ":/icons/icons/ui/chat.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Start a private chat.") ToolTip.text: qsTr("Start a private chat.")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/chat.svg"
onClicked: profile.startChat() onClicked: profile.startChat()
} }
ImageButton { ImageButton {
image: ":/icons/icons/ui/round-remove-button.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Kick the user.") ToolTip.text: qsTr("Kick the user.")
onClicked: profile.kickUser()
visible: !profile.isGlobalUserProfile && profile.room.permissions.canKick()
}
ImageButton {
image: ":/icons/icons/ui/ban.svg"
hoverEnabled: true
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Ban the user.")
onClicked: profile.banUser()
visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan()
}
ImageButton {
image: ":/icons/icons/ui/refresh.svg"
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered image: ":/icons/icons/ui/round-remove-button.svg"
ToolTip.text: qsTr("Refresh device list.") visible: !profile.isGlobalUserProfile && profile.room.permissions.canKick()
onClicked: profile.refreshDevices()
}
}
}
delegate: RowLayout {
required property int verificationStatus
required property string deviceId
required property string deviceName
required property string lastIp
required property var lastTs
width: devicelist.width
spacing: 4
ColumnLayout {
spacing: 0
RowLayout {
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
elide: Text.ElideRight
font.bold: true
color: timelineRoot.palette.text
text: deviceId
}
Image { onClicked: profile.kickUser()
Layout.preferredHeight: 16
Layout.preferredWidth: 16
visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
sourceSize.height: 16 * Screen.devicePixelRatio
sourceSize.width: 16 * Screen.devicePixelRatio
source: {
switch (verificationStatus) {
case VerificationStatus.VERIFIED:
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?green";
case VerificationStatus.UNVERIFIED:
return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?#d6c020";
case VerificationStatus.SELF:
return "image://colorimage/:/icons/icons/ui/checkmark.svg?green";
default:
return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?#d6c020";
}
}
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignTop ToolTip.text: qsTr("Ban the user.")
image: ":/icons/icons/ui/power-off.svg"
hoverEnabled: true
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Sign out this device.") hoverEnabled: true
onClicked: profile.signOutDevice(deviceId) image: ":/icons/icons/ui/ban.svg"
visible: profile.isSelf visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan()
}
}
RowLayout {
id: deviceNameRow
property bool isEditingAllowed
TextInput {
id: deviceNameField
readOnly: !deviceNameRow.isEditingAllowed onClicked: profile.banUser()
text: deviceName
color: timelineRoot.palette.text
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
selectByMouse: true
onAccepted: {
profile.changeDeviceName(deviceId, deviceNameField.text);
deviceNameRow.isEditingAllowed = false;
}
} }
ImageButton { ImageButton {
visible: profile.isSelf ToolTip.text: qsTr("Refresh device list.")
hoverEnabled: true
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Change device name.") hoverEnabled: true
image: deviceNameRow.isEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg" image: ":/icons/icons/ui/refresh.svg"
onClicked: {
if (deviceNameRow.isEditingAllowed) {
profile.changeDeviceName(deviceId, deviceNameField.text);
deviceNameRow.isEditingAllowed = false;
} else {
deviceNameRow.isEditingAllowed = true;
deviceNameField.focus = true;
deviceNameField.selectAll();
}
}
}
}
Text {
visible: profile.isSelf
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
elide: Text.ElideRight
color: timelineRoot.palette.text
text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp ? lastIp : "???")
}
}
Image {
Layout.preferredHeight: 16
Layout.preferredWidth: 16
visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
source: {
switch (verificationStatus) {
case VerificationStatus.VERIFIED:
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?green";
case VerificationStatus.UNVERIFIED:
return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?#d6c020";
case VerificationStatus.SELF:
return "image://colorimage/:/icons/icons/ui/checkmark.svg?green";
default:
return "image://colorimage/:/icons/icons/ui/shield-filled.svg?red";
}
}
}
Button {
id: verifyButton
visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled)
text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify")
onClicked: {
if (verificationStatus == VerificationStatus.VERIFIED)
profile.unverify(deviceId);
else
profile.verify(deviceId);
}
}
onClicked: profile.refreshDevices()
} }
footer: DialogButtonBox {
z: 2
width: devicelist.width
alignment: Qt.AlignRight
standardButtons: DialogButtonBox.Ok
onAccepted: userProfileDialog.close()
background: Rectangle {
anchors.fill: parent
color: timelineRoot.palette.window
} }
} }
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -15,12 +13,12 @@ Menu {
property var callback property var callback
property var colors property var colors
property alias model: gridView.model
property var textArea
property string emojiCategory: "people" property string emojiCategory: "people"
property real highlightHue: timelineRoot.palette.highlight.hslHue property real highlightHue: timelineRoot.palette.highlight.hslHue
property real highlightSat: timelineRoot.palette.highlight.hslSaturation
property real highlightLight: timelineRoot.palette.highlight.hslLightness property real highlightLight: timelineRoot.palette.highlight.hslLightness
property real highlightSat: timelineRoot.palette.highlight.hslSaturation
property alias model: gridView.model
property var textArea
function show(showAt, callback) { function show(showAt, callback) {
console.debug("Showing emojiPicker"); console.debug("Showing emojiPicker");
@ -28,13 +26,13 @@ Menu {
popup(showAt ? showAt : null); popup(showAt ? showAt : null);
} }
margins: 0
bottomPadding: 1 bottomPadding: 1
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
focus: true
leftPadding: 1 leftPadding: 1
rightPadding: 1 margins: 0
modal: true modal: true
focus: true rightPadding: 1
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
//height: columnView.implicitHeight + 4 //height: columnView.implicitHeight + 4
//width: columnView.implicitWidth //width: columnView.implicitWidth
width: 7 * 52 + 20 width: 7 * 52 + 20
@ -46,28 +44,27 @@ Menu {
ColumnLayout { ColumnLayout {
id: columnView id: columnView
spacing: 0
anchors.leftMargin: 3
anchors.rightMargin: 3
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 3
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 3
anchors.topMargin: 2 anchors.topMargin: 2
spacing: 0
// Search field // Search field
TextField { TextField {
id: emojiSearch id: emojiSearch
Layout.topMargin: 3
Layout.preferredWidth: 7 * 52 + 20 - 6 Layout.preferredWidth: 7 * 52 + 20 - 6
palette: timelineRoot.palette Layout.topMargin: 3
background: null background: null
placeholderTextColor: timelineRoot.palette.placeholderText
color: timelineRoot.palette.text color: timelineRoot.palette.text
palette: timelineRoot.palette
placeholderText: qsTr("Search") placeholderText: qsTr("Search")
selectByMouse: true placeholderTextColor: timelineRoot.palette.placeholderText
rightPadding: clearSearch.width rightPadding: clearSearch.width
selectByMouse: true
onTextChanged: searchTimer.restart() onTextChanged: searchTimer.restart()
onVisibleChanged: { onVisibleChanged: {
if (visible) if (visible)
@ -78,74 +75,71 @@ Menu {
Timer { Timer {
id: searchTimer id: searchTimer
interval: 350 // tweak as needed? interval: 350 // tweak as needed?
onTriggered: { onTriggered: {
emojiPopup.model.searchString = emojiSearch.text; emojiPopup.model.searchString = emojiSearch.text;
emojiPopup.model.category = Emoji.Category.Search; emojiPopup.model.category = Emoji.Category.Search;
} }
} }
ToolButton { ToolButton {
id: clearSearch id: clearSearch
background: null
visible: emojiSearch.text !== ''
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText)
focusPolicy: Qt.NoFocus focusPolicy: Qt.NoFocus
onClicked: emojiSearch.clear()
hoverEnabled: true hoverEnabled: true
background: null icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText)
visible: emojiSearch.text !== ''
onClicked: emojiSearch.clear()
anchors { anchors {
verticalCenter: parent.verticalCenter
right: parent.right right: parent.right
verticalCenter: parent.verticalCenter
} }
// clear the default hover effects. // clear the default hover effects.
Image { Image {
height: parent.height - 2 * Nheko.paddingSmall height: parent.height - 2 * Nheko.paddingSmall
width: height
source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText)
width: height
anchors { anchors {
verticalCenter: parent.verticalCenter
right: parent.right
margins: Nheko.paddingSmall margins: Nheko.paddingSmall
right: parent.right
verticalCenter: parent.verticalCenter
} }
} }
} }
} }
// emoji grid // emoji grid
GridView { GridView {
id: gridView id: gridView
Layout.leftMargin: 4
Layout.preferredHeight: cellHeight * 5 Layout.preferredHeight: cellHeight * 5
Layout.preferredWidth: 7 * 52 + 20 Layout.preferredWidth: 7 * 52 + 20
Layout.leftMargin: 4
cellWidth: 52
cellHeight: 52
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
cacheBuffer: 500
cellHeight: 52
cellWidth: 52
clip: true clip: true
currentIndex: -1 // prevent sorting from stealing focus currentIndex: -1 // prevent sorting from stealing focus
cacheBuffer: 500
ScrollBar.vertical: ScrollBar {
id: emojiScroll
}
// Individual emoji // Individual emoji
delegate: AbstractButton { delegate: AbstractButton {
width: 48
height: 48
hoverEnabled: true
ToolTip.text: model.shortName ToolTip.text: model.shortName
ToolTip.visible: hovered ToolTip.visible: hovered
// TODO: maybe add favorites at some point? height: 48
onClicked: { hoverEnabled: true
console.debug("Picked " + model.unicode); width: 48
emojiPopup.close();
callback(model.unicode); background: Rectangle {
anchors.fill: parent
color: hovered ? timelineRoot.palette.highlight : 'transparent'
radius: 5
} }
// give the emoji a little oomf // give the emoji a little oomf
@ -159,97 +153,73 @@ Menu {
// color: "#80000000" // color: "#80000000"
// source: parent.contentItem // source: parent.contentItem
// } // }
contentItem: Text { contentItem: Text {
horizontalAlignment: Text.AlignHCenter color: timelineRoot.palette.text
verticalAlignment: Text.AlignVCenter
font.family: Settings.emojiFont font.family: Settings.emojiFont
font.pixelSize: 36 font.pixelSize: 36
horizontalAlignment: Text.AlignHCenter
text: model.unicode.replace('\ufe0f', '') text: model.unicode.replace('\ufe0f', '')
color: timelineRoot.palette.text verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
anchors.fill: parent
color: hovered ? timelineRoot.palette.highlight : 'transparent'
radius: 5
} }
// TODO: maybe add favorites at some point?
onClicked: {
console.debug("Picked " + model.unicode);
emojiPopup.close();
callback(model.unicode);
} }
ScrollBar.vertical: ScrollBar {
id: emojiScroll
} }
} }
// Separator // Separator
Rectangle { Rectangle {
visible: emojiSearch.text === ''
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 1 Layout.preferredHeight: 1
color: emojiPopup.Nheko.theme.separator color: emojiPopup.Nheko.theme.separator
visible: emojiSearch.text === ''
} }
// Category picker row // Category picker row
RowLayout { RowLayout {
visible: emojiSearch.text === '' Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
Layout.bottomMargin: 0 Layout.bottomMargin: 0
Layout.preferredHeight: 42 Layout.preferredHeight: 42
implicitHeight: 42 implicitHeight: 42
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom visible: emojiSearch.text === ''
// Display the normal categories // Display the normal categories
Repeater { Repeater {
model: [ model: [
// TODO: Would like to get 'simple' icons for the categories // TODO: Would like to get 'simple' icons for the categories
{ {
image: ":/icons/icons/emoji-categories/people.svg", "image": ":/icons/icons/emoji-categories/people.svg",
category: Emoji.Category.People "category": Emoji.Category.People
}, }, {
"image": ":/icons/icons/emoji-categories/nature.svg",
{ "category": Emoji.Category.Nature
image: ":/icons/icons/emoji-categories/nature.svg", }, {
category: Emoji.Category.Nature "image": ":/icons/icons/emoji-categories/foods.svg",
}, "category": Emoji.Category.Food
}, {
{ "image": ":/icons/icons/emoji-categories/activity.svg",
image: ":/icons/icons/emoji-categories/foods.svg", "category": Emoji.Category.Activity
category: Emoji.Category.Food }, {
}, "image": ":/icons/icons/emoji-categories/travel.svg",
"category": Emoji.Category.Travel
{ }, {
image: ":/icons/icons/emoji-categories/activity.svg", "image": ":/icons/icons/emoji-categories/objects.svg",
category: Emoji.Category.Activity "category": Emoji.Category.Objects
}, }, {
"image": ":/icons/icons/emoji-categories/symbols.svg",
{ "category": Emoji.Category.Symbols
image: ":/icons/icons/emoji-categories/travel.svg", }, {
category: Emoji.Category.Travel "image": ":/icons/icons/emoji-categories/flags.svg",
}, "category": Emoji.Category.Flags
}]
{
image: ":/icons/icons/emoji-categories/objects.svg",
category: Emoji.Category.Objects
},
{
image: ":/icons/icons/emoji-categories/symbols.svg",
category: Emoji.Category.Symbols
},
{
image: ":/icons/icons/emoji-categories/flags.svg",
category: Emoji.Category.Flags
}
]
delegate: AbstractButton { delegate: AbstractButton {
Layout.preferredWidth: 36
Layout.preferredHeight: 36 Layout.preferredHeight: 36
hoverEnabled: true Layout.preferredWidth: 36
ToolTip.text: { ToolTip.text: {
switch (modelData.category) { switch (modelData.category) {
case Emoji.Category.People: case Emoji.Category.People:
@ -271,47 +241,42 @@ Menu {
} }
} }
ToolTip.visible: hovered ToolTip.visible: hovered
onClicked: { hoverEnabled: true
//emojiPopup.model.category = model.category;
gridView.positionViewAtIndex(emojiPopup.model.sourceModel.categoryToIndex(modelData.category), GridView.Beginning);
}
MouseArea {
id: mouseArea
background: Rectangle {
anchors.fill: parent anchors.fill: parent
onPressed: mouse.accepted = false border.color: emojiPopup.model.category === modelData.category ? timelineRoot.palette.highlight : 'transparent'
cursorShape: Qt.PointingHandCursor color: emojiPopup.model.category === modelData.category ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : 'transparent'
radius: 5
} }
contentItem: Image { contentItem: Image {
horizontalAlignment: Image.AlignHCenter
verticalAlignment: Image.AlignVCenter
fillMode: Image.Pad fillMode: Image.Pad
height: 32 height: 32
width: 32 horizontalAlignment: Image.AlignHCenter
smooth: true
mipmap: true mipmap: true
sourceSize.width: 32 * Screen.devicePixelRatio smooth: true
sourceSize.height: 32 * Screen.devicePixelRatio
source: "image://colorimage/" + modelData.image + "?" + (hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) source: "image://colorimage/" + modelData.image + "?" + (hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText)
sourceSize.height: 32 * Screen.devicePixelRatio
sourceSize.width: 32 * Screen.devicePixelRatio
verticalAlignment: Image.AlignVCenter
width: 32
} }
background: Rectangle { onClicked: {
anchors.fill: parent //emojiPopup.model.category = model.category;
color: emojiPopup.model.category === modelData.category ? Qt.hsla(highlightHue, highlightSat, highlightLight, 0.2) : 'transparent' gridView.positionViewAtIndex(emojiPopup.model.sourceModel.categoryToIndex(modelData.category), GridView.Beginning);
radius: 5
border.color: emojiPopup.model.category === modelData.category ? timelineRoot.palette.highlight : 'transparent'
} }
} MouseArea {
id: mouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: mouse.accepted = false
}
}
} }
} }
} }
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -14,15 +12,15 @@ Menu {
property var callback property var callback
property var colors property var colors
property string roomid
property alias model: gridView.model
property var textArea
property real highlightHue: timelineRoot.palette.highlight.hslHue property real highlightHue: timelineRoot.palette.highlight.hslHue
property real highlightSat: timelineRoot.palette.highlight.hslSaturation
property real highlightLight: timelineRoot.palette.highlight.hslLightness property real highlightLight: timelineRoot.palette.highlight.hslLightness
property real highlightSat: timelineRoot.palette.highlight.hslSaturation
property alias model: gridView.model
property string roomid
readonly property int stickerDim: 128 readonly property int stickerDim: 128
readonly property int stickerDimPad: 128 + Nheko.paddingSmall readonly property int stickerDimPad: 128 + Nheko.paddingSmall
readonly property int stickersPerRow: 3 readonly property int stickersPerRow: 3
property var textArea
function show(showAt, roomid_, callback) { function show(showAt, roomid_, callback) {
console.debug("Showing sticker picker"); console.debug("Showing sticker picker");
@ -31,13 +29,13 @@ Menu {
popup(showAt ? showAt : null); popup(showAt ? showAt : null);
} }
margins: 0
bottomPadding: 1 bottomPadding: 1
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
focus: true
leftPadding: 1 leftPadding: 1
rightPadding: 1 margins: 0
modal: true modal: true
focus: true rightPadding: 1
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
width: stickersPerRow * stickerDimPad + 20 width: stickersPerRow * stickerDimPad + 20
Rectangle { Rectangle {
@ -47,28 +45,27 @@ Menu {
ColumnLayout { ColumnLayout {
id: columnView id: columnView
spacing: 0
anchors.leftMargin: 3
anchors.rightMargin: 3
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 3
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 3
anchors.topMargin: 2 anchors.topMargin: 2
spacing: 0
// Search field // Search field
TextField { TextField {
id: emojiSearch id: emojiSearch
Layout.topMargin: 3
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - 6 Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - 6
palette: timelineRoot.palette Layout.topMargin: 3
background: null background: null
placeholderTextColor: timelineRoot.palette.placeholderText
color: timelineRoot.palette.text color: timelineRoot.palette.text
palette: timelineRoot.palette
placeholderText: qsTr("Search") placeholderText: qsTr("Search")
selectByMouse: true placeholderTextColor: timelineRoot.palette.placeholderText
rightPadding: clearSearch.width rightPadding: clearSearch.width
selectByMouse: true
onTextChanged: searchTimer.restart() onTextChanged: searchTimer.restart()
onVisibleChanged: { onVisibleChanged: {
if (visible) if (visible)
@ -79,97 +76,85 @@ Menu {
Timer { Timer {
id: searchTimer id: searchTimer
interval: 350 // tweak as needed? interval: 350 // tweak as needed?
onTriggered: stickerPopup.model.searchString = emojiSearch.text onTriggered: stickerPopup.model.searchString = emojiSearch.text
} }
ToolButton { ToolButton {
id: clearSearch id: clearSearch
background: null
visible: emojiSearch.text !== ''
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText)
focusPolicy: Qt.NoFocus focusPolicy: Qt.NoFocus
onClicked: emojiSearch.clear()
hoverEnabled: true hoverEnabled: true
background: null icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText)
visible: emojiSearch.text !== ''
onClicked: emojiSearch.clear()
anchors { anchors {
verticalCenter: parent.verticalCenter
right: parent.right right: parent.right
verticalCenter: parent.verticalCenter
} }
// clear the default hover effects. // clear the default hover effects.
Image { Image {
height: parent.height - 2 * Nheko.paddingSmall height: parent.height - 2 * Nheko.paddingSmall
width: height
source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText) source: "image://colorimage/:/icons/icons/ui/round-remove-button.svg?" + (clearSearch.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText)
width: height
anchors { anchors {
verticalCenter: parent.verticalCenter
right: parent.right
margins: Nheko.paddingSmall margins: Nheko.paddingSmall
right: parent.right
verticalCenter: parent.verticalCenter
} }
} }
} }
} }
// emoji grid // emoji grid
GridView { GridView {
id: gridView id: gridView
Layout.leftMargin: 4
model: roomid ? TimelineManager.completerFor("stickers", roomid) : null
Layout.preferredHeight: cellHeight * 3.5 Layout.preferredHeight: cellHeight * 3.5
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 Layout.preferredWidth: stickersPerRow * stickerDimPad + 20
Layout.leftMargin: 4
cellWidth: stickerDimPad
cellHeight: stickerDimPad
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
cacheBuffer: 500
cellHeight: stickerDimPad
cellWidth: stickerDimPad
clip: true clip: true
currentIndex: -1 // prevent sorting from stealing focus currentIndex: -1 // prevent sorting from stealing focus
cacheBuffer: 500 model: roomid ? TimelineManager.completerFor("stickers", roomid) : null
ScrollBar.vertical: ScrollBar {
id: emojiScroll
}
// Individual emoji // Individual emoji
delegate: AbstractButton { delegate: AbstractButton {
width: stickerDim
height: stickerDim
hoverEnabled: true
ToolTip.text: ":" + model.shortcode + ": - " + model.body ToolTip.text: ":" + model.shortcode + ": - " + model.body
ToolTip.visible: hovered ToolTip.visible: hovered
// TODO: maybe add favorites at some point?
onClicked: {
console.debug("Picked " + model.shortcode);
stickerPopup.close();
callback(model.originalRow);
}
contentItem: Image {
height: stickerDim height: stickerDim
hoverEnabled: true
width: stickerDim width: stickerDim
source: model.url.replace("mxc://", "image://MxcImage/") + "?scale"
fillMode: Image.PreserveAspectFit
}
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: hovered ? timelineRoot.palette.highlight : 'transparent' color: hovered ? timelineRoot.palette.highlight : 'transparent'
radius: 5 radius: 5
} }
contentItem: Image {
fillMode: Image.PreserveAspectFit
height: stickerDim
source: model.url.replace("mxc://", "image://MxcImage/") + "?scale"
width: stickerDim
} }
ScrollBar.vertical: ScrollBar { // TODO: maybe add favorites at some point?
id: emojiScroll onClicked: {
console.debug("Picked " + model.shortcode);
stickerPopup.close();
callback(model.originalRow);
}
} }
} }
} }
} }
} }

@ -1,189 +1,172 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.15 import QtQuick.Window 2.15
import im.nheko import im.nheko
import "../components/" import "../components"
import "../ui/" import "../ui"
import "../" import "../"
Item { Item {
id: loginPage id: loginPage
property int maxExpansion: 400
property string error: login.error property string error: login.error
property int maxExpansion: 400
Login { Login {
id: login id: login
} }
ScrollView { ScrollView {
id: scroll id: scroll
clip: false
palette: timelineRoot.palette
ScrollBar.horizontal.visible: false ScrollBar.horizontal.visible: false
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Nheko.paddingLarge
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: Math.min(loginPage.height, col.implicitHeight) clip: false
anchors.margins: Nheko.paddingLarge
contentWidth: availableWidth contentWidth: availableWidth
height: Math.min(loginPage.height, col.implicitHeight)
palette: timelineRoot.palette
ColumnLayout { ColumnLayout {
id: col id: col
spacing: Nheko.paddingMedium
anchors.horizontalCenter: parent.horizontalCenter 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 { Image {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/login.png"
height: 128 height: 128
source: "qrc:/logos/login.png"
width: 128 width: 128
} }
RowLayout { RowLayout {
Layout.fillWidth: true
spacing: Nheko.paddingLarge spacing: Nheko.paddingLarge
Layout.fillWidth: true
MatrixTextField { MatrixTextField {
id: matrixIdLabel id: matrixIdLabel
Keys.forwardTo: [pwBtn, ssoRepeater]
ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :.\nYou can also put your homeserver address there, if your server doesn't support .well-known lookup.\nExample: @user:server.my\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.")
label: qsTr("Matrix ID") label: qsTr("Matrix ID")
placeholderText: qsTr("e.g @joe:matrix.org") placeholderText: qsTr("e.g @joe:matrix.org")
onEditingFinished: login.mxid = text
ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :.\nYou can also put your homeserver address there, if your server doesn't support .well-known lookup.\nExample: @user:server.my\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.") onEditingFinished: login.mxid = text
Keys.forwardTo: [pwBtn, ssoRepeater]
} }
Spinner { Spinner {
height: matrixIdLabel.height/2
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
visible: running
running: login.lookingUpHs
foreground: timelineRoot.palette.mid foreground: timelineRoot.palette.mid
height: matrixIdLabel.height / 2
running: login.lookingUpHs
visible: running
} }
} }
MatrixText { MatrixText {
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: login.mxidError text: login.mxidError
textFormat: Text.PlainText
visible: text visible: text
} }
MatrixTextField { MatrixTextField {
id: passwordLabel id: passwordLabel
Keys.forwardTo: [pwBtn, ssoRepeater]
Layout.fillWidth: true Layout.fillWidth: true
label: qsTr("Password")
echoMode: TextInput.Password
ToolTip.text: qsTr("Your password.") ToolTip.text: qsTr("Your password.")
echoMode: TextInput.Password
label: qsTr("Password")
visible: login.passwordSupported visible: login.passwordSupported
Keys.forwardTo: [pwBtn, ssoRepeater]
} }
MatrixTextField { MatrixTextField {
id: deviceNameLabel id: deviceNameLabel
Keys.forwardTo: [pwBtn, ssoRepeater]
Layout.fillWidth: true Layout.fillWidth: true
ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.")
label: qsTr("Device name") label: qsTr("Device name")
placeholderText: login.initialDeviceName() placeholderText: login.initialDeviceName()
ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.")
Keys.forwardTo: [pwBtn, ssoRepeater]
} }
MatrixTextField { MatrixTextField {
id: hsLabel id: hsLabel
enabled: visible Keys.forwardTo: [pwBtn, ssoRepeater]
visible: login.homeserverNeeded
Layout.fillWidth: true Layout.fillWidth: true
ToolTip.text: qsTr("The address that can be used to contact you homeservers client API.\nExample: https://server.my:8787")
enabled: visible
label: qsTr("Homeserver address") label: qsTr("Homeserver address")
placeholderText: qsTr("server.my:8787") placeholderText: qsTr("server.my:8787")
text: login.homeserver text: login.homeserver
visible: login.homeserverNeeded
onEditingFinished: login.homeserver = text onEditingFinished: login.homeserver = text
ToolTip.text: qsTr("The address that can be used to contact you homeservers client API.\nExample: https://server.my:8787")
Keys.forwardTo: [pwBtn, ssoRepeater]
} }
Item { Item {
height: Nheko.avatarSize
Layout.fillWidth: true Layout.fillWidth: true
height: Nheko.avatarSize
Spinner { Spinner {
height: parent.height
anchors.centerIn: parent anchors.centerIn: parent
visible: running
running: login.loggingIn
foreground: timelineRoot.palette.mid foreground: timelineRoot.palette.mid
height: parent.height
running: login.loggingIn
visible: running
} }
} }
MatrixText { MatrixText {
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: loginPage.error text: loginPage.error
textFormat: Text.PlainText
visible: text visible: text
} }
FlatButton { FlatButton {
id: pwBtn 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() { 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.onEnterPressed: pwBtn.pwLogin()
Keys.onReturnPressed: pwBtn.pwLogin() Keys.onReturnPressed: pwBtn.pwLogin()
Keys.enabled: pwBtn.enabled && login.passwordSupported onClicked: pwBtn.pwLogin()
} }
Repeater { Repeater {
id: ssoRepeater id: ssoRepeater
model: login.identityProviders model: login.identityProviders
delegate: FlatButton { delegate: FlatButton {
id: ssoBtn 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() { 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.onEnterPressed: ssoBtn.ssoLogin()
Keys.onReturnPressed: ssoBtn.ssoLogin() Keys.onReturnPressed: ssoBtn.ssoLogin()
Keys.enabled: ssoBtn.enabled && !login.passwordSupported onClicked: ssoBtn.ssoLogin()
} }
} }
} }
} }
ImageButton { ImageButton {
anchors.top: parent.top ToolTip.text: qsTr("Back")
ToolTip.visible: hovered
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
width: Nheko.avatarSize anchors.top: parent.top
height: Nheko.avatarSize height: Nheko.avatarSize
image: ":/icons/icons/ui/angle-arrow-left.svg" image: ":/icons/icons/ui/angle-arrow-left.svg"
ToolTip.visible: hovered width: Nheko.avatarSize
ToolTip.text: qsTr("Back")
onClicked: mainWindow.pop() onClicked: mainWindow.pop()
} }
} }

@ -1,215 +1,192 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.15 import QtQuick.Window 2.15
import im.nheko import im.nheko
import "../components/" import "../components"
import "../ui/" import "../ui"
import "../" import "../"
Item { Item {
id: registrationPage id: registrationPage
property int maxExpansion: 400
property string error: regis.error property string error: regis.error
property int maxExpansion: 400
Registration { Registration {
id: regis id: regis
} }
ScrollView { ScrollView {
id: scroll id: scroll
clip: false
palette: timelineRoot.palette
ScrollBar.horizontal.visible: false ScrollBar.horizontal.visible: false
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Nheko.paddingLarge
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: Math.min(registrationPage.height, col.implicitHeight) clip: false
anchors.margins: Nheko.paddingLarge
contentWidth: availableWidth contentWidth: availableWidth
height: Math.min(registrationPage.height, col.implicitHeight)
palette: timelineRoot.palette
ColumnLayout { ColumnLayout {
id: col id: col
spacing: Nheko.paddingMedium
anchors.horizontalCenter: parent.horizontalCenter 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 { Image {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/login.png"
height: 128 height: 128
source: "qrc:/logos/login.png"
width: 128 width: 128
} }
RowLayout { RowLayout {
Layout.fillWidth: true
spacing: Nheko.paddingLarge spacing: Nheko.paddingLarge
Layout.fillWidth: true
MatrixTextField { MatrixTextField {
id: hsLabel 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") label: qsTr("Homeserver")
placeholderText: qsTr("your.server") 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 { Spinner {
height: hsLabel.height/2
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
visible: running
running: regis.lookingUpHs
foreground: timelineRoot.palette.mid foreground: timelineRoot.palette.mid
height: hsLabel.height / 2
running: regis.lookingUpHs
visible: running
} }
} }
MatrixText { MatrixText {
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: regis.hsError text: regis.hsError
textFormat: Text.PlainText
visible: text visible: text
} }
RowLayout { RowLayout {
Layout.fillWidth: true
spacing: Nheko.paddingLarge spacing: Nheko.paddingLarge
visible: regis.supported visible: regis.supported
Layout.fillWidth: true
MatrixTextField { MatrixTextField {
id: usernameLabel id: usernameLabel
Layout.fillWidth: true 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 /.") 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) onEditingFinished: regis.checkUsername(text)
} }
Spinner { Spinner {
height: usernameLabel.height/2
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
visible: running
running: regis.lookingUpUsername
foreground: timelineRoot.palette.mid foreground: timelineRoot.palette.mid
height: usernameLabel.height / 2
running: regis.lookingUpUsername
visible: running
} }
Image { Image {
width: usernameLabel.height/2
height: width
Layout.preferredHeight: usernameLabel.height/2
Layout.preferredWidth: usernameLabel.height/2
Layout.alignment: Qt.AlignBottom 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) Layout.preferredHeight: usernameLabel.height / 2
visible: regis.usernameAvailable || regis.usernameUnavailable Layout.preferredWidth: usernameLabel.height / 2
ToolTip.visible: ma.hovered
ToolTip.text: qsTr("Back") ToolTip.text: qsTr("Back")
ToolTip.visible: ma.hovered
height: width
source: regis.usernameAvailable ? ("image://colorimage/:/icons/icons/ui/checkmark.svg?green") : ("image://colorimage/:/icons/icons/ui/dismiss.svg?" + Nheko.theme.error)
sourceSize.height: height * Screen.devicePixelRatio sourceSize.height: height * Screen.devicePixelRatio
sourceSize.width: width * Screen.devicePixelRatio sourceSize.width: width * Screen.devicePixelRatio
visible: regis.usernameAvailable || regis.usernameUnavailable
width: usernameLabel.height / 2
HoverHandler { HoverHandler {
id: ma id: ma
} }
} }
} }
MatrixText { MatrixText {
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: regis.usernameError text: regis.usernameError
textFormat: Text.PlainText
visible: text visible: text
} }
MatrixTextField { MatrixTextField {
visible: regis.supported
id: passwordLabel id: passwordLabel
Layout.fillWidth: true 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.") 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 { MatrixTextField {
visible: regis.supported
id: passwordConfirmationLabel id: passwordConfirmationLabel
Layout.fillWidth: true Layout.fillWidth: true
label: qsTr("Password confirmation")
echoMode: TextInput.Password echoMode: TextInput.Password
label: qsTr("Password confirmation")
visible: regis.supported
} }
MatrixText { MatrixText {
visible: regis.supported
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: passwordLabel.text != passwordConfirmationLabel.text ? qsTr("Your passwords do not match!") : "" text: passwordLabel.text != passwordConfirmationLabel.text ? qsTr("Your passwords do not match!") : ""
textFormat: Text.PlainText
visible: regis.supported
} }
MatrixTextField { MatrixTextField {
visible: regis.supported
id: deviceNameLabel id: deviceNameLabel
Layout.fillWidth: true Layout.fillWidth: true
ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.")
label: qsTr("Device name") label: qsTr("Device name")
placeholderText: regis.initialDeviceName() placeholderText: regis.initialDeviceName()
ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.") visible: regis.supported
} }
Item { Item {
height: Nheko.avatarSize
Layout.fillWidth: true Layout.fillWidth: true
height: Nheko.avatarSize
Spinner { Spinner {
height: parent.height
anchors.centerIn: parent anchors.centerIn: parent
visible: running
running: regis.registering
foreground: timelineRoot.palette.mid foreground: timelineRoot.palette.mid
height: parent.height
running: regis.registering
visible: running
} }
} }
MatrixText { MatrixText {
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: registrationPage.error text: registrationPage.error
textFormat: Text.PlainText
visible: text visible: text
} }
FlatButton { FlatButton {
id: regisBtn id: regisBtn
visible: regis.supported
enabled: usernameLabel.text && passwordLabel.text && passwordLabel.text == passwordConfirmationLabel.text
Layout.alignment: Qt.AlignHCenter
text: qsTr("REGISTER")
function 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.onEnterPressed: regisBtn.register()
Keys.onReturnPressed: regisBtn.register() Keys.onReturnPressed: regisBtn.register()
Keys.enabled: regisBtn.enabled && regis.supported onClicked: regisBtn.register()
} }
} }
} }
ImageButton { ImageButton {
anchors.top: parent.top ToolTip.text: qsTr("Back")
ToolTip.visible: hovered
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
width: Nheko.avatarSize anchors.top: parent.top
height: Nheko.avatarSize height: Nheko.avatarSize
image: ":/icons/icons/ui/angle-arrow-left.svg" image: ":/icons/icons/ui/angle-arrow-left.svg"
ToolTip.visible: hovered width: Nheko.avatarSize
ToolTip.text: qsTr("Back")
onClicked: mainWindow.pop() onClicked: mainWindow.pop()
} }
} }

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import ".."
import "../ui" import "../ui"
import Qt.labs.platform 1.1 as Platform import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15 import QtQuick 2.15
@ -18,92 +16,94 @@ Rectangle {
property int collapsePoint: 800 property int collapsePoint: 800
property bool collapsed: width < collapsePoint property bool collapsed: width < collapsePoint
color: timelineRoot.palette.window color: timelineRoot.palette.window
ScrollView { ScrollView {
id: scroll id: scroll
palette: timelineRoot.palette
ScrollBar.horizontal.visible: false ScrollBar.horizontal.visible: false
anchors.fill: parent anchors.fill: parent
anchors.topMargin: (collapsed? backButton.height : 0)+Nheko.paddingLarge anchors.topMargin: (collapsed ? backButton.height : 0) + Nheko.paddingLarge
leftPadding: collapsed? Nheko.paddingMedium : Nheko.paddingLarge
bottomPadding: Nheko.paddingLarge bottomPadding: Nheko.paddingLarge
contentWidth: availableWidth contentWidth: availableWidth
leftPadding: collapsed ? Nheko.paddingMedium : Nheko.paddingLarge
palette: timelineRoot.palette
ColumnLayout { ColumnLayout {
id: grid id: grid
spacing: Nheko.paddingMedium
anchors.fill: parent 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 anchors.rightMargin: anchors.leftMargin
spacing: Nheko.paddingMedium
Repeater { Repeater {
Layout.fillWidth: true
model: UserSettingsModel model: UserSettingsModel
Layout.fillWidth:true
delegate: GridLayout { delegate: GridLayout {
columns: collapsed? 1 : 2
rows: collapsed? 2: 1
required property var model
id: r id: r
required property var model
columns: collapsed ? 1 : 2
rows: collapsed ? 2 : 1
Label { Label {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
color: timelineRoot.palette.text
text: model.name
//Layout.column: 0 //Layout.column: 0
Layout.columnSpan: (model.type == UserSettingsModel.SectionTitle && !userSettingsDialog.collapsed) ? 2 : 1 Layout.columnSpan: (model.type == UserSettingsModel.SectionTitle && !userSettingsDialog.collapsed) ? 2 : 1
Layout.fillWidth: true
//Layout.row: model.index //Layout.row: model.index
//Layout.minimumWidth: implicitWidth //Layout.minimumWidth: implicitWidth
Layout.leftMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium Layout.leftMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium
Layout.topMargin: model.type == UserSettingsModel.SectionTitle ? Nheko.paddingLarge : 0 Layout.topMargin: model.type == UserSettingsModel.SectionTitle ? Nheko.paddingLarge : 0
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: model.description ?? ""
ToolTip.visible: hovered.hovered && model.description
color: timelineRoot.palette.text
font.pointSize: 1.1 * fontMetrics.font.pointSize font.pointSize: 1.1 * fontMetrics.font.pointSize
text: model.name
wrapMode: Text.Wrap
HoverHandler { HoverHandler {
id: hovered id: hovered
enabled: model.description ?? false enabled: model.description ?? false
} }
ToolTip.visible: hovered.hovered && model.description
ToolTip.text: model.description ?? ""
ToolTip.delay: Nheko.tooltipDelay
wrapMode: Text.Wrap
} }
DelegateChooser { DelegateChooser {
id: chooser id: chooser
roleValue: model.type
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.columnSpan: (model.type == UserSettingsModel.SectionTitle && !userSettingsDialog.collapsed) ? 2 : 1 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.preferredHeight: child.height
Layout.preferredWidth: Math.min(child.implicitWidth, child.width || 1000) Layout.preferredWidth: Math.min(child.implicitWidth, child.width || 1000)
Layout.maximumWidth: model.type == UserSettingsModel.SectionTitle ? Number.POSITIVE_INFINITY : 400
Layout.fillWidth: model.type == UserSettingsModel.SectionTitle || model.type == UserSettingsModel.Options || model.type == UserSettingsModel.Number
Layout.rightMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium Layout.rightMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium
roleValue: model.type
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.Toggle roleValue: UserSettingsModel.Toggle
ToggleButton { ToggleButton {
checked: model.value checked: model.value
onCheckedChanged: model.value = checked
enabled: model.enabled enabled: model.enabled
onCheckedChanged: model.value = checked
} }
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.Options roleValue: UserSettingsModel.Options
ComboBox { ComboBox {
anchors.right: parent.right anchors.right: parent.right
width: Math.min(parent.width, implicitWidth)
model: r.model.values
currentIndex: r.model.value currentIndex: r.model.value
model: r.model.values
width: Math.min(parent.width, implicitWidth)
onCurrentIndexChanged: r.model.value = currentIndex onCurrentIndexChanged: r.model.value = currentIndex
WheelHandler{} // suppress scrolling changing values WheelHandler {
} // suppress scrolling changing values
} }
} }
DelegateChoice { DelegateChoice {
@ -111,15 +111,17 @@ Rectangle {
SpinBox { SpinBox {
anchors.right: parent.right anchors.right: parent.right
width: Math.min(parent.width, implicitWidth) editable: true
from: model.valueLowerBound from: model.valueLowerBound
to: model.valueUpperBound
stepSize: model.valueStep stepSize: model.valueStep
to: model.valueUpperBound
value: model.value value: model.value
width: Math.min(parent.width, implicitWidth)
onValueChanged: model.value = value onValueChanged: model.value = value
editable: true
WheelHandler{} // suppress scrolling changing values WheelHandler {
} // suppress scrolling changing values
} }
} }
DelegateChoice { DelegateChoice {
@ -128,56 +130,58 @@ Rectangle {
SpinBox { SpinBox {
id: spinbox id: spinbox
readonly property double div: 100
readonly property int decimals: 2 readonly property int decimals: 2
readonly property double div: 100
property real realValue: value / div
anchors.right: parent.right anchors.right: parent.right
width: Math.min(parent.width, implicitWidth) editable: true
from: model.valueLowerBound * div from: model.valueLowerBound * div
to: model.valueUpperBound * div
stepSize: model.valueStep * 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 value: model.value * div
onValueChanged: model.value = value/div valueFromText: function (text, locale) {
editable: true return Number.fromLocaleString(locale, text) * spinbox.div;
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)
} }
width: Math.min(parent.width, implicitWidth)
textFromValue: function(value, locale) { validator: DoubleValidator {
return Number(value / spinbox.div).toLocaleString(locale, 'f', spinbox.decimals) 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) { onValueChanged: model.value = value / div
return Number.fromLocaleString(locale, text) * spinbox.div
}
WheelHandler{} // suppress scrolling changing values WheelHandler {
} // suppress scrolling changing values
} }
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.ReadOnlyText roleValue: UserSettingsModel.ReadOnlyText
TextEdit { TextEdit {
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: model.value
readOnly: true readOnly: true
selectByMouse: !Settings.mobileMode selectByMouse: !Settings.mobileMode
text: model.value
textFormat: Text.PlainText textFormat: Text.PlainText
} }
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.SectionTitle roleValue: UserSettingsModel.SectionTitle
Item { Item {
width: grid.width
height: fontMetrics.lineSpacing height: fontMetrics.lineSpacing
width: grid.width
Rectangle { Rectangle {
anchors.topMargin: Nheko.paddingSmall
anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: Nheko.paddingSmall
color: timelineRoot.palette.placeholderText color: timelineRoot.palette.placeholderText
height: 1 height: 1
} }
@ -185,6 +189,7 @@ Rectangle {
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.KeyStatus roleValue: UserSettingsModel.KeyStatus
Text { Text {
color: model.good ? "green" : Nheko.theme.error color: model.good ? "green" : Nheko.theme.error
text: model.value ? qsTr("CACHED") : qsTr("NOT CACHED") text: model.value ? qsTr("CACHED") : qsTr("NOT CACHED")
@ -192,26 +197,32 @@ Rectangle {
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.SessionKeyImportExport roleValue: UserSettingsModel.SessionKeyImportExport
RowLayout { RowLayout {
Button { Button {
text: qsTr("IMPORT") text: qsTr("IMPORT")
onClicked: UserSettingsModel.importSessionKeys() onClicked: UserSettingsModel.importSessionKeys()
} }
Button { Button {
text: qsTr("EXPORT") text: qsTr("EXPORT")
onClicked: UserSettingsModel.exportSessionKeys() onClicked: UserSettingsModel.exportSessionKeys()
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.XSignKeysRequestDownload roleValue: UserSettingsModel.XSignKeysRequestDownload
RowLayout { RowLayout {
Button { Button {
text: qsTr("DOWNLOAD") text: qsTr("DOWNLOAD")
onClicked: UserSettingsModel.downloadCrossSigningSecrets() onClicked: UserSettingsModel.downloadCrossSigningSecrets()
} }
Button { Button {
text: qsTr("REQUEST") text: qsTr("REQUEST")
onClicked: UserSettingsModel.requestCrossSigningSecrets() onClicked: UserSettingsModel.requestCrossSigningSecrets()
} }
} }
@ -226,19 +237,17 @@ Rectangle {
} }
} }
} }
ImageButton { ImageButton {
id: backButton id: backButton
anchors.top: parent.top ToolTip.text: qsTr("Back")
ToolTip.visible: hovered
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
width: Nheko.avatarSize anchors.top: parent.top
height: Nheko.avatarSize height: Nheko.avatarSize
image: ":/icons/icons/ui/angle-arrow-left.svg" image: ":/icons/icons/ui/angle-arrow-left.svg"
ToolTip.visible: hovered width: Nheko.avatarSize
ToolTip.text: qsTr("Back")
onClicked: mainWindow.pop() onClicked: mainWindow.pop()
} }
} }

@ -1,64 +1,61 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.15 import QtQuick.Window 2.15
import im.nheko import im.nheko
import "../components/" import "../components"
ColumnLayout { ColumnLayout {
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
} }
Image { Image {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/splash.png"
height: 256 height: 256
source: "qrc:/logos/splash.png"
width: 256 width: 256
} }
Label { Label {
Layout.margins: Nheko.paddingLarge
Layout.bottomMargin: 0
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 0
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.") Layout.margins: Nheko.paddingLarge
color: timelineRoot.palette.text color: timelineRoot.palette.text
font.pointSize: fontMetrics.font.pointSize*2 font.pointSize: fontMetrics.font.pointSize * 2
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.")
wrapMode: Text.Wrap
} }
Label { Label {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Enjoy your stay!") Layout.margins: Nheko.paddingLarge
color: timelineRoot.palette.text color: timelineRoot.palette.text
font.pointSize: fontMetrics.font.pointSize*1.5 font.pointSize: fontMetrics.font.pointSize * 1.5
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: qsTr("Enjoy your stay!")
wrapMode: Text.Wrap
} }
RowLayout { RowLayout {
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
FlatButton { FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingLarge
text: qsTr("REGISTER") text: qsTr("REGISTER")
onClicked: { onClicked: {
mainWindow.push(registerPage); mainWindow.push(registerPage);
} }
} }
FlatButton { FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingLarge
text: qsTr("LOGIN") text: qsTr("LOGIN")
onClicked: { onClicked: {
mainWindow.push(loginPage); mainWindow.push(loginPage);
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import im.nheko import im.nheko
@ -10,42 +8,39 @@ import im.nheko
Slider { Slider {
id: control id: control
property color progressColor: timelineRoot.palette.highlight
property bool alwaysShowSlider: true property bool alwaysShowSlider: true
property color progressColor: timelineRoot.palette.highlight
property int sliderRadius: 16 property int sliderRadius: 16
value: 0
implicitHeight: sliderRadius implicitHeight: sliderRadius
padding: 0 padding: 0
value: 0
background: Rectangle { background: Rectangle {
x: control.leftPadding + handle.width / 2 color: timelineRoot.palette.placeholderText
y: control.topPadding + control.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: control.sliderRadius / 4
width: control.availableWidth - handle.width
height: implicitHeight height: implicitHeight
implicitHeight: control.sliderRadius / 4
implicitWidth: 200
radius: height / 2 radius: height / 2
color: timelineRoot.palette.placeholderText width: control.availableWidth - handle.width
x: control.leftPadding + handle.width / 2
y: control.topPadding + control.availableHeight / 2 - height / 2
Rectangle { Rectangle {
width: control.visualPosition * parent.width
height: parent.height
color: control.progressColor color: control.progressColor
height: parent.height
radius: 2 radius: 2
width: control.visualPosition * parent.width
} }
} }
handle: Rectangle { handle: Rectangle {
x: control.leftPadding + control.visualPosition * background.width border.color: control.progressColor
y: control.topPadding + control.availableHeight / 2 - height / 2 color: control.progressColor
implicitWidth: control.sliderRadius
implicitHeight: control.sliderRadius implicitHeight: control.sliderRadius
implicitWidth: control.sliderRadius
radius: control.sliderRadius / 2 radius: control.sliderRadius / 2
color: control.progressColor
visible: Settings.mobileMode || control.alwaysShowSlider || control.hovered || control.pressed 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
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
@ -11,28 +9,21 @@ Item {
property color color: "#22000000" property color color: "#22000000"
property real maxRadius: Math.max(width, height) property real maxRadius: Math.max(width, height)
readonly property real opacityAnimationDuration: 300
readonly property real radiusAnimationRate: 0.05 readonly property real radiusAnimationRate: 0.05
readonly property real radiusTailAnimationRate: 0.5 readonly property real radiusTailAnimationRate: 0.5
readonly property real opacityAnimationDuration: 300
property var rippleTarget: parent property var rippleTarget: parent
anchors.fill: parent anchors.fill: parent
PointHandler { PointHandler {
id: ph id: ph
onGrabChanged: {
circle.centerX = point.position.x
circle.centerY = point.position.y
}
target: Rectangle { target: Rectangle {
id: backgroundLayer id: backgroundLayer
parent: rippleTarget
anchors.fill: parent anchors.fill: parent
color: "transparent"
clip: true clip: true
color: "transparent"
parent: rippleTarget
Rectangle { Rectangle {
id: circle id: circle
@ -40,15 +31,14 @@ Item {
property real centerX property real centerX
property real centerY property real centerY
color: ripple.color
height: radius * 2
radius: 0
state: ph.active ? "ACTIVE" : "NORMAL"
width: radius * 2
x: centerX - radius x: centerX - radius
y: centerY - radius y: centerY - radius
height: radius*2
width: radius*2
radius: 0
color: ripple.color
state: ph.active ? "ACTIVE" : "NORMAL"
states: [ states: [
State { State {
name: "NORMAL" name: "NORMAL"
@ -65,26 +55,29 @@ Item {
SequentialAnimation { SequentialAnimation {
//PropertyAction { target: circle; property: "centerX"; value: ph.point.position.x } //PropertyAction { target: circle; property: "centerX"; value: ph.point.position.x }
//PropertyAction { target: circle; property: "centerY"; value: ph.point.position.y } //PropertyAction { target: circle; property: "centerY"; value: ph.point.position.y }
PropertyAction { target: circle; property: "visible"; value: true } PropertyAction {
PropertyAction { target: circle; property: "opacity"; value: 1 } property: "visible"
target: circle
value: true
}
PropertyAction {
property: "opacity"
target: circle
value: 1
}
NumberAnimation { NumberAnimation {
id: radius_animation id: radius_animation
duration: ripple.maxRadius / ripple.radiusAnimationRate
target: circle
properties: "radius"
from: 0 from: 0
properties: "radius"
target: circle
to: ripple.maxRadius to: ripple.maxRadius
duration: ripple.maxRadius / ripple.radiusAnimationRate
easing { easing {
type: Easing.OutQuad type: Easing.OutQuad
} }
} }
} }
}, },
Transition { Transition {
from: "ACTIVE" from: "ACTIVE"
@ -94,38 +87,41 @@ Item {
ParallelAnimation { ParallelAnimation {
NumberAnimation { NumberAnimation {
id: radius_tail_animation id: radius_tail_animation
duration: ripple.maxRadius / ripple.radiusTailAnimationRate
target: circle
properties: "radius" properties: "radius"
target: circle
to: ripple.maxRadius to: ripple.maxRadius
duration: ripple.maxRadius / ripple.radiusTailAnimationRate
easing { easing {
type: Easing.Linear type: Easing.Linear
} }
} }
NumberAnimation { NumberAnimation {
id: opacity_animation id: opacity_animation
duration: ripple.opacityAnimationDuration
target: circle
properties: "opacity" properties: "opacity"
target: circle
to: 0 to: 0
duration: ripple.opacityAnimationDuration
easing { easing {
type: Easing.InQuad type: Easing.InQuad
} }
} }
} }
PropertyAction { target: circle; property: "visible"; value: false } PropertyAction {
property: "visible"
target: circle
value: false
}
} }
} }
] ]
} }
} }
onGrabChanged: {
circle.centerX = point.position.x;
circle.centerY = point.position.y;
}
} }
} }

@ -1,7 +1,5 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import im.nheko import im.nheko
@ -9,8 +7,8 @@ import im.nheko
Popup { Popup {
id: snackbar id: snackbar
property var messages: []
property string currentMessage: "" property string currentMessage: ""
property var messages: []
function showNotification(msg) { function showNotification(msg) {
messages.push(msg); messages.push(msg);
@ -21,78 +19,75 @@ 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 opacity: 0
y: -100
x: (parent.width - width)/2
padding: Nheko.paddingLarge padding: Nheko.paddingLarge
parent: Overlay.overlay
contentItem: Label { x: (parent.width - width) / 2
color: timelineRoot.palette.light y: -100
width: Math.max(Overlay.overlay? Overlay.overlay.width/2 : 0, 400)
text: snackbar.currentMessage
font.bold: true
}
background: Rectangle { background: Rectangle {
radius: Nheko.paddingLarge
color: timelineRoot.palette.dark color: timelineRoot.palette.dark
opacity: 0.8 opacity: 0.8
radius: Nheko.paddingLarge
}
contentItem: Label {
color: timelineRoot.palette.light
font.bold: true
text: snackbar.currentMessage
width: Math.max(Overlay.overlay ? Overlay.overlay.width / 2 : 0, 400)
} }
enter: Transition { enter: Transition {
NumberAnimation { NumberAnimation {
target: snackbar
property: "opacity"
from: 0.0
to: 1.0
duration: 200 duration: 200
easing.type: Easing.OutCubic easing.type: Easing.OutCubic
from: 0.0
property: "opacity"
target: snackbar
to: 1.0
} }
NumberAnimation { NumberAnimation {
target: snackbar
properties: "y"
from: -100
to: 100
duration: 1000 duration: 1000
easing.type: Easing.OutCubic easing.type: Easing.OutCubic
from: -100
properties: "y"
target: snackbar
to: 100
} }
} }
exit: Transition { exit: Transition {
NumberAnimation { NumberAnimation {
target: snackbar
property: "opacity"
from: 1.0
to: 0.0
duration: 300 duration: 300
easing.type: Easing.InCubic easing.type: Easing.InCubic
from: 1.0
property: "opacity"
target: snackbar
to: 0.0
} }
NumberAnimation { NumberAnimation {
target: snackbar
properties: "y"
to: -100
from: 100
duration: 300 duration: 300
easing.type: Easing.InCubic 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()
}
}

@ -1,143 +1,116 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "animations"
import "./animations"
//import QtGraphicalEffects 1.12 //import QtGraphicalEffects 1.12
import QtQuick 2.12 import QtQuick 2.12
Item { Item {
id: spinner 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 real a: Math.PI / 6
readonly property var colors: ["#c0def5", "#87aade", "white"]
readonly property var anims: [anim1, anim2, anim3, anim4, anim5, anim6] 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 glowDuration: 300
readonly property int pauseDuration: barCount * 150
property bool running: true
property int spacing: 0
height: 40 height: 40
width: barCount * (height * 0.375) width: barCount * (height * 0.375)
Row { Row {
id: 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 { Rectangle {
id: rect1 id: rect1
width: ((spinner.width / spinner.barCount) - (spinner.spacing)) * 1.5
height: spinner.height / 3.5
color: "white" color: "white"
height: spinner.height / 3.5
width: ((spinner.width / spinner.barCount) - (spinner.spacing)) * 1.5
} }
Rectangle { Rectangle {
id: rect2 id: rect2
width: (spinner.width / spinner.barCount) - spinner.spacing
height: spinner.height
color: spinner.colors[0] color: spinner.colors[0]
height: spinner.height
width: (spinner.width / spinner.barCount) - spinner.spacing
} }
Rectangle { Rectangle {
id: rect3 id: rect3
width: (spinner.width / spinner.barCount) - spinner.spacing
height: spinner.height
color: spinner.colors[1] color: spinner.colors[1]
height: spinner.height
width: (spinner.width / spinner.barCount) - spinner.spacing
} }
Rectangle { Rectangle {
id: rect4 id: rect4
width: (spinner.width / spinner.barCount) - spinner.spacing
height: spinner.height
color: spinner.colors[2] color: spinner.colors[2]
height: spinner.height
width: (spinner.width / spinner.barCount) - spinner.spacing
} }
Rectangle { Rectangle {
id: rect5 id: rect5
width: (spinner.width / (spinner.barCount + 1)) - spinner.spacing
height: spinner.height / 3.5
color: "white" color: "white"
height: spinner.height / 3.5
width: (spinner.width / (spinner.barCount + 1)) - spinner.spacing
} }
Rectangle { Rectangle {
id: rect6 id: rect6
width: (spinner.width / spinner.barCount) - spinner.spacing
height: spinner.height
color: "white" color: "white"
height: spinner.height
width: (spinner.width / spinner.barCount) - spinner.spacing
} }
BlinkAnimation { BlinkAnimation {
id: anim1 id: anim1
target: rect1
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration glowDuration: spinner.glowDuration
offset: 0 / spinner.barCount offset: 0 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect1
} }
BlinkAnimation { BlinkAnimation {
id: anim2 id: anim2
target: rect2
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration glowDuration: spinner.glowDuration
offset: 1 / spinner.barCount offset: 1 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect2
} }
BlinkAnimation { BlinkAnimation {
id: anim3 id: anim3
target: rect3
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration glowDuration: spinner.glowDuration
offset: 2 / spinner.barCount offset: 2 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect3
} }
BlinkAnimation { BlinkAnimation {
id: anim4 id: anim4
target: rect4
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration glowDuration: spinner.glowDuration
offset: 3 / spinner.barCount offset: 3 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect4
} }
BlinkAnimation { BlinkAnimation {
id: anim5 id: anim5
target: rect5
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration glowDuration: spinner.glowDuration
offset: 4 / spinner.barCount offset: 4 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect5
} }
BlinkAnimation { BlinkAnimation {
id: anim6 id: anim6
target: rect6
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration glowDuration: spinner.glowDuration
offset: 5 / spinner.barCount 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)
}
} }
//Glow { //Glow {
@ -152,5 +125,4 @@ Item {
// } // }
//} //}
} }

@ -1,32 +1,26 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12 import QtQuick 2.12
SequentialAnimation { SequentialAnimation {
property alias target: numberAnimation.target
property alias glowDuration: numberAnimation.duration property alias glowDuration: numberAnimation.duration
property int pauseDuration: 150
property double offset: 0 property double offset: 0
property int pauseDuration: 150
property alias target: numberAnimation.target
loops: Animation.Infinite loops: Animation.Infinite
PauseAnimation { PauseAnimation {
duration: pauseDuration * offset duration: pauseDuration * offset
} }
NumberAnimation { NumberAnimation {
id: numberAnimation id: numberAnimation
property: "opacity"
from: 0 from: 0
property: "opacity"
to: 1 to: 1
} }
PauseAnimation { PauseAnimation {
duration: pauseDuration * (1 - offset) duration: pauseDuration * (1 - offset)
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import "../../" import "../../"
import QtMultimedia 5.15 import QtMultimedia 5.15
@ -15,27 +13,22 @@ Rectangle {
id: control id: control
property alias desiredVolume: volumeSlider.desiredVolume property alias desiredVolume: volumeSlider.desiredVolume
property var duration
property bool mediaLoaded: false
property var mediaState
property bool muted: false property bool muted: false
property bool playingVideo: false property bool playingVideo: false
property var mediaState
property bool mediaLoaded: false
property var duration
property var positionValue: 0
property var position property var position
property var positionValue: 0
property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.state == "shown" property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.state == "shown"
signal playPauseActivated() signal loadActivated
signal loadActivated() signal playPauseActivated
function showControls() {
controlHideTimer.restart();
}
function durationToString(duration) { function durationToString(duration) {
function maybeZeroPrepend(time) { function maybeZeroPrepend(time) {
return (time < 10) ? "0" + time.toString() : time.toString(); return (time < 10) ? "0" + time.toString() : time.toString();
} }
var totalSeconds = Math.floor(duration / 1000); var totalSeconds = Math.floor(duration / 1000);
var seconds = totalSeconds % 60; var seconds = totalSeconds % 60;
var minutes = (Math.floor(totalSeconds / 60)) % 60; var minutes = (Math.floor(totalSeconds / 60)) % 60;
@ -46,16 +39,25 @@ Rectangle {
var hh = hours.toString(); var hh = hours.toString();
if (hours < 1) if (hours < 1)
return mm + ":" + ss; return mm + ":" + ss;
return hh + ":" + mm + ":" + ss; return hh + ":" + mm + ":" + ss;
} }
function showControls() {
controlHideTimer.restart();
}
color: { color: {
var wc = timelineRoot.palette.alternateBase; var wc = timelineRoot.palette.alternateBase;
return Qt.rgba(wc.r, wc.g, wc.b, 0.5); return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
} }
opacity: control.shouldShowControls ? 1 : 0
height: controlLayout.implicitHeight height: controlLayout.implicitHeight
opacity: control.shouldShowControls ? 1 : 0
// Fade controls in/out
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
HoverHandler { HoverHandler {
id: playerMouseArea id: playerMouseArea
@ -64,41 +66,38 @@ Rectangle {
onHoveredChanged: showControls() onHoveredChanged: showControls()
} }
ColumnLayout { ColumnLayout {
id: controlLayout id: controlLayout
enabled: control.shouldShowControls
spacing: 0
anchors.bottom: control.bottom anchors.bottom: control.bottom
anchors.left: control.left anchors.left: control.left
anchors.right: control.right anchors.right: control.right
enabled: control.shouldShowControls
spacing: 0
NhekoSlider { NhekoSlider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: Nheko.paddingSmall Layout.leftMargin: Nheko.paddingSmall
Layout.rightMargin: Nheko.paddingSmall Layout.rightMargin: Nheko.paddingSmall
alwaysShowSlider: false
enabled: control.mediaLoaded enabled: control.mediaLoaded
value: control.positionValue
onMoved: control.position = value
from: 0 from: 0
to: control.duration to: control.duration
alwaysShowSlider: false value: control.positionValue
}
onMoved: control.position = value
}
RowLayout { RowLayout {
Layout.fillWidth: true
Layout.margins: Nheko.paddingSmall Layout.margins: Nheko.paddingSmall
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
Layout.fillWidth: true
// Cache/Play/pause button // Cache/Play/pause button
ImageButton { ImageButton {
id: playbackStateImage id: playbackStateImage
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
buttonTextColor: timelineRoot.palette.text
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
buttonTextColor: timelineRoot.palette.text
image: { image: {
if (control.mediaLoaded) { if (control.mediaLoaded) {
if (control.mediaState == MediaPlayer.PlayingState) if (control.mediaState == MediaPlayer.PlayingState)
@ -109,38 +108,48 @@ Rectangle {
return ":/icons/icons/ui/download.svg"; return ":/icons/icons/ui/download.svg";
} }
} }
onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated() onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated()
} }
ImageButton { ImageButton {
id: volumeButton id: volumeButton
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
buttonTextColor: timelineRoot.palette.text
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
buttonTextColor: timelineRoot.palette.text
image: { image: {
if (control.muted || control.desiredVolume <= 0) if (control.muted || control.desiredVolume <= 0)
return ":/icons/icons/ui/volume-off-indicator.svg"; return ":/icons/icons/ui/volume-off-indicator.svg";
else else
return ":/icons/icons/ui/volume-up.svg"; return ":/icons/icons/ui/volume-up.svg";
} }
onClicked: control.muted = !control.muted onClicked: control.muted = !control.muted
} }
NhekoSlider { NhekoSlider {
id: volumeSlider id: volumeSlider
property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale) property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale)
state: ""
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.preferredWidth: 0 Layout.preferredWidth: 0
opacity: 0 opacity: 0
orientation: Qt.Horizontal orientation: Qt.Horizontal
state: ""
value: 1 value: 1
onDesiredVolumeChanged: {
control.muted = !(desiredVolume > 0); states: State {
name: "shown"
when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed
PropertyChanges {
Layout.preferredWidth: 100
target: volumeSlider
}
PropertyChanges {
opacity: 1
target: volumeSlider
}
} }
transitions: [ transitions: [
Transition { Transition {
@ -151,20 +160,16 @@ Rectangle {
PauseAnimation { PauseAnimation {
duration: 50 duration: 50
} }
NumberAnimation { NumberAnimation {
duration: 100 duration: 100
properties: "opacity"
easing.type: Easing.InQuad easing.type: Easing.InQuad
properties: "opacity"
} }
} }
NumberAnimation { NumberAnimation {
properties: "Layout.preferredWidth"
duration: 150 duration: 150
properties: "Layout.preferredWidth"
} }
}, },
Transition { Transition {
from: "shown" from: "shown"
@ -174,72 +179,40 @@ Rectangle {
PauseAnimation { PauseAnimation {
duration: 100 duration: 100
} }
ParallelAnimation { ParallelAnimation {
NumberAnimation { NumberAnimation {
duration: 100 duration: 100
properties: "opacity"
easing.type: Easing.InQuad easing.type: Easing.InQuad
properties: "opacity"
} }
NumberAnimation { NumberAnimation {
properties: "Layout.preferredWidth"
duration: 150 duration: 150
properties: "Layout.preferredWidth"
} }
} }
} }
} }
] ]
states: State { onDesiredVolumeChanged: {
name: "shown" control.muted = !(desiredVolume > 0);
when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed
PropertyChanges {
target: volumeSlider
Layout.preferredWidth: 100
}
PropertyChanges {
target: volumeSlider
opacity: 1
}
} }
} }
Label { Label {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration)
color: timelineRoot.palette.text color: timelineRoot.palette.text
text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration)
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
} }
// For hiding controls on stationary cursor // For hiding controls on stationary cursor
Timer { Timer {
id: controlHideTimer id: controlHideTimer
interval: 1500 //ms interval: 1500 //ms
repeat: false repeat: false
} }
// Fade controls in/out
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -10,51 +8,46 @@ import QtQuick.Layouts 1.2
import im.nheko import im.nheko
Rectangle { Rectangle {
visible: CallManager.isOnCall
color: callInviteBar.color color: callInviteBar.color
implicitHeight: visible ? rowLayout.height + 8 : 0 implicitHeight: visible ? rowLayout.height + 8 : 0
visible: CallManager.isOnCall
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
if (CallManager.callType != Voip.VOICE) if (CallManager.callType != Voip.VOICE)
stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1; stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1;
} }
} }
RowLayout { RowLayout {
id: rowLayout id: rowLayout
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 8
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 8
Avatar { Avatar {
width: Nheko.avatarSize displayName: CallManager.callPartyDisplayName
height: Nheko.avatarSize height: Nheko.avatarSize
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
userid: CallManager.callParty userid: CallManager.callParty
displayName: CallManager.callPartyDisplayName width: Nheko.avatarSize
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId) onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
} }
Label { Label {
Layout.leftMargin: 8 Layout.leftMargin: 8
color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1 font.pointSize: fontMetrics.font.pointSize * 1.1
text: CallManager.callPartyDisplayName text: CallManager.callPartyDisplayName
color: "#000000"
} }
Image { Image {
id: callTypeIcon id: callTypeIcon
Layout.leftMargin: 4 Layout.leftMargin: 4
Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24
} }
Item { Item {
states: [ states: [
State { State {
@ -62,41 +55,35 @@ Rectangle {
when: CallManager.callType == Voip.VOICE when: CallManager.callType == Voip.VOICE
PropertyChanges { PropertyChanges {
target: callTypeIcon
source: "qrc:/icons/icons/ui/place-call.svg" source: "qrc:/icons/icons/ui/place-call.svg"
target: callTypeIcon
} }
}, },
State { State {
name: "VIDEO" name: "VIDEO"
when: CallManager.callType == Voip.VIDEO when: CallManager.callType == Voip.VIDEO
PropertyChanges { PropertyChanges {
target: callTypeIcon
source: "qrc:/icons/icons/ui/video.svg" source: "qrc:/icons/icons/ui/video.svg"
target: callTypeIcon
} }
}, },
State { State {
name: "SCREEN" name: "SCREEN"
when: CallManager.callType == Voip.SCREEN when: CallManager.callType == Voip.SCREEN
PropertyChanges { PropertyChanges {
target: callTypeIcon
source: "qrc:/icons/icons/ui/screen-share.svg" source: "qrc:/icons/icons/ui/screen-share.svg"
target: callTypeIcon
} }
} }
] ]
} }
Label { Label {
id: callStateLabel id: callStateLabel
font.pointSize: fontMetrics.font.pointSize * 1.1
color: "#000000" color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1
} }
Item { Item {
states: [ states: [
State { State {
@ -107,7 +94,6 @@ Rectangle {
target: callStateLabel target: callStateLabel
text: qsTr("Calling...") text: qsTr("Calling...")
} }
}, },
State { State {
name: "CONNECTING" name: "CONNECTING"
@ -117,7 +103,6 @@ Rectangle {
target: callStateLabel target: callStateLabel
text: qsTr("Connecting...") text: qsTr("Connecting...")
} }
}, },
State { State {
name: "ANSWERSENT" name: "ANSWERSENT"
@ -127,7 +112,6 @@ Rectangle {
target: callStateLabel target: callStateLabel
text: qsTr("Connecting...") text: qsTr("Connecting...")
} }
}, },
State { State {
name: "CONNECTED" name: "CONNECTED"
@ -137,17 +121,14 @@ Rectangle {
target: callStateLabel target: callStateLabel
text: "00:00" text: "00:00"
} }
PropertyChanges { PropertyChanges {
target: callTimer
startTime: Math.floor((new Date()).getTime() / 1000) startTime: Math.floor((new Date()).getTime() / 1000)
target: callTimer
} }
PropertyChanges { PropertyChanges {
target: stackLayout
currentIndex: CallManager.callType != Voip.VOICE ? 1 : 0 currentIndex: CallManager.callType != Voip.VOICE ? 1 : 0
target: stackLayout
} }
}, },
State { State {
name: "DISCONNECTED" name: "DISCONNECTED"
@ -157,16 +138,13 @@ Rectangle {
target: callStateLabel target: callStateLabel
text: "" text: ""
} }
PropertyChanges { PropertyChanges {
target: stackLayout
currentIndex: 0 currentIndex: 0
target: stackLayout
} }
} }
] ]
} }
Timer { Timer {
id: callTimer id: callTimer
@ -177,8 +155,9 @@ Rectangle {
} }
interval: 1000 interval: 1000
running: CallManager.callState == Voip.CONNECTED
repeat: true repeat: true
running: CallManager.callState == Voip.CONNECTED
onTriggered: { onTriggered: {
var d = new Date(); var d = new Date();
let seconds = Math.floor(d.getTime() / 1000 - startTime); let seconds = Math.floor(d.getTime() / 1000 - startTime);
@ -188,44 +167,40 @@ Rectangle {
callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s); callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s);
} }
} }
Label { Label {
Layout.leftMargin: 16 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" 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 { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
ImageButton { ImageButton {
visible: CallManager.haveLocalPiP ToolTip.text: qsTr("Hide/Show Picture-in-Picture")
width: 24 ToolTip.visible: hovered
height: 24
buttonTextColor: "#000000" buttonTextColor: "#000000"
image: ":/icons/icons/ui/picture-in-picture.svg" height: 24
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered image: ":/icons/icons/ui/picture-in-picture.svg"
ToolTip.text: qsTr("Hide/Show Picture-in-Picture") visible: CallManager.haveLocalPiP
width: 24
onClicked: CallManager.toggleLocalPiP() onClicked: CallManager.toggleLocalPiP()
} }
ImageButton { ImageButton {
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 16 Layout.rightMargin: 16
width: 24 ToolTip.text: CallManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic")
height: 24 ToolTip.visible: hovered
buttonTextColor: "#000000" buttonTextColor: "#000000"
image: CallManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.svg" : ":/icons/icons/ui/microphone-mute.svg" height: 24
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered image: CallManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.svg" : ":/icons/icons/ui/microphone-mute.svg"
ToolTip.text: CallManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic") width: 24
onClicked: CallManager.toggleMicMute() onClicked: CallManager.toggleMicMute()
} }
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
@ -11,79 +9,68 @@ import im.nheko
Popup { Popup {
modal: true modal: true
palette: timelineRoot.palette palette: timelineRoot.palette
background: Rectangle {
border.color: timelineRoot.palette.windowText
color: timelineRoot.palette.window
}
// only set the anchors on Qt 5.12 or higher // 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 // see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
Component.onCompleted: { Component.onCompleted: {
if (anchors) if (anchors)
anchors.centerIn = parent; anchors.centerIn = parent;
} }
ColumnLayout { ColumnLayout {
spacing: 16 spacing: 16
ColumnLayout { ColumnLayout {
spacing: 8
Layout.topMargin: 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.topMargin: 8
spacing: 8
RowLayout { RowLayout {
Image { Image {
Layout.preferredWidth: 22
Layout.preferredHeight: 22 Layout.preferredHeight: 22
Layout.preferredWidth: 22
source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + timelineRoot.palette.windowText source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + timelineRoot.palette.windowText
} }
ComboBox { ComboBox {
id: micCombo id: micCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.mics model: CallManager.mics
} }
} }
RowLayout { RowLayout {
visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0
Image { Image {
Layout.preferredWidth: 22
Layout.preferredHeight: 22 Layout.preferredHeight: 22
Layout.preferredWidth: 22
source: "image://colorimage/:/icons/icons/ui/video-call.svg?" + timelineRoot.palette.windowText source: "image://colorimage/:/icons/icons/ui/video-call.svg?" + timelineRoot.palette.windowText
} }
ComboBox { ComboBox {
id: cameraCombo id: cameraCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.cameras model: CallManager.cameras
} }
} }
} }
DialogButtonBox { DialogButtonBox {
Layout.leftMargin: 128 Layout.leftMargin: 128
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: { onAccepted: {
Settings.microphone = micCombo.currentText; Settings.microphone = micCombo.currentText;
if (cameraCombo.visible) if (cameraCombo.visible)
Settings.camera = cameraCombo.currentText; Settings.camera = cameraCombo.currentText;
close(); close();
} }
onRejected: { onRejected: {
close(); close();
} }
} }
}
background: Rectangle {
color: timelineRoot.palette.window
border.color: timelineRoot.palette.windowText
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -11,54 +9,51 @@ import im.nheko
Popup { Popup {
id: callInv id: callInv
closePolicy: Popup.NoAutoClose closePolicy: Popup.NoAutoClose
width: parent.width
height: parent.height height: parent.height
palette: timelineRoot.palette palette: timelineRoot.palette
width: parent.width
background: Rectangle {
border.color: timelineRoot.palette.windowText
color: timelineRoot.palette.window
}
Component { Component {
id: deviceError id: deviceError
DeviceError { DeviceError {
} }
} }
Connections { Connections {
function onNewInviteState() { function onNewInviteState() {
if (!CallManager.haveCallInvite) if (!CallManager.haveCallInvite)
close(); close();
} }
target: CallManager target: CallManager
} }
ColumnLayout { ColumnLayout {
anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
Label { Label {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Layout.topMargin: callInv.parent.height / 25
Layout.fillWidth: true Layout.fillWidth: true
text: CallManager.callPartyDisplayName Layout.topMargin: callInv.parent.height / 25
font.pointSize: fontMetrics.font.pointSize * 2
color: timelineRoot.palette.windowText color: timelineRoot.palette.windowText
font.pointSize: fontMetrics.font.pointSize * 2
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: CallManager.callPartyDisplayName
} }
Avatar { Avatar {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: callInv.height / 5 Layout.preferredHeight: callInv.height / 5
Layout.preferredWidth: callInv.height / 5 Layout.preferredWidth: callInv.height / 5
displayName: CallManager.callPartyDisplayName
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
userid: CallManager.callParty userid: CallManager.callParty
displayName: CallManager.callPartyDisplayName
} }
ColumnLayout { ColumnLayout {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Layout.bottomMargin: callInv.height / 25 Layout.bottomMargin: callInv.height / 25
@ -67,20 +62,17 @@ Popup {
property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg" property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg"
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: callInv.height / 10
Layout.preferredHeight: callInv.height / 10 Layout.preferredHeight: callInv.height / 10
Layout.preferredWidth: callInv.height / 10
source: "image://colorimage/" + image + "?" + timelineRoot.palette.windowText source: "image://colorimage/" + image + "?" + timelineRoot.palette.windowText
} }
Label { Label {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
font.pointSize: fontMetrics.font.pointSize * 2
color: timelineRoot.palette.windowText color: timelineRoot.palette.windowText
font.pointSize: fontMetrics.font.pointSize * 2
text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
} }
} }
ColumnLayout { ColumnLayout {
id: deviceCombos id: deviceCombos
@ -93,41 +85,32 @@ Popup {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Image { Image {
Layout.preferredWidth: deviceCombos.imageSize
Layout.preferredHeight: deviceCombos.imageSize Layout.preferredHeight: deviceCombos.imageSize
Layout.preferredWidth: deviceCombos.imageSize
source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + timelineRoot.palette.windowText source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + timelineRoot.palette.windowText
} }
ComboBox { ComboBox {
id: micCombo id: micCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.mics model: CallManager.mics
} }
} }
RowLayout { RowLayout {
visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0
Image { Image {
Layout.preferredWidth: deviceCombos.imageSize
Layout.preferredHeight: deviceCombos.imageSize Layout.preferredHeight: deviceCombos.imageSize
Layout.preferredWidth: deviceCombos.imageSize
source: "image://colorimage/:/icons/icons/ui/video.svg?" + timelineRoot.palette.windowText source: "image://colorimage/:/icons/icons/ui/video.svg?" + timelineRoot.palette.windowText
} }
ComboBox { ComboBox {
id: cameraCombo id: cameraCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.cameras model: CallManager.cameras
} }
} }
} }
RowLayout { RowLayout {
id: buttonLayout id: buttonLayout
@ -150,60 +133,48 @@ Popup {
spacing: callInv.height / 6 spacing: callInv.height / 6
RoundButton { RoundButton {
implicitWidth: buttonLayout.buttonSize
implicitHeight: buttonLayout.buttonSize implicitHeight: buttonLayout.buttonSize
onClicked: { implicitWidth: buttonLayout.buttonSize
CallManager.hangUp();
close();
}
background: Rectangle { background: Rectangle {
radius: buttonLayout.buttonSize / 2
color: "#ff0000" color: "#ff0000"
radius: buttonLayout.buttonSize / 2
} }
contentItem: Image { contentItem: Image {
source: "image://colorimage/:/icons/icons/ui/end-call.svg?#ffffff" source: "image://colorimage/:/icons/icons/ui/end-call.svg?#ffffff"
} }
onClicked: {
CallManager.hangUp();
close();
}
} }
RoundButton { RoundButton {
id: acceptButton id: acceptButton
property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg" property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg"
implicitWidth: buttonLayout.buttonSize
implicitHeight: buttonLayout.buttonSize implicitHeight: buttonLayout.buttonSize
onClicked: { implicitWidth: buttonLayout.buttonSize
if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText;
if (cameraCombo.visible)
Settings.camera = cameraCombo.currentText;
CallManager.acceptInvite();
close();
}
}
background: Rectangle { background: Rectangle {
radius: buttonLayout.buttonSize / 2
color: "#00ff00" color: "#00ff00"
radius: buttonLayout.buttonSize / 2
} }
contentItem: Image { contentItem: Image {
source: "image://colorimage/" + acceptButton.image + "?#ffffff" 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 {
color: timelineRoot.palette.window
border.color: timelineRoot.palette.windowText
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -10,88 +8,78 @@ import QtQuick.Layouts 1.2
import im.nheko import im.nheko
Rectangle { Rectangle {
visible: CallManager.haveCallInvite && !Settings.mobileMode
color: "#2ECC71" color: "#2ECC71"
implicitHeight: visible ? rowLayout.height + 8 : 0 implicitHeight: visible ? rowLayout.height + 8 : 0
visible: CallManager.haveCallInvite && !Settings.mobileMode
Component { Component {
id: devicesDialog id: devicesDialog
CallDevices { CallDevices {
} }
} }
Component { Component {
id: deviceError id: deviceError
DeviceError { DeviceError {
} }
} }
RowLayout { RowLayout {
id: rowLayout id: rowLayout
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 8
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 8
Avatar { Avatar {
width: Nheko.avatarSize displayName: CallManager.callPartyDisplayName
height: Nheko.avatarSize height: Nheko.avatarSize
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/") url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
userid: CallManager.callParty userid: CallManager.callParty
displayName: CallManager.callPartyDisplayName width: Nheko.avatarSize
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId) onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
} }
Label { Label {
Layout.leftMargin: 8 Layout.leftMargin: 8
color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1 font.pointSize: fontMetrics.font.pointSize * 1.1
text: CallManager.callPartyDisplayName text: CallManager.callPartyDisplayName
color: "#000000"
} }
Image { Image {
Layout.leftMargin: 4 Layout.leftMargin: 4
Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24
source: CallManager.callType == Voip.CallType.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg" source: CallManager.callType == Voip.CallType.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg"
} }
Label { Label {
color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1 font.pointSize: fontMetrics.font.pointSize * 1.1
text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call") text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
color: "#000000"
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
ImageButton { ImageButton {
Layout.rightMargin: 16 Layout.rightMargin: 16
width: 20 ToolTip.text: qsTr("Devices")
height: 20 ToolTip.visible: hovered
buttonTextColor: "#000000" buttonTextColor: "#000000"
image: ":/icons/icons/ui/settings.svg" height: 20
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered image: ":/icons/icons/ui/settings.svg"
ToolTip.text: qsTr("Devices") width: 20
onClicked: { onClicked: {
var dialog = devicesDialog.createObject(timelineRoot); var dialog = devicesDialog.createObject(timelineRoot);
dialog.open(); dialog.open();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
} }
} }
Button { Button {
Layout.rightMargin: 4 Layout.rightMargin: 4
icon.source: CallManager.callType == Voip.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg" icon.source: CallManager.callType == Voip.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg"
text: qsTr("Accept")
palette: timelineRoot.palette palette: timelineRoot.palette
text: qsTr("Accept")
onClicked: { onClicked: {
if (CallManager.mics.length == 0) { if (CallManager.mics.length == 0) {
var dialog = deviceError.createObject(timelineRoot, { var dialog = deviceError.createObject(timelineRoot, {
@ -100,7 +88,7 @@ Rectangle {
}); });
dialog.open(); dialog.open();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
return ; return;
} else if (!CallManager.mics.includes(Settings.microphone)) { } else if (!CallManager.mics.includes(Settings.microphone)) {
var dialog = deviceError.createObject(timelineRoot, { var dialog = deviceError.createObject(timelineRoot, {
"errorString": qsTr("Unknown microphone: %1").arg(Settings.microphone), "errorString": qsTr("Unknown microphone: %1").arg(Settings.microphone),
@ -108,7 +96,7 @@ Rectangle {
}); });
dialog.open(); dialog.open();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
return ; return;
} }
if (CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) { if (CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) {
var dialog = deviceError.createObject(timelineRoot, { var dialog = deviceError.createObject(timelineRoot, {
@ -117,22 +105,20 @@ Rectangle {
}); });
dialog.open(); dialog.open();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
return ; return;
} }
CallManager.acceptInvite(); CallManager.acceptInvite();
} }
} }
Button { Button {
Layout.rightMargin: 16 Layout.rightMargin: 16
icon.source: "qrc:/icons/icons/ui/end-call.svg" icon.source: "qrc:/icons/icons/ui/end-call.svg"
text: qsTr("Decline")
palette: timelineRoot.palette palette: timelineRoot.palette
text: qsTr("Decline")
onClicked: { onClicked: {
CallManager.hangUp(); CallManager.hangUp();
} }
} }
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
@ -13,31 +11,28 @@ Popup {
property var image property var image
modal: true modal: true
background: Rectangle {
border.color: timelineRoot.palette.windowText
color: timelineRoot.palette.window
}
// only set the anchors on Qt 5.12 or higher // 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 // see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
Component.onCompleted: { Component.onCompleted: {
if (anchors) if (anchors)
anchors.centerIn = parent; anchors.centerIn = parent;
} }
RowLayout { RowLayout {
Image { Image {
Layout.preferredWidth: 16
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16
source: "image://colorimage/" + image + "?" + timelineRoot.palette.windowText source: "image://colorimage/" + image + "?" + timelineRoot.palette.windowText
} }
Label { Label {
text: errorString
color: timelineRoot.palette.windowText color: timelineRoot.palette.windowText
text: errorString
} }
}
background: Rectangle {
color: timelineRoot.palette.window
border.color: timelineRoot.palette.windowText
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -11,46 +9,43 @@ import im.nheko
Popup { Popup {
modal: true modal: true
palette: timelineRoot.palette
background: Rectangle {
border.color: timelineRoot.palette.windowText
color: timelineRoot.palette.window
}
// only set the anchors on Qt 5.12 or higher // 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 // see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
Component.onCompleted: { Component.onCompleted: {
if (anchors) if (anchors)
anchors.centerIn = parent; anchors.centerIn = parent;
} }
palette: timelineRoot.palette
Component { Component {
id: deviceError id: deviceError
DeviceError { DeviceError {
} }
} }
ColumnLayout { ColumnLayout {
id: columnLayout id: columnLayout
spacing: 16 spacing: 16
RowLayout { RowLayout {
Layout.topMargin: 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.topMargin: 8
Label { Label {
text: qsTr("Place a call to %1?").arg(room.roomName)
color: timelineRoot.palette.windowText color: timelineRoot.palette.windowText
text: qsTr("Place a call to %1?").arg(room.roomName)
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
RowLayout { RowLayout {
id: buttonLayout id: buttonLayout
function validateMic() { function validateMic() {
if (CallManager.mics.length == 0) { if (CallManager.mics.length == 0) {
var dialog = deviceError.createObject(timelineRoot, { var dialog = deviceError.createObject(timelineRoot, {
@ -69,17 +64,18 @@ Popup {
Avatar { Avatar {
Layout.rightMargin: cameraCombo.visible ? 16 : 64 Layout.rightMargin: cameraCombo.visible ? 16 : 64
width: Nheko.avatarSize
height: Nheko.avatarSize
url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
displayName: room.roomName displayName: room.roomName
height: Nheko.avatarSize
roomid: room.roomId roomid: room.roomId
url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
width: Nheko.avatarSize
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId) onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
} }
Button { Button {
text: qsTr("Voice")
icon.source: "qrc:/icons/icons/ui/place-call.svg" icon.source: "qrc:/icons/icons/ui/place-call.svg"
text: qsTr("Voice")
onClicked: { onClicked: {
if (buttonLayout.validateMic()) { if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText; Settings.microphone = micCombo.currentText;
@ -88,11 +84,11 @@ Popup {
} }
} }
} }
Button { Button {
visible: CallManager.cameras.length > 0
text: qsTr("Video")
icon.source: "qrc:/icons/icons/ui/video.svg" icon.source: "qrc:/icons/icons/ui/video.svg"
text: qsTr("Video")
visible: CallManager.cameras.length > 0
onClicked: { onClicked: {
if (buttonLayout.validateMic()) { if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText; Settings.microphone = micCombo.currentText;
@ -102,16 +98,15 @@ Popup {
} }
} }
} }
Button { Button {
visible: CallManager.screenShareSupported
text: qsTr("Screen")
icon.source: "qrc:/icons/icons/ui/screen-share.svg" icon.source: "qrc:/icons/icons/ui/screen-share.svg"
text: qsTr("Screen")
visible: CallManager.screenShareSupported
onClicked: { onClicked: {
if (buttonLayout.validateMic()) { if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText; Settings.microphone = micCombo.currentText;
Settings.camera = cameraCombo.currentText; Settings.camera = cameraCombo.currentText;
var dialog = screenShareDialog.createObject(timelineRoot); var dialog = screenShareDialog.createObject(timelineRoot);
dialog.open(); dialog.open();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
@ -119,67 +114,50 @@ Popup {
} }
} }
} }
Button { Button {
text: qsTr("Cancel") text: qsTr("Cancel")
onClicked: { onClicked: {
close(); close();
} }
} }
} }
ColumnLayout { ColumnLayout {
spacing: 8 spacing: 8
RowLayout { RowLayout {
Layout.bottomMargin: cameraCombo.visible ? 0 : 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.bottomMargin: cameraCombo.visible ? 0 : 8
Image { Image {
Layout.preferredWidth: 22
Layout.preferredHeight: 22 Layout.preferredHeight: 22
Layout.preferredWidth: 22
source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + timelineRoot.palette.windowText source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + timelineRoot.palette.windowText
} }
ComboBox { ComboBox {
id: micCombo id: micCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.mics model: CallManager.mics
} }
} }
RowLayout { RowLayout {
visible: CallManager.cameras.length > 0 Layout.bottomMargin: 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.bottomMargin: 8 visible: CallManager.cameras.length > 0
Image { Image {
Layout.preferredWidth: 22
Layout.preferredHeight: 22 Layout.preferredHeight: 22
Layout.preferredWidth: 22
source: "image://colorimage/:/icons/icons/ui/video.svg?" + timelineRoot.palette.windowText source: "image://colorimage/:/icons/icons/ui/video.svg?" + timelineRoot.palette.windowText
} }
ComboBox { ComboBox {
id: cameraCombo id: cameraCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.cameras model: CallManager.cameras
} }
} }
} }
} }
background: Rectangle {
color: timelineRoot.palette.window
border.color: timelineRoot.palette.windowText
}
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -10,154 +8,129 @@ import QtQuick.Layouts 1.2
import im.nheko import im.nheko
Popup { Popup {
anchors.centerIn: parent
modal: true modal: true
palette: timelineRoot.palette
anchors.centerIn: parent; background: Rectangle {
border.color: timelineRoot.palette.windowText
color: timelineRoot.palette.window
}
Component.onCompleted: { Component.onCompleted: {
frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate); frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate);
} }
palette: timelineRoot.palette
ColumnLayout { ColumnLayout {
Label { Label {
Layout.topMargin: 16 Layout.alignment: Qt.AlignLeft
Layout.bottomMargin: 16 Layout.bottomMargin: 16
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.alignment: Qt.AlignLeft Layout.topMargin: 16
text: qsTr("Share desktop with %1?").arg(room.roomName)
color: timelineRoot.palette.windowText color: timelineRoot.palette.windowText
text: qsTr("Share desktop with %1?").arg(room.roomName)
} }
RowLayout { RowLayout {
Layout.bottomMargin: 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.bottomMargin: 8
Label { Label {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Window:")
color: timelineRoot.palette.windowText color: timelineRoot.palette.windowText
text: qsTr("Window:")
} }
ComboBox { ComboBox {
id: windowCombo id: windowCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.windowList() model: CallManager.windowList()
} }
} }
RowLayout { RowLayout {
Layout.bottomMargin: 8
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
Layout.bottomMargin: 8
Label { Label {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: qsTr("Frame rate:")
color: timelineRoot.palette.windowText color: timelineRoot.palette.windowText
text: qsTr("Frame rate:")
} }
ComboBox { ComboBox {
id: frameRateCombo id: frameRateCombo
Layout.fillWidth: true Layout.fillWidth: true
model: ["25", "20", "15", "10", "5", "2", "1"] model: ["25", "20", "15", "10", "5", "2", "1"]
} }
} }
GridLayout { GridLayout {
Layout.margins: 8
columns: 2 columns: 2
rowSpacing: 10 rowSpacing: 10
Layout.margins: 8
MatrixText { MatrixText {
text: qsTr("Include your camera picture-in-picture") text: qsTr("Include your camera picture-in-picture")
} }
ToggleButton { ToggleButton {
id: pipCheckBox id: pipCheckBox
enabled: CallManager.cameras.length > 0
checked: CallManager.cameras.length > 0 && Settings.screenSharePiP
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: CallManager.cameras.length > 0 && Settings.screenSharePiP
enabled: CallManager.cameras.length > 0
} }
MatrixText { MatrixText {
text: qsTr("Request remote camera")
ToolTip.text: qsTr("View your callee's camera like a regular video call") ToolTip.text: qsTr("View your callee's camera like a regular video call")
ToolTip.visible: hovered ToolTip.visible: hovered
text: qsTr("Request remote camera")
} }
ToggleButton { ToggleButton {
id: remoteVideoCheckBox id: remoteVideoCheckBox
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: Settings.screenShareRemoteVideo
ToolTip.text: qsTr("View your callee's camera like a regular video call") ToolTip.text: qsTr("View your callee's camera like a regular video call")
ToolTip.visible: hovered ToolTip.visible: hovered
checked: Settings.screenShareRemoteVideo
} }
MatrixText { MatrixText {
text: qsTr("Hide mouse cursor") text: qsTr("Hide mouse cursor")
} }
ToggleButton { ToggleButton {
id: hideCursorCheckBox id: hideCursorCheckBox
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: Settings.screenShareHideCursor checked: Settings.screenShareHideCursor
} }
} }
RowLayout { RowLayout {
Layout.margins: 8 Layout.margins: 8
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
Button { Button {
text: qsTr("Share")
icon.source: "qrc:/icons/icons/ui/screen-share.svg" icon.source: "qrc:/icons/icons/ui/screen-share.svg"
text: qsTr("Share")
onClicked: { onClicked: {
Settings.screenShareFrameRate = frameRateCombo.currentText; Settings.screenShareFrameRate = frameRateCombo.currentText;
Settings.screenSharePiP = pipCheckBox.checked; Settings.screenSharePiP = pipCheckBox.checked;
Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked; Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked;
Settings.screenShareHideCursor = hideCursorCheckBox.checked; Settings.screenShareHideCursor = hideCursorCheckBox.checked;
CallManager.sendInvite(room.roomId, Voip.SCREEN, windowCombo.currentIndex); CallManager.sendInvite(room.roomId, Voip.SCREEN, windowCombo.currentIndex);
close(); close();
} }
} }
Button { Button {
text: qsTr("Preview") text: qsTr("Preview")
onClicked: { onClicked: {
CallManager.previewWindow(windowCombo.currentIndex); CallManager.previewWindow(windowCombo.currentIndex);
} }
} }
Button { Button {
text: qsTr("Cancel") text: qsTr("Cancel")
onClicked: { onClicked: {
close(); close();
} }
} }
}
} }
background: Rectangle {
color: timelineRoot.palette.window
border.color: timelineRoot.palette.windowText
} }
} }

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9 import QtQuick 2.9
import org.freedesktop.gstreamer.GLVideoItem 1.0 import org.freedesktop.gstreamer.GLVideoItem 1.0

Loading…
Cancel
Save