Enable qmlformat in linting

pull/1603/head
Loren Burkholder 1 year ago
parent ee6ee46e7c
commit 814d9c6a17
No known key found for this signature in database
GPG Key ID: AB62CB312CEC2BBC
  1. 8
      .ci/format.sh
  2. 6
      resources/qml/CommunitiesList.qml
  3. 16
      resources/qml/Completer.qml
  4. 4
      resources/qml/ForwardCompleter.qml
  5. 2
      resources/qml/MatrixTextField.qml
  6. 20
      resources/qml/MessageInput.qml
  7. 87
      resources/qml/MessageView.qml
  8. 2
      resources/qml/PrivacyScreen.qml
  9. 2
      resources/qml/ReplyPopup.qml
  10. 68
      resources/qml/RoomList.qml
  11. 189
      resources/qml/TimelineBubbleMessageStyle.qml
  12. 183
      resources/qml/TimelineDefaultMessageStyle.qml
  13. 12
      resources/qml/TimelineMetadata.qml
  14. 37
      resources/qml/TimelineSectionHeader.qml
  15. 10
      resources/qml/TimelineView.qml
  16. 4
      resources/qml/TopBar.qml
  17. 125
      resources/qml/components/AdaptiveLayout.qml
  18. 16
      resources/qml/components/AdaptiveLayoutElement.qml
  19. 50
      resources/qml/components/AvatarListTile.qml
  20. 52
      resources/qml/components/FlatButton.qml
  21. 27
      resources/qml/components/MainWindowDialog.qml
  22. 22
      resources/qml/components/NhekoTabButton.qml
  23. 29
      resources/qml/components/NotificationBubble.qml
  24. 15
      resources/qml/components/PowerlevelIndicator.qml
  25. 139
      resources/qml/components/ReorderableListview.qml
  26. 63
      resources/qml/components/SpaceMenuLevel.qml
  27. 15
      resources/qml/components/TextButton.qml
  28. 35
      resources/qml/components/UserListRow.qml
  29. 37
      resources/qml/delegates/Encrypted.qml
  30. 41
      resources/qml/delegates/EncryptionEnabled.qml
  31. 48
      resources/qml/delegates/FileMessage.qml
  32. 93
      resources/qml/delegates/ImageMessage.qml
  33. 8
      resources/qml/delegates/NoticeMessage.qml
  34. 6
      resources/qml/delegates/Pill.qml
  35. 4
      resources/qml/delegates/Placeholder.qml
  36. 57
      resources/qml/delegates/PlayableMediaMessage.qml
  37. 37
      resources/qml/delegates/Redacted.qml
  38. 72
      resources/qml/delegates/Reply.qml
  39. 19
      resources/qml/delegates/TextMessage.qml
  40. 38
      resources/qml/device-verification/DeviceVerification.qml
  41. 31
      resources/qml/device-verification/DigitVerification.qml
  42. 9
      resources/qml/device-verification/EmojiElement.qml
  43. 687
      resources/qml/device-verification/EmojiVerification.qml
  44. 33
      resources/qml/device-verification/Failed.qml
  45. 18
      resources/qml/device-verification/NewVerificationRequest.qml
  46. 16
      resources/qml/device-verification/Success.qml
  47. 38
      resources/qml/device-verification/Waiting.qml
  48. 108
      resources/qml/dialogs/AliasEditor.qml
  49. 107
      resources/qml/dialogs/AllowedRoomsSettingsDialog.qml
  50. 126
      resources/qml/dialogs/ConfirmJoinRoomDialog.qml
  51. 83
      resources/qml/dialogs/CreateDirect.qml
  52. 122
      resources/qml/dialogs/CreateRoom.qml
  53. 116
      resources/qml/dialogs/EventExpirationDialog.qml
  54. 35
      resources/qml/dialogs/FallbackAuthDialog.qml
  55. 90
      resources/qml/dialogs/HiddenEventsDialog.qml
  56. 56
      resources/qml/dialogs/IgnoredUsers.qml
  57. 76
      resources/qml/dialogs/ImageOverlay.qml
  58. 226
      resources/qml/dialogs/ImagePackEditorDialog.qml
  59. 193
      resources/qml/dialogs/ImagePackSettingsDialog.qml
  60. 51
      resources/qml/dialogs/InputDialog.qml
  61. 189
      resources/qml/dialogs/InviteDialog.qml
  62. 63
      resources/qml/dialogs/JoinRoomDialog.qml
  63. 13
      resources/qml/dialogs/LeaveRoomDialog.qml
  64. 9
      resources/qml/dialogs/LogoutDialog.qml
  65. 862
      resources/qml/dialogs/PhoneNumberInputDialog.qml
  66. 324
      resources/qml/dialogs/PowerLevelEditor.qml
  67. 131
      resources/qml/dialogs/PowerLevelSpacesApplyDialog.qml
  68. 27
      resources/qml/dialogs/RawMessageDialog.qml
  69. 35
      resources/qml/dialogs/ReCaptchaDialog.qml
  70. 76
      resources/qml/dialogs/ReadReceipts.qml
  71. 39
      resources/qml/dialogs/ReportMessage.qml
  72. 182
      resources/qml/dialogs/RoomDirectory.qml
  73. 143
      resources/qml/dialogs/RoomMembers.qml
  74. 471
      resources/qml/dialogs/RoomSettingsDialog.qml
  75. 318
      resources/qml/dialogs/UserProfile.qml
  76. 185
      resources/qml/emoji/StickerPicker.qml
  77. 125
      resources/qml/pages/LoginPage.qml
  78. 137
      resources/qml/pages/RegisterPage.qml
  79. 141
      resources/qml/pages/UserSettingsPage.qml
  80. 45
      resources/qml/pages/WelcomePage.qml
  81. 33
      resources/qml/ui/NhekoSlider.qml
  82. 67
      resources/qml/ui/Ripple.qml
  83. 96
      resources/qml/ui/Snackbar.qml
  84. 97
      resources/qml/ui/Spinner.qml
  85. 134
      resources/qml/ui/TimelineEffects.qml
  86. 9
      resources/qml/ui/animations/BlinkAnimation.qml
  87. 115
      resources/qml/ui/media/MediaControls.qml
  88. 73
      resources/qml/voip/ActiveCallBar.qml
  89. 31
      resources/qml/voip/CallDevices.qml
  90. 95
      resources/qml/voip/CallInvite.qml
  91. 71
      resources/qml/voip/CallInviteBar.qml
  92. 20
      resources/qml/voip/DeviceError.qml
  93. 71
      resources/qml/voip/PlaceCall.qml
  94. 95
      resources/qml/voip/ScreenShare.qml

@ -15,6 +15,14 @@ do
clang-format -i "$f" clang-format -i "$f"
done; done;
if command -v /usr/lib64/qt6/bin/qmlformat &> /dev/null; then
/usr/lib64/qt6/bin/qmlformat -i $QML_FILES
elif command -v /usr/lib/qt6/bin/qmlformat &> /dev/null; then
/usr/lib/qt6/bin/qmlformat -i $QML_FILES
else
echo "No qmlformat found, skipping check!"
fi
git diff --exit-code git diff --exit-code
if command -v /usr/lib64/qt6/bin/qmllint &> /dev/null; then if command -v /usr/lib64/qt6/bin/qmllint &> /dev/null; then

@ -35,9 +35,9 @@ Page {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
boundsBehavior: Flickable.StopAtBounds
height: parent.height height: parent.height
model: Communities.filtered() model: Communities.filtered()
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
id: scrollbar id: scrollbar
@ -138,10 +138,11 @@ Page {
id: avatar id: avatar
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: avatarSize
Layout.preferredWidth: avatarSize
color: communityItem.backgroundColor color: communityItem.backgroundColor
displayName: model.displayName displayName: model.displayName
enabled: false enabled: false
Layout.preferredHeight: avatarSize
roomid: model.id roomid: model.id
textColor: model.avatarUrl?.startsWith(":/") == true ? communityItem.unimportantText : communityItem.importantText textColor: model.avatarUrl?.startsWith(":/") == true ? communityItem.unimportantText : communityItem.importantText
url: { url: {
@ -152,7 +153,6 @@ Page {
else else
return ""; return "";
} }
Layout.preferredWidth: avatarSize
NotificationBubble { NotificationBubble {
anchors.bottom: avatar.bottom anchors.bottom: avatar.bottom

@ -149,10 +149,10 @@ Control {
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
displayName: model.displayName
enabled: false
Layout.preferredHeight: popup.avatarHeight Layout.preferredHeight: popup.avatarHeight
Layout.preferredWidth: popup.avatarWidth Layout.preferredWidth: popup.avatarWidth
displayName: model.displayName
enabled: false
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.userid userid: model.userid
} }
@ -180,14 +180,14 @@ Control {
visible: !!model.unicode visible: !!model.unicode
} }
Avatar { Avatar {
Layout.preferredHeight: popup.avatarHeight
Layout.preferredWidth: popup.avatarWidth
crop: false crop: false
displayName: model.shortcode displayName: model.shortcode
enabled: false enabled: false
Layout.preferredHeight: popup.avatarHeight
//userid: model.shortcode //userid: model.shortcode
url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/") url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/")
visible: !model.unicode visible: !model.unicode
Layout.preferredWidth: popup.avatarWidth
} }
Label { Label {
Layout.leftMargin: Nheko.paddingSmall Layout.leftMargin: Nheko.paddingSmall
@ -227,12 +227,12 @@ Control {
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
Layout.preferredHeight: popup.avatarHeight
Layout.preferredWidth: popup.avatarWidth
displayName: model.roomName displayName: model.roomName
enabled: false enabled: false
Layout.preferredHeight: popup.avatarHeight
roomid: model.roomid roomid: model.roomid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
Layout.preferredWidth: popup.avatarWidth
} }
Label { Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
@ -251,12 +251,12 @@ Control {
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
Layout.preferredHeight: popup.avatarHeight
Layout.preferredWidth: popup.avatarWidth
displayName: model.roomName displayName: model.roomName
enabled: false enabled: false
Layout.preferredHeight: popup.avatarHeight
roomid: model.roomid roomid: model.roomid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
Layout.preferredWidth: popup.avatarWidth
} }
Label { Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text color: model.index == popup.currentIndex ? palette.highlightedText : palette.text

@ -55,8 +55,8 @@ Popup {
id: replyPreview id: replyPreview
eventId: mid eventId: mid
userColor: TimelineManager.userColor(replyPreview.userId, palette.window)
maxWidth: parent.width maxWidth: parent.width
userColor: TimelineManager.userColor(replyPreview.userId, palette.window)
} }
MatrixTextField { MatrixTextField {
id: roomTextInput id: roomTextInput
@ -64,7 +64,7 @@ Popup {
color: palette.text color: palette.text
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2 width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
Keys.onPressed: (event) => { Keys.onPressed: event => {
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;
completerPopup.up(); completerPopup.up();

@ -140,8 +140,8 @@ ColumnLayout {
id: blueBar id: blueBar
Layout.fillWidth: true Layout.fillWidth: true
color: palette.highlight
Layout.preferredHeight: 1 Layout.preferredHeight: 1
color: palette.highlight
Rectangle { Rectangle {
id: blackBar id: blackBar

@ -44,14 +44,14 @@ Rectangle {
ImageButton { ImageButton {
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : (CallManager.isOnCallOnOtherDevice ? qsTr("Already on a call") : qsTr("Place a call")) ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : (CallManager.isOnCallOnOtherDevice ? qsTr("Already on a call") : qsTr("Place a call"))
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
hoverEnabled: true 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"
opacity: (CallManager.haveCallInvite || CallManager.isOnCallOnOtherDevice) ? 0.3 : 1 opacity: (CallManager.haveCallInvite || CallManager.isOnCallOnOtherDevice) ? 0.3 : 1
visible: CallManager.callsSupported && showAllButtons visible: CallManager.callsSupported && showAllButtons
Layout.preferredWidth: 22
onClicked: { onClicked: {
if (room) { if (room) {
@ -72,13 +72,13 @@ Rectangle {
ImageButton { ImageButton {
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.text: qsTr("Send a file") ToolTip.text: qsTr("Send a file")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/attach.svg" image: ":/icons/icons/ui/attach.svg"
visible: showAllButtons visible: showAllButtons
Layout.preferredWidth: 22
onClicked: room.input.openFileSelection() onClicked: room.input.openFileSelection()
@ -393,13 +393,13 @@ Rectangle {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.text: qsTr("Stickers") ToolTip.text: qsTr("Stickers")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/sticky-note-solid.svg" image: ":/icons/icons/ui/sticky-note-solid.svg"
visible: showAllButtons visible: showAllButtons
Layout.preferredWidth: 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(row); room.input.sticker(row);
@ -417,12 +417,12 @@ Rectangle {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.text: qsTr("Emoji") ToolTip.text: qsTr("Emoji")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/smile.svg" image: ":/icons/icons/ui/smile.svg"
Layout.preferredWidth: 22
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, room.roomId, function (plaintext, markdown) { onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, room.roomId, function (plaintext, markdown) {
messageInput.insert(messageInput.cursorPosition, markdown); messageInput.insert(messageInput.cursorPosition, markdown);
@ -438,13 +438,13 @@ Rectangle {
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
Layout.preferredHeight: 22
Layout.preferredWidth: 22
Layout.rightMargin: 8 Layout.rightMargin: 8
ToolTip.text: qsTr("Send") ToolTip.text: qsTr("Send")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/send.svg" image: ":/icons/icons/ui/send.svg"
Layout.preferredWidth: 22
onClicked: { onClicked: {
room.input.send(); room.input.send();

@ -16,8 +16,8 @@ Item {
property int availableWidth: width property int availableWidth: width
property int padding: Nheko.paddingMedium property int padding: Nheko.paddingMedium
property string searchString: ""
property Room roommodel: room property Room roommodel: room
property string searchString: ""
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu // HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
Connections { Connections {
@ -41,6 +41,7 @@ Item {
property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - (scrollbar.interactive ? scrollbar.width : 0) property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - (scrollbar.interactive ? scrollbar.width : 0)
readonly property alias filteringInProgress: filteredTimeline.filteringInProgress readonly property alias filteringInProgress: filteredTimeline.filteringInProgress
property int lastScrollPos: 0
ScrollBar.vertical: scrollbar ScrollBar.vertical: scrollbar
anchors.fill: parent anchors.fill: parent
@ -49,6 +50,7 @@ Item {
//onModelChanged: if (room) room.sendReset() //onModelChanged: if (room) room.sendReset()
//reuseItems: true //reuseItems: true
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
delegate: Settings.bubbles ? bubbleMessageStyle : defaultMessageStyle
displayMarginBeginning: height / 4 displayMarginBeginning: height / 4
displayMarginEnd: height / 4 displayMarginEnd: height / 4
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room
@ -56,35 +58,6 @@ Item {
spacing: 2 spacing: 2
verticalLayoutDirection: ListView.BottomToTop verticalLayoutDirection: ListView.BottomToTop
property int lastScrollPos: 0
// Fixup the scroll position when the height changes. Without this, the view is kept around the center of the currently visible content, while we usually want to stick to the bottom.
onMovementEnded: lastScrollPos = (contentY+height)
onModelChanged: lastScrollPos = (contentY+height)
onHeightChanged: contentY = (lastScrollPos-height)
Component {
id: defaultMessageStyle
TimelineDefaultMessageStyle {
messageActions: messageActionsC
messageContextMenu: messageContextMenuC
replyContextMenu: replyContextMenuC
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
}
}
Component {
id: bubbleMessageStyle
TimelineBubbleMessageStyle {
messageActions: messageActionsC
messageContextMenu: messageContextMenuC
replyContextMenu: replyContextMenuC
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
}
}
delegate: Settings.bubbles ? bubbleMessageStyle : defaultMessageStyle
footer: Item { footer: Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Nheko.paddingLarge anchors.margins: Nheko.paddingLarge
@ -109,7 +82,32 @@ Item {
if (atYEnd && room) if (atYEnd && room)
model.currentIndex = 0; model.currentIndex = 0;
} }
onHeightChanged: contentY = (lastScrollPos - height)
onModelChanged: lastScrollPos = (contentY + height)
// Fixup the scroll position when the height changes. Without this, the view is kept around the center of the currently visible content, while we usually want to stick to the bottom.
onMovementEnded: lastScrollPos = (contentY + height)
Component {
id: defaultMessageStyle
TimelineDefaultMessageStyle {
messageActions: messageActionsC
messageContextMenu: messageContextMenuC
replyContextMenu: replyContextMenuC
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
}
}
Component {
id: bubbleMessageStyle
TimelineBubbleMessageStyle {
messageActions: messageActionsC
messageContextMenu: messageContextMenuC
replyContextMenu: replyContextMenuC
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
}
}
TimelineFilter { TimelineFilter {
id: filteredTimeline id: filteredTimeline
@ -124,13 +122,13 @@ Item {
// use comma to update on scroll // use comma to update on scroll
property alias model: row.model property alias model: row.model
anchors.bottom: attached?.top
anchors.right: attached?.right
hoverEnabled: true hoverEnabled: true
padding: Nheko.paddingSmall padding: Nheko.paddingSmall
parent: chat.contentItem
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered) visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered)
z: 10 z: 10
parent: chat.contentItem
anchors.bottom: attached?.top
anchors.right: attached?.right
background: Rectangle { background: Rectangle {
border.color: palette.buttonText border.color: palette.buttonText
@ -200,6 +198,7 @@ Item {
} }
} }
ImageButton { ImageButton {
Layout.preferredWidth: 16
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Edit") ToolTip.text: qsTr("Edit")
ToolTip.visible: hovered ToolTip.visible: hovered
@ -207,7 +206,6 @@ Item {
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/edit.svg" image: ":/icons/icons/ui/edit.svg"
visible: !!row.model && row.model.isEditable visible: !!row.model && row.model.isEditable
Layout.preferredWidth: 16
onClicked: { onClicked: {
if (row.model.isEditable) if (row.model.isEditable)
@ -217,13 +215,13 @@ Item {
ImageButton { ImageButton {
id: reactButton id: reactButton
Layout.preferredWidth: 16
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("React") ToolTip.text: qsTr("React")
ToolTip.visible: hovered ToolTip.visible: hovered
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/smile-add.svg" image: ":/icons/icons/ui/smile-add.svg"
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
Layout.preferredWidth: 16
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, room.roomId, function (plaintext, markdown) { onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, room.roomId, function (plaintext, markdown) {
var event_id = row.model ? row.model.eventId : ""; var event_id = row.model ? row.model.eventId : "";
@ -232,28 +230,29 @@ Item {
}) })
} }
ImageButton { ImageButton {
Layout.preferredWidth: 16
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: (row.model && row.model.threadId) ? qsTr("Reply in thread") : qsTr("New thread") ToolTip.text: (row.model && row.model.threadId) ? qsTr("Reply in thread") : qsTr("New thread")
ToolTip.visible: hovered ToolTip.visible: hovered
hoverEnabled: true hoverEnabled: true
image: (row.model && row.model.threadId) ? ":/icons/icons/ui/thread.svg" : ":/icons/icons/ui/new-thread.svg" image: (row.model && row.model.threadId) ? ":/icons/icons/ui/thread.svg" : ":/icons/icons/ui/new-thread.svg"
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
Layout.preferredWidth: 16
onClicked: room.thread = (row.model.threadId || row.model.eventId) onClicked: room.thread = (row.model.threadId || row.model.eventId)
} }
ImageButton { ImageButton {
Layout.preferredWidth: 16
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Reply") ToolTip.text: qsTr("Reply")
ToolTip.visible: hovered ToolTip.visible: hovered
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/reply.svg" image: ":/icons/icons/ui/reply.svg"
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
Layout.preferredWidth: 16
onClicked: room.reply = row.model.eventId onClicked: room.reply = row.model.eventId
} }
ImageButton { ImageButton {
Layout.preferredWidth: 16
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Go to message") ToolTip.text: qsTr("Go to message")
ToolTip.visible: hovered ToolTip.visible: hovered
@ -261,7 +260,6 @@ Item {
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/go-to.svg" image: ":/icons/icons/ui/go-to.svg"
visible: !!row.model && filteredTimeline.filterByContent visible: !!row.model && filteredTimeline.filterByContent
Layout.preferredWidth: 16
onClicked: { onClicked: {
topBar.searchString = ""; topBar.searchString = "";
@ -271,12 +269,12 @@ Item {
ImageButton { ImageButton {
id: optionsButton id: optionsButton
Layout.preferredWidth: 16
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Options") ToolTip.text: qsTr("Options")
ToolTip.visible: hovered ToolTip.visible: hovered
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/options.svg" image: ":/icons/icons/ui/options.svg"
Layout.preferredWidth: 16
onClicked: messageContextMenuC.show(row.model.eventId, row.model.threadId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton) onClicked: messageContextMenuC.show(row.model.eventId, row.model.threadId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
} }
@ -413,9 +411,9 @@ Item {
Component { Component {
id: reportDialog id: reportDialog
ReportMessage {} ReportMessage {
}
} }
Platform.MenuItem { Platform.MenuItem {
enabled: visible enabled: visible
text: qsTr("Go to &message") text: qsTr("Go to &message")
@ -523,10 +521,13 @@ Item {
} }
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Report message")
enabled: visible enabled: visible
text: qsTr("Report message")
onTriggered: function () { onTriggered: function () {
var dialog = reportDialog.createObject(timelineRoot, {"eventId": messageContextMenu.eventId}); var dialog = reportDialog.createObject(timelineRoot, {
"eventId": messageContextMenu.eventId
});
dialog.show(); dialog.show();
dialog.forceActiveFocus(); dialog.forceActiveFocus();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);

@ -50,8 +50,8 @@ Item {
name: "Visible" name: "Visible"
PropertyChanges { PropertyChanges {
screenSaver.visible: true
screenSaver.opacity: 1 screenSaver.opacity: 1
screenSaver.visible: true
} }
}, },
State { State {

@ -30,9 +30,9 @@ Rectangle {
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: Nheko.paddingSmall anchors.topMargin: Nheko.paddingSmall
eventId: room?.reply ?? "" eventId: room?.reply ?? ""
maxWidth: parent.width - anchors.leftMargin - anchors.rightMargin
userColor: TimelineManager.userColor(modelData.userId, palette.window) userColor: TimelineManager.userColor(modelData.userId, palette.window)
visible: room && room.reply visible: room && room.reply
maxWidth: parent.width - anchors.leftMargin - anchors.rightMargin
} }
ImageButton { ImageButton {
id: closeReplyButton id: closeReplyButton

@ -26,8 +26,8 @@ Page {
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
color: Nheko.theme.separator
Layout.preferredHeight: 1 Layout.preferredHeight: 1
color: Nheko.theme.separator
} }
Pane { Pane {
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
@ -45,11 +45,11 @@ Page {
ImageButton { ImageButton {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Start a new chat") ToolTip.text: qsTr("Start a new chat")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
Layout.preferredWidth: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/add-square-button.svg" image: ":/icons/icons/ui/add-square-button.svg"
@ -97,11 +97,11 @@ Page {
ImageButton { ImageButton {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Room directory") ToolTip.text: qsTr("Room directory")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
Layout.preferredWidth: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/room-directory.svg" image: ":/icons/icons/ui/room-directory.svg"
visible: !collapsed visible: !collapsed
@ -115,11 +115,11 @@ Page {
ImageButton { ImageButton {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Search rooms (Ctrl+K)") ToolTip.text: qsTr("Search rooms (Ctrl+K)")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
Layout.preferredWidth: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/search.svg" image: ":/icons/icons/ui/search.svg"
ripple: false ripple: false
@ -139,11 +139,11 @@ Page {
ImageButton { ImageButton {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: 22
Layout.preferredWidth: 22
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("User settings") ToolTip.text: qsTr("User settings")
ToolTip.visible: hovered ToolTip.visible: hovered
Layout.preferredHeight: 22
Layout.preferredWidth: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/settings.svg" image: ":/icons/icons/ui/settings.svg"
ripple: false ripple: false
@ -268,37 +268,45 @@ Page {
} }
Platform.MenuSeparator { Platform.MenuSeparator {
} }
Platform.MenuItemGroup { Platform.MenuItemGroup {
id: onlineStateGroup id: onlineStateGroup
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Automatic online status")
group: onlineStateGroup
checkable: true checkable: true
checked: Settings.presence == Settings.AutomaticPresence checked: Settings.presence == Settings.AutomaticPresence
onTriggered: if (checked) Settings.presence = Settings.AutomaticPresence group: onlineStateGroup
text: qsTr("Automatic online status")
onTriggered: if (checked)
Settings.presence = Settings.AutomaticPresence
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Online")
group: onlineStateGroup
checkable: true checkable: true
checked: Settings.presence == Settings.Online checked: Settings.presence == Settings.Online
onTriggered: if (checked) Settings.presence = Settings.Online group: onlineStateGroup
text: qsTr("Online")
onTriggered: if (checked)
Settings.presence = Settings.Online
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Unavailable")
group: onlineStateGroup
checkable: true checkable: true
checked: Settings.presence == Settings.Unavailable checked: Settings.presence == Settings.Unavailable
onTriggered: if (checked) Settings.presence = Settings.Unavailable group: onlineStateGroup
text: qsTr("Unavailable")
onTriggered: if (checked)
Settings.presence = Settings.Unavailable
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Offline")
group: onlineStateGroup
checkable: true checkable: true
checked: Settings.presence == Settings.Offline checked: Settings.presence == Settings.Offline
onTriggered: if (checked) Settings.presence = Settings.Offline group: onlineStateGroup
text: qsTr("Offline")
onTriggered: if (checked)
Settings.presence = Settings.Offline
} }
} }
TapHandler { TapHandler {
@ -319,8 +327,8 @@ Page {
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
color: Nheko.theme.separator
Layout.preferredHeight: 2 Layout.preferredHeight: 2
color: Nheko.theme.separator
} }
Rectangle { Rectangle {
id: unverifiedStuffBubble id: unverifiedStuffBubble
@ -366,14 +374,14 @@ Page {
id: closeUnverifiedBubble id: closeUnverifiedBubble
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
Layout.preferredHeight: fontMetrics.font.pixelSize
Layout.preferredWidth: fontMetrics.font.pixelSize
Layout.rightMargin: Nheko.paddingMedium Layout.rightMargin: Nheko.paddingMedium
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Close") ToolTip.text: qsTr("Close")
ToolTip.visible: closeUnverifiedBubble.hovered ToolTip.visible: closeUnverifiedBubble.hovered
Layout.preferredHeight: fontMetrics.font.pixelSize
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg" image: ":/icons/icons/ui/dismiss.svg"
Layout.preferredWidth: fontMetrics.font.pixelSize
onClicked: unverifiedStuffBubble.visible = false onClicked: unverifiedStuffBubble.visible = false
} }
@ -398,8 +406,8 @@ Page {
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
color: Nheko.theme.separator
Layout.preferredHeight: 1 Layout.preferredHeight: 1
color: Nheko.theme.separator
visible: unverifiedStuffBubble.visible visible: unverifiedStuffBubble.visible
} }
} }
@ -436,9 +444,9 @@ Page {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
boundsBehavior: Flickable.StopAtBounds
height: parent.height height: parent.height
model: Rooms model: Rooms
boundsBehavior: Flickable.StopAtBounds
//reuseItems: true //reuseItems: true
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
@ -550,13 +558,13 @@ Page {
id: avatar id: avatar
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: avatarSize
Layout.preferredWidth: avatarSize
displayName: roomName displayName: roomName
enabled: false enabled: false
roomid: roomId roomid: roomId
url: avatarUrl.replace("mxc://", "image://MxcImage/") url: avatarUrl.replace("mxc://", "image://MxcImage/")
userid: isDirect ? directChatOtherUserId : "" userid: isDirect ? directChatOtherUserId : ""
Layout.preferredWidth: avatarSize
Layout.preferredHeight: avatarSize
NotificationBubble { NotificationBubble {
id: collapsedNotificationBubble id: collapsedNotificationBubble
@ -576,8 +584,8 @@ Page {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.minimumWidth: 100 Layout.minimumWidth: 100
Layout.preferredWidth: roomItem.width - avatar.width
Layout.preferredHeight: avatar.height Layout.preferredHeight: avatar.height
Layout.preferredWidth: roomItem.width - avatar.width
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
visible: !collapsed visible: !collapsed

@ -9,54 +9,53 @@ import im.nheko
TimelineEvent { TimelineEvent {
id: wrapper id: wrapper
ListView.delayRemove: true
width: chat.delegateMaxWidth property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header
height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10)
anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter property int bubbleMargin: 40
//room: chatRoot.roommodel //room: chatRoot.roommodel
required property var day required property var day
required property bool isSender property alias hovered: messageHover.hovered
required property int index required property int index
required property bool isEditable
required property bool isEdited
required property bool isEncrypted
required property bool isSender
required property Item messageActions
required property QtObject messageContextMenu
required property int notificationlevel
property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day) property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day)
property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent) property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent)
property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId) property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId)
required property date timestamp
required property string userId
required property string userName
required property string threadId
required property int userPowerlevel
required property bool isEdited
required property bool isEncrypted
required property var reactions required property var reactions
required property QtObject replyContextMenu
property bool scrolledToThis: false
required property int status required property int status
required property string threadId
required property date timestamp
required property int trustlevel required property int trustlevel
required property int notificationlevel
required property int type required property int type
required property bool isEditable required property string userId
required property string userName
required property QtObject messageContextMenu required property int userPowerlevel
required property QtObject replyContextMenu
required property Item messageActions
property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header
property alias hovered: messageHover.hovered
property bool scrolledToThis: false
ListView.delayRemove: true
anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter
height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10)
mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0) + 4 mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0) + 4
replyInset: mainInset + 4 + Nheko.paddingSmall
property int bubbleMargin: 40
maxWidth: chat.delegateMaxWidth - avatarMargin - bubbleMargin maxWidth: chat.delegateMaxWidth - avatarMargin - bubbleMargin
replyInset: mainInset + 4 + Nheko.paddingSmall
width: chat.delegateMaxWidth
data: [ data: [
Loader { Loader {
id: section id: section
active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent
visible: status == Loader.Ready
z: 4
//asynchronous: true //asynchronous: true
sourceComponent: TimelineSectionHeader { sourceComponent: TimelineSectionHeader {
day: wrapper.day day: wrapper.day
@ -71,13 +70,12 @@ TimelineEvent {
userName: wrapper.userName userName: wrapper.userName
userPowerlevel: wrapper.userPowerlevel userPowerlevel: wrapper.userPowerlevel
} }
visible: status == Loader.Ready },
z: 4
},
Rectangle { Rectangle {
anchors.fill: gridContainer
property color threadColor: TimelineManager.userColor(wrapper.threadId, palette.base)
property color threadBackgroundColor: wrapper.threadId ? Qt.tint(palette.base, Qt.hsla(threadColor.hslHue, 0.7, threadColor.hslLightness, 0.1)) : "transparent" property color threadBackgroundColor: wrapper.threadId ? Qt.tint(palette.base, Qt.hsla(threadColor.hslHue, 0.7, threadColor.hslLightness, 0.1)) : "transparent"
property color threadColor: TimelineManager.userColor(wrapper.threadId, palette.base)
anchors.fill: gridContainer
color: (Settings.messageHoverHighlight && messageHover.hovered) ? palette.alternateBase : threadBackgroundColor color: (Settings.messageHoverHighlight && messageHover.hovered) ? palette.alternateBase : threadBackgroundColor
// this looks better without margins // this looks better without margins
@ -91,8 +89,8 @@ TimelineEvent {
}, },
Rectangle { Rectangle {
id: scrollHighlight id: scrollHighlight
anchors.fill: gridContainer
anchors.fill: gridContainer
color: palette.highlight color: palette.highlight
enabled: false enabled: false
opacity: 0 opacity: 0
@ -133,37 +131,43 @@ TimelineEvent {
Item { Item {
id: gridContainer id: gridContainer
width: wrapper.width - wrapper.avatarMargin
implicitHeight: messageBubble.implicitHeight implicitHeight: messageBubble.implicitHeight
width: wrapper.width - wrapper.avatarMargin
x: wrapper.avatarMargin x: wrapper.avatarMargin
y: section.visible && section.active ? section.y + section.height : 0 y: section.visible && section.active ? section.y + section.height : 0
HoverHandler { HoverHandler {
id: messageHover id: messageHover
blocking: false blocking: false
onHoveredChanged: () => { onHoveredChanged: () => {
if (!Settings.mobileMode && hovered) { if (!Settings.mobileMode && hovered) {
if (!messageActions.hovered) { if (!messageActions.hovered) {
messageActions.model = wrapper; messageActions.model = wrapper;
messageActions.attached = wrapper; messageActions.attached = wrapper;
messageActions.anchors.bottomMargin = -gridContainer.y messageActions.anchors.bottomMargin = -gridContainer.y;
//messageActions.anchors.rightMargin = metadata.width //messageActions.anchors.rightMargin = metadata.width
} }
} }
} }
} }
AbstractButton { AbstractButton {
id: messageBubble id: messageBubble
property color userColor: TimelineManager.userColor(wrapper.main?.userId ?? '', palette.base)
anchors.horizontalCenter: wrapper.isStateEvent ? parent.horizontalCenter : undefined
anchors.left: (wrapper.isStateEvent || wrapper.isSender) ? undefined : parent.left // qmllint disable Quick.anchor-combinations anchors.left: (wrapper.isStateEvent || wrapper.isSender) ? undefined : parent.left // qmllint disable Quick.anchor-combinations
anchors.right: (wrapper.isStateEvent || !wrapper.isSender) ? undefined : parent.right anchors.right: (wrapper.isStateEvent || !wrapper.isSender) ? undefined : parent.right
anchors.horizontalCenter: wrapper.isStateEvent ? parent.horizontalCenter : undefined padding: wrapper.isStateEvent ? 0 : 4
property color userColor: TimelineManager.userColor(wrapper.main?.userId ?? '', palette.base)
background: Rectangle {
border.color: Nheko.theme.red
border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0
color: !wrapper.isStateEvent ? Qt.tint(palette.base, Qt.hsla(messageBubble.userColor.hslHue, wrapper.hovered ? 0.8 : 0.5, messageBubble.userColor.hslLightness, 0.2)) : "transparent"
radius: 4
}
contentItem: Item { contentItem: Item {
id: contentPlacementContainer id: contentPlacementContainer
@ -173,52 +177,47 @@ TimelineEvent {
// property bool fitsMetadataInside: wrapper.main?.positionAt ? (wrapper.main.positionAt(wrapper.main.width, wrapper.main.height - 4) == wrapper.main.positionAt(wrapper.main.width - metadata.width, wrapper.main.height - 4)) : false // property bool fitsMetadataInside: wrapper.main?.positionAt ? (wrapper.main.positionAt(wrapper.main.width, wrapper.main.height - 4) == wrapper.main.positionAt(wrapper.main.width - metadata.width, wrapper.main.height - 4)) : false
property bool fitsMetadataInside: false property bool fitsMetadataInside: false
implicitWidth: Math.max((wrapper.reply?.width ?? 0) + wrapper.replyInset, (wrapper.main?.width ?? 0) + wrapper.mainInset + ((fitsMetadata && !fitsMetadataInside) ? metadata.width : 0))
implicitHeight: contentColumn.implicitHeight + ((fitsMetadata || fitsMetadataInside) ? 0 : metadata.height) implicitHeight: contentColumn.implicitHeight + ((fitsMetadata || fitsMetadataInside) ? 0 : metadata.height)
implicitWidth: Math.max((wrapper.reply?.width ?? 0) + wrapper.replyInset, (wrapper.main?.width ?? 0) + wrapper.mainInset + ((fitsMetadata && !fitsMetadataInside) ? metadata.width : 0))
TimelineMetadata { TimelineMetadata {
id: metadata id: metadata
scaling: 0.75
anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right
visible: !wrapper.isStateEvent
eventId: wrapper.eventId eventId: wrapper.eventId
status: wrapper.status
trustlevel: wrapper.trustlevel
isEdited: wrapper.isEdited isEdited: wrapper.isEdited
isEncrypted: wrapper.isEncrypted isEncrypted: wrapper.isEncrypted
room: wrapper.room
scaling: 0.75
status: wrapper.status
threadId: wrapper.threadId threadId: wrapper.threadId
timestamp: wrapper.timestamp timestamp: wrapper.timestamp
room: wrapper.room trustlevel: wrapper.trustlevel
visible: !wrapper.isStateEvent
} }
Column { Column {
id: contentColumn id: contentColumn
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
data: [replyRow, wrapper.main]
AbstractButton { AbstractButton {
id: replyRow id: replyRow
visible: wrapper.reply
height: replyLine.height
anchors.left: parent.left
anchors.right: parent.right
property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base) property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base)
anchors.left: parent.left
anchors.right: parent.right
clip: true clip: true
height: replyLine.height
visible: wrapper.reply
NhekoCursorShape { background: Rectangle {
anchors.fill: parent //width: replyRow.implicitContentWidth
cursorShape: Qt.PointingHandCursor color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
} }
contentItem: Row { contentItem: Row {
id: replyRowLay id: replyRowLay
@ -226,94 +225,81 @@ TimelineEvent {
Rectangle { Rectangle {
id: replyLine id: replyLine
height: Math.min( wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
color: replyRow.userColor color: replyRow.userColor
height: Math.min(wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
width: 4 width: 4
} }
Column { Column {
spacing: 0
id: replyCol id: replyCol
data: [replyUserButton, wrapper.reply,]
spacing: 0
AbstractButton { AbstractButton {
id: replyUserButton id: replyUserButton
contentItem: Label { contentItem: Label {
id: userName_ id: userName_
text: wrapper.reply?.userName ?? ''
color: replyRow.userColor color: replyRow.userColor
text: wrapper.reply?.userName ?? ''
textFormat: Text.RichText textFormat: Text.RichText
width: wrapper.maxWidth width: wrapper.maxWidth
//elideWidth: wrapper.maxWidth //elideWidth: wrapper.maxWidth
} }
onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId) onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId)
} }
data: [
replyUserButton,
wrapper.reply,
]
} }
} }
background: Rectangle {
//width: replyRow.implicitContentWidth
color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
}
onClicked: { onClicked: {
let link = wrapper.reply.hoveredLink let link = wrapper.reply.hoveredLink;
if (link) { if (link) {
Nheko.openLink(link) Nheko.openLink(link);
} else { } else {
console.log("Scrolling to "+wrapper.replyTo); console.log("Scrolling to " + wrapper.replyTo);
wrapper.room.showEvent(wrapper.replyTo) wrapper.room.showEvent(wrapper.replyTo);
} }
} }
onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX-replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo) onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX - replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo)
NhekoCursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
TapHandler { TapHandler {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onSingleTapped: (eventPoint) => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x-replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo)
gesturePolicy: TapHandler.ReleaseWithinBounds
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
gesturePolicy: TapHandler.ReleaseWithinBounds
onSingleTapped: eventPoint => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x - replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo)
} }
} }
data: [replyRow, wrapper.main]
} }
}
padding: wrapper.isStateEvent ? 0 : 4
background: Rectangle {
color: !wrapper.isStateEvent ? Qt.tint(palette.base, Qt.hsla(messageBubble.userColor.hslHue, wrapper.hovered ? 0.8 : 0.5, messageBubble.userColor.hslLightness, 0.2)) : "transparent"
radius: 4
border.color: Nheko.theme.red
border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0
} }
} }
DragHandler { DragHandler {
id: replyDragHandler id: replyDragHandler
yAxis.enabled: false
xAxis.enabled: true xAxis.enabled: true
xAxis.minimum: wrapper.avatarMargin - 100
xAxis.maximum: wrapper.avatarMargin xAxis.maximum: wrapper.avatarMargin
xAxis.minimum: wrapper.avatarMargin - 100
yAxis.enabled: false
onActiveChanged: { onActiveChanged: {
if (!replyDragHandler.active) { if (!replyDragHandler.active) {
if (replyDragHandler.xAxis.minimum <= replyDragHandler.xAxis.activeValue + 1) { if (replyDragHandler.xAxis.minimum <= replyDragHandler.xAxis.activeValue + 1) {
wrapper.room.reply = wrapper.eventId wrapper.room.reply = wrapper.eventId;
} }
gridContainer.x = wrapper.avatarMargin; gridContainer.x = wrapper.avatarMargin;
} }
} }
} }
TapHandler { TapHandler {
onDoubleTapped: wrapper.room.reply = wrapper.eventId onDoubleTapped: wrapper.room.reply = wrapper.eventId
} }
}, },
Reactions { Reactions {
id: reactionRow id: reactionRow
@ -347,4 +333,3 @@ TimelineEvent {
} }
] ]
} }

@ -9,52 +9,52 @@ import im.nheko
TimelineEvent { TimelineEvent {
id: wrapper id: wrapper
ListView.delayRemove: true
width: chat.delegateMaxWidth property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header
height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10)
anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter
//room: chatRoot.roommodel //room: chatRoot.roommodel
required property var day required property var day
required property bool isSender property alias hovered: messageHover.hovered
required property int index required property int index
required property bool isEditable
required property bool isEdited
required property bool isEncrypted
required property bool isSender
required property Item messageActions
required property QtObject messageContextMenu
required property int notificationlevel
property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day) property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day)
property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent) property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent)
property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId) property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId)
required property date timestamp
required property string userId
required property string userName
required property string threadId
required property int userPowerlevel
required property bool isEdited
required property bool isEncrypted
required property var reactions required property var reactions
required property QtObject replyContextMenu
property bool scrolledToThis: false
required property int status required property int status
required property string threadId
required property date timestamp
required property int trustlevel required property int trustlevel
required property int notificationlevel
required property int type required property int type
required property bool isEditable required property string userId
required property string userName
required property QtObject messageContextMenu required property int userPowerlevel
required property QtObject replyContextMenu
required property Item messageActions
property int avatarMargin: (wrapper.isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) // align bubble with section header
property alias hovered: messageHover.hovered
property bool scrolledToThis: false
ListView.delayRemove: true
anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter
height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10)
mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0) mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0)
replyInset: mainInset + 4 + Nheko.paddingSmall
maxWidth: chat.delegateMaxWidth - avatarMargin - metadata.width maxWidth: chat.delegateMaxWidth - avatarMargin - metadata.width
replyInset: mainInset + 4 + Nheko.paddingSmall
width: chat.delegateMaxWidth
data: [ data: [
Loader { Loader {
id: section id: section
active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent active: wrapper.previousMessageUserId !== wrapper.userId || wrapper.previousMessageDay !== wrapper.day || wrapper.previousMessageIsStateEvent !== wrapper.isStateEvent
visible: status == Loader.Ready
z: 4
//asynchronous: true //asynchronous: true
sourceComponent: TimelineSectionHeader { sourceComponent: TimelineSectionHeader {
day: wrapper.day day: wrapper.day
@ -69,9 +69,7 @@ TimelineEvent {
userName: wrapper.userName userName: wrapper.userName
userPowerlevel: wrapper.userPowerlevel userPowerlevel: wrapper.userPowerlevel
} }
visible: status == Loader.Ready },
z: 4
},
Rectangle { Rectangle {
anchors.fill: gridContainer anchors.fill: gridContainer
color: (Settings.messageHoverHighlight && messageHover.hovered) ? palette.alternateBase : "transparent" color: (Settings.messageHoverHighlight && messageHover.hovered) ? palette.alternateBase : "transparent"
@ -87,8 +85,8 @@ TimelineEvent {
}, },
Rectangle { Rectangle {
id: scrollHighlight id: scrollHighlight
anchors.fill: gridContainer
anchors.fill: gridContainer
color: palette.highlight color: palette.highlight
enabled: false enabled: false
opacity: 0 opacity: 0
@ -127,41 +125,41 @@ TimelineEvent {
} }
}, },
Rectangle { Rectangle {
anchors.left: gridContainer.left
anchors.leftMargin: -2
anchors.top: gridContainer.top anchors.top: gridContainer.top
anchors.left: gridContainer.left
anchors.topMargin: -2 anchors.topMargin: -2
anchors.leftMargin: -2
color: "transparent"
border.color: Nheko.theme.red border.color: Nheko.theme.red
border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0 border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0
radius: 4 color: "transparent"
height: contentColumn.implicitHeight + 4 height: contentColumn.implicitHeight + 4
radius: 4
width: contentColumn.implicitWidth + 4 width: contentColumn.implicitWidth + 4
}, },
Row { Row {
id: gridContainer id: gridContainer
spacing: Nheko.paddingSmall
width: wrapper.width - wrapper.avatarMargin width: wrapper.width - wrapper.avatarMargin
x: wrapper.avatarMargin x: wrapper.avatarMargin
y: section.visible && section.active ? section.y + section.height : 0 y: section.visible && section.active ? section.y + section.height : 0
spacing: Nheko.paddingSmall
HoverHandler { HoverHandler {
id: messageHover id: messageHover
blocking: false blocking: false
onHoveredChanged: () => { onHoveredChanged: () => {
if (!Settings.mobileMode && hovered) { if (!Settings.mobileMode && hovered) {
if (!messageActions.hovered) { if (!messageActions.hovered) {
messageActions.model = wrapper; messageActions.model = wrapper;
messageActions.attached = wrapper; messageActions.attached = wrapper;
messageActions.anchors.bottomMargin = -gridContainer.y messageActions.anchors.bottomMargin = -gridContainer.y;
messageActions.anchors.rightMargin = metadata.width messageActions.anchors.rightMargin = metadata.width;
} }
} }
} }
} }
AbstractButton { AbstractButton {
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Part of a thread") ToolTip.text: qsTr("Part of a thread")
@ -179,31 +177,29 @@ TimelineEvent {
color: TimelineManager.userColor(wrapper.threadId, palette.base) color: TimelineManager.userColor(wrapper.threadId, palette.base)
} }
} }
Item { Item {
height: 1
visible: wrapper.isStateEvent visible: wrapper.isStateEvent
width: (wrapper.maxWidth - (wrapper.main?.width ?? 0)) / 2 width: (wrapper.maxWidth - (wrapper.main?.width ?? 0)) / 2
height: 1
} }
Column { Column {
id: contentColumn id: contentColumn
data: [replyRow, wrapper.main,]
AbstractButton { AbstractButton {
id: replyRow id: replyRow
visible: wrapper.reply
height: replyLine.height
property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base) property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base)
clip: true clip: true
height: replyLine.height
visible: wrapper.reply
NhekoCursorShape { background: Rectangle {
anchors.fill: parent //width: replyRow.implicitContentWidth
cursorShape: Qt.PointingHandCursor color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
} }
contentItem: Row { contentItem: Row {
id: replyRowLay id: replyRowLay
@ -211,103 +207,96 @@ TimelineEvent {
Rectangle { Rectangle {
id: replyLine id: replyLine
height: Math.min( wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
color: replyRow.userColor color: replyRow.userColor
height: Math.min(wrapper.reply?.height, timelineView.height / 10) + Nheko.paddingSmall + replyUserButton.height
width: 4 width: 4
} }
Column { Column {
spacing: 0
id: replyCol id: replyCol
data: [replyUserButton, wrapper.reply,]
spacing: 0
AbstractButton { AbstractButton {
id: replyUserButton id: replyUserButton
contentItem: Label { contentItem: Label {
id: userName_ id: userName_
text: wrapper.reply?.userName ?? ''
color: replyRow.userColor color: replyRow.userColor
text: wrapper.reply?.userName ?? ''
textFormat: Text.RichText textFormat: Text.RichText
width: wrapper.maxWidth width: wrapper.maxWidth
//elideWidth: wrapper.maxWidth //elideWidth: wrapper.maxWidth
} }
onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId) onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId)
} }
data: [
replyUserButton,
wrapper.reply,
]
} }
} }
background: Rectangle {
//width: replyRow.implicitContentWidth
color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1))
}
onClicked: { onClicked: {
let link = wrapper.reply.hoveredLink let link = wrapper.reply.hoveredLink;
if (link) { if (link) {
Nheko.openLink(link) Nheko.openLink(link);
} else { } else {
console.log("Scrolling to "+wrapper.replyTo); console.log("Scrolling to " + wrapper.replyTo);
wrapper.room.showEvent(wrapper.replyTo) wrapper.room.showEvent(wrapper.replyTo);
} }
} }
onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX-replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo) onPressAndHold: wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(pressX - replyLine.width - Nheko.paddingSmall, pressY - replyUserButton.implicitHeight) : "", wrapper.replyTo)
NhekoCursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
TapHandler { TapHandler {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onSingleTapped: (eventPoint) => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x-replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo)
gesturePolicy: TapHandler.ReleaseWithinBounds
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
gesturePolicy: TapHandler.ReleaseWithinBounds
onSingleTapped: eventPoint => wrapper.replyContextMenu.show(wrapper.reply.copyText ?? "", wrapper.reply.linkAt ? wrapper.reply.linkAt(eventPoint.position.x - replyLine.width - Nheko.paddingSmall, eventPoint.position.y - replyUserButton.implicitHeight) : "", wrapper.replyTo)
} }
} }
data: [
replyRow, wrapper.main,
]
} }
DragHandler { DragHandler {
id: replyDragHandler id: replyDragHandler
yAxis.enabled: false
xAxis.enabled: true xAxis.enabled: true
xAxis.minimum: wrapper.avatarMargin - 100
xAxis.maximum: wrapper.avatarMargin xAxis.maximum: wrapper.avatarMargin
xAxis.minimum: wrapper.avatarMargin - 100
yAxis.enabled: false
onActiveChanged: { onActiveChanged: {
if (!replyDragHandler.active) { if (!replyDragHandler.active) {
if (replyDragHandler.xAxis.minimum <= replyDragHandler.xAxis.activeValue + 1) { if (replyDragHandler.xAxis.minimum <= replyDragHandler.xAxis.activeValue + 1) {
wrapper.room.reply = wrapper.eventId wrapper.room.reply = wrapper.eventId;
} }
gridContainer.x = wrapper.avatarMargin; gridContainer.x = wrapper.avatarMargin;
} }
} }
} }
TapHandler { TapHandler {
onDoubleTapped: wrapper.room.reply = wrapper.eventId onDoubleTapped: wrapper.room.reply = wrapper.eventId
} }
}, },
TimelineMetadata { TimelineMetadata {
id: metadata id: metadata
scaling: 1 anchors.right: parent.right
eventId: wrapper.eventId
anchors.right: parent.right isEdited: wrapper.isEdited
y: section.visible && section.active ? section.y + section.height : 0 isEncrypted: wrapper.isEncrypted
room: wrapper.room
visible: !wrapper.isStateEvent scaling: 1
status: wrapper.status
eventId: wrapper.eventId threadId: wrapper.threadId
status: wrapper.status timestamp: wrapper.timestamp
trustlevel: wrapper.trustlevel trustlevel: wrapper.trustlevel
isEdited: wrapper.isEdited visible: !wrapper.isStateEvent
isEncrypted: wrapper.isEncrypted y: section.visible && section.active ? section.y + section.height : 0
threadId: wrapper.threadId },
timestamp: wrapper.timestamp
room: wrapper.room
},
Reactions { Reactions {
id: reactionRow id: reactionRow

@ -11,17 +11,16 @@ import im.nheko
RowLayout { RowLayout {
id: metadata id: metadata
property int iconSize: Math.floor(fontMetrics.ascent * scaling)
required property double scaling
required property string eventId required property string eventId
required property int status property int iconSize: Math.floor(fontMetrics.ascent * scaling)
required property int trustlevel
required property bool isEdited required property bool isEdited
required property bool isEncrypted required property bool isEncrypted
required property Room room
required property double scaling
required property int status
required property string threadId required property string threadId
required property date timestamp required property date timestamp
required property Room room required property int trustlevel
spacing: 2 spacing: 2
@ -43,6 +42,7 @@ RowLayout {
sourceSize.height: parent.iconSize * Screen.devicePixelRatio sourceSize.height: parent.iconSize * Screen.devicePixelRatio
sourceSize.width: parent.iconSize * Screen.devicePixelRatio sourceSize.width: parent.iconSize * Screen.devicePixelRatio
visible: metadata.isEdited || metadata.eventId == metadata.room.edit visible: metadata.isEdited || metadata.eventId == metadata.room.edit
HoverHandler { HoverHandler {
id: editHovered id: editHovered

@ -6,11 +6,9 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Window import QtQuick.Window
import im.nheko import im.nheko
import "./components" import "./components"
Column { Column {
required property var day required property var day
required property bool isSender required property bool isSender
required property bool isStateEvent required property bool isStateEvent
@ -79,31 +77,14 @@ Column {
target: room target: room
} }
AbstractButton { AbstractButton {
id: userNameButton id: userNameButton
PowerlevelIndicator {
id: powerlevelIndicator
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
powerlevel: userPowerlevel
height: fontMetrics.ascent
width: height
sourceSize.width: fontMetrics.lineSpacing
sourceSize.height: fontMetrics.lineSpacing
permissions: room ? room.permissions : null
visible: isAdmin || isModerator
}
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userId ToolTip.text: userId
ToolTip.visible: hovered ToolTip.visible: hovered
leftPadding: powerlevelIndicator.visible ? 16 : 0
leftInset: 0 leftInset: 0
leftPadding: powerlevelIndicator.visible ? 16 : 0
rightInset: 0 rightInset: 0
rightPadding: 0 rightPadding: 0
@ -117,6 +98,19 @@ Column {
onClicked: room.openUserProfile(userId) onClicked: room.openUserProfile(userId)
PowerlevelIndicator {
id: powerlevelIndicator
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
height: fontMetrics.ascent
permissions: room ? room.permissions : null
powerlevel: userPowerlevel
sourceSize.height: fontMetrics.lineSpacing
sourceSize.width: fontMetrics.lineSpacing
visible: isAdmin || isModerator
width: height
}
TextMetrics { TextMetrics {
id: userNameTextMetrics id: userNameTextMetrics
@ -153,7 +147,7 @@ Column {
Connections { Connections {
function onPresenceChanged(id) { function onPresenceChanged(id) {
if (id == userId) if (id == userId)
statusMsg.userStatus = Presence.userStatus(userId); statusMsg.userStatus = Presence.userStatus(userId);
} }
target: Presence target: Presence
@ -161,4 +155,3 @@ Column {
} }
} }
} }

@ -59,7 +59,7 @@ Item {
visible: TimelineManager.isInitialSync visible: TimelineManager.isInitialSync
z: 3 z: 3
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: 100 duration: 100
} }
@ -188,9 +188,9 @@ Item {
displayName: parent.roomName displayName: parent.roomName
enabled: false enabled: false
implicitHeight: 130 implicitHeight: 130
implicitWidth: 130
roomid: parent.roomId roomid: parent.roomId
url: parent.avatarUrl.replace("mxc://", "image://MxcImage/") url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
implicitWidth: 130
} }
RowLayout { RowLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -293,10 +293,10 @@ Item {
displayName: roomPreview?.inviterDisplayName ?? "" displayName: roomPreview?.inviterDisplayName ?? ""
enabled: true enabled: true
implicitHeight: 48 implicitHeight: 48
implicitWidth: 48
roomid: preview.roomId roomid: preview.roomId
url: (roomPreview?.inviterAvatarUrl ?? "").replace("mxc://", "image://MxcImage/") url: (roomPreview?.inviterAvatarUrl ?? "").replace("mxc://", "image://MxcImage/")
userid: roomPreview?.inviterUserId ?? "" userid: roomPreview?.inviterUserId ?? ""
implicitWidth: 48
onClicked: TimelineManager.openGlobalUserProfile(roomPreview.inviterUserId) onClicked: TimelineManager.openGlobalUserProfile(roomPreview.inviterUserId)
} }
@ -378,8 +378,8 @@ Item {
running: false running: false
onTriggered: { onTriggered: {
timelineEffects.removeParticles() timelineEffects.removeParticles();
shouldEffectsRun = false shouldEffectsRun = false;
} }
} }
Connections { Connections {

@ -124,9 +124,9 @@ Pane {
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
Layout.row: 2 Layout.row: 2
clip: true clip: true
enabled: false
// don't use the disabled color // don't use the disabled color
color: topBar.palette.text color: topBar.palette.text
enabled: false
selectByMouse: false selectByMouse: false
text: roomTopic text: roomTopic
} }
@ -287,9 +287,9 @@ Pane {
property var e: room ? room.getDump(modelData, "pins") : {} property var e: room ? room.getDump(modelData, "pins") : {}
maxWidth: pinnedMessages.width
//Layout.preferredHeight: height //Layout.preferredHeight: height
eventId: e.eventId ?? "" eventId: e.eventId ?? ""
maxWidth: pinnedMessages.width
userColor: TimelineManager.userColor(e.userId, palette.window) userColor: TimelineManager.userColor(e.userId, palette.window)
Connections { Connections {

@ -13,64 +13,16 @@ Container {
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;
@ -79,6 +31,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
@ -87,49 +43,84 @@ 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
model: container.contentModel boundsBehavior: Flickable.StopAtBounds
snapMode: ListView.SnapOneItem currentIndex: container.singlePageMode ? container.pageIndex : 0
orientation: ListView.Horizontal highlightMoveDuration: (container.singlePageMode && !Settings.reducedMotion) ? 200 : 0
highlightRangeMode: ListView.StrictlyEnforceRange highlightRangeMode: ListView.StrictlyEnforceRange
interactive: singlePageMode interactive: singlePageMode
highlightMoveDuration: (container.singlePageMode && !Settings.reducedMotion) ? 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
} }

@ -5,20 +5,20 @@
import QtQuick import QtQuick
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(() => {
return parent.singlePageMode ? parent.width : width - splitterWidth; return parent.singlePageMode ? parent.width : width - splitterWidth;
}); });
children[0].height = Qt.binding(() => { children[0].height = Qt.binding(() => {
return parent.height; return parent.height;
}); });
} }
} }

@ -10,25 +10,26 @@ 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: palette.window property color background: palette.window
property color importantText: palette.text
property color unimportantText: palette.buttonText
property color bubbleBackground: palette.highlight property color bubbleBackground: palette.highlight
property color bubbleText: palette.highlightedText property color bubbleText: palette.highlightedText
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
required property string avatarUrl
required property string title
required property string subtitle
required property int index
required property int selectedIndex
property bool crop: true property bool crop: true
property color importantText: 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: palette.buttonText
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"
@ -37,13 +38,12 @@ Rectangle {
PropertyChanges { PropertyChanges {
tile { tile {
background: palette.dark background: palette.dark
importantText: palette.brightText
unimportantText: palette.brightText
bubbleBackground: palette.highlight bubbleBackground: palette.highlight
bubbleText: palette.highlightedText bubbleText: palette.highlightedText
importantText: palette.brightText
unimportantText: palette.brightText
} }
} }
}, },
State { State {
name: "selected" name: "selected"
@ -52,37 +52,35 @@ Rectangle {
PropertyChanges { PropertyChanges {
tile { tile {
background: palette.highlight background: palette.highlight
importantText: palette.highlightedText
unimportantText: palette.highlightedText
bubbleBackground: palette.highlightedText bubbleBackground: palette.highlightedText
bubbleText: palette.highlight bubbleText: palette.highlight
importantText: palette.highlightedText
unimportantText: 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
implicitHeight: avatarSize implicitHeight: avatarSize
implicitWidth: avatarSize implicitWidth: avatarSize
url: tile.avatarUrl.replace("mxc://", "image://MxcImage/") url: tile.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: title
crop: tile.crop
} }
ColumnLayout { ColumnLayout {
id: textContent id: textContent
@ -103,33 +101,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
} }
} }
} }
} }
} }

@ -12,53 +12,51 @@ 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(palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
MultiEffect { //height: control.contentItem.implicitHeight * 2
anchors.fill: control.background //width: control.contentItem.implicitWidth * 2
shadowHorizontalOffset: 3 radius: height / 8
shadowVerticalOffset: 3
shadowBlur: 8
shadowEnabled: true
shadowColor: "#80000000"
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: 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: palette.light
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: control.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
} }
} }
background: Rectangle { MultiEffect {
//height: control.contentItem.implicitHeight * 2 anchors.fill: control.background
//width: control.contentItem.implicitWidth * 2 shadowBlur: 8
radius: height / 8 shadowColor: "#80000000"
color: Qt.lighter(palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1)) shadowEnabled: true
shadowHorizontalOffset: 3
shadowVerticalOffset: 3
source: control.background
} }
} }

@ -10,30 +10,29 @@ 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: 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: palette.window
border.color: Nheko.theme.separator
border.width: 1
radius: Nheko.paddingSmall
}
} }

@ -9,21 +9,19 @@ import im.nheko 1.0
TabButton { TabButton {
id: control id: control
contentItem: Text {
text: control.text
font: control.font
opacity: enabled ? 1.0 : 0.3
color: control.down ? palette.highlightedText : palette.text
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
background: Rectangle { background: Rectangle {
border.color: control.down ? palette.highlight : Nheko.theme.separator border.color: control.down ? palette.highlight : Nheko.theme.separator
color: control.checked ? palette.highlight : palette.base
border.width: 1 border.width: 1
color: control.checked ? palette.highlight : palette.base
radius: 2 radius: 2
} }
contentItem: Text {
color: control.down ? palette.highlightedText : palette.text
elide: Text.ElideRight
font: control.font
horizontalAlignment: Text.AlignHCenter
opacity: enabled ? 1.0 : 0.3
text: control.text
verticalAlignment: Text.AlignVCenter
}
} }

@ -9,39 +9,38 @@ import im.nheko 1.0
Rectangle { Rectangle {
id: bubbleRoot id: bubbleRoot
required property int notificationCount
required property bool hasLoudNotification
required property color bubbleBackgroundColor required property color bubbleBackgroundColor
required property color bubbleTextColor required property color bubbleTextColor
property bool mayBeVisible: true
property alias font: notificationBubbleText.font property alias font: notificationBubbleText.font
baselineOffset: notificationBubbleText.baseline - bubbleRoot.top required property bool hasLoudNotification
property bool mayBeVisible: true
required property int notificationCount
visible: mayBeVisible && notificationCount > 0 ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: notificationCount
ToolTip.visible: notificationBubbleHover.hovered && (notificationCount > 9999)
baselineOffset: notificationBubbleText.baseline - bubbleRoot.top
color: hasLoudNotification ? Nheko.theme.red : bubbleBackgroundColor
implicitHeight: notificationBubbleText.height + Nheko.paddingMedium implicitHeight: notificationBubbleText.height + Nheko.paddingMedium
implicitWidth: Math.max(notificationBubbleText.width, height) implicitWidth: Math.max(notificationBubbleText.width, height)
radius: height / 2 radius: height / 2
color: hasLoudNotification ? Nheko.theme.red : bubbleBackgroundColor visible: mayBeVisible && notificationCount > 0
ToolTip.text: notificationCount
ToolTip.delay: Nheko.tooltipDelay
ToolTip.visible: notificationBubbleHover.hovered && (notificationCount > 9999)
Label { Label {
id: notificationBubbleText id: notificationBubbleText
anchors.centerIn: bubbleRoot anchors.centerIn: bubbleRoot
horizontalAlignment: Text.AlignHCenter color: bubbleRoot.hasLoudNotification ? "white" : bubbleRoot.bubbleTextColor
verticalAlignment: Text.AlignVCenter
width: Math.max(implicitWidth + Nheko.paddingMedium, bubbleRoot.height)
font.bold: true font.bold: true
font.pixelSize: fontMetrics.font.pixelSize * 0.8 font.pixelSize: fontMetrics.font.pixelSize * 0.8
color: bubbleRoot.hasLoudNotification ? "white" : bubbleRoot.bubbleTextColor horizontalAlignment: Text.AlignHCenter
text: bubbleRoot.notificationCount > 9999 ? "9999+" : bubbleRoot.notificationCount text: bubbleRoot.notificationCount > 9999 ? "9999+" : bubbleRoot.notificationCount
verticalAlignment: Text.AlignVCenter
width: Math.max(implicitWidth + Nheko.paddingMedium, bubbleRoot.height)
HoverHandler { HoverHandler {
id: notificationBubbleHover id: notificationBubbleHover
}
}
} }
} }

@ -7,24 +7,20 @@ import QtQuick.Controls
import im.nheko import im.nheko
Image { Image {
required property int powerlevel
required property var permissions
readonly property bool isAdmin: permissions ? permissions.changeLevel(MtxEvent.PowerLevels) <= powerlevel : false readonly property bool isAdmin: permissions ? permissions.changeLevel(MtxEvent.PowerLevels) <= powerlevel : false
readonly property bool isModerator: permissions ? permissions.redactLevel() <= powerlevel : false
readonly property bool isDefault: permissions ? permissions.defaultLevel() <= powerlevel : false readonly property bool isDefault: permissions ? permissions.defaultLevel() <= powerlevel : false
readonly property bool isModerator: permissions ? permissions.redactLevel() <= powerlevel : false
required property var permissions
required property int powerlevel
readonly property string sourceUrl: { readonly property string sourceUrl: {
if (isAdmin) if (isAdmin)
return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?"; return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?";
else if (isModerator) else if (isModerator)
return "image://colorimage/:/icons/icons/ui/ribbon.svg?"; return "image://colorimage/:/icons/icons/ui/ribbon.svg?";
else else
return "image://colorimage/:/icons/icons/ui/person.svg?"; return "image://colorimage/:/icons/icons/ui/person.svg?";
} }
source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText)
ToolTip.visible: ma.hovered
ToolTip.text: { ToolTip.text: {
if (isAdmin) if (isAdmin)
return qsTr("Administrator: %1").arg(powerlevel); return qsTr("Administrator: %1").arg(powerlevel);
@ -33,8 +29,11 @@ Image {
else else
return qsTr("User: %1").arg(powerlevel); return qsTr("User: %1").arg(powerlevel);
} }
ToolTip.visible: ma.hovered
source: sourceUrl + (ma.hovered ? palette.highlight : palette.buttonText)
HoverHandler { HoverHandler {
id: ma id: ma
} }
} }

@ -8,8 +8,8 @@ import QtQml.Models
Item { Item {
id: root id: root
property alias model: visualModel.model
property Component delegate property Component delegate
property alias model: visualModel.model
Component { Component {
id: dragDelegate id: dragDelegate
@ -17,104 +17,117 @@ Item {
MouseArea { MouseArea {
id: dragArea id: dragArea
required property var model property bool held: false
required property int index required property int index
required property var model
drag.axis: Drag.YAxis
drag.target: held ? content : undefined
enabled: model.moveable == undefined || model.moveable enabled: model.moveable == undefined || model.moveable
property bool held: false
anchors { left: parent.left; right: parent.right }
height: content.height height: content.height
drag.target: held ? content : undefined onHeldChanged: if (held)
drag.axis: Drag.YAxis ListView.view.currentIndex = dragArea.index
else
ListView.view.currentIndex = -1
onPressAndHold: held = true onPressAndHold: held = true
onPressed: if (mouse.source !== Qt.MouseEventNotSynthesized) { held = true } onPressed: if (mouse.source !== Qt.MouseEventNotSynthesized) {
held = true;
}
onReleased: held = false onReleased: held = false
onHeldChanged: if (held) ListView.view.currentIndex = dragArea.index; else ListView.view.currentIndex = -1
anchors {
left: parent.left
right: parent.right
}
Rectangle { Rectangle {
id: content id: content
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
width: dragArea.width; height: actualDelegate.implicitHeight + 4
border.width: dragArea.enabled ? 1 : 0
border.color: palette.highlight
color: dragArea.held ? palette.highlight : palette.base
Behavior on color { ColorAnimation { duration: 100 } }
radius: 2
Drag.active: dragArea.held Drag.active: dragArea.held
Drag.source: dragArea
Drag.hotSpot.x: width / 2 Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2 Drag.hotSpot.y: height / 2
Drag.source: dragArea
border.color: palette.highlight
border.width: dragArea.enabled ? 1 : 0
color: dragArea.held ? palette.highlight : palette.base
height: actualDelegate.implicitHeight + 4
radius: 2
width: dragArea.width
Behavior on color {
ColorAnimation {
duration: 100
}
}
states: State { states: State {
when: dragArea.held when: dragArea.held
ParentChange { target: content; parent: root } ParentChange {
parent: root
target: content
}
AnchorChanges { AnchorChanges {
target: content target: content
anchors { horizontalCenter: undefined; verticalCenter: undefined }
anchors {
horizontalCenter: undefined
verticalCenter: undefined
}
} }
} }
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
Loader { Loader {
id: actualDelegate id: actualDelegate
sourceComponent: root.delegate
property var model: dragArea.model
property int index: dragArea.index property int index: dragArea.index
property var model: dragArea.model
property int offset: -view.contentY + dragArea.y property int offset: -view.contentY + dragArea.y
anchors { fill: parent; margins: 2 }
}
} sourceComponent: root.delegate
anchors {
fill: parent
margins: 2
}
}
}
DropArea { DropArea {
enabled: index != 0 || model.moveable == undefined || model.moveable enabled: index != 0 || model.moveable == undefined || model.moveable
anchors { fill: parent; margins: 8 }
onEntered: (drag)=> { onEntered: drag => {
visualModel.model.move(drag.source.index, dragArea.index) visualModel.model.move(drag.source.index, dragArea.index);
}
} }
anchors {
fill: parent
margins: 8
}
} }
} }
}
DelegateModel {
id: visualModel
delegate: dragDelegate
DelegateModel { }
id: visualModel ListView {
id: view
delegate: dragDelegate
} cacheBuffer: 50
clip: true
ListView { highlightRangeMode: ListView.ApplyRange
id: view model: visualModel
preferredHighlightBegin: 0.2 * height
clip: true preferredHighlightEnd: 0.8 * height
spacing: 4
anchors { fill: parent; margins: 2 }
anchors {
model: visualModel fill: parent
margins: 2
highlightRangeMode: ListView.ApplyRange
preferredHighlightBegin: 0.2 * height
preferredHighlightEnd: 0.8 * height
spacing: 4
cacheBuffer: 50
} }
} }
}

@ -9,76 +9,87 @@ import im.nheko 1.0
Platform.Menu { Platform.Menu {
id: spacesMenu id: spacesMenu
property string roomid
property Component childMenu property Component childMenu
property bool loadChildren: false
property int position: modelData == undefined ? -2 : modelData.treeIndex property int position: modelData == undefined ? -2 : modelData.treeIndex
property string roomid
title: modelData != undefined ? modelData.name : qsTr("Add or remove from community") title: modelData != undefined ? modelData.name : qsTr("Add or remove from community")
property bool loadChildren: false
onAboutToShow: loadChildren = true onAboutToShow: loadChildren = true
//onAboutToHide: loadChildren = false //onAboutToHide: loadChildren = false
Platform.MenuItemGroup { Platform.MenuItemGroup {
id: modificationGroup id: modificationGroup
visible: position != -1 visible: position != -1
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Official community for this room")
group: modificationGroup
checkable: true checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && modelData.canonical) checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && modelData.canonical)
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent) enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, true) group: modificationGroup
text: qsTr("Official community for this room")
onTriggered: if (checked)
Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, true)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Affiliated community for this room")
group: modificationGroup
checkable: true checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && !modelData.canonical) checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && !modelData.canonical)
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent) enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, false) group: modificationGroup
text: qsTr("Affiliated community for this room")
onTriggered: if (checked)
Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, false)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Listed only for community members")
group: modificationGroup
checkable: true checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && !modelData.parentValid) checked: spacesMenu.position >= 0 && (modelData.childValid && !modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || modelData.childValid) && (!modelData.parentValid || modelData.canEditParent)) enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, true, false) group: modificationGroup
text: qsTr("Listed only for community members")
onTriggered: if (checked)
Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, true, false)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Listed only for room members")
group: modificationGroup
checkable: true checkable: true
checked: spacesMenu.position >= 0 && (!modelData.childValid && modelData.parentValid) checked: spacesMenu.position >= 0 && (!modelData.childValid && modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild) && (modelData.parentValid || modelData.canEditParent)) enabled: spacesMenu.position >= 0 && ((modelData.canEditChild) && (modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, false, false) group: modificationGroup
text: qsTr("Listed only for room members")
onTriggered: if (checked)
Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, false, false)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Not related")
group: modificationGroup
checkable: true checkable: true
checked: spacesMenu.position >= 0 && (!modelData.childValid && !modelData.parentValid) checked: spacesMenu.position >= 0 && (!modelData.childValid && !modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || !modelData.childValid) && (!modelData.parentValid || modelData.canEditParent)) enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || !modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, false, false) group: modificationGroup
} text: qsTr("Not related")
onTriggered: if (checked)
Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, false, false)
}
Platform.MenuSeparator { Platform.MenuSeparator {
text: qsTr("Subcommunities")
group: modificationGroup group: modificationGroup
text: qsTr("Subcommunities")
visible: modificationGroup.visible && inst.model != undefined visible: modificationGroup.visible && inst.model != undefined
} }
Instantiator { Instantiator {
id: inst id: inst
model: spacesMenu.loadChildren ? Communities.spaceChildrenListFromIndex(spacesMenu.roomid, spacesMenu.position) : undefined
onObjectAdded: (idx, o) => {
spacesMenu.insertMenu(idx + (spacesMenu.position != -1 ? 6 : 0), o)
}
//onObjectRemoved: spacesMenu.removeMenu(object) //onObjectRemoved: spacesMenu.removeMenu(object)
delegate: childMenu delegate: childMenu
model: spacesMenu.loadChildren ? Communities.spaceChildrenListFromIndex(spacesMenu.roomid, spacesMenu.position) : undefined
onObjectAdded: (idx, o) => {
spacesMenu.insertMenu(idx + (spacesMenu.position != -1 ? 6 : 0), o);
}
} }
} }

@ -10,37 +10,34 @@ import im.nheko 1.0 // for cursor shape
AbstractButton { AbstractButton {
id: button id: button
property color buttonTextColor: palette.buttonText
property alias cursor: mouseArea.cursorShape property alias cursor: mouseArea.cursorShape
property color highlightColor: palette.highlight property color highlightColor: palette.highlight
property color buttonTextColor: palette.buttonText
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)
} }
} }

@ -9,43 +9,50 @@ import QtQuick.Layouts 1.12
import im.nheko 1.0 import im.nheko 1.0
ItemDelegate { ItemDelegate {
property string avatarUrl
property alias bgColor: background.color property alias bgColor: background.color
property alias userid: avatar.userid
property alias displayName: avatar.displayName property alias displayName: avatar.displayName
property string avatarUrl property alias userid: avatar.userid
implicitHeight: layout.implicitHeight + Nheko.paddingSmall * 2 implicitHeight: layout.implicitHeight + Nheko.paddingSmall * 2
background: Rectangle {id: background}
background: Rectangle {
id: background
}
GridLayout { GridLayout {
id: layout id: layout
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - Nheko.paddingSmall * 2 columnSpacing: Nheko.paddingMedium
rows: 2
columns: 2 columns: 2
rowSpacing: Nheko.paddingSmall rowSpacing: Nheko.paddingSmall
columnSpacing: Nheko.paddingMedium rows: 2
width: parent.width - Nheko.paddingSmall * 2
Avatar { Avatar {
id: avatar id: avatar
Layout.rowSpan: 2
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
url: avatarUrl.replace("mxc://", "image://MxcImage/") Layout.preferredHeight: Nheko.avatarSize
Layout.preferredWidth: Nheko.avatarSize
Layout.rowSpan: 2
enabled: false enabled: false
url: avatarUrl.replace("mxc://", "image://MxcImage/")
} }
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: displayName
color: TimelineManager.userColor(userid, palette.window) color: TimelineManager.userColor(userid, palette.window)
font.pointSize: fontMetrics.font.pointSize font.pointSize: fontMetrics.font.pointSize
text: displayName
} }
Label { Label {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
text: userid Layout.fillWidth: true
color: palette.buttonText color: palette.buttonText
font.pointSize: fontMetrics.font.pointSize * 0.9 font.pointSize: fontMetrics.font.pointSize * 0.9
text: userid
} }
} }
} }

@ -13,29 +13,37 @@ Control {
required property int encryptionError required property int encryptionError
required property string eventId required property string eventId
padding: Nheko.paddingMedium
implicitHeight: contents.implicitHeight + Nheko.paddingMedium * 2
Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2
implicitHeight: contents.implicitHeight + Nheko.paddingMedium * 2
padding: Nheko.paddingMedium
background: Rectangle {
color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingMedium
visible: !Settings.bubbles // the bubble in a bubble looks odd
}
contentItem: RowLayout { contentItem: RowLayout {
id: contents id: contents
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
Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24
source: "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.error
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingSmall
Layout.fillWidth: true Layout.fillWidth: true
spacing: Nheko.paddingSmall
Label { Label {
id: encryptedText id: encryptedText
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1
color: palette.text
text: { text: {
switch (r.encryptionError) { switch (r.encryptionError) {
case Olm.MissingSession: case Olm.MissingSession:
@ -56,24 +64,13 @@ Control {
} }
textFormat: Text.PlainText textFormat: Text.PlainText
wrapMode: Label.WordWrap wrapMode: Label.WordWrap
color: palette.text
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1
} }
Button { Button {
visible: r.encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex
text: qsTr("Request key") text: qsTr("Request key")
visible: r.encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex
onClicked: room.requestKeyForEvent(eventId) onClicked: room.requestKeyForEvent(eventId)
} }
} }
}
background: Rectangle {
color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingMedium
visible: !Settings.bubbles // the bubble in a bubble looks odd
} }
} }

@ -13,53 +13,48 @@ Control {
required property string userName required property string userName
padding: Nheko.paddingMedium Layout.fillWidth: true
//implicitHeight: contents.implicitHeight + padd * 2 //implicitHeight: contents.implicitHeight + padd * 2
Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2 Layout.maximumWidth: contents.Layout.maximumWidth + padding * 2
Layout.fillWidth: true padding: Nheko.paddingMedium
background: Rectangle {
border.color: Nheko.theme.green
border.width: 2
color: palette.alternateBase
height: contents.implicitHeight + Nheko.paddingMedium * 2
radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium
}
contentItem: RowLayout { contentItem: RowLayout {
id: contents id: contents
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
Image { Image {
source: "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24
source: "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingSmall
Layout.fillWidth: true Layout.fillWidth: true
spacing: Nheko.paddingSmall
MatrixText { MatrixText {
text: qsTr("%1 enabled end-to-end encryption").arg(r.userName)
font.bold: true
font.pointSize: 14
color: palette.text
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1 Layout.maximumWidth: implicitWidth + 1
color: palette.text
font.bold: true
font.pointSize: 14
text: qsTr("%1 enabled end-to-end encryption").arg(r.userName)
} }
Label { Label {
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1
text: qsTr("Encryption keeps your messages safe by only allowing the people you sent the message to to read it. For extra security, if you want to make sure you are talking to the right people, you can verify them in real life.") text: qsTr("Encryption keeps your messages safe by only allowing the people you sent the message to to read it. For extra security, if you want to make sure you are talking to the right people, you can verify them in real life.")
textFormat: Text.PlainText textFormat: Text.PlainText
wrapMode: Label.WordWrap wrapMode: Label.WordWrap
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1
} }
} }
}
background: Rectangle {
radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium
height: contents.implicitHeight + Nheko.paddingMedium * 2
color: palette.alternateBase
border.color: Nheko.theme.green
border.width: 2
} }
} }

@ -13,15 +13,19 @@ Control {
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: false
padding: Settings.bubbles? 8 : 12
//Layout.preferredHeight: rowa.implicitHeight + padding //Layout.preferredHeight: rowa.implicitHeight + padding
//Layout.maximumWidth: rowa.Layout.maximumWidth + metadataWidth + padding //Layout.maximumWidth: rowa.Layout.maximumWidth + metadataWidth + padding
property int metadataWidth: 0 property int metadataWidth: 0
property bool fitsMetadata: false
Layout.maximumWidth: rowa.Layout.maximumWidth + padding * 2 Layout.maximumWidth: rowa.Layout.maximumWidth + padding * 2
padding: Settings.bubbles ? 8 : 12
background: Rectangle {
color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
visible: !Settings.bubbles // the bubble in a bubble looks odd
}
contentItem: RowLayout { contentItem: RowLayout {
id: rowa id: rowa
@ -30,36 +34,32 @@ Control {
Rectangle { Rectangle {
id: button id: button
color: palette.light
radius: 22
Layout.preferredHeight: 44 Layout.preferredHeight: 44
Layout.preferredWidth: 44 Layout.preferredWidth: 44
color: palette.light
radius: 22
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
@ -68,31 +68,21 @@ Control {
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1 Layout.maximumWidth: implicitWidth + 1
color: palette.text
elide: Text.ElideRight
text: evRoot.filename text: evRoot.filename
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight
color: palette.text
} }
Text { Text {
id: filesize_ id: filesize_
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1 Layout.maximumWidth: implicitWidth + 1
color: palette.text
elide: Text.ElideRight
text: evRoot.filesize text: evRoot.filesize
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight
color: palette.text
} }
} }
} }
background: Rectangle {
color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
visible: !Settings.bubbles // the bubble in a bubble looks odd
}
} }

@ -8,27 +8,28 @@ import QtQuick.Controls
import im.nheko import im.nheko
AbstractButton { AbstractButton {
required property int type
required property int originalWidth
required property int originalHeight
required property double proportionalHeight
required property string url
required property string blurhash required property string blurhash
required property string body required property string body
required property string filename
required property string eventId
required property int containerHeight required property int containerHeight
property double divisor: EventDelegateChooser.isReply ? 10 : 4 property double divisor: EventDelegateChooser.isReply ? 10 : 4
required property string eventId
required property string filename
property bool fitsMetadata: parent != null ? (parent.width - width) > metadataWidth + 4 : false
property int metadataWidth
required property int originalHeight
required property int originalWidth
required property double proportionalHeight
required property int type
required property string url
EventDelegateChooser.aspectRatio: proportionalHeight
EventDelegateChooser.keepAspectRatio: true EventDelegateChooser.keepAspectRatio: true
EventDelegateChooser.maxWidth: originalWidth
EventDelegateChooser.maxHeight: containerHeight / divisor EventDelegateChooser.maxHeight: containerHeight / divisor
EventDelegateChooser.aspectRatio: proportionalHeight EventDelegateChooser.maxWidth: originalWidth
hoverEnabled: true
enabled: !EventDelegateChooser.isReply enabled: !EventDelegateChooser.isReply
hoverEnabled: true
state: (img.status != Image.Ready || timeline.privacyScreen.active) ? "BlurhashVisible" : "ImageVisible" state: (img.status != Image.Ready || timeline.privacyScreen.active) ? "BlurhashVisible" : "ImageVisible"
states: [ states: [
State { State {
name: "BlurhashVisible" name: "BlurhashVisible"
@ -39,11 +40,9 @@ AbstractButton {
visible: (img.status != Image.Ready) || (timeline.privacyScreen.active && blurhash) visible: (img.status != Image.Ready) || (timeline.privacyScreen.active && blurhash)
} }
} }
PropertyChanges { PropertyChanges {
img.opacity: 0 img.opacity: 0
} }
PropertyChanges { PropertyChanges {
mxcimage.opacity: 0 mxcimage.opacity: 0
} }
@ -57,11 +56,9 @@ AbstractButton {
visible: false visible: false
} }
} }
PropertyChanges { PropertyChanges {
img.opacity: 1 img.opacity: 1
} }
PropertyChanges { PropertyChanges {
mxcimage.opacity: 1 mxcimage.opacity: 1
} }
@ -70,114 +67,98 @@ AbstractButton {
transitions: [ transitions: [
Transition { Transition {
from: "ImageVisible" from: "ImageVisible"
to: "BlurhashVisible"
reversible: true reversible: true
to: "BlurhashVisible"
SequentialAnimation { SequentialAnimation {
PropertyAction { PropertyAction {
target: blurhash_
property: "visible" property: "visible"
target: blurhash_
} }
ParallelAnimation { ParallelAnimation {
NumberAnimation { NumberAnimation {
target: blurhash_
property: "opacity"
duration: 300 duration: 300
easing.type: Easing.Linear easing.type: Easing.Linear
property: "opacity"
target: blurhash_
} }
NumberAnimation { NumberAnimation {
target: img
property: "opacity"
duration: 300 duration: 300
easing.type: Easing.Linear easing.type: Easing.Linear
property: "opacity"
target: img
} }
NumberAnimation { NumberAnimation {
target: mxcimage
property: "opacity"
duration: 300 duration: 300
easing.type: Easing.Linear easing.type: Easing.Linear
property: "opacity"
target: mxcimage
} }
} }
} }
} }
] ]
property int metadataWidth onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId, originalWidth, proportionalHeight)
property bool fitsMetadata: parent != null ? (parent.width - width) > metadataWidth+4 : false
Image { Image {
id: img id: img
visible: !mxcimage.loaded
anchors.fill: parent anchors.fill: parent
source: url != "" ? (url.replace("mxc://", "image://MxcImage/") + "?scale") : ""
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
horizontalAlignment: Image.AlignLeft horizontalAlignment: Image.AlignLeft
smooth: true
mipmap: true mipmap: true
smooth: true
source: url != "" ? (url.replace("mxc://", "image://MxcImage/") + "?scale") : ""
sourceSize.height: Math.min(Screen.desktopAvailableHeight, (originalWidth < 1 ? Screen.desktopAvailableHeight : originalWidth * proportionalHeight)) * Screen.devicePixelRatio
sourceSize.width: Math.min(Screen.desktopAvailableWidth, originalWidth < 1 ? Screen.desktopAvailableWidth : originalWidth) * Screen.devicePixelRatio sourceSize.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
roomm: room
play: !Settings.animateImagesOnHover || parent.hovered
eventId: parent.eventId
anchors.fill: parent anchors.fill: parent
eventId: parent.eventId
play: !Settings.animateImagesOnHover || parent.hovered
roomm: room
visible: loaded
} }
Image { Image {
id: blurhash_ id: blurhash_
source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + palette.buttonText) anchors.fill: parent
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?" + palette.buttonText)
sourceSize.height: parent.height * Screen.devicePixelRatio sourceSize.height: parent.height * Screen.devicePixelRatio
sourceSize.width: parent.width * Screen.devicePixelRatio
anchors.fill: parent
} }
onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId, originalWidth, proportionalHeight);
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: palette.window color: 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: 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: palette.text verticalAlignment: Text.AlignVCenter
} }
} }
} }

@ -5,11 +5,11 @@
import QtQuick 2.5 import QtQuick 2.5
import im.nheko 1.0 import im.nheko 1.0
TextMessage { TextMessage {
property bool isStateEvent property bool isStateEvent
font.italic: true
color: palette.buttonText color: palette.buttonText
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
} }

@ -7,14 +7,14 @@ import QtQuick.Controls 2.1
Label { Label {
property bool isStateEvent property bool isStateEvent
color: palette.text color: 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: palette.alternateBase color: palette.alternateBase
radius: parent.height / 2
} }
} }

@ -8,7 +8,7 @@ import im.nheko 1.0
MatrixText { MatrixText {
required property string typeString required property string typeString
text: qsTr("unimplemented event: ") + typeString // width: parent.width
// width: parent.width
color: palette.inactive.text color: palette.inactive.text
text: qsTr("unimplemented event: ") + typeString
} }

@ -11,80 +11,79 @@ import im.nheko
Item { Item {
id: content id: content
required property double proportionalHeight required property string body
required property int type property double divisor: EventDelegateChooser.isReply ? 10 : 4
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: EventDelegateChooser.isReply ? 10 : 4 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, implicitWidth)
height: (type == MtxEvent.VideoMessage ? width*proportionalHeight : 80) + fileInfoLabel.height
//implicitHeight: 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
implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth * Math.min((timelineView.height / divisor) / (tempWidth * proportionalHeight), 1)) : 500
width: Math.min(parent?.width ?? implicitWidth, implicitWidth)
MxcMedia { MxcMedia {
id: mxcmedia id: mxcmedia
// TODO: Show error in overlay or so? // TODO: Show error in overlay or so?
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: content.type == MtxEvent.VideoMessage ? palette.window : "transparent" color: content.type == MtxEvent.VideoMessage ? 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: content.thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : "image://colorimage/:/icons/icons/ui/video-file.svg?" + palette.windowText
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
source: content.thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : "image://colorimage/:/icons/icons/ui/video-file.svg?" + palette.windowText
VideoOutput { VideoOutput {
id: videoOutput id: videoOutput
visible: content.type == MtxEvent.VideoMessage
clip: true
anchors.fill: parent anchors.fill: parent
clip: true
fillMode: VideoOutput.PreserveAspectFit fillMode: VideoOutput.PreserveAspectFit
orientation: mxcmedia.orientation orientation: mxcmedia.orientation
visible: content.type == MtxEvent.VideoMessage
} }
} }
MediaControls { MediaControls {
id: mediaControls id: mediaControls
anchors.bottom: videoContainer.bottom
anchors.left: videoContainer.left anchors.left: videoContainer.left
anchors.right: videoContainer.right anchors.right: videoContainer.right
anchors.bottom: videoContainer.bottom
playingVideo: content.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.playbackState mediaState: mxcmedia.playbackState
onPositionChanged: mxcmedia.position = position playingVideo: content.type == MtxEvent.VideoMessage
onPlayPauseActivated: mxcmedia.playbackState == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play() positionValue: mxcmedia.position
onLoadActivated: mxcmedia.eventId = eventId onLoadActivated: mxcmedia.eventId = eventId
onPlayPauseActivated: mxcmedia.playbackState == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
onPositionChanged: mxcmedia.position = position
} }
} }
@ -93,15 +92,13 @@ Item {
id: fileInfoLabel id: fileInfoLabel
anchors.top: videoContainer.bottom anchors.top: videoContainer.bottom
color: palette.text
elide: Text.ElideRight
text: content.body + " [" + filesize + "]" text: content.body + " [" + filesize + "]"
textFormat: Text.RichText textFormat: Text.RichText
elide: Text.ElideRight
color: palette.text
background: Rectangle { background: Rectangle {
color: palette.base color: palette.base
} }
} }
} }

@ -10,47 +10,50 @@ import im.nheko
Control { Control {
id: msgRoot id: msgRoot
property int metadataWidth: 0 required property string eventId
property bool fitsMetadata: false //parent.width - redactedLayout.width > metadataWidth + 4 property bool fitsMetadata: false //parent.width - redactedLayout.width > metadataWidth + 4
required property string eventId property int metadataWidth: 0
required property Room room required property Room room
Layout.maximumWidth: redactedLayout.Layout.maximumWidth + padding * 2
padding: Nheko.paddingSmall
background: Rectangle {
color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
}
contentItem: RowLayout { contentItem: RowLayout {
id: redactedLayout id: redactedLayout
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
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?" + palette.text source: "image://colorimage/:/icons/icons/ui/delete.svg?" + palette.text
} }
Label { Label {
id: redactedLabel id: redactedLabel
Layout.margins: 0
property var redactedPair: msgRoot.room.formatRedactedEvent(msgRoot.eventId)
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.maximumWidth: implicitWidth + 1
Layout.fillWidth: true Layout.fillWidth: true
property var redactedPair: msgRoot.room.formatRedactedEvent(msgRoot.eventId) Layout.margins: 0
Layout.maximumWidth: implicitWidth + 1
ToolTip.text: redactedPair["second"]
ToolTip.visible: hh.hovered
text: redactedPair["first"] text: redactedPair["first"]
wrapMode: Label.WordWrap wrapMode: Label.WordWrap
ToolTip.text: redactedPair["second"]
ToolTip.visible: hh.hovered
HoverHandler { HoverHandler {
id: hh id: hh
} }
} }
} }
padding: Nheko.paddingSmall
Layout.maximumWidth: redactedLayout.Layout.maximumWidth + padding * 2
background: Rectangle {
color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
}
} }

@ -11,43 +11,35 @@ import "../"
AbstractButton { AbstractButton {
id: r id: r
property color userColor: "red"
property bool keepFullText: false
required property string eventId required property string eventId
property bool keepFullText: false
required property int maxWidth
property var room_: room property var room_: room
property color userColor: "red"
property string userId: eventId ? room.dataById(eventId, Room.UserId, "") : "" property string userId: eventId ? room.dataById(eventId, Room.UserId, "") : ""
property string userName: eventId ? room.dataById(eventId, Room.UserName, "") : "" property string userName: eventId ? room.dataById(eventId, Room.UserName, "") : ""
implicitHeight: replyContainer.implicitHeight implicitHeight: replyContainer.implicitHeight
implicitWidth: replyContainer.implicitWidth implicitWidth: replyContainer.implicitWidth
required property int maxWidth
NhekoCursorShape { background: Rectangle {
anchors.fill: parent id: backgroundItem
cursorShape: Qt.PointingHandCursor
}
onClicked: { property color bgColor: palette.base
let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX-colorline.width, pressY - userName_.implicitHeight); property color userColor: TimelineManager.userColor(r.userId, palette.base)
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)
color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1))
z: -1
}
contentItem: TimelineEvent { contentItem: TimelineEvent {
id: timelineEvent id: timelineEvent
isStateEvent: false
room: r.room_
eventId: r.eventId eventId: r.eventId
replyTo: "" isStateEvent: false
mainInset: 4 + Nheko.paddingMedium mainInset: 4 + Nheko.paddingMedium
maxWidth: r.maxWidth maxWidth: r.maxWidth
replyTo: ""
room: r.room_
//height: replyContainer.implicitHeight //height: replyContainer.implicitHeight
data: Row { data: Row {
@ -58,14 +50,14 @@ AbstractButton {
Rectangle { Rectangle {
id: colorline id: colorline
width: 4
height: content.height
color: TimelineManager.userColor(r.userId, palette.base) color: TimelineManager.userColor(r.userId, palette.base)
height: content.height
width: 4
} }
Column { Column {
id: content id: content
data: [usernameBtn, timelineEvent.main,]
spacing: 0 spacing: 0
AbstractButton { AbstractButton {
@ -73,29 +65,31 @@ AbstractButton {
contentItem: Label { contentItem: Label {
id: userName_ id: userName_
text: r.userName
color: r.userColor color: r.userColor
text: r.userName
textFormat: Text.RichText textFormat: Text.RichText
width: timelineEvent.main?.width width: timelineEvent.main?.width
} }
onClicked: room.openUserProfile(r.userId) onClicked: room.openUserProfile(r.userId)
} }
data: [
usernameBtn, timelineEvent.main,
]
} }
} }
} }
background: Rectangle { onClicked: {
id: backgroundItem let link = reply.child.linkAt != undefined && reply.child.linkAt(pressX - colorline.width, pressY - userName_.implicitHeight);
if (link) {
z: -1 Nheko.openLink(link);
property color userColor: TimelineManager.userColor(r.userId, palette.base) } else {
property color bgColor: palette.base room.showEvent(r.eventId);
color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1)) }
} }
onPressAndHold: replyContextMenu.show(reply.child.copyText, reply.child.linkAt(pressX - colorline.width, pressY - userName_.implicitHeight), r.eventId)
NhekoCursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
} }

@ -7,14 +7,17 @@ import im.nheko
MatrixText { MatrixText {
required property string body required property string body
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body
property bool fitsMetadata: false //positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4)
required property string formatted
required property bool isOnlyEmoji required property bool isOnlyEmoji
property bool isReply: EventDelegateChooser.isReply property bool isReply: EventDelegateChooser.isReply
required property bool keepFullText required property bool keepFullText
required property string formatted
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body
property int metadataWidth: 100 property int metadataWidth: 100
property bool fitsMetadata: false //positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4)
enabled: !isReply
font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
// table border-collapse doesn't seem to work // table border-collapse doesn't seem to work
text: ` text: `
@ -30,7 +33,7 @@ MatrixText {
} }
table th, table th,
table td { table td {
padding: ` + Math.ceil(fontMetrics.lineSpacing/2) + `px; padding: ` + Math.ceil(fontMetrics.lineSpacing / 2) + `px;
} }
blockquote { margin-left: 1em; } blockquote { margin-left: 1em; }
` + (!Settings.mobileMode ? `span[data-mx-spoiler] { ` + (!Settings.mobileMode ? `span[data-mx-spoiler] {
@ -40,13 +43,9 @@ MatrixText {
`</style> `</style>
` + formatted.replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>") ` + formatted.replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>")
enabled: !isReply
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
} }
} }

@ -12,82 +12,69 @@ ApplicationWindow {
property var flow property var flow
onClosing: VerificationManager.removeVerificationFlow(flow)
title: stack.currentItem ? (stack.currentItem.title_ || "") : ""
modality: Qt.NonModal
color: palette.window color: palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: stack.currentItem.implicitHeight + 2 * Nheko.paddingMedium
//height: stack.currentItem.implicitHeight //height: stack.currentItem.implicitHeight
minimumHeight: stack.currentItem.implicitHeight + 2 * Nheko.paddingLarge minimumHeight: stack.currentItem.implicitHeight + 2 * Nheko.paddingLarge
height: stack.currentItem.implicitHeight + 2 * Nheko.paddingMedium
minimumWidth: 400 minimumWidth: 400
modality: Qt.NonModal
title: stack.currentItem ? (stack.currentItem.title_ || "") : ""
width: 400 width: 400
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
background: Rectangle { background: Rectangle {
color: palette.window color: palette.window
} }
onClosing: VerificationManager.removeVerificationFlow(flow)
StackView { StackView {
id: stack id: stack
anchors.centerIn: parent anchors.centerIn: parent
implicitHeight: dialog.height - 2 * Nheko.paddingMedium
implicitWidth: dialog.width - 2 * Nheko.paddingMedium
initialItem: newVerificationRequest initialItem: newVerificationRequest
implicitWidth: dialog.width - 2* Nheko.paddingMedium
implicitHeight: dialog.height - 2* Nheko.paddingMedium
} }
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"
@ -95,7 +82,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, newVerificationRequest) script: stack.replace(null, newVerificationRequest)
} }
}, },
State { State {
name: "CompareEmoji" name: "CompareEmoji"
@ -103,7 +89,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, emojiVerification) script: stack.replace(null, emojiVerification)
} }
}, },
State { State {
name: "CompareNumber" name: "CompareNumber"
@ -111,7 +96,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, digitVerification) script: stack.replace(null, digitVerification)
} }
}, },
State { State {
name: "WaitingForKeys" name: "WaitingForKeys"
@ -119,7 +103,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, waiting) script: stack.replace(null, waiting)
} }
}, },
State { State {
name: "WaitingForOtherToAccept" name: "WaitingForOtherToAccept"
@ -127,7 +110,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, waiting) script: stack.replace(null, waiting)
} }
}, },
State { State {
name: "WaitingForMac" name: "WaitingForMac"
@ -135,7 +117,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, waiting) script: stack.replace(null, waiting)
} }
}, },
State { State {
name: "Success" name: "Success"
@ -143,7 +124,6 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, success) script: stack.replace(null, success)
} }
}, },
State { State {
name: "Failed" name: "Failed"
@ -151,9 +131,7 @@ ApplicationWindow {
StateChangeScript { StateChangeScript {
script: stack.replace(null, failed) script: stack.replace(null, failed)
} }
} }
] ]
} }
} }

@ -12,59 +12,56 @@ ColumnLayout {
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: palette.text color: palette.text
text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!")
verticalAlignment: Text.AlignVCenter 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: 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: palette.text
} }
Label { Label {
color: 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: palette.text
} }
Label { Label {
color: 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: 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()
} }
} }
} }

@ -8,9 +8,9 @@ import QtQuick.Layouts
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 {
@ -21,17 +21,14 @@ Rectangle {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
Label { Label {
Layout.preferredHeight: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: col.emoji.emoji Layout.preferredHeight: font.pixelSize * 2
font.pixelSize: Qt.application.font.pixelSize * 2 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 text: col.emoji.description
} }
} }
} }

@ -13,339 +13,340 @@ ColumnLayout {
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: palette.text color: palette.text
text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
} }
Item { Layout.fillHeight: true; }
RowLayout { RowLayout {
id: emojis id: emojis
property var mapping: [{ property var mapping: [{
"number": 0, "number": 0,
"emoji": "🐶", "emoji": "🐶",
"description": "Dog", "description": "Dog",
"unicode": "U+1F436" "unicode": "U+1F436"
}, { }, {
"number": 1, "number": 1,
"emoji": "🐱", "emoji": "🐱",
"description": "Cat", "description": "Cat",
"unicode": "U+1F431" "unicode": "U+1F431"
}, { }, {
"number": 2, "number": 2,
"emoji": "🦁", "emoji": "🦁",
"description": "Lion", "description": "Lion",
"unicode": "U+1F981" "unicode": "U+1F981"
}, { }, {
"number": 3, "number": 3,
"emoji": "🐎", "emoji": "🐎",
"description": "Horse", "description": "Horse",
"unicode": "U+1F40E" "unicode": "U+1F40E"
}, { }, {
"number": 4, "number": 4,
"emoji": "🦄", "emoji": "🦄",
"description": "Unicorn", "description": "Unicorn",
"unicode": "U+1F984" "unicode": "U+1F984"
}, { }, {
"number": 5, "number": 5,
"emoji": "🐷", "emoji": "🐷",
"description": "Pig", "description": "Pig",
"unicode": "U+1F437" "unicode": "U+1F437"
}, { }, {
"number": 6, "number": 6,
"emoji": "🐘", "emoji": "🐘",
"description": "Elephant", "description": "Elephant",
"unicode": "U+1F418" "unicode": "U+1F418"
}, { }, {
"number": 7, "number": 7,
"emoji": "🐰", "emoji": "🐰",
"description": "Rabbit", "description": "Rabbit",
"unicode": "U+1F430" "unicode": "U+1F430"
}, { }, {
"number": 8, "number": 8,
"emoji": "🐼", "emoji": "🐼",
"description": "Panda", "description": "Panda",
"unicode": "U+1F43C" "unicode": "U+1F43C"
}, { }, {
"number": 9, "number": 9,
"emoji": "🐓", "emoji": "🐓",
"description": "Rooster", "description": "Rooster",
"unicode": "U+1F413" "unicode": "U+1F413"
}, { }, {
"number": 10, "number": 10,
"emoji": "🐧", "emoji": "🐧",
"description": "Penguin", "description": "Penguin",
"unicode": "U+1F427" "unicode": "U+1F427"
}, { }, {
"number": 11, "number": 11,
"emoji": "🐢", "emoji": "🐢",
"description": "Turtle", "description": "Turtle",
"unicode": "U+1F422" "unicode": "U+1F422"
}, { }, {
"number": 12, "number": 12,
"emoji": "🐟", "emoji": "🐟",
"description": "Fish", "description": "Fish",
"unicode": "U+1F41F" "unicode": "U+1F41F"
}, { }, {
"number": 13, "number": 13,
"emoji": "🐙", "emoji": "🐙",
"description": "Octopus", "description": "Octopus",
"unicode": "U+1F419" "unicode": "U+1F419"
}, { }, {
"number": 14, "number": 14,
"emoji": "🦋", "emoji": "🦋",
"description": "Butterfly", "description": "Butterfly",
"unicode": "U+1F98B" "unicode": "U+1F98B"
}, { }, {
"number": 15, "number": 15,
"emoji": "🌷", "emoji": "🌷",
"description": "Flower", "description": "Flower",
"unicode": "U+1F337" "unicode": "U+1F337"
}, { }, {
"number": 16, "number": 16,
"emoji": "🌳", "emoji": "🌳",
"description": "Tree", "description": "Tree",
"unicode": "U+1F333" "unicode": "U+1F333"
}, { }, {
"number": 17, "number": 17,
"emoji": "🌵", "emoji": "🌵",
"description": "Cactus", "description": "Cactus",
"unicode": "U+1F335" "unicode": "U+1F335"
}, { }, {
"number": 18, "number": 18,
"emoji": "🍄", "emoji": "🍄",
"description": "Mushroom", "description": "Mushroom",
"unicode": "U+1F344" "unicode": "U+1F344"
}, { }, {
"number": 19, "number": 19,
"emoji": "🌏", "emoji": "🌏",
"description": "Globe", "description": "Globe",
"unicode": "U+1F30F" "unicode": "U+1F30F"
}, { }, {
"number": 20, "number": 20,
"emoji": "🌙", "emoji": "🌙",
"description": "Moon", "description": "Moon",
"unicode": "U+1F319" "unicode": "U+1F319"
}, { }, {
"number": 21, "number": 21,
"emoji": "☁", "emoji": "☁",
"description": "Cloud", "description": "Cloud",
"unicode": "U+2601U+FE0F" "unicode": "U+2601U+FE0F"
}, { }, {
"number": 22, "number": 22,
"emoji": "🔥", "emoji": "🔥",
"description": "Fire", "description": "Fire",
"unicode": "U+1F525" "unicode": "U+1F525"
}, { }, {
"number": 23, "number": 23,
"emoji": "🍌", "emoji": "🍌",
"description": "Banana", "description": "Banana",
"unicode": "U+1F34C" "unicode": "U+1F34C"
}, { }, {
"number": 24, "number": 24,
"emoji": "🍎", "emoji": "🍎",
"description": "Apple", "description": "Apple",
"unicode": "U+1F34E" "unicode": "U+1F34E"
}, { }, {
"number": 25, "number": 25,
"emoji": "🍓", "emoji": "🍓",
"description": "Strawberry", "description": "Strawberry",
"unicode": "U+1F353" "unicode": "U+1F353"
}, { }, {
"number": 26, "number": 26,
"emoji": "🌽", "emoji": "🌽",
"description": "Corn", "description": "Corn",
"unicode": "U+1F33D" "unicode": "U+1F33D"
}, { }, {
"number": 27, "number": 27,
"emoji": "🍕", "emoji": "🍕",
"description": "Pizza", "description": "Pizza",
"unicode": "U+1F355" "unicode": "U+1F355"
}, { }, {
"number": 28, "number": 28,
"emoji": "🎂", "emoji": "🎂",
"description": "Cake", "description": "Cake",
"unicode": "U+1F382" "unicode": "U+1F382"
}, { }, {
"number": 29, "number": 29,
"emoji": "❤", "emoji": "❤",
"description": "Heart", "description": "Heart",
"unicode": "U+2764U+FE0F" "unicode": "U+2764U+FE0F"
}, { }, {
"number": 30, "number": 30,
"emoji": "😀", "emoji": "😀",
"description": "Smiley", "description": "Smiley",
"unicode": "U+1F600" "unicode": "U+1F600"
}, { }, {
"number": 31, "number": 31,
"emoji": "🤖", "emoji": "🤖",
"description": "Robot", "description": "Robot",
"unicode": "U+1F916" "unicode": "U+1F916"
}, { }, {
"number": 32, "number": 32,
"emoji": "🎩", "emoji": "🎩",
"description": "Hat", "description": "Hat",
"unicode": "U+1F3A9" "unicode": "U+1F3A9"
}, { }, {
"number": 33, "number": 33,
"emoji": "👓", "emoji": "👓",
"description": "Glasses", "description": "Glasses",
"unicode": "U+1F453" "unicode": "U+1F453"
}, { }, {
"number": 34, "number": 34,
"emoji": "🔧", "emoji": "🔧",
"description": "Spanner", "description": "Spanner",
"unicode": "U+1F527" "unicode": "U+1F527"
}, { }, {
"number": 35, "number": 35,
"emoji": "🎅", "emoji": "🎅",
"description": "Santa", "description": "Santa",
"unicode": "U+1F385" "unicode": "U+1F385"
}, { }, {
"number": 36, "number": 36,
"emoji": "👍", "emoji": "👍",
"description": "Thumbs Up", "description": "Thumbs Up",
"unicode": "U+1F44D" "unicode": "U+1F44D"
}, { }, {
"number": 37, "number": 37,
"emoji": "☂", "emoji": "☂",
"description": "Umbrella", "description": "Umbrella",
"unicode": "U+2602U+FE0F" "unicode": "U+2602U+FE0F"
}, { }, {
"number": 38, "number": 38,
"emoji": "⌛", "emoji": "⌛",
"description": "Hourglass", "description": "Hourglass",
"unicode": "U+231B" "unicode": "U+231B"
}, { }, {
"number": 39, "number": 39,
"emoji": "⏰", "emoji": "⏰",
"description": "Clock", "description": "Clock",
"unicode": "U+23F0" "unicode": "U+23F0"
}, { }, {
"number": 40, "number": 40,
"emoji": "🎁", "emoji": "🎁",
"description": "Gift", "description": "Gift",
"unicode": "U+1F381" "unicode": "U+1F381"
}, { }, {
"number": 41, "number": 41,
"emoji": "💡", "emoji": "💡",
"description": "Light Bulb", "description": "Light Bulb",
"unicode": "U+1F4A1" "unicode": "U+1F4A1"
}, { }, {
"number": 42, "number": 42,
"emoji": "📕", "emoji": "📕",
"description": "Book", "description": "Book",
"unicode": "U+1F4D5" "unicode": "U+1F4D5"
}, { }, {
"number": 43, "number": 43,
"emoji": "✏", "emoji": "✏",
"description": "Pencil", "description": "Pencil",
"unicode": "U+270FU+FE0F" "unicode": "U+270FU+FE0F"
}, { }, {
"number": 44, "number": 44,
"emoji": "📎", "emoji": "📎",
"description": "Paperclip", "description": "Paperclip",
"unicode": "U+1F4CE" "unicode": "U+1F4CE"
}, { }, {
"number": 45, "number": 45,
"emoji": "✂", "emoji": "✂",
"description": "Scissors", "description": "Scissors",
"unicode": "U+2702U+FE0F" "unicode": "U+2702U+FE0F"
}, { }, {
"number": 46, "number": 46,
"emoji": "🔒", "emoji": "🔒",
"description": "Lock", "description": "Lock",
"unicode": "U+1F512" "unicode": "U+1F512"
}, { }, {
"number": 47, "number": 47,
"emoji": "🔑", "emoji": "🔑",
"description": "Key", "description": "Key",
"unicode": "U+1F511" "unicode": "U+1F511"
}, { }, {
"number": 48, "number": 48,
"emoji": "🔨", "emoji": "🔨",
"description": "Hammer", "description": "Hammer",
"unicode": "U+1F528" "unicode": "U+1F528"
}, { }, {
"number": 49, "number": 49,
"emoji": "☎", "emoji": "☎",
"description": "Telephone", "description": "Telephone",
"unicode": "U+260EU+FE0F" "unicode": "U+260EU+FE0F"
}, { }, {
"number": 50, "number": 50,
"emoji": "🏁", "emoji": "🏁",
"description": "Flag", "description": "Flag",
"unicode": "U+1F3C1" "unicode": "U+1F3C1"
}, { }, {
"number": 51, "number": 51,
"emoji": "🚂", "emoji": "🚂",
"description": "Train", "description": "Train",
"unicode": "U+1F682" "unicode": "U+1F682"
}, { }, {
"number": 52, "number": 52,
"emoji": "🚲", "emoji": "🚲",
"description": "Bicycle", "description": "Bicycle",
"unicode": "U+1F6B2" "unicode": "U+1F6B2"
}, { }, {
"number": 53, "number": 53,
"emoji": "✈", "emoji": "✈",
"description": "Aeroplane", "description": "Aeroplane",
"unicode": "U+2708U+FE0F" "unicode": "U+2708U+FE0F"
}, { }, {
"number": 54, "number": 54,
"emoji": "🚀", "emoji": "🚀",
"description": "Rocket", "description": "Rocket",
"unicode": "U+1F680" "unicode": "U+1F680"
}, { }, {
"number": 55, "number": 55,
"emoji": "🏆", "emoji": "🏆",
"description": "Trophy", "description": "Trophy",
"unicode": "U+1F3C6" "unicode": "U+1F3C6"
}, { }, {
"number": 56, "number": 56,
"emoji": "⚽", "emoji": "⚽",
"description": "Ball", "description": "Ball",
"unicode": "U+26BD" "unicode": "U+26BD"
}, { }, {
"number": 57, "number": 57,
"emoji": "🎸", "emoji": "🎸",
"description": "Guitar", "description": "Guitar",
"unicode": "U+1F3B8" "unicode": "U+1F3B8"
}, { }, {
"number": 58, "number": 58,
"emoji": "🎺", "emoji": "🎺",
"description": "Trumpet", "description": "Trumpet",
"unicode": "U+1F3BA" "unicode": "U+1F3BA"
}, { }, {
"number": 59, "number": 59,
"emoji": "🔔", "emoji": "🔔",
"description": "Bell", "description": "Bell",
"unicode": "U+1F514" "unicode": "U+1F514"
}, { }, {
"number": 60, "number": 60,
"emoji": "⚓", "emoji": "⚓",
"description": "Anchor", "description": "Anchor",
"unicode": "U+2693" "unicode": "U+2693"
}, { }, {
"number": 61, "number": 61,
"emoji": "🎧", "emoji": "🎧",
"description": "Headphones", "description": "Headphones",
"unicode": "U+1F3A7" "unicode": "U+1F3A7"
}, { }, {
"number": 62, "number": 62,
"emoji": "📁", "emoji": "📁",
"description": "Folder", "description": "Folder",
"unicode": "U+1F4C1" "unicode": "U+1F4C1"
}, { }, {
"number": 63, "number": 63,
"emoji": "📌", "emoji": "📌",
"description": "Pin", "description": "Pin",
"unicode": "U+1F4CC" "unicode": "U+1F4CC"
}] }]
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -370,58 +371,52 @@ ColumnLayout {
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: palette.text color: 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: palette.text color: palette.text
text: col.emoji.description
} }
} }
} }
} }
} }
Item { Layout.fillHeight: true; } Item {
Layout.fillHeight: true
}
Label { Label {
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
text: qsTr("The displayed emoji might look different in different clients if a different font is used. Similarly they might be translated into different languages. Nonetheless they should depict one of 64 different objects or animals. For example a lion and a cat are different, but a cat is the same even if one client just shows a cat face, while another client shows a full cat body.")
color: palette.text color: palette.text
text: qsTr("The displayed emoji might look different in different clients if a different font is used. Similarly they might be translated into different languages. Nonetheless they should depict one of 64 different objects or animals. For example a lion and a cat are different, but a cat is the same even if one client just shows a cat face, while another client shows a full cat body.")
verticalAlignment: Text.AlignVCenter 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: 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()
} }
} }
} }

@ -9,49 +9,48 @@ import im.nheko 1.0
ColumnLayout { ColumnLayout {
property string title: qsTr("Verification failed") property string title: qsTr("Verification failed")
spacing: 16 spacing: 16
Text { Text {
id: content id: content
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
color: palette.text
text: { text: {
switch (flow.error) { switch (flow.error) {
case DeviceVerificationFlow.UnknownMethod: case DeviceVerificationFlow.UnknownMethod:
return qsTr("Other client does not support our verification protocol."); return qsTr("Other client does not support our verification protocol.");
case DeviceVerificationFlow.MismatchedCommitment: case DeviceVerificationFlow.MismatchedCommitment:
case DeviceVerificationFlow.MismatchedSAS: case DeviceVerificationFlow.MismatchedSAS:
case DeviceVerificationFlow.KeyMismatch: case DeviceVerificationFlow.KeyMismatch:
return qsTr("Key mismatch detected!"); return qsTr("Key mismatch detected!");
case DeviceVerificationFlow.Timeout: case DeviceVerificationFlow.Timeout:
return qsTr("Device verification timed out."); return qsTr("Device verification timed out.");
case DeviceVerificationFlow.User: case DeviceVerificationFlow.User:
return qsTr("Other party canceled the verification."); return qsTr("Other party canceled the verification.");
case DeviceVerificationFlow.OutOfOrder: case DeviceVerificationFlow.OutOfOrder:
return qsTr("Verification messages received out of order!"); return qsTr("Verification messages received out of order!");
default: default:
return qsTr("Unknown verification error."); return qsTr("Unknown verification error.");
} }
} }
color: 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()
} }
} }
} }

@ -12,11 +12,11 @@ ColumnLayout {
spacing: 16 spacing: 16
Label { Label {
Layout.fillWidth: true
// Self verification // Self verification
Layout.preferredWidth: 400 Layout.preferredWidth: 400
Layout.fillWidth: true color: palette.text
wrapMode: Text.Wrap
text: { text: {
if (flow.sender) { if (flow.sender) {
if (flow.isSelfVerification) if (flow.isSelfVerification)
@ -35,32 +35,30 @@ ColumnLayout {
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: 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()
} }
} }
} }

@ -14,27 +14,25 @@ ColumnLayout {
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: palette.text color: 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()
} }
} }
} }

@ -10,52 +10,52 @@ import im.nheko 1.0
ColumnLayout { ColumnLayout {
property string title: qsTr("Waiting for other party…") property string title: qsTr("Waiting for other party…")
spacing: 16 spacing: 16
Label { Label {
id: content id: content
Layout.preferredWidth: 400
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap Layout.preferredWidth: 400
color: palette.text
text: { text: {
switch (flow.state) { switch (flow.state) {
case "WaitingForOtherToAccept": case "WaitingForOtherToAccept":
return qsTr("Waiting for other side to accept the verification request."); return qsTr("Waiting for other side to accept the verification request.");
case "WaitingForKeys": case "WaitingForKeys":
return qsTr("Waiting for other side to continue the verification process."); return qsTr("Waiting for other side to continue the verification process.");
case "WaitingForMac": case "WaitingForMac":
return qsTr("Waiting for other side to complete the verification process."); return qsTr("Waiting for other side to complete the verification process.");
default: default:
return ""; return "";
} }
} }
color: 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: palette.mid foreground: 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
} }
} }
} }

@ -8,21 +8,31 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import im.nheko import im.nheko
ApplicationWindow { ApplicationWindow {
id: aliasEditorW id: aliasEditorW
property var roomSettings
property var editingModel: Nheko.editAliases(roomSettings.roomId) property var editingModel: Nheko.editAliases(roomSettings.roomId)
property var roomSettings
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
minimumWidth: 300
minimumHeight: 400
height: 600 height: 600
minimumHeight: 400
minimumWidth: 300
modality: Qt.NonModal
title: qsTr("Aliases to %1").arg(roomSettings.roomName)
width: 500 width: 500
title: qsTr("Aliases to %1").arg(roomSettings.roomName); footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
editingModel.commit();
aliasEditorW.close();
}
onRejected: aliasEditorW.close()
}
// Shortcut { // Shortcut {
// sequence: StandardKey.Cancel // sequence: StandardKey.Cancel
@ -30,32 +40,27 @@ ApplicationWindow {
// } // }
ColumnLayout { ColumnLayout {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: 0 spacing: 0
MatrixText { MatrixText {
text: qsTr("List of aliases to this room. Usually you can only add aliases on your server. You can have one canonical alias and many alternate aliases.") Layout.bottomMargin: Nheko.paddingMedium
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false Layout.fillHeight: false
Layout.fillWidth: true
color: palette.text color: palette.text
Layout.bottomMargin: Nheko.paddingMedium font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("List of aliases to this room. Usually you can only add aliases on your server. You can have one canonical alias and many alternate aliases.")
} }
ListView { ListView {
Layout.fillWidth: true
Layout.fillHeight: true
id: view id: view
Layout.fillHeight: true
Layout.fillWidth: true
cacheBuffer: 50
clip: true clip: true
model: editingModel model: editingModel
spacing: 4 spacing: 4
cacheBuffer: 50
delegate: RowLayout { delegate: RowLayout {
anchors.left: parent.left anchors.left: parent.left
@ -63,79 +68,70 @@ ApplicationWindow {
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.name
color: model.isPublished ? palette.text : Nheko.theme.error color: model.isPublished ? palette.text : Nheko.theme.error
text: model.name
textFormat: Text.PlainText textFormat: Text.PlainText
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.margins: 2 Layout.margins: 2
image: ":/icons/icons/ui/star.svg" ToolTip.text: model.isCanonical ? qsTr("Primary alias") : qsTr("Make primary alias")
hoverEnabled: true ToolTip.visible: hovered
buttonTextColor: model.isCanonical ? palette.highlight : palette.text buttonTextColor: model.isCanonical ? palette.highlight : palette.text
highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor
hoverEnabled: true
ToolTip.visible: hovered image: ":/icons/icons/ui/star.svg"
ToolTip.text: model.isCanonical ? qsTr("Primary alias") : qsTr("Make primary alias")
onClicked: editingModel.makeCanonical(model.index) onClicked: editingModel.makeCanonical(model.index)
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.margins: 2 Layout.margins: 2
image: ":/icons/icons/ui/building-shop.svg" ToolTip.text: qsTr("Advertise as an alias in this room")
hoverEnabled: true ToolTip.visible: hovered
buttonTextColor: model.isAdvertized ? palette.highlight : palette.text buttonTextColor: model.isAdvertized ? palette.highlight : palette.text
highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor
hoverEnabled: true
ToolTip.visible: hovered image: ":/icons/icons/ui/building-shop.svg"
ToolTip.text: qsTr("Advertise as an alias in this room")
onClicked: editingModel.toggleAdvertize(model.index) onClicked: editingModel.toggleAdvertize(model.index)
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.margins: 2 Layout.margins: 2
image: ":/icons/icons/ui/room-directory.svg"
hoverEnabled: true
buttonTextColor: model.isPublished ? palette.highlight : palette.text
ToolTip.visible: hovered
ToolTip.text: qsTr("Publish in room directory") ToolTip.text: qsTr("Publish in room directory")
ToolTip.visible: hovered
buttonTextColor: model.isPublished ? palette.highlight : palette.text
hoverEnabled: true
image: ":/icons/icons/ui/room-directory.svg"
onClicked: editingModel.togglePublish(model.index) onClicked: editingModel.togglePublish(model.index)
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.margins: 2 Layout.margins: 2
image: ":/icons/icons/ui/dismiss.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Remove this alias") ToolTip.text: qsTr("Remove this alias")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg"
onClicked: editingModel.deleteAlias(model.index) onClicked: editingModel.deleteAlias(model.index)
} }
} }
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
Layout.fillWidth: true Layout.fillWidth: true
spacing: Nheko.paddingMedium
MatrixTextField { MatrixTextField {
id: newAliasVal id: newAliasVal
focus: true
Layout.fillWidth: true Layout.fillWidth: true
selectByMouse: true
font.pixelSize: fontMetrics.font.pixelSize
color: palette.text color: palette.text
focus: true
font.pixelSize: fontMetrics.font.pixelSize
placeholderText: qsTr("#new-alias:server.tld") placeholderText: qsTr("#new-alias:server.tld")
selectByMouse: true
Component.onCompleted: forceActiveFocus() Component.onCompleted: forceActiveFocus()
Keys.onPressed: { Keys.onPressed: {
@ -145,10 +141,10 @@ ApplicationWindow {
} }
} }
} }
Button { Button {
text: qsTr("Add")
Layout.preferredWidth: 100 Layout.preferredWidth: 100
text: qsTr("Add")
onClicked: { onClicked: {
editingModel.addAlias(newAliasVal.text); editingModel.addAlias(newAliasVal.text);
newAliasVal.clear(); newAliasVal.clear();
@ -156,16 +152,4 @@ ApplicationWindow {
} }
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
editingModel.commit();
aliasEditorW.close();
}
onRejected: aliasEditorW.close();
}
} }

@ -14,47 +14,54 @@ ApplicationWindow {
property var roomSettings property var roomSettings
minimumWidth: 340
minimumHeight: 450
width: 450
height: 680
color: palette.window color: 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
title: qsTr("Allowed rooms settings") title: qsTr("Allowed rooms settings")
width: 450
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
roomSettings.applyAllowedFromModel();
allowedDialog.close();
}
onRejected: allowedDialog.close()
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: roomSettingsDialog.close() onActivated: roomSettingsDialog.close()
} }
ColumnLayout { ColumnLayout {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: 0 spacing: 0
MatrixText { MatrixText {
text: qsTr("List of rooms that allow access to this room. Anyone who is in any of those rooms can join this room.") Layout.bottomMargin: Nheko.paddingMedium
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false Layout.fillHeight: false
Layout.fillWidth: true
color: palette.text color: palette.text
Layout.bottomMargin: Nheko.paddingMedium font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("List of rooms that allow access to this room. Anyone who is in any of those rooms can join this room.")
} }
ListView { ListView {
Layout.fillWidth: true
Layout.fillHeight: true
id: view id: view
Layout.fillHeight: true
Layout.fillWidth: true
cacheBuffer: 50
clip: true clip: true
model: roomSettings.allowedRoomsModel model: roomSettings.allowedRoomsModel
spacing: 4 spacing: 4
cacheBuffer: 50
delegate: RowLayout { delegate: RowLayout {
anchors.left: parent.left anchors.left: parent.left
@ -62,63 +69,57 @@ ApplicationWindow {
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.name
color: palette.text color: palette.text
text: model.name
textFormat: Text.PlainText textFormat: Text.PlainText
} }
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.isParent ? qsTr("Parent community") : qsTr("Other room")
color: palette.buttonText color: palette.buttonText
text: model.isParent ? qsTr("Parent community") : qsTr("Other room")
textFormat: Text.PlainText textFormat: Text.PlainText
} }
} }
ToggleButton { ToggleButton {
checked: model.allowed
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: model.allowed
onCheckedChanged: model.allowed = checked onCheckedChanged: model.allowed = checked
} }
} }
} }
Column {
Column{
id: roomEntryCompleter id: roomEntryCompleter
Layout.fillWidth: true
Layout.fillWidth: true
spacing: 1 spacing: 1
z: 5 z: 5
Completer { Completer {
id: roomCompleter id: roomCompleter
visible: roomEntry.text.length > 0
width: parent.width
roomId: allowedDialog.roomSettings.roomId
completerName: "room"
bottomToTop: true
fullWidth: true
avatarHeight: Nheko.avatarSize / 2 avatarHeight: Nheko.avatarSize / 2
avatarWidth: Nheko.avatarSize / 2 avatarWidth: Nheko.avatarSize / 2
bottomToTop: true
centerRowContent: false centerRowContent: false
completerName: "room"
fullWidth: true
roomId: allowedDialog.roomSettings.roomId
rowMargin: 2 rowMargin: 2
rowSpacing: 2 rowSpacing: 2
visible: roomEntry.text.length > 0
width: parent.width
} }
MatrixTextField { MatrixTextField {
id: roomEntry id: roomEntry
width: parent.width color: palette.text
placeholderText: qsTr("Enter additional rooms not in the list yet...") placeholderText: qsTr("Enter additional rooms not in the list yet...")
width: parent.width
color: palette.text
onTextEdited: {
roomCompleter.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;
@ -126,45 +127,31 @@ ApplicationWindow {
} else if (event.key == Qt.Key_Down || event.key == Qt.Key_Tab) { } else if (event.key == Qt.Key_Down || event.key == Qt.Key_Tab) {
event.accepted = true; event.accepted = true;
if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))
roomCompleter.up(); roomCompleter.up();
else else
roomCompleter.down(); roomCompleter.down();
} else if (event.matches(StandardKey.InsertParagraphSeparator)) { } else if (event.matches(StandardKey.InsertParagraphSeparator)) {
roomCompleter.finishCompletion(); roomCompleter.finishCompletion();
event.accepted = true; event.accepted = true;
} }
} }
onTextEdited: {
roomCompleter.completer.searchString = text;
}
} }
} }
Connections { Connections {
function onCompletionSelected(id) { function onCompletionSelected(id) {
console.log("selected: " + id); console.log("selected: " + id);
roomSettings.allowedRoomsModel.addRoom(id); roomSettings.allowedRoomsModel.addRoom(id);
roomEntry.clear(); roomEntry.clear();
} }
function onCountChanged() { function onCountChanged() {
if (roomCompleter.count > 0 && (roomCompleter.currentIndex < 0 || roomCompleter.currentIndex >= roomCompleter.count)) if (roomCompleter.count > 0 && (roomCompleter.currentIndex < 0 || roomCompleter.currentIndex >= roomCompleter.count))
roomCompleter.currentIndex = 0; roomCompleter.currentIndex = 0;
} }
target: roomCompleter target: roomCompleter
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
roomSettings.applyAllowedFromModel();
allowedDialog.close();
}
onRejected: allowedDialog.close()
}
} }

@ -15,134 +15,124 @@ ApplicationWindow {
required property RoomSummary summary required property RoomSummary summary
title: summary.isSpace ? qsTr("Confirm community join") : qsTr("Confirm room join")
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
color: palette.window color: palette.window
width: 350 flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: content.implicitHeight + Nheko.paddingLarge + footer.implicitHeight height: content.implicitHeight + Nheko.paddingLarge + footer.implicitHeight
modality: Qt.WindowModal
title: summary.isSpace ? qsTr("Confirm community join") : qsTr("Confirm room join")
width: 350
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Cancel
onAccepted: {
summary.reason = reason.text;
summary.join();
joinRoomRoot.close();
}
onRejected: {
joinRoomRoot.close();
}
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: input.text.match("#.+?:.{3,}")
text: summary.isKnockOnly ? qsTr("Knock") : qsTr("Join")
}
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: dbb.rejected() onActivated: dbb.rejected()
} }
ColumnLayout { ColumnLayout {
id: content id: content
spacing: Nheko.paddingMedium
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Avatar { Avatar {
Layout.topMargin: Nheko.paddingMedium Layout.alignment: Qt.AlignHCenter
url: summary.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
roomid: summary.roomid
displayName: summary.roomName
Layout.preferredHeight: 130 Layout.preferredHeight: 130
Layout.preferredWidth: 130 Layout.preferredWidth: 130
Layout.alignment: Qt.AlignHCenter Layout.topMargin: Nheko.paddingMedium
displayName: summary.roomName
roomid: summary.roomid
url: summary.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
} }
Spinner { Spinner {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
visible: !summary.isLoaded
foreground: palette.mid foreground: palette.mid
running: !summary.isLoaded running: !summary.isLoaded
visible: !summary.isLoaded
} }
TextEdit { TextEdit {
readOnly: true
textFormat: TextEdit.RichText
text: summary.roomName
font.pixelSize: fontMetrics.font.pixelSize * 2
color: palette.text
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text
font.pixelSize: fontMetrics.font.pixelSize * 2
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap readOnly: true
selectByMouse: true selectByMouse: true
text: summary.roomName
textFormat: TextEdit.RichText
wrapMode: TextEdit.Wrap
} }
TextEdit { TextEdit {
readOnly: true
textFormat: TextEdit.RichText
text: summary.roomid
font.pixelSize: fontMetrics.font.pixelSize * 0.8
color: palette.text
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text
font.pixelSize: fontMetrics.font.pixelSize * 0.8
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap readOnly: true
selectByMouse: true selectByMouse: true
text: summary.roomid
textFormat: TextEdit.RichText
wrapMode: TextEdit.Wrap
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingMedium
MatrixText { MatrixText {
text: qsTr("%n member(s)", "", summary.memberCount) text: qsTr("%n member(s)", "", summary.memberCount)
} }
ImageButton { ImageButton {
image: ":/icons/icons/ui/people.svg"
enabled: false enabled: false
image: ":/icons/icons/ui/people.svg"
} }
} }
TextEdit { TextEdit {
readOnly: true
textFormat: TextEdit.RichText
text: summary.roomTopic
color: palette.text
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap readOnly: true
selectByMouse: true selectByMouse: true
text: summary.roomTopic
textFormat: TextEdit.RichText
wrapMode: TextEdit.Wrap
} }
Label { Label {
id: promptLabel id: promptLabel
text: summary.isKnockOnly ? qsTr("This room can't be joined directly. You can, however, knock on the room and room members can accept or decline this join request. You can additionally provide a reason for them to let you in below:") : qsTr("Do you want to join this room? You can optionally add a reason below:")
color: palette.text
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text
font.bold: true
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: summary.isKnockOnly ? qsTr("This room can't be joined directly. You can, however, knock on the room and room members can accept or decline this join request. You can additionally provide a reason for them to let you in below:") : qsTr("Do you want to join this room? You can optionally add a reason below:")
wrapMode: Text.Wrap wrapMode: Text.Wrap
font.bold: true
} }
MatrixTextField { MatrixTextField {
id: reason id: reason
focus: true
Layout.fillWidth: true Layout.fillWidth: true
focus: true
text: joinRoomRoot.summary.reason text: joinRoomRoot.summary.reason
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Cancel
onAccepted: {
summary.reason = reason.text;
summary.join();
joinRoomRoot.close();
}
onRejected: {
joinRoomRoot.close();
}
Button {
text: summary.isKnockOnly ? qsTr("Knock") : qsTr("Join")
enabled: input.text.match("#.+?:.{3,}")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
}
} }

@ -11,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 && profile
text: "Start Direct Chat"
}
}
onVisibilityChanged: { onVisibilityChanged: {
userID.forceActiveFocus(); userID.forceActiveFocus();
@ -25,91 +43,82 @@ 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, palette.window) color: TimelineManager.userColor(userID.text, 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: palette.buttonText color: palette.buttonText
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: {
// we can't use "isValidMxid" here, since the property might only be reevaluated after this change handler. // we can't use "isValidMxid" here, since the property might only be reevaluated after this change handler.
if(text.match("@.+?:.{3,}")) { if (text.match("@.+?:.{3,}")) {
profile = TimelineManager.getGlobalUserProfile(text); 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: palette.text color: 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 && profile
}
onRejected: createDirectRoot.close();
onAccepted: {
profile.startChat(encryption.checked)
createDirectRoot.close()
} }
} }
} }

@ -14,11 +14,32 @@ ApplicationWindow {
property bool space: false property bool space: false
title: space ? qsTr("New community") : qsTr("New Room")
minimumWidth: Math.max(rootLayout.implicitWidth+2*rootLayout.anchors.margins, footer.implicitWidth + Nheko.paddingLarge)
minimumHeight: rootLayout.implicitHeight+footer.implicitHeight+2*rootLayout.anchors.margins
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
minimumHeight: rootLayout.implicitHeight + footer.implicitHeight + 2 * rootLayout.anchors.margins
minimumWidth: Math.max(rootLayout.implicitWidth + 2 * rootLayout.anchors.margins, footer.implicitWidth + Nheko.paddingLarge)
modality: Qt.NonModal
title: space ? qsTr("New community") : qsTr("New Room")
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
onAccepted: {
var preset = 0;
if (isPublic.checked) {
preset = 1;
} else {
preset = isTrusted.checked ? 2 : 0;
}
Nheko.createRoom(space, newRoomName.text, newRoomTopic.text, newRoomAlias.text, isEncrypted.checked, preset);
createRoomRoot.close();
}
onRejected: createRoomRoot.close()
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Create Room")
}
}
onVisibilityChanged: { onVisibilityChanged: {
newRoomName.forceActiveFocus(); newRoomName.forceActiveFocus();
@ -26,10 +47,12 @@ ApplicationWindow {
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: createRoomRoot.close() onActivated: createRoomRoot.close()
} }
GridLayout { GridLayout {
id: rootLayout id: rootLayout
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingLarge anchors.margins: Nheko.paddingLarge
columns: 2 columns: 2
@ -37,127 +60,118 @@ ApplicationWindow {
MatrixTextField { MatrixTextField {
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")
} }
MatrixTextField { MatrixTextField {
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: "#"
color: palette.text color: palette.text
text: "#"
} }
MatrixTextField { MatrixTextField {
id: newRoomAlias id: newRoomAlias
focus: true focus: true
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: palette.text color: 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: palette.text color: 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 {
visible: !space
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: palette.text color: palette.text
text: qsTr("Trusted")
visible: !space
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 {
visible: !space 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
visible: !space
} }
Label { Label {
visible: !space
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: palette.text color: palette.text
text: qsTr("Encryption")
visible: !space
HoverHandler { HoverHandler {
id: encryptionHover id: encryptionHover
} }
ToolTip.visible: encryptionHover.hovered
ToolTip.text: qsTr("Caution: Encryption cannot be disabled")
ToolTip.delay: Nheko.tooltipDelay
} }
ToggleButton { ToggleButton {
visible: !space id: isEncrypted
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
id: isEncrypted
checked: false checked: false
visible: !space
} }
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(space, newRoomName.text, newRoomTopic.text, newRoomAlias.text, isEncrypted.checked, preset)
createRoomRoot.close();
} }
} }
} }

@ -11,157 +11,151 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: dialog id: dialog
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
width: 275
height: 330 height: 330
minimumWidth: 250
minimumHeight: 220 minimumHeight: 220
minimumWidth: 250
EventExpiry { modality: Qt.NonModal
id: eventExpiry
roomid: dialog.roomid
}
title: { title: {
if (roomid) { if (roomid) {
return qsTr("Event expiration for %1").arg(roomName); return qsTr("Event expiration for %1").arg(roomName);
} } else {
else {
return qsTr("Event expiration"); return qsTr("Event expiration");
} }
} }
width: 275
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
eventExpiry.save();
dialog.close();
}
onRejected: dialog.close()
}
EventExpiry {
id: eventExpiry
roomid: dialog.roomid
}
Shortcut { 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("You can configure when your messages will be deleted in %1. This only happens when Nheko is open and has permissions to delete messages until Matrix servers support this feature natively. In general 0 means disable.").arg(roomName); return qsTr("You can configure when your messages will be deleted in %1. This only happens when Nheko is open and has permissions to delete messages until Matrix servers support this feature natively. In general 0 means disable.").arg(roomName);
} } else {
else {
return qsTr("You can configure when your messages will be deleted in all rooms unless configured otherwise. This only happens when Nheko is open and has permissions to delete messages until Matrix servers support this feature natively. In general 0 means disable."); return qsTr("You can configure when your messages will be deleted in all rooms unless configured otherwise. This only happens when Nheko is open and has permissions to delete messages until Matrix servers support this feature natively. In general 0 means disable.");
} }
} }
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.2)
Layout.fillWidth: true
Layout.fillHeight: false
} }
GridLayout { 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("Expire events after X days") Layout.fillWidth: true
ToolTip.text: qsTr("Automatically redacts messages after X days, unless otherwise protected. Set to 0 to disable.") ToolTip.text: qsTr("Automatically redacts messages after X days, unless otherwise protected. Set to 0 to disable.")
ToolTip.visible: hh1.hovered ToolTip.visible: hh1.hovered
Layout.fillWidth: true text: qsTr("Expire events after X days")
HoverHandler { HoverHandler {
id: hh1 id: hh1
} }
} }
SpinBox { SpinBox {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
editable: true
from: 0 from: 0
to: 1000
stepSize: 1 stepSize: 1
to: 1000
value: eventExpiry.expireEventsAfterDays value: eventExpiry.expireEventsAfterDays
onValueChanged: eventExpiry.expireEventsAfterDays = value onValueChanged: eventExpiry.expireEventsAfterDays = value
editable: true
} }
MatrixText { MatrixText {
text: qsTr("Only keep latest X events") Layout.fillWidth: true
ToolTip.text: qsTr("Deletes your events in this room if there are more than X newer messages unless otherwise protected. Set to 0 to disable.") ToolTip.text: qsTr("Deletes your events in this room if there are more than X newer messages unless otherwise protected. Set to 0 to disable.")
ToolTip.visible: hh2.hovered ToolTip.visible: hh2.hovered
Layout.fillWidth: true text: qsTr("Only keep latest X events")
HoverHandler { HoverHandler {
id: hh2 id: hh2
} }
} }
SpinBox { SpinBox {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
editable: true
from: 0 from: 0
to: 1000000
stepSize: 1 stepSize: 1
to: 1000000
value: eventExpiry.expireEventsAfterCount value: eventExpiry.expireEventsAfterCount
onValueChanged: eventExpiry.expireEventsAfterCount = value onValueChanged: eventExpiry.expireEventsAfterCount = value
editable: true
} }
MatrixText { MatrixText {
text: qsTr("Always keep latest X events") Layout.fillWidth: true
ToolTip.text: qsTr("This prevents events to be deleted by the above 2 settings if they are the latest X messages from you in the room.") ToolTip.text: qsTr("This prevents events to be deleted by the above 2 settings if they are the latest X messages from you in the room.")
ToolTip.visible: hh3.hovered ToolTip.visible: hh3.hovered
Layout.fillWidth: true text: qsTr("Always keep latest X events")
HoverHandler { HoverHandler {
id: hh3 id: hh3
} }
} }
SpinBox { SpinBox {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
editable: true
from: 0 from: 0
to: 1000000
stepSize: 1 stepSize: 1
to: 1000000
value: eventExpiry.protectLatestEvents value: eventExpiry.protectLatestEvents
onValueChanged: eventExpiry.protectLatestEvents = value onValueChanged: eventExpiry.protectLatestEvents = value
editable: true
} }
MatrixText { MatrixText {
text: qsTr("Include state events") Layout.fillWidth: true
ToolTip.text: qsTr("If this is turned on, old state events also get redacted. The latest state event of any type+key combination is excluded from redaction to not remove the room name and similar state by accident.") ToolTip.text: qsTr("If this is turned on, old state events also get redacted. The latest state event of any type+key combination is excluded from redaction to not remove the room name and similar state by accident.")
ToolTip.visible: hh4.hovered ToolTip.visible: hh4.hovered
Layout.fillWidth: true text: qsTr("Include state events")
HoverHandler { HoverHandler {
id: hh4 id: hh4
} }
} }
ToggleButton { ToggleButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: eventExpiry.expireStateEvents checked: eventExpiry.expireStateEvents
onToggled: eventExpiry.expireStateEvents = checked onToggled: eventExpiry.expireStateEvents = checked
} }
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
eventExpiry.save();
dialog.close();
}
onRejected: dialog.close();
}
} }

@ -15,49 +15,46 @@ ApplicationWindow {
fallback.confirm(); fallback.confirm();
fallbackRoot.close(); fallbackRoot.close();
} }
function reject() { function reject() {
fallback.cancel(); fallback.cancel();
fallbackRoot.close(); fallbackRoot.close();
} }
color: palette.window color: palette.window
title: qsTr("Fallback authentication")
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: msg.implicitHeight + footer.implicitHeight height: msg.implicitHeight + footer.implicitHeight
title: qsTr("Fallback authentication")
width: Math.max(msg.implicitWidth, footer.implicitWidth) width: Math.max(msg.implicitWidth, footer.implicitWidth)
Shortcut {
sequence: StandardKey.Cancel
onActivated: fallbackRoot.reject()
}
Label {
id: msg
anchors.fill: parent
padding: 8
text: qsTr("Open the fallback, follow the steps, and confirm after completing them.")
}
footer: DialogButtonBox { footer: DialogButtonBox {
onAccepted: fallbackRoot.accept() onAccepted: fallbackRoot.accept()
onRejected: fallbackRoot.reject() onRejected: fallbackRoot.reject()
Button { Button {
text: qsTr("Open Fallback in Browser") text: qsTr("Open Fallback in Browser")
onClicked: fallback.openFallbackAuth() onClicked: fallback.openFallbackAuth()
} }
Button { Button {
text: qsTr("Cancel")
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
text: qsTr("Cancel")
} }
Button { Button {
text: qsTr("Confirm")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Confirm")
} }
} }
Shortcut {
sequence: StandardKey.Cancel
onActivated: fallbackRoot.reject()
}
Label {
id: msg
anchors.fill: parent
padding: 8
text: qsTr("Open the fallback, follow the steps, and confirm after completing them.")
}
} }

@ -11,119 +11,115 @@ import im.nheko 1.0
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
width: 275
height: 220 height: 220
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");
} }
} }
width: 275
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
hiddenEvents.save();
hiddenEventsDialog.close();
}
onRejected: hiddenEventsDialog.close()
}
HiddenEvents {
id: hiddenEvents
roomid: hiddenEventsDialog.roomid
}
Shortcut { 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();
}
} }

@ -13,72 +13,74 @@ import "../"
Window { Window {
id: ignoredUsers id: ignoredUsers
title: qsTr("Ignored users") color: palette.window
flags: Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 650 height: 650
width: 420
minimumHeight: 420 minimumHeight: 420
color: palette.window title: qsTr("Ignored users")
width: 420
ListView { ListView {
id: view id: view
anchors.fill: parent anchors.fill: parent
spacing: Nheko.paddingMedium
footerPositioning: ListView.OverlayFooter footerPositioning: ListView.OverlayFooter
model: TimelineManager.ignoredUsers model: TimelineManager.ignoredUsers
header: ColumnLayout { spacing: Nheko.paddingMedium
Text {
Layout.fillWidth: true
Layout.maximumWidth: view.width
wrapMode: Text.Wrap
color: palette.text
text: qsTr("Ignoring a user hides their messages (they can still see yours!).")
}
Item { Layout.preferredHeight: Nheko.paddingLarge }
}
delegate: RowLayout { delegate: RowLayout {
property var profile: TimelineManager.getGlobalUserProfile(modelData) property var profile: TimelineManager.getGlobalUserProfile(modelData)
width: view.width width: view.width
Avatar { Avatar {
enabled: false
displayName: profile.displayName displayName: profile.displayName
userid: profile.userid enabled: false
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: profile.userid
} }
Text { Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
elide: Text.ElideRight Layout.fillWidth: true
color: palette.text color: palette.text
elide: Text.ElideRight
text: modelData text: modelData
} }
ImageButton { ImageButton {
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
image: ":/icons/icons/ui/dismiss.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Stop Ignoring.") ToolTip.text: qsTr("Stop Ignoring.")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg"
onClicked: profile.ignored = false onClicked: profile.ignored = false
} }
} }
footer: DialogButtonBox { footer: DialogButtonBox {
z: 2
width: view.width
alignment: Qt.AlignRight alignment: Qt.AlignRight
standardButtons: DialogButtonBox.Ok standardButtons: DialogButtonBox.Ok
onAccepted: ignoredUsers.close() width: view.width
z: 2
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: palette.window color: palette.window
} }
onAccepted: ignoredUsers.close()
}
header: ColumnLayout {
Text {
Layout.fillWidth: true
Layout.maximumWidth: view.width
color: palette.text
text: qsTr("Ignoring a user hides their messages (they can still see yours!).")
wrapMode: Text.Wrap
}
Item {
Layout.preferredHeight: Nheko.paddingLarge
}
} }
} }
} }

@ -4,33 +4,32 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Window 2.15 import QtQuick.Window 2.15
import ".." import ".."
import im.nheko 1.0 import im.nheko 1.0
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
required property string url
//visibility: Window.FullScreen
color: Qt.rgba(0.2, 0.2, 0.2, 0.66)
flags: Qt.FramelessWindowHint flags: Qt.FramelessWindowHint
//visibility: Window.FullScreen
color: Qt.rgba(0.2,0.2,0.2,0.66)
Component.onCompleted: Nheko.setWindowRole(imageOverlay, "imageoverlay") Component.onCompleted: Nheko.setWindowRole(imageOverlay, "imageoverlay")
Shortcut { Shortcut {
sequences: [StandardKey.Cancel] sequences: [StandardKey.Cancel]
onActivated: imageOverlay.close() onActivated: imageOverlay.close()
} }
Shortcut { Shortcut {
sequences: [StandardKey.Copy] sequences: [StandardKey.Copy]
onActivated: { onActivated: {
if (room) { if (room) {
room.copyMedia(eventId); room.copyMedia(eventId);
@ -39,94 +38,85 @@ Window {
} }
} }
} }
TapHandler { TapHandler {
onSingleTapped: imageOverlay.close(); onSingleTapped: imageOverlay.close()
} }
Item { Item {
id: imgContainer id: imgContainer
property int imgSrcWidth: (imageOverlay.originalWidth && imageOverlay.originalWidth > 100) ? imageOverlay.originalWidth : Screen.width
property int imgSrcHeight: imageOverlay.proportionalHeight ? imgSrcWidth * imageOverlay.proportionalHeight : Screen.height property int imgSrcHeight: imageOverlay.proportionalHeight ? imgSrcWidth * imageOverlay.proportionalHeight : Screen.height
property int imgSrcWidth: (imageOverlay.originalWidth && imageOverlay.originalWidth > 100) ? imageOverlay.originalWidth : Screen.width
height: Math.min(parent.height || Screen.height, imgSrcHeight) height: Math.min(parent.height || Screen.height, imgSrcHeight)
width: Math.min(parent.width || Screen.width, imgSrcWidth) width: Math.min(parent.width || Screen.width, imgSrcWidth)
x: (parent.width - width) / 2 x: (parent.width - width) / 2
y: (parent.height - height) / 2 y: (parent.height - height) / 2
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"
// workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432: // workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432:
// Magic Mouse pretends to be a trackpad but doesn't work with PinchHandler // Magic Mouse pretends to be a trackpad but doesn't work with PinchHandler
// and we don't yet distinguish mice and trackpads on Wayland either // and we don't yet distinguish mice and trackpads on Wayland either
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
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/copy.svg" image: ":/icons/icons/ui/copy.svg"
width: 48
//ToolTip.visible: hovered //ToolTip.visible: hovered
//ToolTip.delay: Nheko.tooltipDelay //ToolTip.delay: Nheko.tooltipDelay
@ -142,12 +132,11 @@ Window {
imageOverlay.close(); imageOverlay.close();
} }
} }
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
@ -165,16 +154,15 @@ 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()
} }
} }
} }

@ -14,34 +14,46 @@ 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
color: palette.base color: palette.base
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 600
modality: Qt.WindowModal
title: qsTr("Editing image pack")
width: 600
footer: DialogButtonBox {
id: buttons
standardButtons: DialogButtonBox.Save | DialogButtonBox.Cancel
onAccepted: {
imagePack.save();
win.close();
}
onRejected: win.close()
}
AdaptiveLayout { 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 clip: true
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
@ -49,75 +61,67 @@ ApplicationWindow {
model: imagePack model: imagePack
delegate: AvatarListTile {
id: packItem
header: AvatarListTile { property color background: palette.window
title: imagePack.packname required property string body
avatarUrl: imagePack.avatarUrl property color bubbleBackground: palette.highlight
roomid: imagePack.statekey property color bubbleText: palette.highlightedText
subtitle: imagePack.statekey property color importantText: palette.text
index: -1 required property string shortCode
property color unimportantText: palette.buttonText
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: palette.highlight
} }
} }
footer: Button { footer: Button {
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
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) acceptLabel: qsTr("Add to pack")
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: palette.window
property color importantText: palette.text
property color unimportantText: palette.buttonText
property color bubbleBackground: palette.highlight
property color bubbleText: palette.highlightedText
required property string shortCode
required property string url
required property string body
title: shortCode
subtitle: body
avatarUrl: url
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: palette.highlight
height: parent.height - Nheko.paddingSmall * 2
width: 3
} }
} }
} }
} }
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: packinfoC id: packinfoC
@ -127,211 +131,189 @@ ApplicationWindow {
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/")
displayName: imagePack.packname
roomid: imagePack.statekey
Layout.preferredHeight: 130 Layout.preferredHeight: 130
Layout.preferredWidth: 130 Layout.preferredWidth: 130
crop: false crop: false
Layout.alignment: Qt.AlignHCenter displayName: imagePack.packname
roomid: imagePack.statekey
url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/")
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"
displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
roomid: displayName
Layout.preferredHeight: 130 Layout.preferredHeight: 130
Layout.preferredWidth: 130 Layout.preferredWidth: 130
crop: false crop: false
Layout.alignment: Qt.AlignHCenter displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
roomid: displayName
url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/") + "?scale"
} }
MatrixTextField { MatrixTextField {
Layout.fillWidth: true property int bindingCounter: 0
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true
label: qsTr("Shortcode") label: qsTr("Shortcode")
property int bindingCounter: 0
text: bindingCounter, imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) text: bindingCounter, imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
onTextEdited: { onTextEdited: {
imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode); imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode);
// force text field to update in case the model disagreed with the new value. // force text field to update in case the model disagreed with the new value.
bindingCounter++; bindingCounter++;
} }
} }
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()
} }
} }

@ -12,95 +12,74 @@ import im.nheko 1.0
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
color: palette.base color: palette.base
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 600
modality: Qt.NonModal
title: qsTr("Image pack settings")
width: 800
footer: DialogButtonBox {
id: buttons
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Close")
onClicked: win.close()
}
}
Component { 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 {
onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": packlist.newPack(false)
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
}
Layout.preferredWidth: packlistC.width
visible: !packlist.containsAccountPack
text: qsTr("Create account pack")
}
Button {
onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": packlist.newPack(true)
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
}
Layout.preferredWidth: packlistC.width
visible: room.permissions.canChange(MtxEvent.ImagePackInRoom)
text: qsTr("New room pack")
}
}
delegate: AvatarListTile { delegate: AvatarListTile {
id: packItem id: packItem
property color background: palette.window property color background: palette.window
property color importantText: palette.text
property color unimportantText: palette.buttonText
property color bubbleBackground: palette.highlight property color bubbleBackground: palette.highlight
property color bubbleText: palette.highlightedText property color bubbleText: 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
required property bool fromSpace required property bool fromSpace
property color importantText: palette.text
required property string statekey required property string statekey
property color unimportantText: palette.buttonText
title: displayName roomid: statekey
selectedIndex: currentPackIndex
subtitle: { subtitle: {
if (fromAccountData) if (fromAccountData)
return qsTr("Private pack"); return qsTr("Private pack");
@ -111,19 +90,42 @@ 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 {
Layout.preferredWidth: packlistC.width
text: qsTr("Create account pack")
visible: !packlist.containsAccountPack
} onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": packlist.newPack(false)
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
}
}
Button {
Layout.preferredWidth: packlistC.width
text: qsTr("New room pack")
visible: room.permissions.canChange(MtxEvent.ImagePackInRoom)
onClicked: {
var dialog = packEditor.createObject(timelineRoot, {
"imagePack": packlist.newPack(true)
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
}
}
}
}
} }
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: packinfoC id: packinfoC
@ -133,9 +135,9 @@ ApplicationWindow {
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,119 +145,94 @@ ApplicationWindow {
spacing: Nheko.paddingLarge spacing: Nheko.paddingLarge
Avatar { Avatar {
url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/") Layout.alignment: Qt.AlignHCenter
displayName: packinfo.packName
roomid: packinfo.statekey
Layout.preferredHeight: 100 Layout.preferredHeight: 100
Layout.preferredWidth: 100 Layout.preferredWidth: 100
Layout.alignment: Qt.AlignHCenter displayName: packinfo.packName
enabled: false enabled: false
roomid: packinfo.statekey
url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/")
} }
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
textFormat: TextEdit.PlainText textFormat: TextEdit.PlainText
} }
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
textFormat: TextEdit.PlainText textFormat: TextEdit.PlainText
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
}); });
dialog.show(); dialog.show();
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
height: stickerDim
contentItem: Image { hoverEnabled: true
height: stickerDim 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 ? palette.highlight : 'transparent' color: hovered ? 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()
} }
} }
} }

@ -11,57 +11,56 @@ import im.nheko 1.0
ApplicationWindow { ApplicationWindow {
id: inputDialog id: inputDialog
property alias prompt: promptLabel.text
property alias echoMode: statusInput.echoMode property alias echoMode: statusInput.echoMode
signal accepted(text: string) property alias prompt: promptLabel.text
modality: Qt.NonModal signal accepted(string text)
flags: Qt.Dialog
width: 350
height: fontMetrics.lineSpacing * 7
function forceActiveFocus() { function forceActiveFocus() {
statusInput.forceActiveFocus(); statusInput.forceActiveFocus();
} }
flags: Qt.Dialog
height: fontMetrics.lineSpacing * 7
modality: Qt.NonModal
width: 350
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
inputDialog.accepted(statusInput.text);
inputDialog.close();
}
onRejected: {
inputDialog.close();
}
}
Shortcut { 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: palette.text color: 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: {
inputDialog.accepted(statusInput.text);
inputDialog.close(); onAccepted: dbb.accepted()
}
onRejected: {
inputDialog.close();
} }
} }
} }

@ -12,84 +12,106 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: inviteDialogRoot id: inviteDialogRoot
property InviteesModel invitees
property var friendsCompleter property var friendsCompleter
property InviteesModel invitees
property var profile property var profile
minimumWidth: 300
Component.onCompleted: {
friendsCompleter = TimelineManager.completerFor("user", "friends")
width = 600
}
function addInvite(mxid, displayName, avatarUrl) { function addInvite(mxid, displayName, avatarUrl) {
if (mxid.match("@.+?:.{3,}")) { if (mxid.match("@.+?:.{3,}")) {
invitees.addUser(mxid, displayName, avatarUrl); invitees.addUser(mxid, displayName, avatarUrl);
} else } else
console.log("invalid mxid: " + mxid) console.log("invalid mxid: " + mxid);
} }
function cleanUpAndClose() { function cleanUpAndClose() {
if (inviteeEntry.isValidMxid) if (inviteeEntry.isValidMxid)
addInvite(inviteeEntry.text, "", ""); addInvite(inviteeEntry.text, "", "");
invitees.accept(); invitees.accept();
close(); close();
} }
title: qsTr("Invite users to %1").arg(invitees.room.plainRoomName)
height: 380
width: 340
color: palette.window color: palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 380
minimumWidth: 300
title: qsTr("Invite users to %1").arg(invitees.room.plainRoomName)
width: 340
footer: DialogButtonBox {
id: buttons
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: invitees.count > 0
text: qsTr("Invite")
onClicked: cleanUpAndClose()
}
Button {
DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole
text: qsTr("Cancel")
onClicked: inviteDialogRoot.close()
}
}
Component.onCompleted: {
friendsCompleter = TimelineManager.completerFor("user", "friends");
width = 600;
}
Shortcut { 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
Flow { Flow {
layoutDirection: Qt.LeftToRight
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: implicitHeight Layout.preferredHeight: implicitHeight
layoutDirection: Qt.LeftToRight
spacing: 4 spacing: 4
visible: !inviteesList.visible visible: !inviteesList.visible
Repeater { Repeater {
id: inviteesRepeater id: inviteesRepeater
model: invitees model: invitees
delegate: ItemDelegate { delegate: ItemDelegate {
onClicked: invitees.removeUser(model.mxid)
id: inviteeButton id: inviteeButton
contentItem: Label {
anchors.centerIn: parent
id: inviteeUserid
text: model.displayName != "" ? model.displayName : model.userid
color: inviteeButton.hovered ? palette.highlightedText: palette.text
maximumLineCount: 1
}
background: Rectangle { background: Rectangle {
border.color: palette.text border.color: palette.text
color: inviteeButton.hovered ? palette.highlight : palette.window
border.width: 1 border.width: 1
color: inviteeButton.hovered ? palette.highlight : palette.window
radius: inviteeButton.height / 2 radius: inviteeButton.height / 2
} }
contentItem: Label {
id: inviteeUserid
anchors.centerIn: parent
color: inviteeButton.hovered ? palette.highlightedText : palette.text
maximumLineCount: 1
text: model.displayName != "" ? model.displayName : model.userid
}
onClicked: invitees.removeUser(model.mxid)
} }
} }
} }
Label { Label {
text: qsTr("Search user")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Search user")
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
@ -99,147 +121,136 @@ ApplicationWindow {
property bool isValidMxid: text.match("@.+?:.{3,}") property bool isValidMxid: text.match("@.+?:.{3,}")
Layout.fillWidth: true
backgroundColor: palette.window backgroundColor: palette.window
placeholderText: qsTr("@user:yourserver.example.com", "Example user id. The name 'user' can be localized however you want.") placeholderText: qsTr("@user:yourserver.example.com", "Example user id. The name 'user' can be localized however you want.")
Layout.fillWidth: true
onAccepted: {
if (isValidMxid) {
addInvite(text, "", "");
clear()
}
else if (userSearch.count > 0) {
addInvite(userSearch.itemAtIndex(0).userid, userSearch.itemAtIndex(0).displayName, userSearch.itemAtIndex(0).avatarUrl)
clear()
}
}
Component.onCompleted: forceActiveFocus() 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(text, "", "");
clear();
} else if (userSearch.count > 0) {
addInvite(userSearch.itemAtIndex(0).userid, userSearch.itemAtIndex(0).displayName, userSearch.itemAtIndex(0).avatarUrl);
clear();
}
} }
onTextChanged: { onTextChanged: {
searchTimer.restart() searchTimer.restart();
if(isValidMxid) { if (isValidMxid) {
profile = TimelineManager.getGlobalUserProfile(text); profile = TimelineManager.getGlobalUserProfile(text);
} else } else
profile = null; profile = null;
} }
Timer { Timer {
id: searchTimer id: searchTimer
interval: 350 interval: 350
onTriggered: { onTriggered: {
userSearch.model.setSearchString(parent.text) userSearch.model.setSearchString(parent.text);
} }
} }
} }
ToggleButton { ToggleButton {
id: searchOnServer id: searchOnServer
checked: false checked: false
onClicked: userSearch.model.setSearchString(inviteeEntry.text) onClicked: userSearch.model.setSearchString(inviteeEntry.text)
} }
MatrixText { MatrixText {
text: qsTr("Search on Server") text: qsTr("Search on Server")
} }
} }
RowLayout { RowLayout {
UserListRow { UserListRow {
visible: inviteeEntry.isValidMxid
id: del3 id: del3
Layout.preferredWidth: inviteDialogRoot.width/2
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Layout.preferredHeight: implicitHeight Layout.preferredHeight: implicitHeight
displayName: profile? profile.displayName : "" Layout.preferredWidth: inviteDialogRoot.width / 2
avatarUrl: profile? profile.avatarUrl : "" avatarUrl: profile ? profile.avatarUrl : ""
bgColor: del3.hovered ? palette.dark : inviteDialogRoot.color
displayName: profile ? profile.displayName : ""
userid: inviteeEntry.text userid: inviteeEntry.text
visible: inviteeEntry.isValidMxid
onClicked: addInvite(inviteeEntry.text, displayName, avatarUrl) onClicked: addInvite(inviteeEntry.text, displayName, avatarUrl)
bgColor: del3.hovered ? palette.dark : inviteDialogRoot.color
} }
ListView { ListView {
visible: !inviteeEntry.isValidMxid
id: userSearch id: userSearch
model: searchOnServer.checked? userDirectory : friendsCompleter
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
clip: true clip: true
model: searchOnServer.checked ? userDirectory : friendsCompleter
visible: !inviteeEntry.isValidMxid
delegate: UserListRow { delegate: UserListRow {
id: del2 id: del2
width: ListView.view.width
height: implicitHeight avatarUrl: model.avatarUrl
bgColor: del2.hovered ? palette.dark : inviteDialogRoot.color
displayName: model.displayName displayName: model.displayName
height: implicitHeight
userid: model.userid userid: model.userid
avatarUrl: model.avatarUrl width: ListView.view.width
onClicked: addInvite(userid, displayName, avatarUrl) onClicked: addInvite(userid, displayName, avatarUrl)
bgColor: del2.hovered ? palette.dark : inviteDialogRoot.color
} }
} }
Rectangle { Rectangle {
Layout.fillHeight: true Layout.fillHeight: true
visible: inviteesList.visible
Layout.preferredWidth: 1 Layout.preferredWidth: 1
color: Nheko.theme.separator color: Nheko.theme.separator
visible: inviteesList.visible
} }
ListView { ListView {
id: inviteesList id: inviteesList
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
model: invitees Layout.fillWidth: true
clip: true clip: true
model: invitees
visible: inviteDialogRoot.width >= 500 visible: inviteDialogRoot.width >= 500
delegate: UserListRow { delegate: UserListRow {
id: del id: del
avatarUrl: model.avatarUrl
bgColor: del.hovered ? palette.dark : inviteDialogRoot.color
displayName: model.displayName
height: implicitHeight
hoverEnabled: true hoverEnabled: true
userid: model.mxid
width: ListView.view.width width: ListView.view.width
height: implicitHeight
onClicked: TimelineManager.openGlobalUserProfile(model.mxid) onClicked: TimelineManager.openGlobalUserProfile(model.mxid)
userid: model.mxid
avatarUrl: model.avatarUrl
displayName: model.displayName
bgColor: del.hovered ? palette.dark : inviteDialogRoot.color
ImageButton { ImageButton {
id: removeButton
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Nheko.paddingSmall anchors.rightMargin: Nheko.paddingSmall
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: Nheko.paddingSmall anchors.topMargin: Nheko.paddingSmall
id: removeButton
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()
}
} }
} }

@ -11,62 +11,59 @@ import im.nheko 1.0
ApplicationWindow { ApplicationWindow {
id: joinRoomRoot id: joinRoomRoot
title: qsTr("Join room")
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
color: palette.window color: palette.window
width: 350 flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: fontMetrics.lineSpacing * 7 height: fontMetrics.lineSpacing * 7
modality: Qt.WindowModal
title: qsTr("Join room")
width: 350
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Cancel
onAccepted: {
Nheko.joinRoom(input.text);
joinRoomRoot.close();
}
onRejected: {
joinRoomRoot.close();
}
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: input.text.match("#.+?:.{3,}")
text: qsTr("Join")
}
}
Shortcut { 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: palette.text color: 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: qsTr("Join")
enabled: input.text.match("#.+?:.{3,}")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
}
} }

@ -9,21 +9,20 @@ 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
onAccepted: { modality: Qt.ApplicationModal
text: qsTr("Are you sure you want to leave?")
title: qsTr("Leave room")
onAccepted: {
if (CallManager.haveCallInvite) { if (CallManager.haveCallInvite) {
callManager.rejectInvite(); callManager.rejectInvite();
} else if (CallManager.isOnCall) { } else if (CallManager.isOnCall) {
CallManager.hangUp(); CallManager.hangUp();
} }
Rooms.leave(roomId, reason) Rooms.leave(roomId, reason);
} }
} }

@ -9,10 +9,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

@ -9,21 +9,38 @@ import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import im.nheko 1.0 import im.nheko 1.0
ApplicationWindow { ApplicationWindow {
id: plEditorW id: plEditorW
property var roomSettings
property var editingModel: Nheko.editPowerlevels(roomSettings.roomId) property var editingModel: Nheko.editPowerlevels(roomSettings.roomId)
property var roomSettings
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
minimumWidth: 300
minimumHeight: 400
height: 600 height: 600
minimumHeight: 400
minimumWidth: 300
modality: Qt.NonModal
title: qsTr("Permissions in %1").arg(roomSettings.roomName)
width: 300 width: 300
title: qsTr("Permissions in %1").arg(roomSettings.roomName); footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
if (editingModel.isSpace) {
// TODO(Nico): Replace with showing a list of spaces to apply to
editingModel.updateSpacesModel();
plEditorW.close();
timelineRoot.showSpacePLApplyPrompt(roomSettings, editingModel);
} else {
editingModel.commit();
plEditorW.close();
}
}
onRejected: plEditorW.close()
}
// Shortcut { // Shortcut {
// sequence: StandardKey.Cancel // sequence: StandardKey.Cancel
@ -31,22 +48,21 @@ ApplicationWindow {
// } // }
ColumnLayout { ColumnLayout {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: 0 spacing: 0
MatrixText { MatrixText {
text: qsTr("Be careful when editing permissions. You can't lower the permissions of people with a same or higher level than you. Be careful when promoting others.") Layout.bottomMargin: Nheko.paddingMedium
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false Layout.fillHeight: false
Layout.fillWidth: true
color: palette.text color: palette.text
Layout.bottomMargin: Nheko.paddingMedium font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("Be careful when editing permissions. You can't lower the permissions of people with a same or higher level than you. Be careful when promoting others.")
} }
TabBar { TabBar {
id: bar id: bar
Layout.preferredWidth: parent.width Layout.preferredWidth: parent.width
NhekoTabButton { NhekoTabButton {
@ -57,95 +73,95 @@ ApplicationWindow {
} }
} }
Rectangle { Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
color: palette.alternateBase Layout.fillWidth: true
border.width: 1
border.color: Nheko.theme.separator border.color: Nheko.theme.separator
border.width: 1
color: palette.alternateBase
StackLayout { StackLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
currentIndex: bar.currentIndex currentIndex: bar.currentIndex
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
MatrixText { MatrixText {
text: qsTr("Move permissions between roles to change them")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false Layout.fillHeight: false
Layout.fillWidth: true
color: palette.text color: palette.text
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("Move permissions between roles to change them")
} }
ReorderableListview { ReorderableListview {
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
model: editingModel.types model: editingModel.types
delegate: RowLayout { delegate: RowLayout {
Column { Column {
Layout.fillWidth: true Layout.fillWidth: true
Text { visible: model.isType; text: model.displayName; color: palette.text}
Text { Text {
visible: !model.isType; color: palette.text
text: model.displayName
visible: model.isType
}
Text {
color: palette.text
text: { text: {
if (editingModel.adminLevel == model.powerlevel) if (editingModel.adminLevel == model.powerlevel)
return qsTr("Administrator (%1)").arg(model.powerlevel) return qsTr("Administrator (%1)").arg(model.powerlevel);
else if (editingModel.moderatorLevel == model.powerlevel) else if (editingModel.moderatorLevel == model.powerlevel)
return qsTr("Moderator (%1)").arg(model.powerlevel) return qsTr("Moderator (%1)").arg(model.powerlevel);
else if (editingModel.defaultUserLevel == model.powerlevel) else if (editingModel.defaultUserLevel == model.powerlevel)
return qsTr("User (%1)").arg(model.powerlevel) return qsTr("User (%1)").arg(model.powerlevel);
else else
return qsTr("Custom (%1)").arg(model.powerlevel) return qsTr("Custom (%1)").arg(model.powerlevel);
} }
color: palette.text visible: !model.isType
} }
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.rightMargin: 2 Layout.rightMargin: 2
image: model.isType ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg"
visible: !model.isType || model.removeable
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: model.isType ? qsTr("Remove event type") : qsTr("Add event type") ToolTip.text: model.isType ? qsTr("Remove event type") : qsTr("Add event type")
ToolTip.visible: hovered
hoverEnabled: true
image: model.isType ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg"
visible: !model.isType || model.removeable
onClicked: { onClicked: {
if (model.isType) { if (model.isType) {
editingModel.types.remove(index); editingModel.types.remove(index);
} else { } else {
typeEntry.y = offset typeEntry.y = offset;
typeEntry.visible = true typeEntry.visible = true;
typeEntry.index = index; typeEntry.index = index;
typeEntry.forceActiveFocus() typeEntry.forceActiveFocus();
} }
} }
} }
} }
MatrixTextField { MatrixTextField {
id: typeEntry id: typeEntry
property int index property int index
color: palette.text
visible: false
width: parent.width width: parent.width
z: 5 z: 5
visible: false
color: palette.text
Keys.onPressed: { Keys.onPressed: {
if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) { if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) {
editingModel.types.add(typeEntry.index, typeEntry.text) editingModel.types.add(typeEntry.index, typeEntry.text);
typeEntry.visible = false; typeEntry.visible = false;
typeEntry.clear(); typeEntry.clear();
event.accepted = true; event.accepted = true;
} } else if (event.matches(StandardKey.Cancel)) {
else if (event.matches(StandardKey.Cancel)) {
typeEntry.visible = false; typeEntry.visible = false;
typeEntry.clear(); typeEntry.clear();
event.accepted = true; event.accepted = true;
@ -153,7 +169,6 @@ ApplicationWindow {
} }
} }
} }
Button { Button {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("Add new role") text: qsTr("Add new role")
@ -164,19 +179,18 @@ ApplicationWindow {
id: newPLLay id: newPLLay
anchors.fill: parent anchors.fill: parent
visible: false
color: palette.alternateBase color: palette.alternateBase
visible: false
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
spacing: Nheko.paddingMedium
SpinBox { SpinBox {
id: newPLVal id: newPLVal
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
editable: true editable: true
//from: -9007199254740991 //from: -9007199254740991
//to: 9007199254740991 //to: 9007199254740991
@ -192,10 +206,10 @@ ApplicationWindow {
} }
} }
} }
Button { Button {
text: qsTr("Add")
Layout.preferredWidth: 100 Layout.preferredWidth: 100
text: qsTr("Add")
onClicked: { onClicked: {
editingModel.addRole(newPLVal.value); editingModel.addRole(newPLVal.value);
newPLLay.visible = false; newPLLay.visible = false;
@ -205,42 +219,109 @@ ApplicationWindow {
} }
} }
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
MatrixText { MatrixText {
text: qsTr("Move users up or down to change their permissions")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false Layout.fillHeight: false
Layout.fillWidth: true
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("Move users up or down to change their permissions")
} }
ReorderableListview { ReorderableListview {
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
model: editingModel.users model: editingModel.users
Column{ delegate: RowLayout {
//anchors { fill: parent; margins: 2 }
id: row
Avatar {
id: avatar
Layout.leftMargin: 2
Layout.preferredHeight: Nheko.avatarSize / 2
Layout.preferredWidth: Nheko.avatarSize / 2
displayName: model.displayName
enabled: false
url: {
if (model.isUser)
return model.avatarUrl.replace("mxc://", "image://MxcImage/");
else if (editingModel.adminLevel >= model.powerlevel)
return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?" + palette.buttonText;
else if (editingModel.moderatorLevel >= model.powerlevel)
return "image://colorimage/:/icons/icons/ui/ribbon.svg?" + palette.buttonText;
else
return "image://colorimage/:/icons/icons/ui/person.svg?" + palette.buttonText;
}
userid: model.mxid
}
Column {
Layout.fillWidth: true
Text {
color: palette.text
text: model.displayName
visible: model.isUser
}
Text {
color: palette.text
text: model.mxid
visible: model.isUser
}
Text {
color: palette.text
text: {
if (editingModel.adminLevel == model.powerlevel)
return qsTr("Administrator (%1)").arg(model.powerlevel);
else if (editingModel.moderatorLevel == model.powerlevel)
return qsTr("Moderator (%1)").arg(model.powerlevel);
else
return qsTr("Custom (%1)").arg(model.powerlevel);
}
visible: !model.isUser
}
}
ImageButton {
Layout.alignment: Qt.AlignRight
Layout.rightMargin: 2
ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user")
ToolTip.visible: hovered
hoverEnabled: true
image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg"
visible: !model.isUser || model.removeable
onClicked: {
if (model.isUser) {
editingModel.users.remove(index);
} else {
userEntryCompleter.y = offset;
userEntryCompleter.visible = true;
userEntryCompleter.index = index;
userEntry.forceActiveFocus();
}
}
}
}
Column {
id: userEntryCompleter id: userEntryCompleter
property int index: 0 property int index: 0
spacing: 1
visible: false visible: false
width: parent.width width: parent.width
spacing: 1
z: 5 z: 5
MatrixTextField { MatrixTextField {
id: userEntry id: userEntry
width: parent.width
//font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6) //font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
color: palette.text color: palette.text
onTextEdited: { width: parent.width
userCompleter.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;
@ -248,9 +329,9 @@ ApplicationWindow {
} else if (event.key == Qt.Key_Down || event.key == Qt.Key_Tab) { } else if (event.key == Qt.Key_Down || event.key == Qt.Key_Tab) {
event.accepted = true; event.accepted = true;
if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier)) if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))
userCompleter.up(); userCompleter.up();
else else
userCompleter.down(); userCompleter.down();
} else if (event.matches(StandardKey.InsertParagraphSeparator)) { } else if (event.matches(StandardKey.InsertParagraphSeparator)) {
if (userCompleter.currentCompletion()) { if (userCompleter.currentCompletion()) {
userCompleter.finishCompletion(); userCompleter.finishCompletion();
@ -264,130 +345,45 @@ ApplicationWindow {
event.accepted = true; event.accepted = true;
} }
} }
onTextEdited: {
userCompleter.completer.searchString = text;
}
} }
Completer { Completer {
id: userCompleter id: userCompleter
visible: userEntry.text.length > 0
width: parent.width
roomId: plEditorW.roomSettings.roomId
completerName: "user"
bottomToTop: false
fullWidth: true
avatarHeight: Nheko.avatarSize / 2 avatarHeight: Nheko.avatarSize / 2
avatarWidth: Nheko.avatarSize / 2 avatarWidth: Nheko.avatarSize / 2
bottomToTop: false
centerRowContent: false centerRowContent: false
completerName: "user"
fullWidth: true
roomId: plEditorW.roomSettings.roomId
rowMargin: 2 rowMargin: 2
rowSpacing: 2 rowSpacing: 2
visible: userEntry.text.length > 0
width: parent.width
} }
} }
Connections { Connections {
id: userCompletionConnections
function onCompletionSelected(id) { function onCompletionSelected(id) {
console.log("selected: " + id); console.log("selected: " + id);
editingModel.users.add(userEntryCompleter.index, id); editingModel.users.add(userEntryCompleter.index, id);
userEntry.clear(); userEntry.clear();
userEntryCompleter.visible = false; userEntryCompleter.visible = false;
} }
function onCountChanged() { function onCountChanged() {
if (userCompleter.count > 0 && (userCompleter.currentIndex < 0 || userCompleter.currentIndex >= userCompleter.count)) if (userCompleter.count > 0 && (userCompleter.currentIndex < 0 || userCompleter.currentIndex >= userCompleter.count))
userCompleter.currentIndex = 0; userCompleter.currentIndex = 0;
} }
target: userCompleter target: userCompleter
id: userCompletionConnections
}
delegate: RowLayout {
//anchors { fill: parent; margins: 2 }
id: row
Avatar {
id: avatar
Layout.preferredHeight: Nheko.avatarSize / 2
Layout.preferredWidth: Nheko.avatarSize / 2
Layout.leftMargin: 2
userid: model.mxid
url: {
if (model.isUser)
return model.avatarUrl.replace("mxc://", "image://MxcImage/")
else if (editingModel.adminLevel >= model.powerlevel)
return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?" + palette.buttonText;
else if (editingModel.moderatorLevel >= model.powerlevel)
return "image://colorimage/:/icons/icons/ui/ribbon.svg?" + palette.buttonText;
else
return "image://colorimage/:/icons/icons/ui/person.svg?" + palette.buttonText;
}
displayName: model.displayName
enabled: false
}
Column {
Layout.fillWidth: true
Text { visible: model.isUser; text: model.displayName; color: palette.text}
Text { visible: model.isUser; text: model.mxid; color: palette.text}
Text {
visible: !model.isUser;
text: {
if (editingModel.adminLevel == model.powerlevel)
return qsTr("Administrator (%1)").arg(model.powerlevel)
else if (editingModel.moderatorLevel == model.powerlevel)
return qsTr("Moderator (%1)").arg(model.powerlevel)
else
return qsTr("Custom (%1)").arg(model.powerlevel)
}
color: palette.text
}
}
ImageButton {
Layout.alignment: Qt.AlignRight
Layout.rightMargin: 2
image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg"
visible: !model.isUser || model.removeable
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user")
onClicked: {
if (model.isUser) {
editingModel.users.remove(index);
} else {
userEntryCompleter.y = offset
userEntryCompleter.visible = true
userEntryCompleter.index = index;
userEntry.forceActiveFocus()
}
}
}
} }
} }
} }
} }
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
if (editingModel.isSpace) {
// TODO(Nico): Replace with showing a list of spaces to apply to
editingModel.updateSpacesModel();
plEditorW.close();
timelineRoot.showSpacePLApplyPrompt(roomSettings, editingModel)
} else {
editingModel.commit();
plEditorW.close();
}
}
onRejected: plEditorW.close();
}
} }

@ -12,79 +12,85 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
id: applyDialog id: applyDialog
property RoomSettings roomSettings
property PowerlevelEditingModels editingModel property PowerlevelEditingModels editingModel
property RoomSettings roomSettings
minimumWidth: 340
minimumHeight: 450
width: 450
height: 680
color: palette.window color: 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
title: qsTr("Apply permission changes") title: qsTr("Apply permission changes")
width: 450
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
editingModel.spaces.commit();
applyDialog.close();
}
onRejected: applyDialog.close()
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: roomSettingsDialog.close() onActivated: roomSettingsDialog.close()
} }
ColumnLayout { ColumnLayout {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingLarge spacing: Nheko.paddingLarge
MatrixText { MatrixText {
text: qsTr("Which of the subcommunities and rooms should these permissions be applied to?") Layout.bottomMargin: Nheko.paddingMedium
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false Layout.fillHeight: false
Layout.fillWidth: true
color: palette.text color: palette.text
Layout.bottomMargin: Nheko.paddingMedium font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("Which of the subcommunities and rooms should these permissions be applied to?")
} }
GridLayout { GridLayout {
Layout.fillWidth: true
Layout.fillHeight: false Layout.fillHeight: false
Layout.fillWidth: true
columns: 2 columns: 2
Label { Label {
text: qsTr("Apply permissions recursively") Layout.fillWidth: true
Layout.fillWidth: true color: palette.text
color: palette.text text: qsTr("Apply permissions recursively")
} }
ToggleButton {
ToggleButton { Layout.alignment: Qt.AlignRight
checked: editingModel.spaces.applyToChildren checked: editingModel.spaces.applyToChildren
Layout.alignment: Qt.AlignRight
onCheckedChanged: editingModel.spaces.applyToChildren = checked
}
Label { onCheckedChanged: editingModel.spaces.applyToChildren = checked
text: qsTr("Overwrite exisiting modifications in rooms") }
Layout.fillWidth: true Label {
color: palette.text Layout.fillWidth: true
} color: palette.text
text: qsTr("Overwrite exisiting modifications in rooms")
}
ToggleButton {
Layout.alignment: Qt.AlignRight
checked: editingModel.spaces.overwriteDiverged
ToggleButton { onCheckedChanged: editingModel.spaces.overwriteDiverged = checked
checked: editingModel.spaces.overwriteDiverged }
Layout.alignment: Qt.AlignRight
onCheckedChanged: editingModel.spaces.overwriteDiverged = checked
}
} }
ListView { ListView {
Layout.fillWidth: true
Layout.fillHeight: true
id: view id: view
Layout.fillHeight: true
Layout.fillWidth: true
cacheBuffer: 50
clip: true clip: true
model: editingModel.spaces model: editingModel.spaces
spacing: 4 spacing: 4
cacheBuffer: 50
delegate: RowLayout { delegate: RowLayout {
anchors.left: parent.left anchors.left: parent.left
@ -92,49 +98,38 @@ ApplicationWindow {
ColumnLayout { ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.displayName
color: palette.text color: palette.text
textFormat: Text.PlainText
elide: Text.ElideRight elide: Text.ElideRight
text: model.displayName
textFormat: Text.PlainText
} }
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
color: palette.buttonText
elide: Text.ElideRight
text: { text: {
if (!model.isEditable) return qsTr("No permissions to apply the new permissions here"); if (!model.isEditable)
if (model.isAlreadyUpToDate) return qsTr("No changes needed"); return qsTr("No permissions to apply the new permissions here");
if (model.isDifferentFromBase) return qsTr("Existing modifications to the permissions in this room will be overwritten"); if (model.isAlreadyUpToDate)
return qsTr("Permissions synchronized with community") return qsTr("No changes needed");
if (model.isDifferentFromBase)
return qsTr("Existing modifications to the permissions in this room will be overwritten");
return qsTr("Permissions synchronized with community");
} }
elide: Text.ElideRight
color: palette.buttonText
textFormat: Text.PlainText textFormat: Text.PlainText
} }
} }
ToggleButton { ToggleButton {
checked: model.applyPermissions
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
onCheckedChanged: model.applyPermissions = checked checked: model.applyPermissions
enabled: model.isEditable enabled: model.isEditable
onCheckedChanged: model.applyPermissions = checked
} }
} }
} }
} }
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
editingModel.spaces.commit();
applyDialog.close();
}
onRejected: applyDialog.close()
}
} }

@ -11,42 +11,39 @@ ApplicationWindow {
property alias rawMessage: rawMessageView.text property alias rawMessage: rawMessageView.text
height: 420
width: 420
color: palette.window color: palette.window
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 420
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
anchors.margins: Nheko.paddingMedium
padding: Nheko.paddingMedium padding: Nheko.paddingMedium
TextArea { TextArea {
id: rawMessageView id: rawMessageView
font: Nheko.monospaceFont() anchors.fill: parent
color: palette.text color: palette.text
font: Nheko.monospaceFont()
readOnly: true readOnly: true
textFormat: Text.PlainText textFormat: Text.PlainText
anchors.fill: parent
background: Rectangle { background: Rectangle {
color: palette.base color: palette.base
} }
} }
} }
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: rawMessageRoot.close()
}
} }

@ -15,49 +15,46 @@ ApplicationWindow {
recaptcha.confirm(); recaptcha.confirm();
recaptchaRoot.close(); recaptchaRoot.close();
} }
function reject() { function reject() {
recaptcha.cancel(); recaptcha.cancel();
recaptchaRoot.close(); recaptchaRoot.close();
} }
color: palette.window color: palette.window
title: recaptcha.context
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: msg.implicitHeight + footer.implicitHeight height: msg.implicitHeight + footer.implicitHeight
title: recaptcha.context
width: Math.max(msg.implicitWidth, footer.implicitWidth) width: Math.max(msg.implicitWidth, footer.implicitWidth)
Shortcut {
sequence: StandardKey.Cancel
onActivated: recaptchaRoot.reject()
}
Label {
id: msg
anchors.fill: parent
padding: 8
text: qsTr("Solve the reCAPTCHA and press the confirm button")
}
footer: DialogButtonBox { footer: DialogButtonBox {
onAccepted: recaptchaRoot.accept() onAccepted: recaptchaRoot.accept()
onRejected: recaptchaRoot.reject() onRejected: recaptchaRoot.reject()
Button { Button {
text: qsTr("Open reCAPTCHA") text: qsTr("Open reCAPTCHA")
onClicked: recaptcha.openReCaptcha() onClicked: recaptcha.openReCaptcha()
} }
Button { Button {
text: qsTr("Cancel")
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
text: qsTr("Cancel")
} }
Button { Button {
text: qsTr("Confirm")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Confirm")
} }
} }
Shortcut {
sequence: StandardKey.Cancel
onActivated: recaptchaRoot.reject()
}
Label {
id: msg
anchors.fill: parent
padding: 8
text: qsTr("Solve the reCAPTCHA and press the confirm button")
}
} }

@ -14,18 +14,24 @@ ApplicationWindow {
property ReadReceiptsProxy readReceipts property ReadReceiptsProxy readReceipts
property Room room property Room room
color: 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
color: 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
@ -34,98 +40,84 @@ ApplicationWindow {
Label { Label {
id: headerTitle id: headerTitle
color: palette.text
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
text: qsTr("Read receipts") color: palette.text
font.pointSize: fontMetrics.font.pointSize * 1.5 font.pointSize: fontMetrics.font.pointSize * 1.5
text: qsTr("Read receipts")
} }
ScrollView { ScrollView {
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
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
onClicked: room.openUserProfile(model.mxid) ToolTip.text: model.mxid
padding: Nheko.paddingMedium ToolTip.visible: hovered
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 ? palette.dark : readReceiptsRoot.color color: del.hovered ? 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 {
id: avatar id: avatar
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize Layout.preferredHeight: Nheko.avatarSize
userid: model.mxid Layout.preferredWidth: Nheko.avatarSize
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: model.displayName displayName: model.displayName
enabled: false enabled: false
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.mxid
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingSmall
Layout.fillWidth: true Layout.fillWidth: true
spacing: Nheko.paddingSmall
ElidedLabel { ElidedLabel {
fullText: model.displayName Layout.fillWidth: true
color: TimelineManager.userColor(model ? model.mxid : "", palette.window) color: TimelineManager.userColor(model ? model.mxid : "", palette.window)
font.pointSize: fontMetrics.font.pointSize
elideWidth: del.width - Nheko.paddingMedium - avatar.width elideWidth: del.width - Nheko.paddingMedium - avatar.width
Layout.fillWidth: true font.pointSize: fontMetrics.font.pointSize
fullText: model.displayName
} }
ElidedLabel { ElidedLabel {
fullText: model.timestamp Layout.fillWidth: true
color: palette.buttonText color: palette.buttonText
font.pointSize: fontMetrics.font.pointSize * 0.9
elideWidth: del.width - Nheko.paddingMedium - avatar.width elideWidth: del.width - Nheko.paddingMedium - avatar.width
Layout.fillWidth: true font.pointSize: fontMetrics.font.pointSize * 0.9
fullText: model.timestamp
} }
} }
} }
NhekoCursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
} }
} }
} }
} }
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: readReceiptsRoot.close()
}
} }

@ -10,71 +10,66 @@ import im.nheko
ApplicationWindow { ApplicationWindow {
required property string eventId required property string eventId
width: 400
height: gl.implicitHeight + 2 * Nheko.paddingMedium height: gl.implicitHeight + 2 * Nheko.paddingMedium
title: qsTr("Report message") title: qsTr("Report message")
width: 400
GridLayout { GridLayout {
id: gl id: gl
columnSpacing: Nheko.paddingMedium
rowSpacing: Nheko.paddingMedium
columns: 2
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
columnSpacing: Nheko.paddingMedium
columns: 2
rowSpacing: Nheko.paddingMedium
Label { Label {
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Label.WordWrap
text: qsTr("This message you are reporting will be sent to your server administrator for review. Please note that not all server administrators review reported content. You should also ask a room moderator to remove the content if necessary.") text: qsTr("This message you are reporting will be sent to your server administrator for review. Please note that not all server administrators review reported content. You should also ask a room moderator to remove the content if necessary.")
wrapMode: Label.WordWrap
} }
Label { Label {
text: qsTr("Enter your reason for reporting:") text: qsTr("Enter your reason for reporting:")
} }
TextField { TextField {
id: reason id: reason
Layout.fillWidth: true Layout.fillWidth: true
} }
Label { Label {
text: qsTr("How bad is the message?") text: qsTr("How bad is the message?")
} }
Slider { Slider {
id: score id: score
Layout.fillWidth: true
from: 0 from: 0
to: -100
stepSize: 25
snapMode: Slider.SnapAlways snapMode: Slider.SnapAlways
Layout.fillWidth: true stepSize: 25
to: -100
}
Item {
} }
Item {}
Label { Label {
text: { text: {
if (score.value === 0) if (score.value === 0)
return qsTr("Not bad") return qsTr("Not bad");
else if (score.value === -25) else if (score.value === -25)
return qsTr("Mild") return qsTr("Mild");
else if (score.value === -50) else if (score.value === -50)
return qsTr("Bad") return qsTr("Bad");
else if (score.value === -75) else if (score.value === -75)
return qsTr("Serious") return qsTr("Serious");
else if (score.value === -100) else if (score.value === -100)
return qsTr("Extremely serious") return qsTr("Extremely serious");
} }
} }
DialogButtonBox { DialogButtonBox {
Layout.columnSpan: 2
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.columnSpan: 2
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: { onAccepted: {
room.reportEvent(eventId, reason.text, score.value); room.reportEvent(eventId, reason.text, score.value);
close(); close();

@ -13,21 +13,72 @@ import im.nheko 1.0
ApplicationWindow { ApplicationWindow {
id: roomDirectoryWindow id: roomDirectoryWindow
visible: true
minimumWidth: 340
minimumHeight: 340
height: 420
width: 650
color: palette.window color: palette.window
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 420
minimumHeight: 340
minimumWidth: 340
modality: Qt.NonModal
title: qsTr("Explore Public Rooms") title: qsTr("Explore Public Rooms")
visible: true
width: 650
footer: RowLayout {
spacing: Nheko.paddingMedium
width: parent.width
Button {
Layout.alignment: Qt.AlignRight
Layout.margins: Nheko.paddingMedium
text: qsTr("Close")
onClicked: roomDirectoryWindow.close()
}
}
header: RowLayout {
id: searchBarLayout
implicitHeight: roomSearch.height
spacing: Nheko.paddingMedium
width: parent.width
MatrixTextField {
id: roomSearch
Layout.fillWidth: true
color: palette.text
focus: true
font.pixelSize: fontMetrics.font.pixelSize
placeholderText: qsTr("Search for public rooms")
selectByMouse: true
Component.onCompleted: forceActiveFocus()
onTextChanged: searchTimer.restart()
}
MatrixTextField {
id: chooseServer
Layout.maximumWidth: 0.3 * header.width
Layout.minimumWidth: 0.3 * header.width
color: palette.text
placeholderText: qsTr("Choose custom homeserver")
onTextChanged: publicRooms.setMatrixServer(text)
}
Timer {
id: searchTimer
interval: 350
onTriggered: roomDirView.model.setSearchTerm(roomSearch.text)
}
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: roomDirectoryWindow.close() onActivated: roomDirectoryWindow.close()
} }
ListView { ListView {
id: roomDirView id: roomDirView
@ -37,171 +88,108 @@ ApplicationWindow {
delegate: Rectangle { delegate: Rectangle {
id: roomDirDelegate id: roomDirDelegate
property int avatarSize: fontMetrics.height * 3.2
property color background: palette.window property color background: palette.window
property color importantText: palette.text property color importantText: palette.text
property color unimportantText: palette.buttonText property color unimportantText: palette.buttonText
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.preferredWidth: roomDirDelegate.avatarSize
Layout.preferredHeight: roomDirDelegate.avatarSize Layout.preferredHeight: roomDirDelegate.avatarSize
Layout.preferredWidth: roomDirDelegate.avatarSize
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") Layout.rightMargin: Nheko.paddingMedium
roomid: model.roomid
displayName: model.name displayName: model.name
roomid: model.roomid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
} }
GridLayout { GridLayout {
id: textContent id: textContent
rows: 2
columns: 2
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.preferredWidth: parent.width - roomAvatar.width Layout.preferredWidth: parent.width - roomAvatar.width
columns: 2
rows: 2
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 {
Layout.alignment: Qt.AlignHCenter
Layout.row: 0
Layout.column: 1
id: roomCount id: roomCount
Layout.alignment: Qt.AlignHCenter
Layout.column: 1
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.roomid !== "" enabled: model.roomid !== ""
text: model.canJoin ? qsTr("Join") : qsTr("Open") text: model.canJoin ? qsTr("Join") : qsTr("Open")
onClicked: { onClicked: {
if (model.canJoin) if (model.canJoin)
publicRooms.joinRoom(model.index); publicRooms.joinRoom(model.index);
else else {
{
Rooms.setCurrentRoom(model.roomid); Rooms.setCurrentRoom(model.roomid);
roomDirectoryWindow.close(); roomDirectoryWindow.close();
} }
} }
} }
} }
} }
} }
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: palette.mid foreground: palette.mid
running: visible
} }
} }
} }
header: RowLayout {
id: searchBarLayout
spacing: Nheko.paddingMedium
width: parent.width
implicitHeight: roomSearch.height
MatrixTextField {
id: roomSearch
focus: true
Layout.fillWidth: true
selectByMouse: true
font.pixelSize: fontMetrics.font.pixelSize
color: palette.text
placeholderText: qsTr("Search for public rooms")
onTextChanged: searchTimer.restart()
Component.onCompleted: forceActiveFocus()
}
MatrixTextField {
id: chooseServer
Layout.minimumWidth: 0.3 * header.width
Layout.maximumWidth: 0.3 * header.width
color: palette.text
placeholderText: qsTr("Choose custom homeserver")
onTextChanged: publicRooms.setMatrixServer(text)
}
Timer {
id: searchTimer
interval: 350
onTriggered: roomDirView.model.setSearchTerm(roomSearch.text)
}
}
footer: RowLayout {
spacing: Nheko.paddingMedium
width: parent.width
Button {
text: qsTr("Close")
onClicked: roomDirectoryWindow.close()
Layout.alignment: Qt.AlignRight
Layout.margins: Nheko.paddingMedium
}
}
} }

@ -17,18 +17,24 @@ ApplicationWindow {
property MemberList members property MemberList members
property Room room property Room room
title: qsTr("Members of %1").arg(members.roomName)
height: 650
width: 420
minimumHeight: 420
color: palette.window color: palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 650
minimumHeight: 420
title: qsTr("Members of %1").arg(members.roomName)
width: 420
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: roomMembersRoot.close()
}
Shortcut { 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,148 +43,146 @@ ApplicationWindow {
Avatar { Avatar {
id: roomAvatar id: roomAvatar
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 130 Layout.preferredHeight: 130
Layout.preferredWidth: 130 Layout.preferredWidth: 130
roomid: members.roomId
displayName: members.roomName displayName: members.roomName
Layout.alignment: Qt.AlignHCenter roomid: members.roomId
url: members.avatarUrl.replace("mxc://", "image://MxcImage/") url: members.avatarUrl.replace("mxc://", "image://MxcImage/")
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: palette.text color: palette.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"
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) onCurrentValueChanged: members.sortBy(currentValue)
Layout.fillWidth: true
} }
} }
ScrollView { ScrollView {
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
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: room.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 ? palette.dark : roomMembersRoot.color color: del.hovered ? palette.dark : roomMembersRoot.color
} }
onClicked: room.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
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize Layout.preferredHeight: Nheko.avatarSize
userid: model.mxid Layout.preferredWidth: Nheko.avatarSize
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: model.displayName displayName: model.displayName
enabled: false enabled: false
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.mxid
} }
ColumnLayout { ColumnLayout {
spacing: Nheko.paddingSmall
Layout.fillWidth: true Layout.fillWidth: true
spacing: Nheko.paddingSmall
ElidedLabel { ElidedLabel {
fullText: model.displayName Layout.fillWidth: true
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
Layout.fillWidth: true font.pixelSize: fontMetrics.font.pixelSize
fullText: model.displayName
} }
ElidedLabel { ElidedLabel {
fullText: model.mxid Layout.fillWidth: true
color: del.hovered ? palette.brightText : palette.buttonText color: del.hovered ? palette.brightText : palette.buttonText
font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9)
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
Layout.fillWidth: true font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9)
fullText: model.mxid
} }
} }
PowerlevelIndicator { PowerlevelIndicator {
powerlevel: model.powerlevel
permissions: room.permissions permissions: room.permissions
powerlevel: model.powerlevel
} }
EncryptionIndicator { EncryptionIndicator {
id: encryptInd id: encryptInd
Layout.preferredWidth: 16
Layout.preferredHeight: 16
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
visible: room.isEncrypted Layout.preferredHeight: 16
encrypted: room.isEncrypted Layout.preferredWidth: 16
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.");
@ -188,23 +192,22 @@ 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
@ -212,18 +215,8 @@ ApplicationWindow {
anchors.centerIn: parent anchors.centerIn: parent
implicitHeight: parent.visible ? 35 : 0 implicitHeight: parent.visible ? 35 : 0
} }
} }
} }
} }
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: roomMembersRoot.close()
} }
} }

@ -16,80 +16,87 @@ ApplicationWindow {
property var roomSettings property var roomSettings
minimumWidth: 340
minimumHeight: 450
width: 450
height: 680
color: palette.window color: 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
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 {
id: displayAvatar id: displayAvatar
Layout.topMargin: Nheko.paddingMedium Layout.alignment: Qt.AlignHCenter
url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
roomid: roomSettings.roomId
displayName: roomSettings.roomName
Layout.preferredHeight: 130 Layout.preferredHeight: 130
Layout.preferredWidth: 130 Layout.preferredWidth: 130
Layout.alignment: Qt.AlignHCenter Layout.topMargin: Nheko.paddingMedium
displayName: roomSettings.roomName
roomid: roomSettings.roomId
url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
onClicked: TimelineManager.openImageOverlay(null, roomSettings.roomAvatarUrl, "", 0, 0) onClicked: TimelineManager.openImageOverlay(null, roomSettings.roomAvatarUrl, "", 0, 0)
ImageButton { ImageButton {
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change room avatar.") ToolTip.text: qsTr("Change room avatar.")
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: roomSettings.canChangeAvatar hoverEnabled: true
image: ":/icons/icons/ui/edit.svg" image: ":/icons/icons/ui/edit.svg"
visible: roomSettings.canChangeAvatar
onClicked: { onClicked: {
roomSettings.updateAvatar(); roomSettings.updateAvatar();
} }
} }
} }
Spinner { Spinner {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
visible: roomSettings.isLoading
foreground: palette.mid foreground: 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
@ -98,43 +105,38 @@ ApplicationWindow {
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: 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: 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);
@ -142,18 +144,21 @@ ApplicationWindow {
event.accepted = true; event.accepted = true;
} }
} }
Keys.onShortcutOverride: event.key === Qt.Key_Enter
ImageButton { ImageButton {
id: nameChangeButton id: nameChangeButton
visible: roomSettings.canChangeName
anchors.leftMargin: Nheko.paddingSmall ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Change name of this room")
ToolTip.visible: hovered
anchors.left: roomName.right anchors.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);
@ -165,69 +170,64 @@ ApplicationWindow {
} }
} }
} }
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingMedium
Label { Label {
text: qsTr("%n member(s)", "", roomSettings.memberCount)
color: palette.text color: palette.text
text: qsTr("%n member(s)", "", roomSettings.memberCount)
} }
ImageButton { ImageButton {
image: ":/icons/icons/ui/people.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("View members of %1").arg(roomSettings.roomName) ToolTip.text: qsTr("View members of %1").arg(roomSettings.roomName)
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/people.svg"
onClicked: TimelineManager.openRoomMembers(Rooms.getRoomById(roomSettings.roomId)) onClicked: TimelineManager.openRoomMembers(Rooms.getRoomById(roomSettings.roomId))
} }
} }
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.plainRoomTopic === "" ? ("<i>" + qsTr("No topic set") + "</i>") : roomSettings.roomTopic)
wrapMode: TextEdit.WordWrap
background: null background: null
clip: true
color: palette.text color: palette.text
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
readOnly: !isTopicEditingAllowed
text: isTopicEditingAllowed ? roomSettings.plainRoomTopic : (roomSettings.plainRoomTopic === "" ? ("<i>" + qsTr("No topic set") + "</i>") : 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);
@ -240,234 +240,219 @@ 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("NOTIFICATIONS")
font.bold: true
color: palette.text
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge Layout.topMargin: Nheko.paddingLarge
color: palette.text
font.bold: true
text: qsTr("NOTIFICATIONS")
} }
Label { Label {
text: qsTr("Notifications")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: 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("ENTRY PERMISSIONS")
font.bold: true
color: palette.text
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge Layout.topMargin: Nheko.paddingLarge
color: palette.text
font.bold: true
text: qsTr("ENTRY PERMISSIONS")
} }
Label { Label {
text: qsTr("Anyone can join")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Anyone can join")
} }
ToggleButton { ToggleButton {
id: publicRoomButton id: publicRoomButton
enabled: roomSettings.canChangeJoinRules
checked: !roomSettings.privateAccess
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: !roomSettings.privateAccess
enabled: roomSettings.canChangeJoinRules
} }
Label { Label {
text: qsTr("Allow knocking")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Allow knocking")
visible: knockingButton.visible visible: knockingButton.visible
} }
ToggleButton { ToggleButton {
id: knockingButton id: knockingButton
visible: !publicRoomButton.checked Layout.alignment: Qt.AlignRight
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsKnocking
checked: roomSettings.knockingEnabled checked: roomSettings.knockingEnabled
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsKnocking
visible: !publicRoomButton.checked
onCheckedChanged: { onCheckedChanged: {
if (checked && !roomSettings.supportsKnockRestricted) restrictedButton.checked = false; if (checked && !roomSettings.supportsKnockRestricted)
restrictedButton.checked = false;
} }
Layout.alignment: Qt.AlignRight
} }
Label { Label {
text: qsTr("Allow joining via other rooms")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Allow joining via other rooms")
visible: restrictedButton.visible visible: restrictedButton.visible
} }
ToggleButton { ToggleButton {
id: restrictedButton id: restrictedButton
visible: !publicRoomButton.checked Layout.alignment: Qt.AlignRight
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
checked: roomSettings.restrictedEnabled checked: roomSettings.restrictedEnabled
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
visible: !publicRoomButton.checked
onCheckedChanged: { onCheckedChanged: {
if (checked && !roomSettings.supportsKnockRestricted) knockingButton.checked = false; if (checked && !roomSettings.supportsKnockRestricted)
knockingButton.checked = false;
} }
Layout.alignment: Qt.AlignRight
} }
Label { Label {
text: qsTr("Rooms to join via")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Rooms to join via")
visible: allowedRoomsButton.visible visible: allowedRoomsButton.visible
} }
Button { Button {
id: allowedRoomsButton id: allowedRoomsButton
visible: restrictedButton.checked && restrictedButton.visible Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("Change the list of rooms users can join this room via. Usually this is the official community of this room.")
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
text: qsTr("Change") text: qsTr("Change")
ToolTip.text: qsTr("Change the list of rooms users can join this room via. Usually this is the official community of this room.") visible: restrictedButton.checked && restrictedButton.visible
onClicked: timelineRoot.showAllowedRoomsEditor(roomSettings) onClicked: timelineRoot.showAllowedRoomsEditor(roomSettings)
Layout.alignment: Qt.AlignRight
} }
Label { Label {
text: qsTr("Allow guests to join")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text color: palette.text
text: qsTr("Allow guests to join")
} }
ToggleButton { ToggleButton {
id: guestAccessButton id: guestAccessButton
enabled: roomSettings.canChangeJoinRules
checked: roomSettings.guestAccess
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: roomSettings.guestAccess
enabled: roomSettings.canChangeJoinRules
} }
Button { Button {
visible: publicRoomButton.checked == roomSettings.privateAccess || knockingButton.checked != roomSettings.knockingEnabled || restrictedButton.checked != roomSettings.restrictedEnabled || guestAccessButton.checked != roomSettings.guestAccess || roomSettings.allowedRoomsModified Layout.columnSpan: 2
Layout.fillWidth: true
enabled: roomSettings.canChangeJoinRules enabled: roomSettings.canChangeJoinRules
text: qsTr("Apply access rules") text: qsTr("Apply access rules")
visible: publicRoomButton.checked == roomSettings.privateAccess || knockingButton.checked != roomSettings.knockingEnabled || restrictedButton.checked != roomSettings.restrictedEnabled || guestAccessButton.checked != roomSettings.guestAccess || roomSettings.allowedRoomsModified
onClicked: roomSettings.changeAccessRules(!publicRoomButton.checked, guestAccessButton.checked, knockingButton.checked, restrictedButton.checked) onClicked: roomSettings.changeAccessRules(!publicRoomButton.checked, guestAccessButton.checked, knockingButton.checked, restrictedButton.checked)
Layout.columnSpan: 2
Layout.fillWidth: true
} }
Label { Label {
text: qsTr("MESSAGE VISIBILITY")
font.bold: true
color: palette.text
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge Layout.topMargin: Nheko.paddingLarge
color: palette.text
font.bold: true
text: qsTr("MESSAGE VISIBILITY")
} }
Label { Label {
text: qsTr("Allow viewing history without joining")
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("This is useful to see previews of the room or view it on public websites.") ToolTip.text: qsTr("This is useful to see previews of the room or view it on public websites.")
ToolTip.visible: publicHistoryHover.hovered ToolTip.visible: publicHistoryHover.hovered
ToolTip.delay: Nheko.tooltipDelay color: palette.text
text: qsTr("Allow viewing history without joining")
HoverHandler { HoverHandler {
id: publicHistoryHover id: publicHistoryHover
} }
} }
ToggleButton { ToggleButton {
id: publicHistoryButton id: publicHistoryButton
enabled: roomSettings.canChangeHistoryVisibility
checked: roomSettings.historyVisibility == RoomSettings.WorldReadable
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
checked: roomSettings.historyVisibility == RoomSettings.WorldReadable
enabled: roomSettings.canChangeHistoryVisibility
} }
Label { Label {
visible: !publicHistoryButton.checked
text: qsTr("Members can see messages since")
Layout.fillWidth: true
color: palette.text
Layout.alignment: Qt.AlignTop | Qt.AlignLeft Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.fillWidth: true
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("How much of the history is visible to joined members. Changing this won't affect the visibility of already sent messages. It only applies to new messages.") ToolTip.text: qsTr("How much of the history is visible to joined members. Changing this won't affect the visibility of already sent messages. It only applies to new messages.")
ToolTip.visible: privateHistoryHover.hovered ToolTip.visible: privateHistoryHover.hovered
ToolTip.delay: Nheko.tooltipDelay color: palette.text
text: qsTr("Members can see messages since")
visible: !publicHistoryButton.checked
HoverHandler { HoverHandler {
id: privateHistoryHover id: privateHistoryHover
} }
} }
ColumnLayout { ColumnLayout {
Layout.alignment: Qt.AlignTop | Qt.AlignRight
Layout.fillWidth: true Layout.fillWidth: true
visible: !publicHistoryButton.checked
enabled: roomSettings.canChangeHistoryVisibility enabled: roomSettings.canChangeHistoryVisibility
Layout.alignment: Qt.AlignTop | Qt.AlignRight visible: !publicHistoryButton.checked
RadioButton { RadioButton {
id: sharedHistory id: sharedHistory
checked: roomSettings.historyVisibility == RoomSettings.Shared
text: qsTr("Everything") ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("As long as the user joined, they can see all previous messages.") ToolTip.text: qsTr("As long as the user joined, they can see all previous messages.")
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay checked: roomSettings.historyVisibility == RoomSettings.Shared
text: qsTr("Everything")
} }
RadioButton { RadioButton {
id: invitedHistory id: invitedHistory
checked: roomSettings.historyVisibility == RoomSettings.Invited
text: qsTr("They got invited") ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Members can only see messages from when they got invited going forward.") ToolTip.text: qsTr("Members can only see messages from when they got invited going forward.")
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay checked: roomSettings.historyVisibility == RoomSettings.Invited
text: qsTr("They got invited")
} }
RadioButton { RadioButton {
id: joinedHistory id: joinedHistory
checked: roomSettings.historyVisibility == RoomSettings.Joined || roomSettings.historyVisibility == RoomSettings.WorldReadable
text: qsTr("They joined") ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Members can only see messages since after they joined.") ToolTip.text: qsTr("Members can only see messages since after they joined.")
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay checked: roomSettings.historyVisibility == RoomSettings.Joined || roomSettings.historyVisibility == RoomSettings.WorldReadable
text: qsTr("They joined")
} }
} }
Button { Button {
visible: roomSettings.historyVisibility != selectedVisibility
enabled: roomSettings.canChangeHistoryVisibility
text: qsTr("Apply visibility changes")
property int selectedVisibility: { property int selectedVisibility: {
if (publicHistoryButton.checked) if (publicHistoryButton.checked)
return RoomSettings.WorldReadable; return RoomSettings.WorldReadable;
@ -477,202 +462,200 @@ ApplicationWindow {
return RoomSettings.Invited; return RoomSettings.Invited;
return RoomSettings.Joined; return RoomSettings.Joined;
} }
onClicked: roomSettings.changeHistoryVisibility(selectedVisibility)
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
} enabled: roomSettings.canChangeHistoryVisibility
text: qsTr("Apply visibility changes")
visible: roomSettings.historyVisibility != selectedVisibility
onClicked: roomSettings.changeHistoryVisibility(selectedVisibility)
}
Label { Label {
text: qsTr("Locally hidden events")
color: palette.text color: palette.text
text: qsTr("Locally 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
} }
Label { Label {
text: qsTr("Automatic event deletion")
color: palette.text color: palette.text
text: qsTr("Automatic event deletion")
} }
EventExpirationDialog { EventExpirationDialog {
id: eventExpirationDialog id: eventExpirationDialog
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 if your events get automatically deleted in this room.") ToolTip.text: qsTr("Select if your events get automatically deleted in this room.")
text: qsTr("Configure")
onClicked: eventExpirationDialog.show() onClicked: eventExpirationDialog.show()
Layout.alignment: Qt.AlignRight
} }
Label { Label {
text: qsTr("GENERAL SETTINGS")
font.bold: true
color: palette.text
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge Layout.topMargin: Nheko.paddingLarge
color: palette.text
font.bold: true
text: qsTr("GENERAL SETTINGS")
} }
Label { Label {
text: qsTr("Encryption")
color: palette.text color: 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;
} }
if (checked === true) if (checked === true)
confirmEncryptionDialog.open(); confirmEncryptionDialog.open();
} }
Layout.alignment: Qt.AlignRight
} }
Platform.MessageDialog { Platform.MessageDialog {
id: confirmEncryptionDialog id: confirmEncryptionDialog
title: qsTr("End-to-End Encryption") buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel
modality: Qt.NonModal
text: qsTr(`Encryption is currently experimental and things might break unexpectedly. <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("Permission")
color: palette.text color: palette.text
text: qsTr("Permission")
} }
Button { Button {
text: qsTr("Configure") Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("View and change the permissions in this room") ToolTip.text: qsTr("View and change the permissions in this room")
text: qsTr("Configure")
onClicked: timelineRoot.showPLEditor(roomSettings) onClicked: timelineRoot.showPLEditor(roomSettings)
Layout.alignment: Qt.AlignRight
} }
Label { Label {
text: qsTr("Aliases")
color: palette.text color: palette.text
text: qsTr("Aliases")
} }
Button { Button {
text: qsTr("Configure") Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("View and change the addresses/aliases of this room") ToolTip.text: qsTr("View and change the addresses/aliases of this room")
text: qsTr("Configure")
onClicked: timelineRoot.showAliasEditor(roomSettings) onClicked: timelineRoot.showAliasEditor(roomSettings)
Layout.alignment: Qt.AlignRight
} }
Label { Label {
text: qsTr("Sticker & Emote Settings")
color: palette.text color: 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("INFO")
font.bold: true
color: palette.text
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.topMargin: Nheko.paddingLarge
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge
color: palette.text
font.bold: true
text: qsTr("INFO")
} }
Label { Label {
text: qsTr("Internal ID")
color: palette.text color: 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
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 0.8) ToolTip.text: qsTr("Copied to clipboard")
ToolTip.visible: toolTipTimer.running
color: palette.text color: 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: palette.text color: 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: palette.text color: 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()
}
} }

@ -17,20 +17,20 @@ ApplicationWindow {
property var profile property var profile
color: palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 650 height: 650
width: 420
minimumWidth: 150
minimumHeight: 150 minimumHeight: 150
color: palette.window minimumWidth: 150
title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
modality: Qt.NonModal modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
width: 420
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: userProfileDialog.close() onActivated: userProfileDialog.close()
} }
ListView { ListView {
id: devicelist id: devicelist
@ -38,61 +38,73 @@ ApplicationWindow {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
clip: true
spacing: 8
boundsBehavior: Flickable.StopAtBounds
anchors.fill: parent anchors.fill: parent
anchors.margins: 10 anchors.margins: 10
boundsBehavior: Flickable.StopAtBounds
clip: true
footerPositioning: ListView.OverlayFooter footerPositioning: ListView.OverlayFooter
model: (selectedTab == 0) ? devicesModel : sharedRoomsModel
spacing: 8
footer: DialogButtonBox {
alignment: Qt.AlignRight
standardButtons: DialogButtonBox.Ok
width: devicelist.width
z: 2
background: Rectangle {
anchors.fill: parent
color: palette.window
}
onAccepted: userProfileDialog.close()
}
header: ColumnLayout { header: ColumnLayout {
id: contentL id: contentL
width: devicelist.width
spacing: Nheko.paddingMedium spacing: Nheko.paddingMedium
width: devicelist.width
Avatar { Avatar {
id: displayAvatar id: displayAvatar
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 130 Layout.preferredHeight: 130
Layout.preferredWidth: 130 Layout.preferredWidth: 130
displayName: profile.displayName displayName: profile.displayName
url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: profile.userid userid: profile.userid
Layout.alignment: Qt.AlignHCenter
onClicked: TimelineManager.openImageOverlay(null, profile.avatarUrl, "", 0, 0) onClicked: TimelineManager.openImageOverlay(null, profile.avatarUrl, "", 0, 0)
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: palette.mid
running: profile.isLoading running: profile.isLoading
visible: profile.isLoading visible: profile.isLoading
foreground: 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
@ -101,16 +113,13 @@ ApplicationWindow {
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;
@ -120,22 +129,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, 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, 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;
@ -143,14 +152,16 @@ ApplicationWindow {
ImageButton { ImageButton {
id: usernameChangeButton id: usernameChangeButton
visible: profile.isSelf
anchors.leftMargin: Nheko.paddingSmall ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change display name globally.") : qsTr("Change display name. Will only apply to this room.")
ToolTip.visible: hovered
anchors.left: displayUsername.right anchors.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);
@ -162,82 +173,79 @@ ApplicationWindow {
} }
} }
} }
} }
MatrixText { MatrixText {
text: profile.userid
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: profile.userid
} }
MatrixText { MatrixText {
id: statusMsg id: statusMsg
text: qsTr("<i><b>Status:</b> %1</i>").arg(userStatus)
visible: userStatus != "" property string userStatus: Presence.userStatus(profile.userid)
Layout.fillWidth: true Layout.fillWidth: true
horizontalAlignment: TextEdit.AlignHCenter
Layout.leftMargin: Nheko.paddingMedium Layout.leftMargin: Nheko.paddingMedium
Layout.rightMargin: Nheko.paddingMedium Layout.rightMargin: Nheko.paddingMedium
font.pointSize: Math.floor(fontMetrics.font.pointSize * 0.9) font.pointSize: Math.floor(fontMetrics.font.pointSize * 0.9)
horizontalAlignment: TextEdit.AlignHCenter
text: qsTr("<i><b>Status:</b> %1</i>").arg(userStatus)
visible: userStatus != ""
property string userStatus: Presence.userStatus(profile.userid)
Connections { Connections {
target: Presence
function onPresenceChanged(id) { function onPresenceChanged(id) {
if (id == profile.userid) statusMsg.userStatus = Presence.userStatus(profile.userid); if (id == profile.userid)
statusMsg.userStatus = Presence.userStatus(profile.userid);
} }
target: Presence
} }
} }
RowLayout { 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
text: qsTr("Room: %1").arg(profile.room ? profile.room.roomName : "") Layout.maximumWidth: parent.parent.width - (parent.spacing * 3) - 16
ToolTip.text: qsTr("This is a room-specific profile. The user's name and avatar may be different from their global versions.") ToolTip.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: 32 Layout.preferredHeight: 32
Layout.preferredWidth: 32 Layout.preferredWidth: 32
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"
@ -259,139 +267,135 @@ ApplicationWindow {
ImageButton { ImageButton {
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
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 {
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
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() ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/round-remove-button.svg"
visible: !profile.isGlobalUserProfile && profile.room.permissions.canKick() visible: !profile.isGlobalUserProfile && profile.room.permissions.canKick()
}
onClicked: profile.kickUser()
}
ImageButton { ImageButton {
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
image: ":/icons/icons/ui/ban.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Ban the user.") ToolTip.text: qsTr("Ban the user.")
onClicked: profile.banUser() ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/ban.svg"
visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan() visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan()
}
onClicked: profile.banUser()
}
ImageButton { ImageButton {
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
image: ":/icons/icons/ui/volume-off-indicator.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: profile.ignored ? qsTr("Unignore the user.") : qsTr("Ignore the user.") ToolTip.text: profile.ignored ? qsTr("Unignore the user.") : qsTr("Ignore the user.")
ToolTip.visible: hovered
buttonTextColor: profile.ignored ? Nheko.theme.red : palette.buttonText buttonTextColor: profile.ignored ? Nheko.theme.red : palette.buttonText
onClicked: profile.ignored = !profile.ignored hoverEnabled: true
image: ":/icons/icons/ui/volume-off-indicator.svg"
visible: !profile.isSelf visible: !profile.isSelf
}
onClicked: profile.ignored = !profile.ignored
}
ImageButton { ImageButton {
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
image: ":/icons/icons/ui/refresh.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Refresh device list.") ToolTip.text: qsTr("Refresh device list.")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/refresh.svg"
onClicked: profile.refreshDevices() onClicked: profile.refreshDevices()
} }
} }
TabBar { TabBar {
id: tabbar id: tabbar
visible: !profile.isSelf
Layout.bottomMargin: Nheko.paddingMedium
Layout.fillWidth: true Layout.fillWidth: true
visible: !profile.isSelf
onCurrentIndexChanged: devicelist.selectedTab = currentIndex onCurrentIndexChanged: devicelist.selectedTab = currentIndex
NhekoTabButton { NhekoTabButton {
text: qsTr("Devices") text: qsTr("Devices")
} }
NhekoTabButton { NhekoTabButton {
text: qsTr("Shared Rooms") text: qsTr("Shared Rooms")
} }
Layout.bottomMargin: Nheko.paddingMedium
} }
} }
model: (selectedTab == 0) ? devicesModel : sharedRoomsModel
DelegateModel { DelegateModel {
id: devicesModel id: devicesModel
model: profile.deviceList model: profile.deviceList
delegate: RowLayout { delegate: RowLayout {
required property int verificationStatus
required property string deviceId required property string deviceId
required property string deviceName required property string deviceName
required property string lastIp required property string lastIp
required property var lastTs required property var lastTs
required property int verificationStatus
width: devicelist.width
spacing: 4 spacing: 4
width: devicelist.width
ColumnLayout { ColumnLayout {
spacing: 0
Layout.leftMargin: Nheko.paddingMedium Layout.leftMargin: Nheko.paddingMedium
Layout.rightMargin: Nheko.paddingMedium Layout.rightMargin: Nheko.paddingMedium
spacing: 0
RowLayout { RowLayout {
Text { Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
color: palette.text
elide: Text.ElideRight elide: Text.ElideRight
font.bold: true font.bold: true
color: palette.text
text: deviceId text: deviceId
} }
Image { Image {
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16
visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
sourceSize.height: 16 * Screen.devicePixelRatio
sourceSize.width: 16 * Screen.devicePixelRatio
source: { source: {
switch (verificationStatus) { switch (verificationStatus) {
case VerificationStatus.VERIFIED: case VerificationStatus.VERIFIED:
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green; return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green;
case VerificationStatus.UNVERIFIED: case VerificationStatus.UNVERIFIED:
return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?" + Nheko.theme.orange; return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?" + Nheko.theme.orange;
case VerificationStatus.SELF: case VerificationStatus.SELF:
return "image://colorimage/:/icons/icons/ui/checkmark.svg?" + Nheko.theme.green; return "image://colorimage/:/icons/icons/ui/checkmark.svg?" + Nheko.theme.green;
default: default:
return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.orange; return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?" + Nheko.theme.orange;
} }
} }
sourceSize.height: 16 * Screen.devicePixelRatio
sourceSize.width: 16 * Screen.devicePixelRatio
visible: profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
image: ":/icons/icons/ui/power-off.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Sign out this device.") ToolTip.text: qsTr("Sign out this device.")
onClicked: profile.signOutDevice(deviceId) ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/power-off.svg"
visible: profile.isSelf visible: profile.isSelf
}
onClicked: profile.signOutDevice(deviceId)
}
} }
RowLayout { RowLayout {
id: deviceNameRow id: deviceNameRow
@ -400,24 +404,25 @@ ApplicationWindow {
TextInput { TextInput {
id: deviceNameField id: deviceNameField
readOnly: !deviceNameRow.isEditingAllowed
text: deviceName
color: palette.text
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true Layout.fillWidth: true
color: palette.text
readOnly: !deviceNameRow.isEditingAllowed
selectByMouse: true selectByMouse: true
text: deviceName
onAccepted: { onAccepted: {
profile.changeDeviceName(deviceId, deviceNameField.text); profile.changeDeviceName(deviceId, deviceNameField.text);
deviceNameRow.isEditingAllowed = false; deviceNameRow.isEditingAllowed = false;
} }
} }
ImageButton { ImageButton {
visible: profile.isSelf
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change device name.") ToolTip.text: qsTr("Change device name.")
ToolTip.visible: hovered
hoverEnabled: true
image: deviceNameRow.isEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg" image: deviceNameRow.isEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
visible: profile.isSelf
onClicked: { onClicked: {
if (deviceNameRow.isEditingAllowed) { if (deviceNameRow.isEditingAllowed) {
profile.changeDeviceName(deviceId, deviceNameField.text); profile.changeDeviceName(deviceId, deviceNameField.text);
@ -429,111 +434,88 @@ ApplicationWindow {
} }
} }
} }
} }
Text { Text {
visible: profile.isSelf
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
elide: Text.ElideRight Layout.fillWidth: true
color: palette.text color: palette.text
elide: Text.ElideRight
text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp ? lastIp : "???") text: qsTr("Last seen %1 from %2").arg(new Date(lastTs).toLocaleString(Locale.ShortFormat)).arg(lastIp ? lastIp : "???")
visible: profile.isSelf
} }
} }
Image { Image {
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16
visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
source: { source: {
switch (verificationStatus) { switch (verificationStatus) {
case VerificationStatus.VERIFIED: case VerificationStatus.VERIFIED:
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green; return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green;
case VerificationStatus.UNVERIFIED: case VerificationStatus.UNVERIFIED:
return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?" + Nheko.theme.orange; return "image://colorimage/:/icons/icons/ui/shield-filled-exclamation-mark.svg?" + Nheko.theme.orange;
case VerificationStatus.SELF: case VerificationStatus.SELF:
return "image://colorimage/:/icons/icons/ui/checkmark.svg?" + Nheko.theme.green; return "image://colorimage/:/icons/icons/ui/checkmark.svg?" + Nheko.theme.green;
default: default:
return "image://colorimage/:/icons/icons/ui/shield-filled.svg?" + Nheko.theme.red; return "image://colorimage/:/icons/icons/ui/shield-filled.svg?" + Nheko.theme.red;
} }
} }
visible: !profile.isSelf && verificationStatus != VerificationStatus.NOT_APPLICABLE
} }
Button { Button {
id: verifyButton id: verifyButton
visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled)
text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify") text: (verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify")
visible: verificationStatus == VerificationStatus.UNVERIFIED && (profile.isSelf || !profile.userVerificationEnabled)
onClicked: { onClicked: {
if (verificationStatus == VerificationStatus.VERIFIED) if (verificationStatus == VerificationStatus.VERIFIED)
profile.unverify(deviceId); profile.unverify(deviceId);
else else
profile.verify(deviceId); profile.verify(deviceId);
} }
} }
} }
} }
DelegateModel { DelegateModel {
id: sharedRoomsModel id: sharedRoomsModel
model: profile.sharedRooms model: profile.sharedRooms
delegate: RowLayout { delegate: RowLayout {
required property string avatarUrl
required property string roomId required property string roomId
required property string roomName required property string roomName
required property string avatarUrl
width: devicelist.width
spacing: 4 spacing: 4
width: devicelist.width
Avatar { Avatar {
id: avatar id: avatar
enabled: false property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Nheko.paddingMedium Layout.leftMargin: Nheko.paddingMedium
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
Layout.preferredHeight: avatarSize Layout.preferredHeight: avatarSize
Layout.preferredWidth: avatarSize Layout.preferredWidth: avatarSize
url: avatarUrl.replace("mxc://", "image://MxcImage/")
roomid: roomId
displayName: roomName displayName: roomName
enabled: false
roomid: roomId
url: avatarUrl.replace("mxc://", "image://MxcImage/")
} }
ElidedLabel { ElidedLabel {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: palette.text
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: Nheko.paddingMedium
color: palette.text
elideWidth: width elideWidth: width
fullText: roomName fullText: roomName
textFormat: Text.PlainText textFormat: Text.PlainText
Layout.rightMargin: Nheko.paddingMedium
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
} }
footer: DialogButtonBox {
z: 2
width: devicelist.width
alignment: Qt.AlignRight
standardButtons: DialogButtonBox.Ok
onAccepted: userProfileDialog.close()
background: Rectangle {
anchors.fill: parent
color: palette.window
}
}
} }
} }

@ -12,17 +12,17 @@ Menu {
id: stickerPopup id: stickerPopup
property var callback property var callback
property string roomid
property alias model: gridView.model
required property bool emoji required property bool emoji
property var textArea
property real highlightHue: palette.highlight.hslHue property real highlightHue: palette.highlight.hslHue
property real highlightSat: palette.highlight.hslSaturation
property real highlightLight: palette.highlight.hslLightness property real highlightLight: palette.highlight.hslLightness
property real highlightSat: palette.highlight.hslSaturation
property alias model: gridView.model
property string roomid
readonly property int sidebarAvatarSize: 24
readonly property int stickerDim: emoji ? 48 : 128 readonly property int stickerDim: emoji ? 48 : 128
readonly property int stickerDimPad: stickerDim + Nheko.paddingSmall readonly property int stickerDimPad: stickerDim + Nheko.paddingSmall
readonly property int stickersPerRow: emoji ? 7 : 3 readonly property int stickersPerRow: emoji ? 7 : 3
readonly property int sidebarAvatarSize: 24 property var textArea
function show(showAt, roomid_, callback) { function show(showAt, roomid_, callback) {
console.debug("Showing sticker picker"); console.debug("Showing sticker picker");
@ -31,29 +31,29 @@ Menu {
popup(showAt ? showAt : null); popup(showAt ? showAt : null);
} }
margins: 2
bottomPadding: 0 bottomPadding: 0
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
focus: true
leftPadding: 0 leftPadding: 0
margins: 2
modal: true
rightPadding: 0 rightPadding: 0
topPadding: 0 topPadding: 0
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
width: sidebarAvatarSize + Nheko.paddingSmall + stickersPerRow * stickerDimPad + 20 width: sidebarAvatarSize + Nheko.paddingSmall + stickersPerRow * stickerDimPad + 20
Rectangle { Rectangle {
color: palette.window color: palette.window
height: columnView.implicitHeight + Nheko.paddingSmall*2 height: columnView.implicitHeight + Nheko.paddingSmall * 2
width: sidebarAvatarSize + Nheko.paddingSmall + stickersPerRow * stickerDimPad + 20 width: sidebarAvatarSize + Nheko.paddingSmall + stickersPerRow * stickerDimPad + 20
GridLayout { GridLayout {
id: columnView id: columnView
anchors.leftMargin: Nheko.paddingSmall
anchors.rightMargin: Nheko.paddingSmall
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Nheko.paddingSmall
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Nheko.paddingSmall
columns: 2 columns: 2
rows: 2 rows: 2
@ -61,14 +61,15 @@ Menu {
TextField { TextField {
id: emojiSearch id: emojiSearch
Layout.column: 1
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall
Layout.row: 0 Layout.row: 0
Layout.column: 1
background: null background: null
placeholderTextColor: palette.buttonText
placeholderText: qsTr("Search") placeholderText: qsTr("Search")
selectByMouse: true placeholderTextColor: palette.buttonText
rightPadding: clearSearch.width rightPadding: clearSearch.width
selectByMouse: true
onTextChanged: searchTimer.restart() onTextChanged: searchTimer.restart()
onVisibleChanged: { onVisibleChanged: {
if (visible) if (visible)
@ -81,23 +82,24 @@ Menu {
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
} }
ImageButton { ImageButton {
id: clearSearch id: clearSearch
focusPolicy: Qt.NoFocus
hoverEnabled: true
image: ":/icons/icons/ui/round-remove-button.svg"
visible: emojiSearch.text !== '' visible: emojiSearch.text !== ''
image: ":/icons/icons/ui/round-remove-button.svg"
focusPolicy: Qt.NoFocus
onClicked: emojiSearch.clear() onClicked: emojiSearch.clear()
hoverEnabled: true
anchors { anchors {
top: parent.top
bottom: parent.bottom bottom: parent.bottom
right: parent.right right: parent.right
rightMargin: Nheko.paddingSmall rightMargin: Nheko.paddingSmall
top: parent.top
} }
} }
} }
@ -106,39 +108,30 @@ Menu {
ListView { ListView {
id: gridView id: gridView
model: roomid ? TimelineManager.completerFor(stickerPopup.emoji ? "emojigrid" : "stickergrid", roomid) : null property int cellHeight: stickerDimPad
Layout.row: 1
Layout.column: 1 Layout.column: 1
Layout.preferredHeight: cellHeight * (stickersPerRow + 0.5) Layout.preferredHeight: cellHeight * (stickersPerRow + 0.5)
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall
property int cellHeight: stickerDimPad Layout.row: 1
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
clip: true clip: true
currentIndex: -1 // prevent sorting from stealing focus currentIndex: -1 // prevent sorting from stealing focus
section.property: "packname" model: roomid ? TimelineManager.completerFor(stickerPopup.emoji ? "emojigrid" : "stickergrid", roomid) : null
section.criteria: ViewSection.FullString section.criteria: ViewSection.FullString
section.delegate: Rectangle { section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
width: gridView.width section.property: "packname"
height: childrenRect.height spacing: Nheko.paddingSmall
color: palette.alternateBase
required property string section ScrollBar.vertical: ScrollBar {
id: emojiScroll
Text {
anchors.left: parent.left
anchors.right: parent.right
text: parent.section
font.bold: true
}
} }
section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
spacing: Nheko.paddingSmall
// Individual emoji // Individual emoji
delegate: Row { delegate: Row {
required property var row; required property var row
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
@ -150,27 +143,17 @@ Menu {
required property var modelData required property var modelData
width: stickerDim
height: stickerDim
hoverEnabled: true
ToolTip.text: ":" + modelData.shortcode + ": - " + (modelData.unicode ? modelData.unicodeName : modelData.body) ToolTip.text: ":" + modelData.shortcode + ": - " + (modelData.unicode ? modelData.unicodeName : modelData.body)
ToolTip.visible: hovered ToolTip.visible: hovered
// TODO: maybe add favorites at some point? height: stickerDim
onClicked: { hoverEnabled: true
console.debug("Picked " + modelData); width: stickerDim
stickerPopup.close();
if (!stickerPopup.emoji) {
// return descriptor to calculate sticker to send
callback(modelData.descriptor);
} else if (modelData.unicode) {
// return the emoji unicode as both plain text and markdown
callback(modelData.unicode, modelData.unicode);
} else {
// return the emoji url as plain text and a markdown link as markdown
callback(modelData.url, modelData.markdown);
}
}
background: Rectangle {
anchors.fill: parent
color: hovered ? palette.highlight : 'transparent'
radius: 5
}
contentItem: DelegateChooser { contentItem: DelegateChooser {
roleValue: del.modelData.unicode != undefined roleValue: del.modelData.unicode != undefined
@ -178,87 +161,99 @@ Menu {
roleValue: true roleValue: true
Text { Text {
width: stickerDim
height: stickerDim
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.family: Settings.emojiFont font.family: Settings.emojiFont
font.pixelSize: 36 font.pixelSize: 36
height: stickerDim
horizontalAlignment: Text.AlignHCenter
text: del.modelData.unicode.replace('\ufe0f', '') text: del.modelData.unicode.replace('\ufe0f', '')
verticalAlignment: Text.AlignVCenter
width: stickerDim
} }
} }
DelegateChoice { DelegateChoice {
roleValue: false roleValue: false
Image { Image {
fillMode: Image.PreserveAspectFit
height: stickerDim height: stickerDim
width: stickerDim
source: del.modelData.url.replace("mxc://", "image://MxcImage/") + "?scale" source: del.modelData.url.replace("mxc://", "image://MxcImage/") + "?scale"
fillMode: Image.PreserveAspectFit width: stickerDim
} }
} }
} }
background: Rectangle { // TODO: maybe add favorites at some point?
anchors.fill: parent onClicked: {
color: hovered ? palette.highlight : 'transparent' console.debug("Picked " + modelData);
radius: 5 stickerPopup.close();
if (!stickerPopup.emoji) {
// return descriptor to calculate sticker to send
callback(modelData.descriptor);
} else if (modelData.unicode) {
// return the emoji unicode as both plain text and markdown
callback(modelData.unicode, modelData.unicode);
} else {
// return the emoji url as plain text and a markdown link as markdown
callback(modelData.url, modelData.markdown);
}
} }
} }
} }
} }
section.delegate: Rectangle {
required property string section
ScrollBar.vertical: ScrollBar { color: palette.alternateBase
id: emojiScroll height: childrenRect.height
} width: gridView.width
Text {
anchors.left: parent.left
anchors.right: parent.right
font.bold: true
text: parent.section
}
}
} }
ListView { ListView {
Layout.row: 1
Layout.column: 0 Layout.column: 0
Layout.preferredWidth: sidebarAvatarSize
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: sidebarAvatarSize
Layout.rightMargin: Nheko.paddingSmall Layout.rightMargin: Nheko.paddingSmall
Layout.row: 1
clip: true
model: gridView.model ? gridView.model.sections : null model: gridView.model ? gridView.model.sections : null
spacing: Nheko.paddingSmall spacing: Nheko.paddingSmall
clip: true
delegate: Avatar { delegate: Avatar {
height: sidebarAvatarSize ToolTip.delay: Nheko.tooltipDelay
width: sidebarAvatarSize ToolTip.text: modelData.name
url: modelData.url.replace("mxc://", "image://MxcImage/") ToolTip.visible: hovered
textColor: modelData.url.startsWith("mxc://") ? palette.text : palette.buttonText
displayName: modelData.name displayName: modelData.name
height: sidebarAvatarSize
hoverEnabled: true
roomid: modelData.name roomid: modelData.name
textColor: modelData.url.startsWith("mxc://") ? palette.text : palette.buttonText
url: modelData.url.replace("mxc://", "image://MxcImage/")
width: sidebarAvatarSize
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: modelData.name
onClicked: gridView.positionViewAtIndex(modelData.firstRowWith, ListView.Beginning) onClicked: gridView.positionViewAtIndex(modelData.firstRowWith, ListView.Beginning)
} }
} }
ImageButton { ImageButton {
Layout.row: 0
Layout.column: 0 Layout.column: 0
Layout.preferredWidth: sidebarAvatarSize
Layout.preferredHeight: sidebarAvatarSize Layout.preferredHeight: sidebarAvatarSize
Layout.preferredWidth: sidebarAvatarSize
Layout.rightMargin: Nheko.paddingSmall Layout.rightMargin: Nheko.paddingSmall
Layout.row: 0
image: ":/icons/icons/ui/settings.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
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")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/settings.svg"
onClicked: TimelineManager.openImagePackSettings(stickerPopup.roomid) onClicked: TimelineManager.openImagePackSettings(stickerPopup.roomid)
} }
} }
} }
} }

@ -13,147 +13,139 @@ 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
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)
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"
Layout.preferredHeight: 128 Layout.preferredHeight: 128
Layout.preferredWidth: 128 Layout.preferredWidth: 128
source: "qrc:/logos/login.png"
} }
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:yourserver.example.com\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.")
label: qsTr("Matrix ID") label: qsTr("Matrix ID")
placeholderText: qsTr("e.g @user:yourserver.example.com") placeholderText: qsTr("e.g @user:yourserver.example.com")
onEditingFinished: login.mxid = text
ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user ID. After the user ID you need to include your server name after a :.\nYou can also put your homeserver address there if your server doesn't support .well-known lookup.\nExample: @user:yourserver.example.com\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.") onEditingFinished: login.mxid = text
Keys.forwardTo: [pwBtn, ssoRepeater]
} }
Spinner { Spinner {
Layout.preferredHeight: matrixIdLabel.height/2
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
Layout.preferredHeight: matrixIdLabel.height / 2
visible: running
running: login.lookingUpHs
foreground: palette.mid foreground: palette.mid
running: login.lookingUpHs
visible: running
} }
} }
MatrixText { MatrixText {
Layout.fillWidth: true Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: login.mxidError text: login.mxidError
textFormat: Text.PlainText
visible: text visible: text
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
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 nothing 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 nothing is provided, a default is used.")
Keys.forwardTo: [pwBtn, ssoRepeater]
} }
MatrixTextField { MatrixTextField {
id: hsLabel id: hsLabel
enabled: visible
visible: login.homeserverNeeded
Keys.forwardTo: [pwBtn, ssoRepeater]
Layout.fillWidth: true Layout.fillWidth: true
ToolTip.text: qsTr("The address that can be used to contact your homeserver's client API.\nExample: https://yourserver.example.com:8787")
enabled: visible
label: qsTr("Homeserver address") label: qsTr("Homeserver address")
placeholderText: qsTr("yourserver.example.com:8787") placeholderText: qsTr("yourserver.example.com: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 your homeserver's client API.\nExample: https://yourserver.example.com:8787")
Keys.forwardTo: [pwBtn, ssoRepeater]
} }
Item { Item {
Layout.preferredHeight: Nheko.avatarSize
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Nheko.avatarSize
Spinner { Spinner {
height: parent.height
anchors.centerIn: parent anchors.centerIn: parent
visible: running
running: login.loggingIn
foreground: palette.mid foreground: palette.mid
height: parent.height
running: login.loggingIn
visible: running
} }
} }
MatrixText { MatrixText {
Layout.fillWidth: true Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: loginPage.error text: loginPage.error
textFormat: Text.PlainText
visible: text visible: text
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
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
@ -161,32 +153,35 @@ Item {
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()
} }
} }

@ -13,208 +13,197 @@ 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
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)
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"
Layout.preferredHeight: 128 Layout.preferredHeight: 128
Layout.preferredWidth: 128 Layout.preferredWidth: 128
source: "qrc:/logos/login.png"
} }
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 {
Layout.preferredHeight: hsLabel.height/2
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
Layout.preferredHeight: hsLabel.height / 2
visible: running
running: regis.lookingUpHs
foreground: palette.mid foreground: palette.mid
running: regis.lookingUpHs
visible: running
} }
} }
MatrixText { MatrixText {
Layout.fillWidth: true Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: regis.hsError text: regis.hsError
textFormat: Text.PlainText
visible: text visible: text
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
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 {
Layout.preferredHeight: usernameLabel.height/2
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
Layout.preferredHeight: usernameLabel.height / 2
visible: running
running: regis.lookingUpUsername
foreground: palette.mid foreground: palette.mid
running: regis.lookingUpUsername
visible: running
} }
Image { Image {
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
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
HoverHandler { HoverHandler {
id: ma id: ma
} }
} }
} }
MatrixText { MatrixText {
Layout.fillWidth: true Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: regis.usernameError text: regis.usernameError
textFormat: Text.PlainText
visible: text && regis.supported visible: text && regis.supported
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
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 {
Layout.fillWidth: true Layout.fillWidth: true
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
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
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 nothing 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 nothing is provided a default is used.") visible: regis.supported
} }
Item { Item {
Layout.preferredHeight: Nheko.avatarSize
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Nheko.avatarSize
Spinner { Spinner {
height: parent.height
anchors.centerIn: parent anchors.centerIn: parent
visible: running
running: regis.registering
foreground: palette.mid foreground: palette.mid
height: parent.height
running: regis.registering
visible: running
} }
} }
MatrixText { MatrixText {
Layout.fillWidth: true Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error color: Nheko.theme.error
text: registrationPage.error text: registrationPage.error
textFormat: Text.PlainText
visible: text visible: text
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
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()
} }
} }

@ -16,6 +16,7 @@ Rectangle {
property int collapsePoint: 600 property int collapsePoint: 600
property bool collapsed: width < collapsePoint property bool collapsed: width < collapsePoint
color: palette.window color: palette.window
ScrollView { ScrollView {
@ -23,87 +24,91 @@ Rectangle {
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
ColumnLayout { ColumnLayout {
id: grid id: grid
spacing: Nheko.paddingMedium
width: scroll.availableWidth
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
width: scroll.availableWidth
Repeater { Repeater {
model: UserSettingsModel model: UserSettingsModel
delegate: GridLayout { delegate: GridLayout {
width: scroll.availableWidth
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
width: scroll.availableWidth
Label { Label {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
color: 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: 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: child.implicitWidth Layout.preferredWidth: child.implicitWidth
Layout.maximumWidth: model.type == UserSettingsModel.SectionTitle ? Number.POSITIVE_INFINITY : 400
Layout.fillWidth: model.type == UserSettingsModel.SectionTitle || model.type == UserSettingsModel.Options || model.type == UserSettingsModel.Number
Layout.rightMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium 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
model: r.model.values
currentIndex: r.model.value currentIndex: r.model.value
implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted
model: r.model.values
width: Math.min(implicitWidth, scroll.availableWidth - Nheko.paddingMedium) width: Math.min(implicitWidth, scroll.availableWidth - Nheko.paddingMedium)
onCurrentIndexChanged: r.model.value = currentIndex onCurrentIndexChanged: r.model.value = currentIndex
implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted
WheelHandler{} // suppress scrolling changing values WheelHandler {
} // suppress scrolling changing values
} }
} }
DelegateChoice { DelegateChoice {
@ -111,14 +116,16 @@ Rectangle {
SpinBox { SpinBox {
anchors.right: parent.right anchors.right: parent.right
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
onValueChanged: model.value = value onValueChanged: model.value = value
editable: true
WheelHandler{} // suppress scrolling changing values WheelHandler {
} // suppress scrolling changing values
} }
} }
DelegateChoice { DelegateChoice {
@ -127,54 +134,56 @@ 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
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)
} }
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: palette.text color: palette.text
text: model.value
readOnly: true readOnly: true
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: palette.buttonText color: palette.buttonText
height: 1 height: 1
} }
@ -182,6 +191,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")
@ -189,34 +199,42 @@ 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()
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.ConfigureHiddenEvents roleValue: UserSettingsModel.ConfigureHiddenEvents
Button { Button {
text: qsTr("CONFIGURE") text: qsTr("CONFIGURE")
onClicked: { onClicked: {
var dialog = hiddenEventsDialog.createObject(); var dialog = hiddenEventsDialog.createObject();
dialog.show(); dialog.show();
@ -226,15 +244,17 @@ Rectangle {
Component { Component {
id: hiddenEventsDialog id: hiddenEventsDialog
HiddenEventsDialog {} HiddenEventsDialog {
}
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: UserSettingsModel.ManageIgnoredUsers roleValue: UserSettingsModel.ManageIgnoredUsers
Button { Button {
text: qsTr("MANAGE") text: qsTr("MANAGE")
onClicked: { onClicked: {
var dialog = ignoredUsersDialog.createObject(); var dialog = ignoredUsersDialog.createObject();
dialog.show(); dialog.show();
@ -244,11 +264,11 @@ Rectangle {
Component { Component {
id: ignoredUsersDialog id: ignoredUsersDialog
IgnoredUsers {} IgnoredUsers {
}
} }
} }
} }
DelegateChoice { DelegateChoice {
Text { Text {
text: model.value text: model.value
@ -259,19 +279,18 @@ 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()
} }
} }

@ -14,86 +14,83 @@ ColumnLayout {
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
} }
Image { Image {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/splash.png"
Layout.preferredHeight: 256 Layout.preferredHeight: 256
Layout.preferredWidth: 256 Layout.preferredWidth: 256
source: "qrc:/logos/splash.png"
} }
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: palette.text color: 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: palette.text color: 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);
} }
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
} }
RowLayout { RowLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingLarge Layout.margins: Nheko.paddingLarge
ToggleButton { ToggleButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.margins: Nheko.paddingLarge
checked: Settings.reducedMotion checked: Settings.reducedMotion
onCheckedChanged: Settings.reducedMotion = checked onCheckedChanged: Settings.reducedMotion = checked
} }
Label { Label {
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.margins: Nheko.paddingLarge Layout.margins: Nheko.paddingLarge
text: qsTr("Reduce animations") ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Nheko uses animations in several places to make stuff pretty. This allows you to turn those off if they make you feel unwell.")
ToolTip.visible: hovered.hovered
color: palette.text color: palette.text
text: qsTr("Reduce animations")
HoverHandler { HoverHandler {
id: hovered id: hovered
} }
ToolTip.visible: hovered.hovered
ToolTip.text: qsTr("Nheko uses animations in several places to make stuff pretty. This allows you to turn those off if they make you feel unwell.")
ToolTip.delay: Nheko.tooltipDelay
} }
} }
Item { Item {

@ -9,42 +9,39 @@ import im.nheko 1.0
Slider { Slider {
id: control id: control
property color progressColor: palette.highlight
property bool alwaysShowSlider: true property bool alwaysShowSlider: true
property color progressColor: 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: palette.buttonText
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: palette.buttonText 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
} }
} }

@ -9,9 +9,9 @@ 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
@ -19,18 +19,13 @@ Item {
PointHandler { PointHandler {
id: ph id: ph
onGrabChanged: (_, point) => {
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
@ -38,15 +33,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"
@ -63,26 +57,30 @@ 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
target: circle duration: ripple.maxRadius / ripple.radiusAnimationRate
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"
@ -93,37 +91,42 @@ Item {
NumberAnimation { NumberAnimation {
id: radius_tail_animation id: radius_tail_animation
target: circle duration: ripple.maxRadius / ripple.radiusTailAnimationRate
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
target: circle duration: ripple.opacityAnimationDuration
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: (_, point) => {
circle.centerX = point.position.x;
circle.centerY = point.position.y;
}
} }
} }

@ -9,11 +9,8 @@ import im.nheko 1.0
Popup { Popup {
id: snackbar id: snackbar
// Workaround palettes not inheriting for popups
palette: timelineRoot.palette
property var messages: []
property string currentMessage: "" property string currentMessage: ""
property var messages: []
function showNotification(msg) { function showNotification(msg) {
messages.push(msg); messages.push(msg);
@ -24,78 +21,79 @@ Popup {
} }
} }
Timer {
id: dismissTimer
interval: 10000
onTriggered: snackbar.close()
}
onAboutToHide: {
messages.shift();
}
onClosed: {
if (messages.length > 0) {
currentMessage = messages[0];
open();
dismissTimer.restart();
}
}
parent: Overlay.overlay
opacity: 0 opacity: 0
y: -100
x: (parent.width - width)/2
padding: Nheko.paddingLarge padding: Nheko.paddingLarge
contentItem: Label { // Workaround palettes not inheriting for popups
color: palette.light palette: timelineRoot.palette
width: Math.max(snackbar.Overlay.overlay? snackbar.Overlay.overlay.width/2 : 0, 400) parent: Overlay.overlay
text: snackbar.currentMessage x: (parent.width - width) / 2
font.bold: true y: -100
}
background: Rectangle { background: Rectangle {
radius: Nheko.paddingLarge
color: palette.dark color: palette.dark
opacity: 0.8 opacity: 0.8
radius: Nheko.paddingLarge
}
contentItem: Label {
color: palette.light
font.bold: true
text: snackbar.currentMessage
width: Math.max(snackbar.Overlay.overlay ? snackbar.Overlay.overlay.width / 2 : 0, 400)
} }
enter: Transition { 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()
}
}

@ -9,15 +9,15 @@ import QtQuick.Effects
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)
@ -25,131 +25,116 @@ Item {
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)
}
} }
MultiEffect { MultiEffect {
anchors.fill: row anchors.fill: row
shadowBlur: 14 shadowBlur: 14
shadowEnabled: true
shadowColor: spinner.foreground shadowColor: spinner.foreground
shadowEnabled: true
source: row source: row
transform: Matrix4x4 { 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) 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)
} }
} }
} }

@ -7,136 +7,134 @@ import QtQuick.Particles 2.15
Item { Item {
id: effectRoot id: effectRoot
readonly property int maxLifespan: Math.max(confettiEmitter.lifeSpan, rainfallEmitter.lifeSpan) readonly property int maxLifespan: Math.max(confettiEmitter.lifeSpan, rainfallEmitter.lifeSpan)
required property bool shouldEffectsRun required property bool shouldEffectsRun
visible: effectRoot.shouldEffectsRun
function pulseConfetti() function pulseConfetti() {
{ confettiEmitter.pulse(effectRoot.height * 2);
confettiEmitter.pulse(effectRoot.height * 2)
} }
function pulseRainfall() {
function pulseRainfall() rainfallEmitter.pulse(effectRoot.height * 3.3);
{
rainfallEmitter.pulse(effectRoot.height * 3.3)
} }
function removeParticles() {
function removeParticles() particleSystem.reset();
{
particleSystem.reset()
} }
visible: effectRoot.shouldEffectsRun
ParticleSystem { ParticleSystem {
id: particleSystem id: particleSystem
Component.onCompleted: stop();
paused: !effectRoot.shouldEffectsRun paused: !effectRoot.shouldEffectsRun
running: effectRoot.shouldEffectsRun running: effectRoot.shouldEffectsRun
}
Component.onCompleted: stop()
}
Emitter { Emitter {
id: confettiEmitter id: confettiEmitter
group: "confetti"
width: effectRoot.width * 3/4
enabled: false
anchors.horizontalCenter: effectRoot.horizontalCenter anchors.horizontalCenter: effectRoot.horizontalCenter
y: effectRoot.height
emitRate: Math.min(400 * Math.sqrt(effectRoot.width * effectRoot.height) / 870, 1000) emitRate: Math.min(400 * Math.sqrt(effectRoot.width * effectRoot.height) / 870, 1000)
enabled: false
group: "confetti"
lifeSpan: 15000 lifeSpan: 15000
system: particleSystem
maximumEmitted: 500 maximumEmitted: 500
velocityFromMovement: 8
size: 16 size: 16
sizeVariation: 4 sizeVariation: 4
system: particleSystem
velocityFromMovement: 8
width: effectRoot.width * 3 / 4
y: effectRoot.height
velocity: PointDirection { velocity: PointDirection {
x: 0 x: 0
y: -Math.min(450 * effectRoot.height / 700, 1000)
xVariation: Math.min(4 * effectRoot.width / 7, 450) xVariation: Math.min(4 * effectRoot.width / 7, 450)
y: -Math.min(450 * effectRoot.height / 700, 1000)
yVariation: 250 yVariation: 250
} }
} }
ImageParticle { ImageParticle {
system: particleSystem color: "white"
colorVariation: 1
entryEffect: ImageParticle.None
groups: ["confetti"] groups: ["confetti"]
source: "qrc:/confettiparticle.svg"
rotationVelocity: 0 rotationVelocity: 0
rotationVelocityVariation: 360 rotationVelocityVariation: 360
colorVariation: 1 source: "qrc:/confettiparticle.svg"
color: "white" system: particleSystem
entryEffect: ImageParticle.None
xVector: PointDirection { xVector: PointDirection {
x: 1 x: 1
y: 0
xVariation: 0.2 xVariation: 0.2
y: 0
yVariation: 0.2 yVariation: 0.2
} }
yVector: PointDirection { yVector: PointDirection {
x: 0 x: 0
y: 0.5
xVariation: 0.2 xVariation: 0.2
y: 0.5
yVariation: 0.2 yVariation: 0.2
} }
} }
Gravity { Gravity {
system: particleSystem
groups: ["confetti"]
anchors.fill: effectRoot anchors.fill: effectRoot
magnitude: 350
angle: 90 angle: 90
groups: ["confetti"]
magnitude: 350
system: particleSystem
} }
Emitter { Emitter {
id: rainfallEmitter id: rainfallEmitter
group: "rain"
width: effectRoot.width
enabled: false
anchors.horizontalCenter: effectRoot.horizontalCenter anchors.horizontalCenter: effectRoot.horizontalCenter
y: -60
emitRate: effectRoot.width / 30 emitRate: effectRoot.width / 30
enabled: false
group: "rain"
lifeSpan: 10000 lifeSpan: 10000
system: particleSystem system: particleSystem
width: effectRoot.width
y: -60
velocity: PointDirection { velocity: PointDirection {
x: 0 x: 0
y: 400
xVariation: 0 xVariation: 0
y: 400
yVariation: 75 yVariation: 75
} }
// causes high CPU load, see: https://bugreports.qt.io/browse/QTBUG-117923 // causes high CPU load, see: https://bugreports.qt.io/browse/QTBUG-117923
//ItemParticle { //ItemParticle {
// system: particleSystem // system: particleSystem
// groups: ["rain"] // groups: ["rain"]
// fade: false // fade: false
// visible: effectRoot.shouldEffectsRun // visible: effectRoot.shouldEffectsRun
// delegate: Rectangle { // delegate: Rectangle {
// width: 2 // width: 2
// height: 30 + 30 * Math.random() // height: 30 + 30 * Math.random()
// radius: 2 // radius: 2
// color: "#0099ff" // color: "#0099ff"
// } // }
//} //}
ImageParticle { ImageParticle {
system: particleSystem color: "#0099ff"
groups: ["rain"] colorVariation: 0
source: "qrc:/confettiparticle.svg" entryEffect: ImageParticle.None
rotationVelocity: 0 groups: ["rain"]
rotationVelocityVariation: 0 rotationVelocity: 0
colorVariation: 0 rotationVelocityVariation: 0
color: "#0099ff" source: "qrc:/confettiparticle.svg"
entryEffect: ImageParticle.None system: particleSystem
xVector: PointDirection {
x: 0.01 xVector: PointDirection {
y: 0 x: 0.01
} y: 0
yVector: PointDirection { }
x: 0 yVector: PointDirection {
y: 5 x: 0
} y: 5
} }
} }
} }
}

@ -5,27 +5,24 @@
import QtQuick import QtQuick
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)
} }
} }

@ -14,27 +14,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;
@ -45,16 +40,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 = palette.alternateBase; var wc = 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
@ -63,41 +67,40 @@ 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: palette.text
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
buttonTextColor: palette.text
image: { image: {
if (control.mediaLoaded) { if (control.mediaLoaded) {
if (control.mediaState == MediaPlayer.PlayingState) if (control.mediaState == MediaPlayer.PlayingState)
@ -108,38 +111,47 @@ 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: palette.text
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24 Layout.preferredWidth: 24
buttonTextColor: 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: volumeSlider.value property real desiredVolume: volumeSlider.value
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 {
volumeSlider.implicitWidth: 100
}
PropertyChanges {
volumeSlider.opacity: 1
}
} }
transitions: [ transitions: [
Transition { Transition {
@ -150,20 +162,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"
@ -173,54 +181,34 @@ 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 {
volumeSlider.implicitWidth: 100
}
PropertyChanges {
volumeSlider.opacity: 1
}
} }
} }
Label { Label {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration)
color: palette.text color: 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
@ -230,13 +218,4 @@ Rectangle {
interval: 1500 //ms interval: 1500 //ms
repeat: false repeat: false
} }
// Fade controls in/out
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
} }

@ -9,51 +9,48 @@ import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
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 {
implicitWidth: Nheko.avatarSize displayName: CallManager.callPartyDisplayName
implicitHeight: Nheko.avatarSize implicitHeight: Nheko.avatarSize
implicitWidth: 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
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 {
@ -63,7 +60,6 @@ Rectangle {
PropertyChanges { PropertyChanges {
callTypeIcon.source: "qrc:/icons/icons/ui/place-call.svg" callTypeIcon.source: "qrc:/icons/icons/ui/place-call.svg"
} }
}, },
State { State {
name: "VIDEO" name: "VIDEO"
@ -72,7 +68,6 @@ Rectangle {
PropertyChanges { PropertyChanges {
callTypeIcon.source: "qrc:/icons/icons/ui/video.svg" callTypeIcon.source: "qrc:/icons/icons/ui/video.svg"
} }
}, },
State { State {
name: "SCREEN" name: "SCREEN"
@ -81,18 +76,15 @@ Rectangle {
PropertyChanges { PropertyChanges {
callTypeIcon.source: "qrc:/icons/icons/ui/screen-share.svg" callTypeIcon.source: "qrc:/icons/icons/ui/screen-share.svg"
} }
} }
] ]
} }
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 {
@ -102,7 +94,6 @@ Rectangle {
PropertyChanges { PropertyChanges {
callStateLabel.text: qsTr("Calling...") callStateLabel.text: qsTr("Calling...")
} }
}, },
State { State {
name: "CONNECTING" name: "CONNECTING"
@ -111,7 +102,6 @@ Rectangle {
PropertyChanges { PropertyChanges {
callStateLabel.text: qsTr("Connecting...") callStateLabel.text: qsTr("Connecting...")
} }
}, },
State { State {
name: "ANSWERSENT" name: "ANSWERSENT"
@ -120,7 +110,6 @@ Rectangle {
PropertyChanges { PropertyChanges {
callStateLabel.text: qsTr("Connecting...") callStateLabel.text: qsTr("Connecting...")
} }
}, },
State { State {
name: "CONNECTED" name: "CONNECTED"
@ -129,15 +118,12 @@ Rectangle {
PropertyChanges { PropertyChanges {
callStateLabel.text: "00:00" callStateLabel.text: "00:00"
} }
PropertyChanges { PropertyChanges {
callTimer.startTime: Math.floor((new Date()).getTime() / 1000) callTimer.startTime: Math.floor((new Date()).getTime() / 1000)
} }
PropertyChanges { PropertyChanges {
stackLayout.currentIndex: CallManager.callType != Voip.VOICE ? 1 : 0 stackLayout.currentIndex: CallManager.callType != Voip.VOICE ? 1 : 0
} }
}, },
State { State {
name: "DISCONNECTED" name: "DISCONNECTED"
@ -152,14 +138,12 @@ Rectangle {
// stackLayout.currentIndex: 0 // stackLayout.currentIndex: 0
//} //}
PropertyChanges { PropertyChanges {
target: stackLayout
currentIndex: 0 // qmllint disable Quick.property-changes-parsed currentIndex: 0 // qmllint disable Quick.property-changes-parsed
target: stackLayout
} }
} }
] ]
} }
Timer { Timer {
id: callTimer id: callTimer
@ -170,8 +154,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);
@ -181,44 +166,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
Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24
ToolTip.text: qsTr("Hide/Show Picture-in-Picture")
ToolTip.visible: hovered
buttonTextColor: "#000000" buttonTextColor: "#000000"
image: ":/icons/icons/ui/picture-in-picture.svg"
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
onClicked: CallManager.toggleLocalPiP() onClicked: CallManager.toggleLocalPiP()
} }
ImageButton { ImageButton {
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 16
Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
Layout.preferredWidth: 24
Layout.rightMargin: 16
ToolTip.text: CallManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic")
ToolTip.visible: hovered
buttonTextColor: "#000000" buttonTextColor: "#000000"
image: CallManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.svg" : ":/icons/icons/ui/microphone-mute.svg"
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")
onClicked: CallManager.toggleMicMute() onClicked: CallManager.toggleMicMute()
} }
} }
} }

@ -9,79 +9,70 @@ import im.nheko 1.0
Popup { Popup {
modal: true modal: true
background: Rectangle {
border.color: palette.windowText
color: 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?" + palette.windowText source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + 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?" + palette.windowText source: "image://colorimage/:/icons/icons/ui/video-call.svg?" + 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: palette.window
border.color: palette.windowText
}
} }

@ -12,51 +12,50 @@ Popup {
id: callInv id: callInv
closePolicy: Popup.NoAutoClose closePolicy: Popup.NoAutoClose
width: parent.width
height: parent.height height: parent.height
width: parent.width
background: Rectangle {
border.color: palette.windowText
color: 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: palette.windowText color: 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
@ -65,20 +64,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 + "?" + palette.windowText source: "image://colorimage/" + image + "?" + 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: palette.windowText color: 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
@ -91,41 +87,34 @@ 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?" + palette.windowText source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + 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?" + palette.windowText source: "image://colorimage/:/icons/icons/ui/video.svg?" + 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
@ -134,9 +123,9 @@ Popup {
function validateMic() { function validateMic() {
if (CallManager.mics.length == 0) { if (CallManager.mics.length == 0) {
var dialog = deviceError.createObject(timelineRoot, { var dialog = deviceError.createObject(timelineRoot, {
"errorString": qsTr("No microphone found."), "errorString": qsTr("No microphone found."),
"image": ":/icons/icons/ui/place-call.svg" "image": ":/icons/icons/ui/place-call.svg"
}); });
dialog.open(); dialog.open();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
return false; return false;
@ -148,60 +137,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.rejectInvite();
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.rejectInvite();
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
implicitWidth: buttonLayout.buttonSize
background: Rectangle {
color: "#00ff00"
radius: buttonLayout.buttonSize / 2
}
contentItem: Image {
source: "image://colorimage/" + acceptButton.image + "?#ffffff"
}
onClicked: { onClicked: {
if (buttonLayout.validateMic()) { if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText; Settings.microphone = micCombo.currentText;
if (cameraCombo.visible) if (cameraCombo.visible)
Settings.camera = cameraCombo.currentText; Settings.camera = cameraCombo.currentText;
CallManager.acceptInvite(); CallManager.acceptInvite();
close(); close();
} }
} }
background: Rectangle {
radius: buttonLayout.buttonSize / 2
color: "#00ff00"
}
contentItem: Image {
source: "image://colorimage/" + acceptButton.image + "?#ffffff"
}
} }
} }
} }
background: Rectangle {
color: palette.window
border.color: palette.windowText
}
} }

@ -9,127 +9,118 @@ import QtQuick.Layouts
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 {
implicitWidth: Nheko.avatarSize displayName: CallManager.callPartyDisplayName
implicitHeight: Nheko.avatarSize implicitHeight: Nheko.avatarSize
implicitWidth: 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
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.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg" source: CallManager.callType == Voip.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.preferredWidth: 20
Layout.preferredHeight: 20 Layout.preferredHeight: 20
Layout.preferredWidth: 20
Layout.rightMargin: 16
ToolTip.text: qsTr("Devices")
ToolTip.visible: hovered
buttonTextColor: "#000000" buttonTextColor: "#000000"
image: ":/icons/icons/ui/settings.svg"
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered image: ":/icons/icons/ui/settings.svg"
ToolTip.text: qsTr("Devices")
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") 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, {
"errorString": qsTr("No microphone found."), "errorString": qsTr("No microphone found."),
"image": ":/icons/icons/ui/place-call.svg" "image": ":/icons/icons/ui/place-call.svg"
}); });
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),
"image": ":/icons/icons/ui/place-call.svg" "image": ":/icons/icons/ui/place-call.svg"
}); });
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, {
"errorString": qsTr("Unknown camera: %1").arg(Settings.camera), "errorString": qsTr("Unknown camera: %1").arg(Settings.camera),
"image": ":/icons/icons/ui/video.svg" "image": ":/icons/icons/ui/video.svg"
}); });
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") text: qsTr("Decline")
onClicked: { onClicked: {
CallManager.rejectInvite(); CallManager.rejectInvite();
} }
} }
} }
} }

@ -8,35 +8,33 @@ import QtQuick.Layouts 1.2
Popup { Popup {
id: r id: r
property string errorString property string errorString
property var image property var image
modal: true modal: true
background: Rectangle {
border.color: palette.windowText
color: 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/" + r.image + "?" + palette.windowText source: "image://colorimage/" + r.image + "?" + palette.windowText
} }
Label { Label {
text: r.errorString
color: palette.windowText color: palette.windowText
text: r.errorString
} }
}
background: Rectangle {
color: palette.window
border.color: palette.windowText
} }
} }

@ -10,12 +10,17 @@ import im.nheko 1.0
Popup { Popup {
modal: true modal: true
background: Rectangle {
border.color: palette.windowText
color: 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;
} }
Component { Component {
@ -23,38 +28,33 @@ Popup {
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: palette.windowText color: 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, {
"errorString": qsTr("No microphone found."), "errorString": qsTr("No microphone found."),
"image": ":/icons/icons/ui/place-call.svg" "image": ":/icons/icons/ui/place-call.svg"
}); });
dialog.open(); dialog.open();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
return false; return false;
@ -66,18 +66,19 @@ Popup {
Layout.rightMargin: 8 Layout.rightMargin: 8
Avatar { Avatar {
Layout.rightMargin: cameraCombo.visible ? 16 : 64
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize Layout.preferredHeight: Nheko.avatarSize
url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") Layout.preferredWidth: Nheko.avatarSize
Layout.rightMargin: cameraCombo.visible ? 16 : 64
displayName: room.roomName displayName: room.roomName
roomid: room.roomId roomid: room.roomId
url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
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;
@ -86,11 +87,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;
@ -100,15 +101,14 @@ Popup {
} }
} }
} }
Button { Button {
text: qsTr("Screen")
icon.source: "qrc:/icons/icons/ui/screen-share.svg" icon.source: "qrc:/icons/icons/ui/screen-share.svg"
text: qsTr("Screen")
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);
@ -116,67 +116,52 @@ 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?" + palette.windowText source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + 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?" + palette.windowText source: "image://colorimage/:/icons/icons/ui/video.svg?" + palette.windowText
} }
ComboBox { ComboBox {
id: cameraCombo id: cameraCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.cameras model: CallManager.cameras
} }
} }
} }
}
background: Rectangle {
color: palette.window
border.color: palette.windowText
} }
} }

@ -9,9 +9,13 @@ import QtQuick.Layouts
import im.nheko import im.nheko
Popup { Popup {
anchors.centerIn: parent
modal: true modal: true
anchors.centerIn: parent; background: Rectangle {
border.color: palette.windowText
color: palette.window
}
Component.onCompleted: { Component.onCompleted: {
frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate); frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate);
@ -22,176 +26,151 @@ Popup {
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: palette.windowText color: 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("Method:") color: palette.windowText
color: palette.windowText text: qsTr("Method:")
} }
ComboBox {
id: screenshareType
ComboBox { Layout.fillWidth: true
id: screenshareType model: CallManager.screenShareTypeList()
Layout.fillWidth: true onCurrentIndexChanged: CallManager.setScreenShareType(currentIndex)
model: CallManager.screenShareTypeList() }
onCurrentIndexChanged: CallManager.setScreenShareType(currentIndex);
}
} }
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: palette.windowText color: palette.windowText
text: qsTr("Window:")
} }
ComboBox { ComboBox {
visible: CallManager.screenShareType == Voip.X11
id: windowCombo id: windowCombo
Layout.fillWidth: true Layout.fillWidth: true
model: CallManager.windowList() model: CallManager.windowList()
visible: CallManager.screenShareType == Voip.X11
} }
Button { Button {
visible: CallManager.screenShareType == Voip.XDP
highlighted: !CallManager.screenShareReady highlighted: !CallManager.screenShareReady
text: qsTr("Request screencast") text: qsTr("Request screencast")
visible: CallManager.screenShareType == Voip.XDP
onClicked: { onClicked: {
Settings.screenShareHideCursor = hideCursorCheckBox.checked; Settings.screenShareHideCursor = hideCursorCheckBox.checked;
CallManager.setupScreenShareXDP(); CallManager.setupScreenShareXDP();
} }
} }
} }
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: palette.windowText color: 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 {
visible: CallManager.screenShareReady
text: qsTr("Share")
icon.source: "qrc:/icons/icons/ui/screen-share.svg" icon.source: "qrc:/icons/icons/ui/screen-share.svg"
text: qsTr("Share")
visible: CallManager.screenShareReady
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 {
visible: CallManager.screenShareReady
text: qsTr("Preview") text: qsTr("Preview")
visible: CallManager.screenShareReady
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: palette.window
border.color: palette.windowText
}
} }

Loading…
Cancel
Save