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. 187
      resources/qml/TimelineBubbleMessageStyle.qml
  12. 163
      resources/qml/TimelineDefaultMessageStyle.qml
  13. 12
      resources/qml/TimelineMetadata.qml
  14. 35
      resources/qml/TimelineSectionHeader.qml
  15. 8
      resources/qml/TimelineView.qml
  16. 4
      resources/qml/TopBar.qml
  17. 125
      resources/qml/components/AdaptiveLayout.qml
  18. 8
      resources/qml/components/AdaptiveLayoutElement.qml
  19. 50
      resources/qml/components/AvatarListTile.qml
  20. 50
      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. 13
      resources/qml/components/PowerlevelIndicator.qml
  25. 115
      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. 47
      resources/qml/device-verification/EmojiVerification.qml
  44. 17
      resources/qml/device-verification/Failed.qml
  45. 18
      resources/qml/device-verification/NewVerificationRequest.qml
  46. 16
      resources/qml/device-verification/Success.qml
  47. 22
      resources/qml/device-verification/Waiting.qml
  48. 108
      resources/qml/dialogs/AliasEditor.qml
  49. 101
      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. 74
      resources/qml/dialogs/ImageOverlay.qml
  58. 226
      resources/qml/dialogs/ImagePackEditorDialog.qml
  59. 181
      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. 316
      resources/qml/dialogs/PowerLevelEditor.qml
  67. 103
      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. 298
      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. 88
      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. 89
      resources/qml/voip/CallInvite.qml
  91. 47
      resources/qml/voip/CallInviteBar.qml
  92. 20
      resources/qml/voip/DeviceError.qml
  93. 65
      resources/qml/voip/PlaceCall.qml
  94. 79
      resources/qml/voip/ScreenShare.qml

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -13,64 +13,16 @@ Container {
id: container
property bool singlePageMode: width < 800
property int splitterGrabMargin: Nheko.paddingSmall
property alias pageIndex: view.currentIndex
property Component handle
property Component handleToucharea
onSinglePageModeChanged: if (!singlePageMode) pageIndex = 0
Component.onCompleted: {
for (var i = 0; i < count - 1; i++) {
let handle_ = handle.createObject(contentChildren[i]);
let split_ = handleToucharea.createObject(contentChildren[i]);
contentChildren[i].width = Qt.binding(function() {
return split_.calculatedWidth;
});
contentChildren[i].splitterWidth = Qt.binding(function() {
return handle_.width;
});
}
contentChildren[count - 1].width = Qt.binding(function() {
if (container.singlePageMode) {
return container.width;
} else {
var w = container.width;
for (var i = 0; i < count - 1; i++) {
if (contentChildren[i].width)
w = w - contentChildren[i].width;
}
return w;
}
});
contentChildren[count - 1].splitterWidth = 0;
for (var i = 0; i < count; i++) {
contentChildren[i].height = Qt.binding(function() {
return container.height;
});
contentChildren[i].children[0].height = Qt.binding(function() {
return container.height;
});
}
}
handle: Rectangle {
z: 3
property Component handle: Rectangle {
anchors.right: parent.right
color: Nheko.theme.separator
height: container.height
width: visible ? 1 : 0
anchors.right: parent.right
z: 3
}
handleToucharea: Item {
property Component handleToucharea: Item {
id: splitter
property int minimumWidth: parent.minimumWidth
property int maximumWidth: parent.maximumWidth
property int collapsedWidth: parent.collapsedWidth
property bool collapsible: parent.collapsible
property int calculatedWidth: {
if (!visible)
return 0;
@ -79,6 +31,10 @@ Container {
else
return (collapsible && x < minimumWidth) ? collapsedWidth : x;
}
property int collapsedWidth: parent.collapsedWidth
property bool collapsible: parent.collapsible
property int maximumWidth: parent.maximumWidth
property int minimumWidth: parent.minimumWidth
enabled: !container.singlePageMode
height: container.height
@ -87,49 +43,84 @@ Container {
z: 3
NhekoCursorShape {
cursorShape: Qt.SizeHorCursor
height: parent.height
width: container.splitterGrabMargin * 2
x: -container.splitterGrabMargin
cursorShape: Qt.SizeHorCursor
}
DragHandler {
id: dragHandler
enabled: !container.singlePageMode
grabPermissions: PointerHandler.CanTakeOverFromAnything | PointerHandler.ApprovesTakeOverByHandlersOfSameType
margin: container.splitterGrabMargin
xAxis.enabled: true
yAxis.enabled: false
xAxis.minimum: splitter.minimumWidth - 1
xAxis.maximum: splitter.maximumWidth
margin: container.splitterGrabMargin
grabPermissions: PointerHandler.CanTakeOverFromAnything | PointerHandler.ApprovesTakeOverByHandlersOfSameType
xAxis.minimum: splitter.minimumWidth - 1
yAxis.enabled: false
onActiveChanged: {
if (!active) {
splitter.x = splitter.calculatedWidth;
splitter.parent.preferredWidth = splitter.calculatedWidth;
}
}
}
HoverHandler {
enabled: !container.singlePageMode
margin: container.splitterGrabMargin
}
}
property alias pageIndex: view.currentIndex
property bool singlePageMode: width < 800
property int splitterGrabMargin: Nheko.paddingSmall
contentItem: ListView {
id: view
model: container.contentModel
snapMode: ListView.SnapOneItem
orientation: ListView.Horizontal
boundsBehavior: Flickable.StopAtBounds
currentIndex: container.singlePageMode ? container.pageIndex : 0
highlightMoveDuration: (container.singlePageMode && !Settings.reducedMotion) ? 200 : 0
highlightRangeMode: ListView.StrictlyEnforceRange
interactive: singlePageMode
highlightMoveDuration: (container.singlePageMode && !Settings.reducedMotion) ? 200 : 0
currentIndex: container.singlePageMode ? container.pageIndex : 0
boundsBehavior: Flickable.StopAtBounds
model: container.contentModel
orientation: ListView.Horizontal
snapMode: ListView.SnapOneItem
}
Component.onCompleted: {
for (var i = 0; i < count - 1; i++) {
let handle_ = handle.createObject(contentChildren[i]);
let split_ = handleToucharea.createObject(contentChildren[i]);
contentChildren[i].width = Qt.binding(function () {
return split_.calculatedWidth;
});
contentChildren[i].splitterWidth = Qt.binding(function () {
return handle_.width;
});
}
contentChildren[count - 1].width = Qt.binding(function () {
if (container.singlePageMode) {
return container.width;
} else {
var w = container.width;
for (var i = 0; i < count - 1; i++) {
if (contentChildren[i].width)
w = w - contentChildren[i].width;
}
return w;
}
});
contentChildren[count - 1].splitterWidth = 0;
for (var i = 0; i < count; i++) {
contentChildren[i].height = Qt.binding(function () {
return container.height;
});
contentChildren[i].children[0].height = Qt.binding(function () {
return container.height;
});
}
}
onSinglePageModeChanged: if (!singlePageMode)
pageIndex = 0
}

@ -5,13 +5,13 @@
import QtQuick
Item {
property int minimumWidth: 100
property int maximumWidth: 400
property bool collapsed: width < minimumWidth
property int collapsedWidth: 40
property bool collapsible: true
property bool collapsed: width < minimumWidth
property int splitterWidth: 1
property int maximumWidth: 400
property int minimumWidth: 100
property int preferredWidth: 100
property int splitterWidth: 1
Component.onCompleted: {
children[0].width = Qt.binding(() => {

@ -10,25 +10,26 @@ import im.nheko
Rectangle {
id: tile
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
required property string avatarUrl
property color background: palette.window
property color importantText: palette.text
property color unimportantText: palette.buttonText
property color bubbleBackground: palette.highlight
property color bubbleText: palette.highlightedText
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
required property string avatarUrl
required property string title
required property string subtitle
required property int index
required property int selectedIndex
property bool crop: true
property color importantText: palette.text
required property int index
property alias roomid: avatar.roomid
required property int selectedIndex
required property string subtitle
required property string title
property color unimportantText: palette.buttonText
property alias userid: avatar.userid
color: background
height: avatarSize + 2 * Nheko.paddingMedium
width: ListView.view.width
state: "normal"
width: ListView.view.width
states: [
State {
name: "highlight"
@ -37,13 +38,12 @@ Rectangle {
PropertyChanges {
tile {
background: palette.dark
importantText: palette.brightText
unimportantText: palette.brightText
bubbleBackground: palette.highlight
bubbleText: palette.highlightedText
importantText: palette.brightText
unimportantText: palette.brightText
}
}
},
State {
name: "selected"
@ -52,37 +52,35 @@ Rectangle {
PropertyChanges {
tile {
background: palette.highlight
importantText: palette.highlightedText
unimportantText: palette.highlightedText
bubbleBackground: palette.highlightedText
bubbleText: palette.highlight
importantText: palette.highlightedText
unimportantText: palette.highlightedText
}
}
}
]
HoverHandler {
id: hovered
}
}
RowLayout {
spacing: Nheko.paddingMedium
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Avatar {
id: avatar
enabled: false
Layout.alignment: Qt.AlignVCenter
crop: tile.crop
displayName: title
enabled: false
implicitHeight: avatarSize
implicitWidth: avatarSize
url: tile.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: title
crop: tile.crop
}
ColumnLayout {
id: textContent
@ -103,33 +101,25 @@ Rectangle {
fullText: title
textFormat: Text.PlainText
}
Item {
Layout.fillWidth: true
}
}
RowLayout {
Layout.fillWidth: true
spacing: 0
ElidedLabel {
color: tile.unimportantText
font.pixelSize: fontMetrics.font.pixelSize * 0.9
elideWidth: textContent.width - Nheko.paddingSmall
font.pixelSize: fontMetrics.font.pixelSize * 0.9
fullText: subtitle
textFormat: Text.PlainText
}
Item {
Layout.fillWidth: true
}
}
}
}
}

@ -12,53 +12,51 @@ import im.nheko
Button {
id: control
property string iconImage: ""
hoverEnabled: true
implicitHeight: Math.ceil(control.contentItem.implicitHeight * 1.70)
implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight)
hoverEnabled: true
property string iconImage: ""
MultiEffect {
anchors.fill: control.background
shadowHorizontalOffset: 3
shadowVerticalOffset: 3
shadowBlur: 8
shadowEnabled: true
shadowColor: "#80000000"
source: control.background
background: Rectangle {
color: Qt.lighter(palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
//height: control.contentItem.implicitHeight * 2
//width: control.contentItem.implicitWidth * 2
radius: height / 8
}
contentItem: RowLayout {
spacing: 0
anchors.centerIn: parent
spacing: 0
Image {
Layout.leftMargin: Nheko.paddingMedium
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.leftMargin: Nheko.paddingMedium
Layout.preferredHeight: fontMetrics.font.pixelSize * 1.5
Layout.preferredWidth: fontMetrics.font.pixelSize * 1.5
visible: !!iconImage
source: iconImage
visible: !!iconImage
}
Text {
Layout.alignment: Qt.AlignHCenter
text: control.text
//font.capitalization: Font.AllUppercase
color: palette.light
elide: Text.ElideRight
//font: control.font
font.capitalization: Font.AllUppercase
font.pointSize: Math.ceil(fontMetrics.font.pointSize * 1.5)
//font.capitalization: Font.AllUppercase
color: palette.light
horizontalAlignment: Text.AlignHCenter
text: control.text
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
background: Rectangle {
//height: control.contentItem.implicitHeight * 2
//width: control.contentItem.implicitWidth * 2
radius: height / 8
color: Qt.lighter(palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
MultiEffect {
anchors.fill: control.background
shadowBlur: 8
shadowColor: "#80000000"
shadowEnabled: true
shadowHorizontalOffset: 3
shadowVerticalOffset: 3
source: control.background
}
}

@ -10,30 +10,29 @@ Dialog {
default property alias inner: scroll.data
property int useableWidth: scroll.width - scroll.ScrollBar.vertical.width
parent: Overlay.overlay
anchors.centerIn: parent
closePolicy: Popup.NoAutoClose
height: (Math.floor(parent.height / 2) - Nheko.paddingLarge) * 2
width: (Math.floor(parent.width / 2) - Nheko.paddingLarge) * 2
padding: 0
modal: true
padding: 0
parent: Overlay.overlay
standardButtons: Dialog.Ok | Dialog.Cancel
closePolicy: Popup.NoAutoClose
width: (Math.floor(parent.width / 2) - Nheko.paddingLarge) * 2
background: Rectangle {
border.color: Nheko.theme.separator
border.width: 1
color: palette.window
radius: Nheko.paddingSmall
}
contentChildren: [
ScrollView {
id: scroll
clip: true
anchors.fill: parent
ScrollBar.horizontal.visible: false
ScrollBar.vertical.visible: true
anchors.fill: parent
clip: true
}
]
background: Rectangle {
color: palette.window
border.color: Nheko.theme.separator
border.width: 1
radius: Nheko.paddingSmall
}
}

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

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

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

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

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

@ -10,37 +10,34 @@ import im.nheko 1.0 // for cursor shape
AbstractButton {
id: button
property color buttonTextColor: palette.buttonText
property alias cursor: mouseArea.cursorShape
property color highlightColor: palette.highlight
property color buttonTextColor: palette.buttonText
focusPolicy: Qt.NoFocus
width: buttonText.implicitWidth
height: buttonText.implicitHeight
implicitWidth: buttonText.implicitWidth
implicitHeight: buttonText.implicitHeight
implicitWidth: buttonText.implicitWidth
width: buttonText.implicitWidth
Label {
id: buttonText
anchors.centerIn: parent
padding: 0
text: button.text
color: button.hovered ? highlightColor : buttonTextColor
font: button.font
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
padding: 0
text: button.text
verticalAlignment: Text.AlignVCenter
}
NhekoCursorShape {
id: mouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
Ripple {
color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5)
}
}

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

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

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

@ -13,15 +13,19 @@ Control {
required property string eventId
required property string filename
required property string filesize
padding: Settings.bubbles? 8 : 12
property bool fitsMetadata: false
//Layout.preferredHeight: rowa.implicitHeight + padding
//Layout.maximumWidth: rowa.Layout.maximumWidth + metadataWidth + padding
property int metadataWidth: 0
property bool fitsMetadata: false
Layout.maximumWidth: rowa.Layout.maximumWidth + padding * 2
padding: Settings.bubbles ? 8 : 12
background: Rectangle {
color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
visible: !Settings.bubbles // the bubble in a bubble looks odd
}
contentItem: RowLayout {
id: rowa
@ -30,36 +34,32 @@ Control {
Rectangle {
id: button
color: palette.light
radius: 22
Layout.preferredHeight: 44
Layout.preferredWidth: 44
color: palette.light
radius: 22
Image {
id: img
anchors.centerIn: parent
fillMode: Image.Pad
height: 40
width: 40
source: "qrc:/icons/icons/ui/download.svg"
sourceSize.height: 40
sourceSize.width: 40
anchors.centerIn: parent
source: "qrc:/icons/icons/ui/download.svg"
fillMode: Image.Pad
width: 40
}
TapHandler {
onSingleTapped: room.saveMedia(eventId)
gesturePolicy: TapHandler.ReleaseWithinBounds
}
onSingleTapped: room.saveMedia(eventId)
}
NhekoCursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
}
ColumnLayout {
id: col
@ -68,31 +68,21 @@ Control {
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1
color: palette.text
elide: Text.ElideRight
text: evRoot.filename
textFormat: Text.PlainText
elide: Text.ElideRight
color: palette.text
}
Text {
id: filesize_
Layout.fillWidth: true
Layout.maximumWidth: implicitWidth + 1
color: palette.text
elide: Text.ElideRight
text: evRoot.filesize
textFormat: Text.PlainText
elide: Text.ElideRight
color: palette.text
}
}
}
background: Rectangle {
color: palette.alternateBase
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
visible: !Settings.bubbles // the bubble in a bubble looks odd
}
}

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

@ -5,11 +5,11 @@
import QtQuick 2.5
import im.nheko 1.0
TextMessage {
property bool isStateEvent
font.italic: true
color: palette.buttonText
font.pointSize: isStateEvent? 0.8*Settings.fontSize : Settings.fontSize
horizontalAlignment: isStateEvent? Text.AlignHCenter : undefined
font.italic: true
font.pointSize: isStateEvent ? 0.8 * Settings.fontSize : Settings.fontSize
horizontalAlignment: isStateEvent ? Text.AlignHCenter : undefined
}

@ -7,14 +7,14 @@ import QtQuick.Controls 2.1
Label {
property bool isStateEvent
color: palette.text
horizontalAlignment: Text.AlignHCenter
height: Math.round(fontMetrics.height * 1.4)
horizontalAlignment: Text.AlignHCenter
width: contentWidth * 1.2
background: Rectangle {
radius: parent.height / 2
color: palette.alternateBase
radius: parent.height / 2
}
}

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

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

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

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

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

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

@ -12,59 +12,56 @@ ColumnLayout {
spacing: 16
Label {
Layout.preferredWidth: 400
Layout.fillWidth: true
wrapMode: Text.Wrap
text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!")
Layout.preferredWidth: 400
color: palette.text
text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!")
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
}
Item { Layout.fillHeight: true; }
RowLayout {
Layout.alignment: Qt.AlignHCenter
Label {
color: palette.text
font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[0]
color: palette.text
}
Label {
color: palette.text
font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[1]
color: palette.text
}
Label {
color: palette.text
font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[2]
color: palette.text
}
}
Item { Layout.fillHeight: true; }
Item {
Layout.fillHeight: true
}
RowLayout {
Button {
Layout.alignment: Qt.AlignLeft
text: qsTr("They do not match!")
onClicked: {
flow.cancel();
dialog.close();
}
}
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("They match!")
onClicked: flow.next()
}
}
}

@ -8,9 +8,9 @@ import QtQuick.Layouts
Rectangle {
color: "red"
height: Qt.application.font.pixelSize * 4
implicitHeight: Qt.application.font.pixelSize * 4
implicitWidth: col.width
height: Qt.application.font.pixelSize * 4
width: col.width
ColumnLayout {
@ -21,17 +21,14 @@ Rectangle {
anchors.bottom: parent.bottom
Label {
Layout.preferredHeight: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter
text: col.emoji.emoji
Layout.preferredHeight: font.pixelSize * 2
font.pixelSize: Qt.application.font.pixelSize * 2
text: col.emoji.emoji
}
Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
text: col.emoji.description
}
}
}

@ -13,15 +13,16 @@ ColumnLayout {
spacing: 16
Label {
Layout.preferredWidth: 400
Layout.fillWidth: true
wrapMode: Text.Wrap
text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
Layout.preferredWidth: 400
color: palette.text
text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
}
Item { Layout.fillHeight: true; }
RowLayout {
id: emojis
@ -370,58 +371,52 @@ ColumnLayout {
Label {
//height: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter
text: col.emoji.emoji
font.pixelSize: Qt.application.font.pixelSize * 2
font.family: Settings.emojiFont
color: palette.text
font.family: Settings.emojiFont
font.pixelSize: Qt.application.font.pixelSize * 2
text: col.emoji.emoji
}
Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
text: col.emoji.description
color: palette.text
text: col.emoji.description
}
}
}
}
}
Item { Layout.fillHeight: true; }
Item {
Layout.fillHeight: true
}
Label {
Layout.preferredWidth: 400
Layout.fillWidth: true
wrapMode: Text.Wrap
text: qsTr("The displayed emoji might look different in different clients if a different font is used. Similarly they might be translated into different languages. Nonetheless they should depict one of 64 different objects or animals. For example a lion and a cat are different, but a cat is the same even if one client just shows a cat face, while another client shows a full cat body.")
Layout.preferredWidth: 400
color: palette.text
text: qsTr("The displayed emoji might look different in different clients if a different font is used. Similarly they might be translated into different languages. Nonetheless they should depict one of 64 different objects or animals. For example a lion and a cat are different, but a cat is the same even if one client just shows a cat face, while another client shows a full cat body.")
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
}
Item { Layout.fillHeight: true; }
RowLayout {
Button {
Layout.alignment: Qt.AlignLeft
text: qsTr("They do not match!")
onClicked: {
flow.cancel();
dialog.close();
}
}
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("They match!")
onClicked: flow.next()
}
}
}

@ -9,14 +9,15 @@ import im.nheko 1.0
ColumnLayout {
property string title: qsTr("Verification failed")
spacing: 16
Text {
id: content
Layout.preferredWidth: 400
Layout.fillWidth: true
wrapMode: Text.Wrap
Layout.preferredWidth: 400
color: palette.text
text: {
switch (flow.error) {
case DeviceVerificationFlow.UnknownMethod:
@ -35,23 +36,21 @@ ColumnLayout {
return qsTr("Unknown verification error.");
}
}
color: palette.text
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
}
Item { Layout.fillHeight: true; }
RowLayout {
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("Close")
onClicked: dialog.close()
}
}
}

@ -12,11 +12,11 @@ ColumnLayout {
spacing: 16
Label {
Layout.fillWidth: true
// Self verification
Layout.preferredWidth: 400
Layout.fillWidth: true
wrapMode: Text.Wrap
color: palette.text
text: {
if (flow.sender) {
if (flow.isSelfVerification)
@ -35,32 +35,30 @@ ColumnLayout {
return qsTr("Your device (%1) has requested to be verified.").arg(flow.deviceId);
}
}
color: palette.text
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
}
Item { Layout.fillHeight: true; }
RowLayout {
Button {
Layout.alignment: Qt.AlignLeft
text: flow.sender ? qsTr("Cancel") : qsTr("Deny")
onClicked: {
flow.cancel();
dialog.close();
}
}
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
text: flow.sender ? qsTr("Start verification") : qsTr("Accept")
onClicked: flow.next()
}
}
}

@ -14,27 +14,25 @@ ColumnLayout {
Label {
id: content
Layout.preferredWidth: 400
Layout.fillWidth: true
wrapMode: Text.Wrap
text: qsTr("Verification successful! Both sides verified their devices!")
Layout.preferredWidth: 400
color: palette.text
text: qsTr("Verification successful! Both sides verified their devices!")
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
}
Item { Layout.fillHeight: true; }
RowLayout {
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("Close")
onClicked: dialog.close()
}
}
}

@ -10,14 +10,15 @@ import im.nheko 1.0
ColumnLayout {
property string title: qsTr("Waiting for other party…")
spacing: 16
Label {
id: content
Layout.preferredWidth: 400
Layout.fillWidth: true
wrapMode: Text.Wrap
Layout.preferredWidth: 400
color: palette.text
text: {
switch (flow.state) {
case "WaitingForOtherToAccept":
@ -30,32 +31,31 @@ ColumnLayout {
return "";
}
}
color: palette.text
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
}
Item { Layout.fillHeight: true; }
Spinner {
Layout.alignment: Qt.AlignHCenter
foreground: palette.mid
}
Item { Layout.fillHeight: true; }
Item {
Layout.fillHeight: true
}
RowLayout {
Button {
Layout.alignment: Qt.AlignLeft
text: qsTr("Cancel")
onClicked: {
flow.cancel();
dialog.close();
}
}
Item {
Layout.fillWidth: true
}
}
}

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

@ -14,47 +14,54 @@ ApplicationWindow {
property var roomSettings
minimumWidth: 340
minimumHeight: 450
width: 450
height: 680
color: palette.window
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 680
minimumHeight: 450
minimumWidth: 340
modality: Qt.NonModal
title: qsTr("Allowed rooms settings")
width: 450
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
roomSettings.applyAllowedFromModel();
allowedDialog.close();
}
onRejected: allowedDialog.close()
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: roomSettingsDialog.close()
}
ColumnLayout {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: 0
MatrixText {
text: qsTr("List of rooms that allow access to this room. Anyone who is in any of those rooms can join this room.")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.bottomMargin: Nheko.paddingMedium
Layout.fillHeight: false
Layout.fillWidth: true
color: palette.text
Layout.bottomMargin: Nheko.paddingMedium
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("List of rooms that allow access to this room. Anyone who is in any of those rooms can join this room.")
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
id: view
Layout.fillHeight: true
Layout.fillWidth: true
cacheBuffer: 50
clip: true
model: roomSettings.allowedRoomsModel
spacing: 4
cacheBuffer: 50
delegate: RowLayout {
anchors.left: parent.left
@ -62,63 +69,57 @@ ApplicationWindow {
ColumnLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
text: model.name
color: palette.text
text: model.name
textFormat: Text.PlainText
}
Text {
Layout.fillWidth: true
text: model.isParent ? qsTr("Parent community") : qsTr("Other room")
color: palette.buttonText
text: model.isParent ? qsTr("Parent community") : qsTr("Other room")
textFormat: Text.PlainText
}
}
ToggleButton {
checked: model.allowed
Layout.alignment: Qt.AlignRight
checked: model.allowed
onCheckedChanged: model.allowed = checked
}
}
}
Column{
Column {
id: roomEntryCompleter
Layout.fillWidth: true
Layout.fillWidth: true
spacing: 1
z: 5
Completer {
id: roomCompleter
visible: roomEntry.text.length > 0
width: parent.width
roomId: allowedDialog.roomSettings.roomId
completerName: "room"
bottomToTop: true
fullWidth: true
avatarHeight: Nheko.avatarSize / 2
avatarWidth: Nheko.avatarSize / 2
bottomToTop: true
centerRowContent: false
completerName: "room"
fullWidth: true
roomId: allowedDialog.roomSettings.roomId
rowMargin: 2
rowSpacing: 2
visible: roomEntry.text.length > 0
width: parent.width
}
MatrixTextField {
id: roomEntry
width: parent.width
color: palette.text
placeholderText: qsTr("Enter additional rooms not in the list yet...")
width: parent.width
color: palette.text
onTextEdited: {
roomCompleter.completer.searchString = text;
}
Keys.onPressed: {
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
event.accepted = true;
@ -134,37 +135,23 @@ ApplicationWindow {
event.accepted = true;
}
}
onTextEdited: {
roomCompleter.completer.searchString = text;
}
}
}
Connections {
function onCompletionSelected(id) {
console.log("selected: " + id);
roomSettings.allowedRoomsModel.addRoom(id);
roomEntry.clear();
}
function onCountChanged() {
if (roomCompleter.count > 0 && (roomCompleter.currentIndex < 0 || roomCompleter.currentIndex >= roomCompleter.count))
roomCompleter.currentIndex = 0;
}
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
title: summary.isSpace ? qsTr("Confirm community join") : qsTr("Confirm room join")
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
color: palette.window
width: 350
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: content.implicitHeight + Nheko.paddingLarge + footer.implicitHeight
modality: Qt.WindowModal
title: summary.isSpace ? qsTr("Confirm community join") : qsTr("Confirm room join")
width: 350
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Cancel
onAccepted: {
summary.reason = reason.text;
summary.join();
joinRoomRoot.close();
}
onRejected: {
joinRoomRoot.close();
}
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: input.text.match("#.+?:.{3,}")
text: summary.isKnockOnly ? qsTr("Knock") : qsTr("Join")
}
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: dbb.rejected()
}
ColumnLayout {
id: content
spacing: Nheko.paddingMedium
anchors.margins: Nheko.paddingMedium
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Avatar {
Layout.topMargin: Nheko.paddingMedium
url: summary.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
roomid: summary.roomid
displayName: summary.roomName
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 130
Layout.preferredWidth: 130
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Nheko.paddingMedium
displayName: summary.roomName
roomid: summary.roomid
url: summary.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
}
Spinner {
Layout.alignment: Qt.AlignHCenter
visible: !summary.isLoaded
foreground: palette.mid
running: !summary.isLoaded
visible: !summary.isLoaded
}
TextEdit {
readOnly: true
textFormat: TextEdit.RichText
text: summary.roomName
font.pixelSize: fontMetrics.font.pixelSize * 2
color: palette.text
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: palette.text
font.pixelSize: fontMetrics.font.pixelSize * 2
horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap
readOnly: true
selectByMouse: true
text: summary.roomName
textFormat: TextEdit.RichText
wrapMode: TextEdit.Wrap
}
TextEdit {
readOnly: true
textFormat: TextEdit.RichText
text: summary.roomid
font.pixelSize: fontMetrics.font.pixelSize * 0.8
color: palette.text
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: palette.text
font.pixelSize: fontMetrics.font.pixelSize * 0.8
horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap
readOnly: true
selectByMouse: true
text: summary.roomid
textFormat: TextEdit.RichText
wrapMode: TextEdit.Wrap
}
RowLayout {
spacing: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingMedium
MatrixText {
text: qsTr("%n member(s)", "", summary.memberCount)
}
ImageButton {
image: ":/icons/icons/ui/people.svg"
enabled: false
image: ":/icons/icons/ui/people.svg"
}
}
TextEdit {
readOnly: true
textFormat: TextEdit.RichText
text: summary.roomTopic
color: palette.text
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: palette.text
horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap
readOnly: true
selectByMouse: true
text: summary.roomTopic
textFormat: TextEdit.RichText
wrapMode: TextEdit.Wrap
}
Label {
id: promptLabel
text: summary.isKnockOnly ? qsTr("This room can't be joined directly. You can, however, knock on the room and room members can accept or decline this join request. You can additionally provide a reason for them to let you in below:") : qsTr("Do you want to join this room? You can optionally add a reason below:")
color: palette.text
Layout.fillWidth: true
color: palette.text
font.bold: true
horizontalAlignment: Text.AlignHCenter
text: summary.isKnockOnly ? qsTr("This room can't be joined directly. You can, however, knock on the room and room members can accept or decline this join request. You can additionally provide a reason for them to let you in below:") : qsTr("Do you want to join this room? You can optionally add a reason below:")
wrapMode: Text.Wrap
font.bold: true
}
MatrixTextField {
id: reason
focus: true
Layout.fillWidth: true
focus: true
text: joinRoomRoot.summary.reason
}
}
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Cancel
onAccepted: {
summary.reason = reason.text;
summary.join();
joinRoomRoot.close();
}
onRejected: {
joinRoomRoot.close();
}
Button {
text: summary.isKnockOnly ? qsTr("Knock") : qsTr("Join")
enabled: input.text.match("#.+?:.{3,}")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
}
}

@ -11,13 +11,31 @@ import im.nheko
ApplicationWindow {
id: createDirectRoot
title: qsTr("Create Direct Chat")
property bool otherUserHasE2ee: profile ? profile.deviceList.rowCount() > 0 : true
property var profile
property bool otherUserHasE2ee: profile? profile.deviceList.rowCount() > 0 : true
minimumHeight: layout.implicitHeight + footer.implicitHeight + Nheko.paddingLarge*2
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
minimumHeight: layout.implicitHeight + footer.implicitHeight + Nheko.paddingLarge * 2
minimumWidth: Math.max(footer.implicitWidth, layout.implicitWidth)
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
title: qsTr("Create Direct Chat")
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
onAccepted: {
profile.startChat(encryption.checked);
createDirectRoot.close();
}
onRejected: createDirectRoot.close()
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: userID.isValidMxid && profile
text: "Start Direct Chat"
}
}
onVisibilityChanged: {
userID.forceActiveFocus();
@ -25,91 +43,82 @@ ApplicationWindow {
Shortcut {
sequence: StandardKey.Cancel
onActivated: createDirectRoot.close()
}
ColumnLayout {
id: layout
anchors.fill: parent
anchors.margins: Nheko.paddingLarge
spacing: userID.height/4
spacing: userID.height / 4
GridLayout {
Layout.fillWidth: true
rows: 2
columnSpacing: Nheko.paddingMedium
columns: 2
rowSpacing: Nheko.paddingSmall
columnSpacing: Nheko.paddingMedium
rows: 2
Avatar {
Layout.rowSpan: 2
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize
Layout.alignment: Qt.AlignLeft
userid: profile? profile.userid : ""
url: profile? profile.avatarUrl.replace("mxc://", "image://MxcImage/") : null
displayName: profile? profile.displayName : ""
Layout.preferredHeight: Nheko.avatarSize
Layout.preferredWidth: Nheko.avatarSize
Layout.rowSpan: 2
displayName: profile ? profile.displayName : ""
enabled: false
url: profile ? profile.avatarUrl.replace("mxc://", "image://MxcImage/") : null
userid: profile ? profile.userid : ""
}
Label {
Layout.fillWidth: true
text: profile? profile.displayName : ""
color: TimelineManager.userColor(userID.text, palette.window)
font.pointSize: fontMetrics.font.pointSize
text: profile ? profile.displayName : ""
}
Label {
Layout.fillWidth: true
text: userID.text
color: palette.buttonText
font.pointSize: fontMetrics.font.pointSize * 0.9
text: userID.text
}
}
MatrixTextField {
id: userID
property bool isValidMxid: text.match("@.+?:.{3,}")
Layout.fillWidth: true
focus: true
label: qsTr("User to invite")
placeholderText: qsTr("@user:server.tld")
onTextChanged: {
// we can't use "isValidMxid" here, since the property might only be reevaluated after this change handler.
if(text.match("@.+?:.{3,}")) {
if (text.match("@.+?:.{3,}")) {
profile = TimelineManager.getGlobalUserProfile(text);
} else
profile = null;
}
}
RowLayout {
Layout.fillWidth: true
Label {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
text: qsTr("Encryption")
Layout.fillWidth: true
color: palette.text
text: qsTr("Encryption")
}
ToggleButton {
Layout.alignment: Qt.AlignRight
id: encryption
checked: otherUserHasE2ee
}
}
Item {Layout.fillHeight: true}
Layout.alignment: Qt.AlignRight
checked: otherUserHasE2ee
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
Button {
text: "Start Direct Chat"
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: userID.isValidMxid && profile
}
onRejected: createDirectRoot.close();
onAccepted: {
profile.startChat(encryption.checked)
createDirectRoot.close()
Item {
Layout.fillHeight: true
}
}
}

@ -14,11 +14,32 @@ ApplicationWindow {
property bool space: false
title: space ? qsTr("New community") : qsTr("New Room")
minimumWidth: Math.max(rootLayout.implicitWidth+2*rootLayout.anchors.margins, footer.implicitWidth + Nheko.paddingLarge)
minimumHeight: rootLayout.implicitHeight+footer.implicitHeight+2*rootLayout.anchors.margins
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
minimumHeight: rootLayout.implicitHeight + footer.implicitHeight + 2 * rootLayout.anchors.margins
minimumWidth: Math.max(rootLayout.implicitWidth + 2 * rootLayout.anchors.margins, footer.implicitWidth + Nheko.paddingLarge)
modality: Qt.NonModal
title: space ? qsTr("New community") : qsTr("New Room")
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
onAccepted: {
var preset = 0;
if (isPublic.checked) {
preset = 1;
} else {
preset = isTrusted.checked ? 2 : 0;
}
Nheko.createRoom(space, newRoomName.text, newRoomTopic.text, newRoomAlias.text, isEncrypted.checked, preset);
createRoomRoot.close();
}
onRejected: createRoomRoot.close()
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Create Room")
}
}
onVisibilityChanged: {
newRoomName.forceActiveFocus();
@ -26,10 +47,12 @@ ApplicationWindow {
Shortcut {
sequence: StandardKey.Cancel
onActivated: createRoomRoot.close()
}
GridLayout {
id: rootLayout
anchors.fill: parent
anchors.margins: Nheko.paddingLarge
columns: 2
@ -37,127 +60,118 @@ ApplicationWindow {
MatrixTextField {
id: newRoomName
Layout.columnSpan: 2
Layout.fillWidth: true
focus: true
label: qsTr("Name")
placeholderText: qsTr("No name")
}
MatrixTextField {
id: newRoomTopic
Layout.columnSpan: 2
Layout.fillWidth: true
focus: true
label: qsTr("Topic")
placeholderText: qsTr("No topic")
}
Item {
Layout.preferredHeight: newRoomName.height / 2
}
RowLayout {
Layout.columnSpan: 2
Layout.fillWidth: true
Label {
Layout.preferredWidth: implicitWidth
text: "#"
color: palette.text
text: "#"
}
MatrixTextField {
id: newRoomAlias
focus: true
placeholderText: qsTr("Alias")
}
Label {
Layout.preferredWidth: implicitWidth
property string userName: userInfoGrid.profile.userid
text: userName.substring(userName.indexOf(":"))
Layout.preferredWidth: implicitWidth
color: palette.text
text: userName.substring(userName.indexOf(":"))
}
}
Label {
Layout.preferredWidth: implicitWidth
Layout.alignment: Qt.AlignLeft
text: qsTr("Public")
Layout.preferredWidth: implicitWidth
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Public rooms can be joined by anyone; private rooms need explicit invites.")
ToolTip.visible: privateHover.hovered
color: palette.text
text: qsTr("Public")
HoverHandler {
id: privateHover
}
ToolTip.visible: privateHover.hovered
ToolTip.text: qsTr("Public rooms can be joined by anyone; private rooms need explicit invites.")
ToolTip.delay: Nheko.tooltipDelay
}
ToggleButton {
id: isPublic
Layout.alignment: Qt.AlignRight
Layout.preferredWidth: implicitWidth
id: isPublic
checked: false
}
Label {
visible: !space
Layout.preferredWidth: implicitWidth
Layout.alignment: Qt.AlignLeft
text: qsTr("Trusted")
Layout.preferredWidth: implicitWidth
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("All invitees are given the same power level as the creator")
ToolTip.visible: trustedHover.hovered
color: palette.text
text: qsTr("Trusted")
visible: !space
HoverHandler {
id: trustedHover
}
ToolTip.visible: trustedHover.hovered
ToolTip.text: qsTr("All invitees are given the same power level as the creator")
ToolTip.delay: Nheko.tooltipDelay
}
ToggleButton {
visible: !space
id: isTrusted
Layout.alignment: Qt.AlignRight
Layout.preferredWidth: implicitWidth
id: isTrusted
checked: false
enabled: !isPublic.checked
visible: !space
}
Label {
visible: !space
Layout.preferredWidth: implicitWidth
Layout.alignment: Qt.AlignLeft
text: qsTr("Encryption")
Layout.preferredWidth: implicitWidth
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Caution: Encryption cannot be disabled")
ToolTip.visible: encryptionHover.hovered
color: palette.text
text: qsTr("Encryption")
visible: !space
HoverHandler {
id: encryptionHover
}
ToolTip.visible: encryptionHover.hovered
ToolTip.text: qsTr("Caution: Encryption cannot be disabled")
ToolTip.delay: Nheko.tooltipDelay
}
ToggleButton {
visible: !space
id: isEncrypted
Layout.alignment: Qt.AlignRight
Layout.preferredWidth: implicitWidth
id: isEncrypted
checked: false
visible: !space
}
Item {Layout.fillHeight: true}
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Cancel
Button {
text: qsTr("Create Room")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
onRejected: createRoomRoot.close();
onAccepted: {
var preset = 0;
if (isPublic.checked) {
preset = 1;
}
else {
preset = isTrusted.checked ? 2 : 0;
}
Nheko.createRoom(space, newRoomName.text, newRoomTopic.text, newRoomAlias.text, isEncrypted.checked, preset)
createRoomRoot.close();
Item {
Layout.fillHeight: true
}
}
}

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

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

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

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

@ -4,33 +4,32 @@
import QtQuick 2.15
import QtQuick.Window 2.15
import ".."
import im.nheko 1.0
Window {
id: imageOverlay
required property string url
required property string eventId
required property Room room
required property int originalWidth
required property double proportionalHeight
required property Room room
required property string url
//visibility: Window.FullScreen
color: Qt.rgba(0.2, 0.2, 0.2, 0.66)
flags: Qt.FramelessWindowHint
//visibility: Window.FullScreen
color: Qt.rgba(0.2,0.2,0.2,0.66)
Component.onCompleted: Nheko.setWindowRole(imageOverlay, "imageoverlay")
Shortcut {
sequences: [StandardKey.Cancel]
onActivated: imageOverlay.close()
}
Shortcut {
sequences: [StandardKey.Copy]
onActivated: {
if (room) {
room.copyMedia(eventId);
@ -39,94 +38,85 @@ Window {
}
}
}
TapHandler {
onSingleTapped: imageOverlay.close();
onSingleTapped: imageOverlay.close()
}
Item {
id: imgContainer
property int imgSrcWidth: (imageOverlay.originalWidth && imageOverlay.originalWidth > 100) ? imageOverlay.originalWidth : Screen.width
property int imgSrcHeight: imageOverlay.proportionalHeight ? imgSrcWidth * imageOverlay.proportionalHeight : Screen.height
property int imgSrcWidth: (imageOverlay.originalWidth && imageOverlay.originalWidth > 100) ? imageOverlay.originalWidth : Screen.width
height: Math.min(parent.height || Screen.height, imgSrcHeight)
width: Math.min(parent.width || Screen.width, imgSrcWidth)
x: (parent.width - width) / 2
y: (parent.height - height) / 2
onScaleChanged: {
if (scale > 10)
scale = 10;
if (scale < 0.1)
scale = 0.1;
}
Image {
id: img
visible: !mxcimage.loaded
property bool loaded: status == Image.Ready
anchors.fill: parent
source: url.replace("mxc://", "image://MxcImage/")
asynchronous: true
fillMode: Image.PreserveAspectFit
smooth: true
mipmap: true
property bool loaded: status == Image.Ready
smooth: true
source: url.replace("mxc://", "image://MxcImage/")
visible: !mxcimage.loaded
}
MxcAnimatedImage {
id: mxcimage
visible: loaded
anchors.fill: parent
roomm: imageOverlay.room
play: !Settings.animateImagesOnHover || mouseArea.hovered
eventId: imageOverlay.eventId
}
onScaleChanged: {
if (scale > 10) scale = 10;
if (scale < 0.1) scale = 0.1
play: !Settings.animateImagesOnHover || mouseArea.hovered
roomm: imageOverlay.room
visible: loaded
}
}
Item {
anchors.fill: parent
PinchHandler {
target: imgContainer
maximumScale: 10
minimumScale: 0.1
target: imgContainer
}
WheelHandler {
property: "scale"
// workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432:
// Magic Mouse pretends to be a trackpad but doesn't work with PinchHandler
// and we don't yet distinguish mice and trackpads on Wayland either
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
property: "scale"
target: imgContainer
}
DragHandler {
target: imgContainer
}
HoverHandler {
id: mouseArea
}
}
Row {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Nheko.paddingLarge
anchors.right: parent.right
anchors.top: parent.top
spacing: Nheko.paddingMedium
ImageButton {
height: 48
width: 48
hoverEnabled: true
image: ":/icons/icons/ui/copy.svg"
width: 48
//ToolTip.visible: hovered
//ToolTip.delay: Nheko.tooltipDelay
@ -142,12 +132,11 @@ Window {
imageOverlay.close();
}
}
ImageButton {
height: 48
width: 48
hoverEnabled: true
image: ":/icons/icons/ui/download.svg"
width: 48
//ToolTip.visible: hovered
//ToolTip.delay: Nheko.tooltipDelay
@ -165,9 +154,9 @@ Window {
}
ImageButton {
height: 48
width: 48
hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg"
width: 48
//ToolTip.visible: hovered
//ToolTip.delay: Nheko.tooltipDelay
@ -176,5 +165,4 @@ Window {
onClicked: imageOverlay.close()
}
}
}

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

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

@ -11,57 +11,56 @@ import im.nheko 1.0
ApplicationWindow {
id: inputDialog
property alias prompt: promptLabel.text
property alias echoMode: statusInput.echoMode
signal accepted(text: string)
property alias prompt: promptLabel.text
modality: Qt.NonModal
flags: Qt.Dialog
width: 350
height: fontMetrics.lineSpacing * 7
signal accepted(string text)
function forceActiveFocus() {
statusInput.forceActiveFocus();
}
flags: Qt.Dialog
height: fontMetrics.lineSpacing * 7
modality: Qt.NonModal
width: 350
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
inputDialog.accepted(statusInput.text);
inputDialog.close();
}
onRejected: {
inputDialog.close();
}
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: dbb.rejected()
}
ColumnLayout {
spacing: Nheko.paddingMedium
anchors.margins: Nheko.paddingMedium
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Label {
id: promptLabel
color: palette.text
}
MatrixTextField {
id: statusInput
Layout.fillWidth: true
onAccepted: dbb.accepted()
focus: true
}
}
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
inputDialog.accepted(statusInput.text);
inputDialog.close();
}
onRejected: {
inputDialog.close();
onAccepted: dbb.accepted()
}
}
}

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

@ -11,62 +11,59 @@ import im.nheko 1.0
ApplicationWindow {
id: joinRoomRoot
title: qsTr("Join room")
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
color: palette.window
width: 350
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: fontMetrics.lineSpacing * 7
modality: Qt.WindowModal
title: qsTr("Join room")
width: 350
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Cancel
onAccepted: {
Nheko.joinRoom(input.text);
joinRoomRoot.close();
}
onRejected: {
joinRoomRoot.close();
}
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: input.text.match("#.+?:.{3,}")
text: qsTr("Join")
}
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: dbb.rejected()
}
ColumnLayout {
spacing: Nheko.paddingMedium
anchors.margins: Nheko.paddingMedium
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
Label {
id: promptLabel
text: qsTr("Room ID or alias")
color: palette.text
text: qsTr("Room ID or alias")
}
MatrixTextField {
id: input
focus: true
Layout.fillWidth: true
focus: true
onAccepted: {
if (input.text.match("#.+?:.{3,}"))
dbb.accepted();
}
}
}
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Cancel
onAccepted: {
Nheko.joinRoom(input.text);
joinRoomRoot.close();
}
onRejected: {
joinRoomRoot.close();
}
Button {
text: qsTr("Join")
enabled: input.text.match("#.+?:.{3,}")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
}
}

@ -9,21 +9,20 @@ import im.nheko
P.MessageDialog {
id: leaveRoomRoot
required property string roomId
property string reason: ""
required property string roomId
title: qsTr("Leave room")
text: qsTr("Are you sure you want to leave?")
modality: Qt.ApplicationModal
buttons: P.MessageDialog.Ok | P.MessageDialog.Cancel
onAccepted: {
modality: Qt.ApplicationModal
text: qsTr("Are you sure you want to leave?")
title: qsTr("Leave room")
onAccepted: {
if (CallManager.haveCallInvite) {
callManager.rejectInvite();
} else if (CallManager.isOnCall) {
CallManager.hangUp();
}
Rooms.leave(roomId, reason)
Rooms.leave(roomId, reason);
}
}

@ -9,10 +9,11 @@ import im.nheko
P.MessageDialog {
id: logoutRoot
title: qsTr("Log out")
text: CallManager.isOnCall ? qsTr("A call is in progress. Log out?") : qsTr("Are you sure you want to log out?")
modality: Qt.WindowModal
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
buttons: P.MessageDialog.Ok | P.MessageDialog.Cancel
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
modality: Qt.WindowModal
text: CallManager.isOnCall ? qsTr("A call is in progress. Log out?") : qsTr("Are you sure you want to log out?")
title: qsTr("Log out")
onAccepted: Nheko.logout()
}

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 im.nheko 1.0
ApplicationWindow {
id: plEditorW
property var roomSettings
property var editingModel: Nheko.editPowerlevels(roomSettings.roomId)
property var roomSettings
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
minimumWidth: 300
minimumHeight: 400
height: 600
minimumHeight: 400
minimumWidth: 300
modality: Qt.NonModal
title: qsTr("Permissions in %1").arg(roomSettings.roomName)
width: 300
title: qsTr("Permissions in %1").arg(roomSettings.roomName);
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
if (editingModel.isSpace) {
// TODO(Nico): Replace with showing a list of spaces to apply to
editingModel.updateSpacesModel();
plEditorW.close();
timelineRoot.showSpacePLApplyPrompt(roomSettings, editingModel);
} else {
editingModel.commit();
plEditorW.close();
}
}
onRejected: plEditorW.close()
}
// Shortcut {
// sequence: StandardKey.Cancel
@ -31,22 +48,21 @@ ApplicationWindow {
// }
ColumnLayout {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: 0
MatrixText {
text: qsTr("Be careful when editing permissions. You can't lower the permissions of people with a same or higher level than you. Be careful when promoting others.")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.bottomMargin: Nheko.paddingMedium
Layout.fillHeight: false
Layout.fillWidth: true
color: palette.text
Layout.bottomMargin: Nheko.paddingMedium
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("Be careful when editing permissions. You can't lower the permissions of people with a same or higher level than you. Be careful when promoting others.")
}
TabBar {
id: bar
Layout.preferredWidth: parent.width
NhekoTabButton {
@ -57,95 +73,95 @@ ApplicationWindow {
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: palette.alternateBase
border.width: 1
Layout.fillWidth: true
border.color: Nheko.theme.separator
border.width: 1
color: palette.alternateBase
StackLayout {
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
currentIndex: bar.currentIndex
ColumnLayout {
spacing: Nheko.paddingMedium
MatrixText {
text: qsTr("Move permissions between roles to change them")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false
Layout.fillWidth: true
color: palette.text
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("Move permissions between roles to change them")
}
ReorderableListview {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillWidth: true
model: editingModel.types
delegate: RowLayout {
Column {
Layout.fillWidth: true
Text { visible: model.isType; text: model.displayName; color: palette.text}
Text {
visible: !model.isType;
color: palette.text
text: model.displayName
visible: model.isType
}
Text {
color: palette.text
text: {
if (editingModel.adminLevel == model.powerlevel)
return qsTr("Administrator (%1)").arg(model.powerlevel)
return qsTr("Administrator (%1)").arg(model.powerlevel);
else if (editingModel.moderatorLevel == model.powerlevel)
return qsTr("Moderator (%1)").arg(model.powerlevel)
return qsTr("Moderator (%1)").arg(model.powerlevel);
else if (editingModel.defaultUserLevel == model.powerlevel)
return qsTr("User (%1)").arg(model.powerlevel)
return qsTr("User (%1)").arg(model.powerlevel);
else
return qsTr("Custom (%1)").arg(model.powerlevel)
return qsTr("Custom (%1)").arg(model.powerlevel);
}
color: palette.text
visible: !model.isType
}
}
ImageButton {
Layout.alignment: Qt.AlignRight
Layout.rightMargin: 2
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
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: model.isType ? qsTr("Remove event type") : qsTr("Add event type")
onClicked: {
if (model.isType) {
editingModel.types.remove(index);
} else {
typeEntry.y = offset
typeEntry.visible = true
typeEntry.y = offset;
typeEntry.visible = true;
typeEntry.index = index;
typeEntry.forceActiveFocus()
typeEntry.forceActiveFocus();
}
}
}
}
MatrixTextField {
id: typeEntry
property int index
color: palette.text
visible: false
width: parent.width
z: 5
visible: false
color: palette.text
Keys.onPressed: {
if (typeEntry.text.includes('.') && event.matches(StandardKey.InsertParagraphSeparator)) {
editingModel.types.add(typeEntry.index, typeEntry.text)
editingModel.types.add(typeEntry.index, typeEntry.text);
typeEntry.visible = false;
typeEntry.clear();
event.accepted = true;
}
else if (event.matches(StandardKey.Cancel)) {
} else if (event.matches(StandardKey.Cancel)) {
typeEntry.visible = false;
typeEntry.clear();
event.accepted = true;
@ -153,7 +169,6 @@ ApplicationWindow {
}
}
}
Button {
Layout.fillWidth: true
text: qsTr("Add new role")
@ -164,19 +179,18 @@ ApplicationWindow {
id: newPLLay
anchors.fill: parent
visible: false
color: palette.alternateBase
visible: false
RowLayout {
spacing: Nheko.paddingMedium
anchors.fill: parent
spacing: Nheko.paddingMedium
SpinBox {
id: newPLVal
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillWidth: true
editable: true
//from: -9007199254740991
//to: 9007199254740991
@ -192,10 +206,10 @@ ApplicationWindow {
}
}
}
Button {
text: qsTr("Add")
Layout.preferredWidth: 100
text: qsTr("Add")
onClicked: {
editingModel.addRole(newPLVal.value);
newPLLay.visible = false;
@ -205,42 +219,109 @@ ApplicationWindow {
}
}
}
ColumnLayout {
spacing: Nheko.paddingMedium
MatrixText {
text: qsTr("Move users up or down to change their permissions")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.fillHeight: false
Layout.fillWidth: true
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("Move users up or down to change their permissions")
}
ReorderableListview {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillWidth: true
model: editingModel.users
Column{
delegate: RowLayout {
//anchors { fill: parent; margins: 2 }
id: row
Avatar {
id: avatar
Layout.leftMargin: 2
Layout.preferredHeight: Nheko.avatarSize / 2
Layout.preferredWidth: Nheko.avatarSize / 2
displayName: model.displayName
enabled: false
url: {
if (model.isUser)
return model.avatarUrl.replace("mxc://", "image://MxcImage/");
else if (editingModel.adminLevel >= model.powerlevel)
return "image://colorimage/:/icons/icons/ui/ribbon_star.svg?" + palette.buttonText;
else if (editingModel.moderatorLevel >= model.powerlevel)
return "image://colorimage/:/icons/icons/ui/ribbon.svg?" + palette.buttonText;
else
return "image://colorimage/:/icons/icons/ui/person.svg?" + palette.buttonText;
}
userid: model.mxid
}
Column {
Layout.fillWidth: true
Text {
color: palette.text
text: model.displayName
visible: model.isUser
}
Text {
color: palette.text
text: model.mxid
visible: model.isUser
}
Text {
color: palette.text
text: {
if (editingModel.adminLevel == model.powerlevel)
return qsTr("Administrator (%1)").arg(model.powerlevel);
else if (editingModel.moderatorLevel == model.powerlevel)
return qsTr("Moderator (%1)").arg(model.powerlevel);
else
return qsTr("Custom (%1)").arg(model.powerlevel);
}
visible: !model.isUser
}
}
ImageButton {
Layout.alignment: Qt.AlignRight
Layout.rightMargin: 2
ToolTip.text: model.isUser ? qsTr("Remove user") : qsTr("Add user")
ToolTip.visible: hovered
hoverEnabled: true
image: model.isUser ? ":/icons/icons/ui/dismiss.svg" : ":/icons/icons/ui/add-square-button.svg"
visible: !model.isUser || model.removeable
onClicked: {
if (model.isUser) {
editingModel.users.remove(index);
} else {
userEntryCompleter.y = offset;
userEntryCompleter.visible = true;
userEntryCompleter.index = index;
userEntry.forceActiveFocus();
}
}
}
}
Column {
id: userEntryCompleter
property int index: 0
spacing: 1
visible: false
width: parent.width
spacing: 1
z: 5
MatrixTextField {
id: userEntry
width: parent.width
//font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
color: palette.text
onTextEdited: {
userCompleter.completer.searchString = text;
}
width: parent.width
Keys.onPressed: {
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
event.accepted = true;
@ -264,130 +345,45 @@ ApplicationWindow {
event.accepted = true;
}
}
onTextEdited: {
userCompleter.completer.searchString = text;
}
}
Completer {
id: userCompleter
visible: userEntry.text.length > 0
width: parent.width
roomId: plEditorW.roomSettings.roomId
completerName: "user"
bottomToTop: false
fullWidth: true
avatarHeight: Nheko.avatarSize / 2
avatarWidth: Nheko.avatarSize / 2
bottomToTop: false
centerRowContent: false
completerName: "user"
fullWidth: true
roomId: plEditorW.roomSettings.roomId
rowMargin: 2
rowSpacing: 2
visible: userEntry.text.length > 0
width: parent.width
}
}
Connections {
id: userCompletionConnections
function onCompletionSelected(id) {
console.log("selected: " + id);
editingModel.users.add(userEntryCompleter.index, id);
userEntry.clear();
userEntryCompleter.visible = false;
}
function onCountChanged() {
if (userCompleter.count > 0 && (userCompleter.currentIndex < 0 || userCompleter.currentIndex >= userCompleter.count))
userCompleter.currentIndex = 0;
}
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 {
id: applyDialog
property RoomSettings roomSettings
property PowerlevelEditingModels editingModel
property RoomSettings roomSettings
minimumWidth: 340
minimumHeight: 450
width: 450
height: 680
color: palette.window
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 680
minimumHeight: 450
minimumWidth: 340
modality: Qt.NonModal
title: qsTr("Apply permission changes")
width: 450
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
editingModel.spaces.commit();
applyDialog.close();
}
onRejected: applyDialog.close()
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: roomSettingsDialog.close()
}
ColumnLayout {
anchors.margins: Nheko.paddingMedium
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingLarge
MatrixText {
text: qsTr("Which of the subcommunities and rooms should these permissions be applied to?")
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true
Layout.bottomMargin: Nheko.paddingMedium
Layout.fillHeight: false
Layout.fillWidth: true
color: palette.text
Layout.bottomMargin: Nheko.paddingMedium
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
text: qsTr("Which of the subcommunities and rooms should these permissions be applied to?")
}
GridLayout {
Layout.fillWidth: true
Layout.fillHeight: false
Layout.fillWidth: true
columns: 2
Label {
text: qsTr("Apply permissions recursively")
Layout.fillWidth: true
color: palette.text
text: qsTr("Apply permissions recursively")
}
ToggleButton {
checked: editingModel.spaces.applyToChildren
Layout.alignment: Qt.AlignRight
checked: editingModel.spaces.applyToChildren
onCheckedChanged: editingModel.spaces.applyToChildren = checked
}
Label {
text: qsTr("Overwrite exisiting modifications in rooms")
Layout.fillWidth: true
color: palette.text
text: qsTr("Overwrite exisiting modifications in rooms")
}
ToggleButton {
checked: editingModel.spaces.overwriteDiverged
Layout.alignment: Qt.AlignRight
checked: editingModel.spaces.overwriteDiverged
onCheckedChanged: editingModel.spaces.overwriteDiverged = checked
}
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
id: view
Layout.fillHeight: true
Layout.fillWidth: true
cacheBuffer: 50
clip: true
model: editingModel.spaces
spacing: 4
cacheBuffer: 50
delegate: RowLayout {
anchors.left: parent.left
@ -92,49 +98,38 @@ ApplicationWindow {
ColumnLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
text: model.displayName
color: palette.text
textFormat: Text.PlainText
elide: Text.ElideRight
text: model.displayName
textFormat: Text.PlainText
}
Text {
Layout.fillWidth: true
color: palette.buttonText
elide: Text.ElideRight
text: {
if (!model.isEditable) return qsTr("No permissions to apply the new permissions here");
if (model.isAlreadyUpToDate) return qsTr("No changes needed");
if (model.isDifferentFromBase) return qsTr("Existing modifications to the permissions in this room will be overwritten");
return qsTr("Permissions synchronized with community")
if (!model.isEditable)
return qsTr("No permissions to apply the new permissions here");
if (model.isAlreadyUpToDate)
return qsTr("No changes needed");
if (model.isDifferentFromBase)
return qsTr("Existing modifications to the permissions in this room will be overwritten");
return qsTr("Permissions synchronized with community");
}
elide: Text.ElideRight
color: palette.buttonText
textFormat: Text.PlainText
}
}
ToggleButton {
checked: model.applyPermissions
Layout.alignment: Qt.AlignRight
onCheckedChanged: model.applyPermissions = checked
checked: model.applyPermissions
enabled: model.isEditable
}
}
}
onCheckedChanged: model.applyPermissions = checked
}
}
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
editingModel.spaces.commit();
applyDialog.close();
}
onRejected: applyDialog.close()
}
}

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

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

@ -14,18 +14,24 @@ ApplicationWindow {
property ReadReceiptsProxy readReceipts
property Room room
color: palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 380
width: 340
minimumHeight: 380
minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium
color: palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
width: 340
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: readReceiptsRoot.close()
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: readReceiptsRoot.close()
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
@ -34,98 +40,84 @@ ApplicationWindow {
Label {
id: headerTitle
color: palette.text
Layout.alignment: Qt.AlignCenter
text: qsTr("Read receipts")
color: palette.text
font.pointSize: fontMetrics.font.pointSize * 1.5
text: qsTr("Read receipts")
}
ScrollView {
padding: Nheko.paddingMedium
ScrollBar.horizontal.visible: false
Layout.fillHeight: true
Layout.minimumHeight: 200
Layout.fillWidth: true
Layout.minimumHeight: 200
ScrollBar.horizontal.visible: false
padding: Nheko.paddingMedium
ListView {
id: readReceiptsList
clip: true
boundsBehavior: Flickable.StopAtBounds
clip: true
model: readReceipts
delegate: ItemDelegate {
id: del
onClicked: room.openUserProfile(model.mxid)
padding: Nheko.paddingMedium
width: ListView.view.width
ToolTip.text: model.mxid
ToolTip.visible: hovered
height: receiptLayout.implicitHeight + Nheko.paddingSmall * 2
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: model.mxid
padding: Nheko.paddingMedium
width: ListView.view.width
background: Rectangle {
color: del.hovered ? palette.dark : readReceiptsRoot.color
}
onClicked: room.openUserProfile(model.mxid)
RowLayout {
id: receiptLayout
spacing: Nheko.paddingMedium
anchors.fill: parent
anchors.margins: Nheko.paddingSmall
spacing: Nheko.paddingMedium
Avatar {
id: avatar
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize
userid: model.mxid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
Layout.preferredWidth: Nheko.avatarSize
displayName: model.displayName
enabled: false
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.mxid
}
ColumnLayout {
spacing: Nheko.paddingSmall
Layout.fillWidth: true
spacing: Nheko.paddingSmall
ElidedLabel {
fullText: model.displayName
Layout.fillWidth: true
color: TimelineManager.userColor(model ? model.mxid : "", palette.window)
font.pointSize: fontMetrics.font.pointSize
elideWidth: del.width - Nheko.paddingMedium - avatar.width
Layout.fillWidth: true
font.pointSize: fontMetrics.font.pointSize
fullText: model.displayName
}
ElidedLabel {
fullText: model.timestamp
Layout.fillWidth: true
color: palette.buttonText
font.pointSize: fontMetrics.font.pointSize * 0.9
elideWidth: del.width - Nheko.paddingMedium - avatar.width
Layout.fillWidth: true
font.pointSize: fontMetrics.font.pointSize * 0.9
fullText: model.timestamp
}
}
}
NhekoCursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
}
}
}
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: readReceiptsRoot.close()
}
}

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

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

@ -17,18 +17,24 @@ ApplicationWindow {
property MemberList members
property Room room
title: qsTr("Members of %1").arg(members.roomName)
height: 650
width: 420
minimumHeight: 420
color: palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 650
minimumHeight: 420
title: qsTr("Members of %1").arg(members.roomName)
width: 420
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: roomMembersRoot.close()
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: roomMembersRoot.close()
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
@ -37,148 +43,146 @@ ApplicationWindow {
Avatar {
id: roomAvatar
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 130
Layout.preferredWidth: 130
roomid: members.roomId
displayName: members.roomName
Layout.alignment: Qt.AlignHCenter
roomid: members.roomId
url: members.avatarUrl.replace("mxc://", "image://MxcImage/")
onClicked: TimelineManager.openRoomSettings(members.roomId)
}
ElidedLabel {
font.pixelSize: fontMetrics.font.pixelSize * 2
fullText: qsTr("%n people in %1", "Summary above list of members", members.memberCount).arg(members.roomName)
Layout.alignment: Qt.AlignHCenter
elideWidth: parent.width - Nheko.paddingMedium
font.pixelSize: fontMetrics.font.pixelSize * 2
fullText: qsTr("%n people in %1", "Summary above list of members", members.memberCount).arg(members.roomName)
}
ImageButton {
Layout.alignment: Qt.AlignHCenter
image: ":/icons/icons/ui/add-square-button.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Invite more people")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/add-square-button.svg"
onClicked: TimelineManager.openInviteUsers(members.roomId)
}
MatrixTextField {
id: searchBar
Layout.fillWidth: true
placeholderText: qsTr("Search...")
onTextChanged: members.setFilterString(text)
Component.onCompleted: forceActiveFocus()
onTextChanged: members.setFilterString(text)
}
RowLayout {
spacing: Nheko.paddingMedium
Label {
text: qsTr("Sort by: ")
color: palette.text
text: qsTr("Sort by: ")
}
ComboBox {
model: ListModel {
ListElement { data: MemberList.Mxid; text: qsTr("User ID") }
ListElement { data: MemberList.DisplayName; text: qsTr("Display name") }
ListElement { data: MemberList.Powerlevel; text: qsTr("Power level") }
}
Layout.fillWidth: true
textRole: "text"
valueRole: "data"
onCurrentValueChanged: members.sortBy(currentValue)
Layout.fillWidth: true
model: ListModel {
ListElement {
data: MemberList.Mxid
text: qsTr("User ID")
}
ListElement {
data: MemberList.DisplayName
text: qsTr("Display name")
}
ListElement {
data: MemberList.Powerlevel
text: qsTr("Power level")
}
}
onCurrentValueChanged: members.sortBy(currentValue)
}
}
ScrollView {
padding: Nheko.paddingMedium
ScrollBar.horizontal.visible: false
Layout.fillHeight: true
Layout.minimumHeight: 200
Layout.fillWidth: true
Layout.minimumHeight: 200
ScrollBar.horizontal.visible: false
padding: Nheko.paddingMedium
ListView {
id: memberList
clip: true
boundsBehavior: Flickable.StopAtBounds
clip: true
model: members
delegate: ItemDelegate {
id: del
onClicked: room.openUserProfile(model.mxid)
padding: Nheko.paddingMedium
width: ListView.view.width
height: memberLayout.implicitHeight + Nheko.paddingSmall * 2
hoverEnabled: true
padding: Nheko.paddingMedium
width: ListView.view.width
background: Rectangle {
color: del.hovered ? palette.dark : roomMembersRoot.color
}
onClicked: room.openUserProfile(model.mxid)
RowLayout {
id: memberLayout
spacing: Nheko.paddingMedium
anchors.centerIn: parent
spacing: Nheko.paddingMedium
width: parent.width - Nheko.paddingSmall * 2
Avatar {
id: avatar
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize
userid: model.mxid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
Layout.preferredWidth: Nheko.avatarSize
displayName: model.displayName
enabled: false
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.mxid
}
ColumnLayout {
spacing: Nheko.paddingSmall
Layout.fillWidth: true
spacing: Nheko.paddingSmall
ElidedLabel {
fullText: model.displayName
Layout.fillWidth: true
color: TimelineManager.userColor(model ? model.mxid : "", del.background.color)
font.pixelSize: fontMetrics.font.pixelSize
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
Layout.fillWidth: true
font.pixelSize: fontMetrics.font.pixelSize
fullText: model.displayName
}
ElidedLabel {
fullText: model.mxid
Layout.fillWidth: true
color: del.hovered ? palette.brightText : palette.buttonText
font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9)
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
Layout.fillWidth: true
font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9)
fullText: model.mxid
}
}
PowerlevelIndicator {
powerlevel: model.powerlevel
permissions: room.permissions
powerlevel: model.powerlevel
}
EncryptionIndicator {
id: encryptInd
Layout.preferredWidth: 16
Layout.preferredHeight: 16
Layout.alignment: Qt.AlignRight
visible: room.isEncrypted
encrypted: room.isEncrypted
trust: encrypted ? model.trustlevel : Crypto.Unverified
Layout.preferredHeight: 16
Layout.preferredWidth: 16
ToolTip.text: {
if (!encrypted)
return qsTr("This room is not encrypted!");
switch (trust) {
case Crypto.Verified:
return qsTr("This user is verified.");
@ -188,23 +192,22 @@ ApplicationWindow {
return qsTr("This user has unverified devices!");
}
}
encrypted: room.isEncrypted
trust: encrypted ? model.trustlevel : Crypto.Unverified
visible: room.isEncrypted
}
}
NhekoCursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
}
footer: Item {
width: parent.width
visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers
anchors.margins: Nheko.paddingMedium
// use the default height if it's visible, otherwise no height at all
height: membersLoadingSpinner.implicitHeight
anchors.margins: Nheko.paddingMedium
visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers
width: parent.width
Spinner {
id: membersLoadingSpinner
@ -212,18 +215,8 @@ ApplicationWindow {
anchors.centerIn: parent
implicitHeight: parent.visible ? 35 : 0
}
}
}
}
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: roomMembersRoot.close()
}
}

@ -16,80 +16,87 @@ ApplicationWindow {
property var roomSettings
minimumWidth: 340
minimumHeight: 450
width: 450
height: 680
color: palette.window
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 680
minimumHeight: 450
minimumWidth: 340
modality: Qt.NonModal
title: qsTr("Room Settings")
width: 450
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: close()
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: roomSettingsDialog.close()
}
Flickable {
id: flickable
boundsBehavior: Flickable.StopAtBounds
anchors.fill: parent
boundsBehavior: Flickable.StopAtBounds
clip: true
flickableDirection: Flickable.VerticalFlick
contentWidth: roomSettingsDialog.width
contentHeight: contentLayout1.height
contentWidth: roomSettingsDialog.width
flickableDirection: Flickable.VerticalFlick
ColumnLayout {
id: contentLayout1
width: parent.width
spacing: Nheko.paddingMedium
width: parent.width
Avatar {
id: displayAvatar
Layout.topMargin: Nheko.paddingMedium
url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
roomid: roomSettings.roomId
displayName: roomSettings.roomName
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 130
Layout.preferredWidth: 130
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Nheko.paddingMedium
displayName: roomSettings.roomName
roomid: roomSettings.roomId
url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
onClicked: TimelineManager.openImageOverlay(null, roomSettings.roomAvatarUrl, "", 0, 0)
ImageButton {
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change room avatar.")
ToolTip.visible: hovered
anchors.left: displayAvatar.left
anchors.top: displayAvatar.top
anchors.leftMargin: Nheko.paddingMedium
anchors.top: displayAvatar.top
anchors.topMargin: Nheko.paddingMedium
visible: roomSettings.canChangeAvatar
hoverEnabled: true
image: ":/icons/icons/ui/edit.svg"
visible: roomSettings.canChangeAvatar
onClicked: {
roomSettings.updateAvatar();
}
}
}
Spinner {
Layout.alignment: Qt.AlignHCenter
visible: roomSettings.isLoading
foreground: palette.mid
running: roomSettings.isLoading
visible: roomSettings.isLoading
}
Text {
id: errorText
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
color: "red"
visible: opacity > 0
opacity: 0
Layout.alignment: Qt.AlignHCenter
visible: opacity > 0
wrapMode: Text.Wrap // somehow still doesn't wrap
Layout.fillWidth: true
}
SequentialAnimation {
id: hideErrorAnimation
@ -98,43 +105,38 @@ ApplicationWindow {
PauseAnimation {
duration: 4000
}
NumberAnimation {
target: errorText
duration: 1000
property: 'opacity'
target: errorText
to: 0
duration: 1000
}
}
Connections {
target: roomSettings
function onDisplayError(errorMessage) {
errorText.text = errorMessage;
errorText.opacity = 1;
hideErrorAnimation.restart();
}
}
target: roomSettings
}
TextEdit {
id: roomName
property bool isNameEditingAllowed: false
readOnly: !isNameEditingAllowed
textFormat: isNameEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
text: isNameEditingAllowed ? roomSettings.plainRoomName : roomSettings.roomName
font.pixelSize: fontMetrics.font.pixelSize * 2
color: palette.text
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: parent.width - (Nheko.paddingSmall * 2) - nameChangeButton.anchors.leftMargin - (nameChangeButton.width * 2)
color: palette.text
font.pixelSize: fontMetrics.font.pixelSize * 2
horizontalAlignment: TextEdit.AlignHCenter
wrapMode: TextEdit.Wrap
readOnly: !isNameEditingAllowed
selectByMouse: true
text: isNameEditingAllowed ? roomSettings.plainRoomName : roomSettings.roomName
textFormat: isNameEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
wrapMode: TextEdit.Wrap
Keys.onShortcutOverride: event.key === Qt.Key_Enter
Keys.onPressed: {
if (event.matches(StandardKey.InsertLineSeparator) || event.matches(StandardKey.InsertParagraphSeparator)) {
roomSettings.changeName(roomName.text);
@ -142,18 +144,21 @@ ApplicationWindow {
event.accepted = true;
}
}
Keys.onShortcutOverride: event.key === Qt.Key_Enter
ImageButton {
id: nameChangeButton
visible: roomSettings.canChangeName
anchors.leftMargin: Nheko.paddingSmall
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Change name of this room")
ToolTip.visible: hovered
anchors.left: roomName.right
anchors.leftMargin: Nheko.paddingSmall
anchors.verticalCenter: roomName.verticalCenter
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change name of this room")
ToolTip.delay: Nheko.tooltipDelay
image: roomName.isNameEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
visible: roomSettings.canChangeName
onClicked: {
if (roomName.isNameEditingAllowed) {
roomSettings.changeName(roomName.text);
@ -165,69 +170,64 @@ ApplicationWindow {
}
}
}
}
RowLayout {
spacing: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingMedium
Label {
text: qsTr("%n member(s)", "", roomSettings.memberCount)
color: palette.text
text: qsTr("%n member(s)", "", roomSettings.memberCount)
}
ImageButton {
image: ":/icons/icons/ui/people.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("View members of %1").arg(roomSettings.roomName)
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/people.svg"
onClicked: TimelineManager.openRoomMembers(Rooms.getRoomById(roomSettings.roomId))
}
}
TextArea {
id: roomTopic
property bool cut: implicitHeight > 100
property bool isTopicEditingAllowed: false
property bool showMore: false
clip: true
Layout.maximumHeight: showMore? Number.POSITIVE_INFINITY : 100
Layout.preferredHeight: implicitHeight
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.leftMargin: Nheko.paddingLarge
Layout.maximumHeight: showMore ? Number.POSITIVE_INFINITY : 100
Layout.preferredHeight: implicitHeight
Layout.rightMargin: Nheko.paddingLarge
property bool isTopicEditingAllowed: false
readOnly: !isTopicEditingAllowed
textFormat: isTopicEditingAllowed ? TextEdit.PlainText : TextEdit.RichText
text: isTopicEditingAllowed
? roomSettings.plainRoomTopic
: (roomSettings.plainRoomTopic === "" ? ("<i>" + qsTr("No topic set") + "</i>") : roomSettings.roomTopic)
wrapMode: TextEdit.WordWrap
background: null
clip: true
color: palette.text
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)
NhekoCursorShape {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
ImageButton {
id: topicChangeButton
Layout.alignment: Qt.AlignHCenter
visible: roomSettings.canChangeTopic
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Change topic of this room")
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Change topic of this room")
ToolTip.visible: hovered
hoverEnabled: true
image: roomTopic.isTopicEditingAllowed ? ":/icons/icons/ui/checkmark.svg" : ":/icons/icons/ui/edit.svg"
visible: roomSettings.canChangeTopic
onClicked: {
if (roomTopic.isTopicEditingAllowed) {
roomSettings.changeTopic(roomTopic.text);
@ -240,234 +240,219 @@ ApplicationWindow {
}
}
}
Item {
Layout.alignment: Qt.AlignHCenter
id: showMorePlaceholder
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: showMoreButton.height
Layout.preferredWidth: showMoreButton.width
visible: roomTopic.cut
}
GridLayout {
Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium
columns: 2
rowSpacing: Nheko.paddingMedium
Layout.margins: Nheko.paddingMedium
Layout.fillWidth: true
Label {
text: qsTr("NOTIFICATIONS")
font.bold: true
color: palette.text
Layout.columnSpan: 2
Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge
color: palette.text
font.bold: true
text: qsTr("NOTIFICATIONS")
}
Label {
text: qsTr("Notifications")
Layout.fillWidth: true
color: palette.text
text: qsTr("Notifications")
}
ComboBox {
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")]
Layout.fillWidth: true
currentIndex: roomSettings.notifications
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")]
onActivated: {
roomSettings.changeNotifications(index);
}
Layout.fillWidth: true
WheelHandler{} // suppress scrolling changing values
}
WheelHandler {
} // suppress scrolling changing values
}
Label {
text: qsTr("ENTRY PERMISSIONS")
font.bold: true
color: palette.text
Layout.columnSpan: 2
Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge
color: palette.text
font.bold: true
text: qsTr("ENTRY PERMISSIONS")
}
Label {
text: qsTr("Anyone can join")
Layout.fillWidth: true
color: palette.text
text: qsTr("Anyone can join")
}
ToggleButton {
id: publicRoomButton
enabled: roomSettings.canChangeJoinRules
checked: !roomSettings.privateAccess
Layout.alignment: Qt.AlignRight
checked: !roomSettings.privateAccess
enabled: roomSettings.canChangeJoinRules
}
Label {
text: qsTr("Allow knocking")
Layout.fillWidth: true
color: palette.text
text: qsTr("Allow knocking")
visible: knockingButton.visible
}
ToggleButton {
id: knockingButton
visible: !publicRoomButton.checked
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsKnocking
Layout.alignment: Qt.AlignRight
checked: roomSettings.knockingEnabled
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsKnocking
visible: !publicRoomButton.checked
onCheckedChanged: {
if (checked && !roomSettings.supportsKnockRestricted) restrictedButton.checked = false;
if (checked && !roomSettings.supportsKnockRestricted)
restrictedButton.checked = false;
}
Layout.alignment: Qt.AlignRight
}
Label {
text: qsTr("Allow joining via other rooms")
Layout.fillWidth: true
color: palette.text
text: qsTr("Allow joining via other rooms")
visible: restrictedButton.visible
}
ToggleButton {
id: restrictedButton
visible: !publicRoomButton.checked
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
Layout.alignment: Qt.AlignRight
checked: roomSettings.restrictedEnabled
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
visible: !publicRoomButton.checked
onCheckedChanged: {
if (checked && !roomSettings.supportsKnockRestricted) knockingButton.checked = false;
if (checked && !roomSettings.supportsKnockRestricted)
knockingButton.checked = false;
}
Layout.alignment: Qt.AlignRight
}
Label {
text: qsTr("Rooms to join via")
Layout.fillWidth: true
color: palette.text
text: qsTr("Rooms to join via")
visible: allowedRoomsButton.visible
}
Button {
id: allowedRoomsButton
visible: restrictedButton.checked && restrictedButton.visible
Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("Change the list of rooms users can join this room via. Usually this is the official community of this room.")
enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
text: qsTr("Change")
ToolTip.text: qsTr("Change the list of rooms users can join this room via. Usually this is the official community of this room.")
visible: restrictedButton.checked && restrictedButton.visible
onClicked: timelineRoot.showAllowedRoomsEditor(roomSettings)
Layout.alignment: Qt.AlignRight
}
Label {
text: qsTr("Allow guests to join")
Layout.fillWidth: true
color: palette.text
text: qsTr("Allow guests to join")
}
ToggleButton {
id: guestAccessButton
enabled: roomSettings.canChangeJoinRules
checked: roomSettings.guestAccess
Layout.alignment: Qt.AlignRight
checked: roomSettings.guestAccess
enabled: roomSettings.canChangeJoinRules
}
Button {
visible: publicRoomButton.checked == roomSettings.privateAccess || knockingButton.checked != roomSettings.knockingEnabled || restrictedButton.checked != roomSettings.restrictedEnabled || guestAccessButton.checked != roomSettings.guestAccess || roomSettings.allowedRoomsModified
Layout.columnSpan: 2
Layout.fillWidth: true
enabled: roomSettings.canChangeJoinRules
text: qsTr("Apply access rules")
visible: publicRoomButton.checked == roomSettings.privateAccess || knockingButton.checked != roomSettings.knockingEnabled || restrictedButton.checked != roomSettings.restrictedEnabled || guestAccessButton.checked != roomSettings.guestAccess || roomSettings.allowedRoomsModified
onClicked: roomSettings.changeAccessRules(!publicRoomButton.checked, guestAccessButton.checked, knockingButton.checked, restrictedButton.checked)
Layout.columnSpan: 2
Layout.fillWidth: true
}
Label {
text: qsTr("MESSAGE VISIBILITY")
font.bold: true
color: palette.text
Layout.columnSpan: 2
Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge
color: palette.text
font.bold: true
text: qsTr("MESSAGE VISIBILITY")
}
Label {
text: qsTr("Allow viewing history without joining")
Layout.fillWidth: true
color: palette.text
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("This is useful to see previews of the room or view it on public websites.")
ToolTip.visible: publicHistoryHover.hovered
ToolTip.delay: Nheko.tooltipDelay
color: palette.text
text: qsTr("Allow viewing history without joining")
HoverHandler {
id: publicHistoryHover
}
}
ToggleButton {
id: publicHistoryButton
enabled: roomSettings.canChangeHistoryVisibility
checked: roomSettings.historyVisibility == RoomSettings.WorldReadable
Layout.alignment: Qt.AlignRight
checked: roomSettings.historyVisibility == RoomSettings.WorldReadable
enabled: roomSettings.canChangeHistoryVisibility
}
Label {
visible: !publicHistoryButton.checked
text: qsTr("Members can see messages since")
Layout.fillWidth: true
color: palette.text
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.fillWidth: true
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("How much of the history is visible to joined members. Changing this won't affect the visibility of already sent messages. It only applies to new messages.")
ToolTip.visible: privateHistoryHover.hovered
ToolTip.delay: Nheko.tooltipDelay
color: palette.text
text: qsTr("Members can see messages since")
visible: !publicHistoryButton.checked
HoverHandler {
id: privateHistoryHover
}
}
ColumnLayout {
Layout.alignment: Qt.AlignTop | Qt.AlignRight
Layout.fillWidth: true
visible: !publicHistoryButton.checked
enabled: roomSettings.canChangeHistoryVisibility
Layout.alignment: Qt.AlignTop | Qt.AlignRight
visible: !publicHistoryButton.checked
RadioButton {
id: sharedHistory
checked: roomSettings.historyVisibility == RoomSettings.Shared
text: qsTr("Everything")
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("As long as the user joined, they can see all previous messages.")
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
checked: roomSettings.historyVisibility == RoomSettings.Shared
text: qsTr("Everything")
}
RadioButton {
id: invitedHistory
checked: roomSettings.historyVisibility == RoomSettings.Invited
text: qsTr("They got invited")
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Members can only see messages from when they got invited going forward.")
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
checked: roomSettings.historyVisibility == RoomSettings.Invited
text: qsTr("They got invited")
}
RadioButton {
id: joinedHistory
checked: roomSettings.historyVisibility == RoomSettings.Joined || roomSettings.historyVisibility == RoomSettings.WorldReadable
text: qsTr("They joined")
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Members can only see messages since after they joined.")
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
checked: roomSettings.historyVisibility == RoomSettings.Joined || roomSettings.historyVisibility == RoomSettings.WorldReadable
text: qsTr("They joined")
}
}
Button {
visible: roomSettings.historyVisibility != selectedVisibility
enabled: roomSettings.canChangeHistoryVisibility
text: qsTr("Apply visibility changes")
property int selectedVisibility: {
if (publicHistoryButton.checked)
return RoomSettings.WorldReadable;
@ -477,202 +462,200 @@ ApplicationWindow {
return RoomSettings.Invited;
return RoomSettings.Joined;
}
onClicked: roomSettings.changeHistoryVisibility(selectedVisibility)
Layout.columnSpan: 2
Layout.fillWidth: true
}
enabled: roomSettings.canChangeHistoryVisibility
text: qsTr("Apply visibility changes")
visible: roomSettings.historyVisibility != selectedVisibility
onClicked: roomSettings.changeHistoryVisibility(selectedVisibility)
}
Label {
text: qsTr("Locally hidden events")
color: palette.text
text: qsTr("Locally hidden events")
}
HiddenEventsDialog {
id: hiddenEventsDialog
roomid: roomSettings.roomId
roomName: roomSettings.roomName
roomid: roomSettings.roomId
}
Button {
text: qsTr("Configure")
Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("Select events to hide in this room")
text: qsTr("Configure")
onClicked: hiddenEventsDialog.show()
Layout.alignment: Qt.AlignRight
}
Label {
text: qsTr("Automatic event deletion")
color: palette.text
text: qsTr("Automatic event deletion")
}
EventExpirationDialog {
id: eventExpirationDialog
roomid: roomSettings.roomId
roomName: roomSettings.roomName
roomid: roomSettings.roomId
}
Button {
text: qsTr("Configure")
Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("Select if your events get automatically deleted in this room.")
text: qsTr("Configure")
onClicked: eventExpirationDialog.show()
Layout.alignment: Qt.AlignRight
}
Label {
text: qsTr("GENERAL SETTINGS")
font.bold: true
color: palette.text
Layout.columnSpan: 2
Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge
color: palette.text
font.bold: true
text: qsTr("GENERAL SETTINGS")
}
Label {
text: qsTr("Encryption")
color: palette.text
text: qsTr("Encryption")
}
ToggleButton {
id: encryptionToggle
Layout.alignment: Qt.AlignRight
checked: roomSettings.isEncryptionEnabled
onCheckedChanged: {
if (roomSettings.isEncryptionEnabled) {
checked = true;
return ;
return;
}
if (checked === true)
confirmEncryptionDialog.open();
}
Layout.alignment: Qt.AlignRight
}
Platform.MessageDialog {
id: confirmEncryptionDialog
title: qsTr("End-to-End Encryption")
buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel
modality: Qt.NonModal
text: qsTr(`Encryption is currently experimental and things might break unexpectedly. <br>
Please take note that it can't be disabled afterwards.`)
modality: Qt.NonModal
title: qsTr("End-to-End Encryption")
onAccepted: {
if (roomSettings.isEncryptionEnabled)
return ;
return;
roomSettings.enableEncryption();
}
onRejected: {
encryptionToggle.checked = false;
}
buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel
}
Label {
text: qsTr("Permission")
color: palette.text
text: qsTr("Permission")
}
Button {
text: qsTr("Configure")
Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("View and change the permissions in this room")
text: qsTr("Configure")
onClicked: timelineRoot.showPLEditor(roomSettings)
Layout.alignment: Qt.AlignRight
}
Label {
text: qsTr("Aliases")
color: palette.text
text: qsTr("Aliases")
}
Button {
text: qsTr("Configure")
Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("View and change the addresses/aliases of this room")
text: qsTr("Configure")
onClicked: timelineRoot.showAliasEditor(roomSettings)
Layout.alignment: Qt.AlignRight
}
Label {
text: qsTr("Sticker & Emote Settings")
color: palette.text
text: qsTr("Sticker & Emote Settings")
}
Button {
text: qsTr("Change")
Layout.alignment: Qt.AlignRight
ToolTip.text: qsTr("Change what packs are enabled, remove packs, or create new ones")
text: qsTr("Change")
onClicked: TimelineManager.openImagePackSettings(roomSettings.roomId)
Layout.alignment: Qt.AlignRight
}
Label {
text: qsTr("INFO")
font.bold: true
color: palette.text
Layout.columnSpan: 2
Layout.topMargin: Nheko.paddingLarge
Layout.fillWidth: true
Layout.topMargin: Nheko.paddingLarge
color: palette.text
font.bold: true
text: qsTr("INFO")
}
Label {
text: qsTr("Internal ID")
color: palette.text
text: qsTr("Internal ID")
}
AbstractButton { // AbstractButton does not allow setting text color
AbstractButton {
// AbstractButton does not allow setting text color
Layout.alignment: Qt.AlignRight
Layout.fillWidth: true
Layout.preferredHeight: idLabel.height
Label { // TextEdit does not trigger onClicked
onClicked: {
textEdit.selectAll();
textEdit.copy();
toolTipTimer.start();
}
Label {
// TextEdit does not trigger onClicked
id: idLabel
text: roomSettings.roomId
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 0.8)
ToolTip.text: qsTr("Copied to clipboard")
ToolTip.visible: toolTipTimer.running
color: palette.text
width: parent.width
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 0.8)
horizontalAlignment: Text.AlignRight
text: roomSettings.roomId
width: parent.width
wrapMode: Text.WrapAnywhere
ToolTip.text: qsTr("Copied to clipboard")
ToolTip.visible: toolTipTimer.running
}
TextEdit{ // label does not allow selection
TextEdit {
// label does not allow selection
id: textEdit
visible: false
text: roomSettings.roomId
}
onClicked: {
textEdit.selectAll()
textEdit.copy()
toolTipTimer.start()
visible: false
}
Timer {
id: toolTipTimer
}
}
Label {
text: qsTr("Room Version")
color: palette.text
text: qsTr("Room Version")
}
Label {
text: roomSettings.roomVersion
font.pixelSize: fontMetrics.font.pixelSize
Layout.alignment: Qt.AlignRight
color: palette.text
font.pixelSize: fontMetrics.font.pixelSize
text: roomSettings.roomVersion
}
}
}
}
Button {
id: showMoreButton
anchors.horizontalCenter: flickable.horizontalCenter
y: Math.min(showMorePlaceholder.y+contentLayout1.y-flickable.contentY,flickable.height-height)
text: roomTopic.showMore ? qsTr("show less") : qsTr("show more")
visible: roomTopic.cut
text: roomTopic.showMore? qsTr("show less") : qsTr("show more")
onClicked: {roomTopic.showMore = !roomTopic.showMore
console.log(flickable.visibleArea)
}
y: Math.min(showMorePlaceholder.y + contentLayout1.y - flickable.contentY, flickable.height - height)
onClicked: {
roomTopic.showMore = !roomTopic.showMore;
console.log(flickable.visibleArea);
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: close()
}
}

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

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

@ -13,147 +13,139 @@ import "../"
Item {
id: loginPage
property int maxExpansion: 400
property string error: login.error
property int maxExpansion: 400
Login {
id: login
}
}
ScrollView {
id: scroll
clip: false
ScrollBar.horizontal.visible: false
anchors.left: parent.left
anchors.margins: Nheko.paddingLarge
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: Math.min(loginPage.height, col.implicitHeight)
anchors.margins: Nheko.paddingLarge
clip: false
contentWidth: availableWidth
height: Math.min(loginPage.height, col.implicitHeight)
ColumnLayout {
id: col
spacing: Nheko.paddingMedium
anchors.horizontalCenter: parent.horizontalCenter
width: Math.min(loginPage.maxExpansion, scroll.width- Nheko.paddingLarge*2)
spacing: Nheko.paddingMedium
width: Math.min(loginPage.maxExpansion, scroll.width - Nheko.paddingLarge * 2)
Image {
Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/login.png"
Layout.preferredHeight: 128
Layout.preferredWidth: 128
source: "qrc:/logos/login.png"
}
RowLayout {
Layout.fillWidth: true
spacing: Nheko.paddingLarge
Layout.fillWidth: true
MatrixTextField {
id: matrixIdLabel
Keys.forwardTo: [pwBtn, ssoRepeater]
ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user ID. After the user ID you need to include your server name after a :.\nYou can also put your homeserver address there if your server doesn't support .well-known lookup.\nExample: @user:yourserver.example.com\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.")
label: qsTr("Matrix ID")
placeholderText: qsTr("e.g @user:yourserver.example.com")
onEditingFinished: login.mxid = text
ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user ID. After the user ID you need to include your server name after a :.\nYou can also put your homeserver address there if your server doesn't support .well-known lookup.\nExample: @user:yourserver.example.com\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.")
Keys.forwardTo: [pwBtn, ssoRepeater]
onEditingFinished: login.mxid = text
}
Spinner {
Layout.preferredHeight: matrixIdLabel.height/2
Layout.alignment: Qt.AlignBottom
visible: running
running: login.lookingUpHs
Layout.preferredHeight: matrixIdLabel.height / 2
foreground: palette.mid
running: login.lookingUpHs
visible: running
}
}
MatrixText {
Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error
text: login.mxidError
textFormat: Text.PlainText
visible: text
wrapMode: TextEdit.Wrap
}
MatrixTextField {
id: passwordLabel
Keys.forwardTo: [pwBtn, ssoRepeater]
Layout.fillWidth: true
label: qsTr("Password")
echoMode: TextInput.Password
ToolTip.text: qsTr("Your password.")
echoMode: TextInput.Password
label: qsTr("Password")
visible: login.passwordSupported
Keys.forwardTo: [pwBtn, ssoRepeater]
}
MatrixTextField {
id: deviceNameLabel
Keys.forwardTo: [pwBtn, ssoRepeater]
Layout.fillWidth: true
ToolTip.text: qsTr("A name for this device which will be shown to others when verifying your devices. If nothing is provided, a default is used.")
label: qsTr("Device name")
placeholderText: login.initialDeviceName()
ToolTip.text: qsTr("A name for this device which will be shown to others when verifying your devices. If nothing is provided, a default is used.")
Keys.forwardTo: [pwBtn, ssoRepeater]
}
MatrixTextField {
id: hsLabel
enabled: visible
visible: login.homeserverNeeded
Keys.forwardTo: [pwBtn, ssoRepeater]
Layout.fillWidth: true
ToolTip.text: qsTr("The address that can be used to contact your homeserver's client API.\nExample: https://yourserver.example.com:8787")
enabled: visible
label: qsTr("Homeserver address")
placeholderText: qsTr("yourserver.example.com:8787")
text: login.homeserver
visible: login.homeserverNeeded
onEditingFinished: login.homeserver = text
ToolTip.text: qsTr("The address that can be used to contact your homeserver's client API.\nExample: https://yourserver.example.com:8787")
Keys.forwardTo: [pwBtn, ssoRepeater]
}
Item {
Layout.preferredHeight: Nheko.avatarSize
Layout.fillWidth: true
Layout.preferredHeight: Nheko.avatarSize
Spinner {
height: parent.height
anchors.centerIn: parent
visible: running
running: login.loggingIn
foreground: palette.mid
height: parent.height
running: login.loggingIn
visible: running
}
}
MatrixText {
Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error
text: loginPage.error
textFormat: Text.PlainText
visible: text
wrapMode: TextEdit.Wrap
}
FlatButton {
id: pwBtn
visible: login.passwordSupported
enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
Layout.alignment: Qt.AlignHCenter
text: qsTr("LOGIN")
function pwLogin() {
login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text)
login.onLoginButtonClicked(Login.Password, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text);
}
onClicked: pwBtn.pwLogin()
Keys.enabled: pwBtn.enabled && login.passwordSupported
Layout.alignment: Qt.AlignHCenter
enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
text: qsTr("LOGIN")
visible: login.passwordSupported
Keys.onEnterPressed: pwBtn.pwLogin()
Keys.onReturnPressed: pwBtn.pwLogin()
Keys.enabled: pwBtn.enabled && login.passwordSupported
onClicked: pwBtn.pwLogin()
}
Repeater {
id: ssoRepeater
@ -161,32 +153,35 @@ Item {
delegate: FlatButton {
id: ssoBtn
visible: login.ssoSupported
enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
Layout.alignment: Qt.AlignHCenter
text: modelData.name
iconImage: modelData.avatarUrl.replace("mxc://", "image://MxcImage/")
function ssoLogin() {
login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, modelData.id, deviceNameLabel.text)
login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, modelData.id, deviceNameLabel.text);
}
onClicked: ssoBtn.ssoLogin()
Keys.enabled: ssoBtn.enabled && !login.passwordSupported
Layout.alignment: Qt.AlignHCenter
enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
iconImage: modelData.avatarUrl.replace("mxc://", "image://MxcImage/")
text: modelData.name
visible: login.ssoSupported
Keys.onEnterPressed: ssoBtn.ssoLogin()
Keys.onReturnPressed: ssoBtn.ssoLogin()
Keys.enabled: ssoBtn.enabled && !login.passwordSupported
onClicked: ssoBtn.ssoLogin()
}
}
}
}
ImageButton {
anchors.top: parent.top
ToolTip.text: qsTr("Back")
ToolTip.visible: hovered
anchors.left: parent.left
anchors.margins: Nheko.paddingMedium
width: Nheko.avatarSize
anchors.top: parent.top
height: Nheko.avatarSize
image: ":/icons/icons/ui/angle-arrow-left.svg"
ToolTip.visible: hovered
ToolTip.text: qsTr("Back")
width: Nheko.avatarSize
onClicked: mainWindow.pop()
}
}

@ -13,208 +13,197 @@ import "../"
Item {
id: registrationPage
property int maxExpansion: 400
property string error: regis.error
property int maxExpansion: 400
Registration {
id: regis
}
}
ScrollView {
id: scroll
clip: false
ScrollBar.horizontal.visible: false
anchors.left: parent.left
anchors.margins: Nheko.paddingLarge
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: Math.min(registrationPage.height, col.implicitHeight)
anchors.margins: Nheko.paddingLarge
clip: false
contentWidth: availableWidth
height: Math.min(registrationPage.height, col.implicitHeight)
ColumnLayout {
id: col
spacing: Nheko.paddingMedium
anchors.horizontalCenter: parent.horizontalCenter
width: Math.min(registrationPage.maxExpansion, scroll.width- Nheko.paddingLarge*2)
spacing: Nheko.paddingMedium
width: Math.min(registrationPage.maxExpansion, scroll.width - Nheko.paddingLarge * 2)
Image {
Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/login.png"
Layout.preferredHeight: 128
Layout.preferredWidth: 128
source: "qrc:/logos/login.png"
}
RowLayout {
Layout.fillWidth: true
spacing: Nheko.paddingLarge
Layout.fillWidth: true
MatrixTextField {
id: hsLabel
ToolTip.text: qsTr("A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own.")
label: qsTr("Homeserver")
placeholderText: qsTr("your.server")
onEditingFinished: regis.setServer(text)
ToolTip.text: qsTr("A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own.")
onEditingFinished: regis.setServer(text)
}
Spinner {
Layout.preferredHeight: hsLabel.height/2
Layout.alignment: Qt.AlignBottom
visible: running
running: regis.lookingUpHs
Layout.preferredHeight: hsLabel.height / 2
foreground: palette.mid
running: regis.lookingUpHs
visible: running
}
}
MatrixText {
Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error
text: regis.hsError
textFormat: Text.PlainText
visible: text
wrapMode: TextEdit.Wrap
}
RowLayout {
Layout.fillWidth: true
spacing: Nheko.paddingLarge
visible: regis.supported
Layout.fillWidth: true
MatrixTextField {
id: usernameLabel
Layout.fillWidth: true
label: qsTr("Username")
ToolTip.text: qsTr("The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /.")
label: qsTr("Username")
onEditingFinished: regis.checkUsername(text)
}
Spinner {
Layout.preferredHeight: usernameLabel.height/2
Layout.alignment: Qt.AlignBottom
visible: running
running: regis.lookingUpUsername
Layout.preferredHeight: usernameLabel.height / 2
foreground: palette.mid
running: regis.lookingUpUsername
visible: running
}
Image {
Layout.preferredHeight: usernameLabel.height/2
Layout.preferredWidth: usernameLabel.height/2
Layout.alignment: Qt.AlignBottom
source: regis.usernameAvailable ? ("image://colorimage/:/icons/icons/ui/checkmark.svg?green") : ("image://colorimage/:/icons/icons/ui/dismiss.svg?"+Nheko.theme.error)
visible: regis.usernameAvailable || regis.usernameUnavailable
ToolTip.visible: ma.hovered
Layout.preferredHeight: usernameLabel.height / 2
Layout.preferredWidth: usernameLabel.height / 2
ToolTip.text: qsTr("Back")
ToolTip.visible: ma.hovered
source: regis.usernameAvailable ? ("image://colorimage/:/icons/icons/ui/checkmark.svg?green") : ("image://colorimage/:/icons/icons/ui/dismiss.svg?" + Nheko.theme.error)
sourceSize.height: height * Screen.devicePixelRatio
sourceSize.width: width * Screen.devicePixelRatio
visible: regis.usernameAvailable || regis.usernameUnavailable
HoverHandler {
id: ma
}
}
}
MatrixText {
Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error
text: regis.usernameError
textFormat: Text.PlainText
visible: text && regis.supported
wrapMode: TextEdit.Wrap
}
MatrixTextField {
visible: regis.supported
id: passwordLabel
Layout.fillWidth: true
label: qsTr("Password")
echoMode: TextInput.Password
ToolTip.text: qsTr("Please choose a secure password. The exact requirements for password strength may depend on your server.")
echoMode: TextInput.Password
label: qsTr("Password")
visible: regis.supported
}
MatrixTextField {
visible: regis.supported
id: passwordConfirmationLabel
Layout.fillWidth: true
label: qsTr("Password confirmation")
echoMode: TextInput.Password
label: qsTr("Password confirmation")
visible: regis.supported
}
MatrixText {
Layout.fillWidth: true
visible: regis.supported
textFormat: Text.PlainText
color: Nheko.theme.error
text: passwordLabel.text != passwordConfirmationLabel.text ? qsTr("Your passwords do not match!") : ""
textFormat: Text.PlainText
visible: regis.supported
wrapMode: TextEdit.Wrap
}
MatrixTextField {
visible: regis.supported
id: deviceNameLabel
Layout.fillWidth: true
ToolTip.text: qsTr("A name for this device which will be shown to others when verifying your devices. If nothing is provided a default is used.")
label: qsTr("Device name")
placeholderText: regis.initialDeviceName()
ToolTip.text: qsTr("A name for this device which will be shown to others when verifying your devices. If nothing is provided a default is used.")
visible: regis.supported
}
Item {
Layout.preferredHeight: Nheko.avatarSize
Layout.fillWidth: true
Layout.preferredHeight: Nheko.avatarSize
Spinner {
height: parent.height
anchors.centerIn: parent
visible: running
running: regis.registering
foreground: palette.mid
height: parent.height
running: regis.registering
visible: running
}
}
MatrixText {
Layout.fillWidth: true
textFormat: Text.PlainText
color: Nheko.theme.error
text: registrationPage.error
textFormat: Text.PlainText
visible: text
wrapMode: TextEdit.Wrap
}
FlatButton {
id: regisBtn
visible: regis.supported
enabled: usernameLabel.text && passwordLabel.text && passwordLabel.text == passwordConfirmationLabel.text
Layout.alignment: Qt.AlignHCenter
text: qsTr("REGISTER")
function register() {
regis.startRegistration(usernameLabel.text, passwordLabel.text, deviceNameLabel.text)
regis.startRegistration(usernameLabel.text, passwordLabel.text, deviceNameLabel.text);
}
onClicked: regisBtn.register()
Keys.enabled: regisBtn.enabled && regis.supported
Layout.alignment: Qt.AlignHCenter
enabled: usernameLabel.text && passwordLabel.text && passwordLabel.text == passwordConfirmationLabel.text
text: qsTr("REGISTER")
visible: regis.supported
Keys.onEnterPressed: regisBtn.register()
Keys.onReturnPressed: regisBtn.register()
Keys.enabled: regisBtn.enabled && regis.supported
onClicked: regisBtn.register()
}
}
}
ImageButton {
anchors.top: parent.top
ToolTip.text: qsTr("Back")
ToolTip.visible: hovered
anchors.left: parent.left
anchors.margins: Nheko.paddingMedium
width: Nheko.avatarSize
anchors.top: parent.top
height: Nheko.avatarSize
image: ":/icons/icons/ui/angle-arrow-left.svg"
ToolTip.visible: hovered
ToolTip.text: qsTr("Back")
width: Nheko.avatarSize
onClicked: mainWindow.pop()
}
}

@ -16,6 +16,7 @@ Rectangle {
property int collapsePoint: 600
property bool collapsed: width < collapsePoint
color: palette.window
ScrollView {
@ -23,87 +24,91 @@ Rectangle {
ScrollBar.horizontal.visible: false
anchors.fill: parent
anchors.topMargin: (collapsed? backButton.height : 0)+Nheko.paddingLarge
leftPadding: collapsed? Nheko.paddingMedium : Nheko.paddingLarge
anchors.topMargin: (collapsed ? backButton.height : 0) + Nheko.paddingLarge
bottomPadding: Nheko.paddingLarge
contentWidth: availableWidth
leftPadding: collapsed ? Nheko.paddingMedium : Nheko.paddingLarge
ColumnLayout {
id: grid
spacing: Nheko.paddingMedium
width: scroll.availableWidth
anchors.fill: parent
anchors.leftMargin: userSettingsDialog.collapsed ? 0 : (userSettingsDialog.width-userSettingsDialog.collapsePoint) * 0.4 + Nheko.paddingLarge
anchors.leftMargin: userSettingsDialog.collapsed ? 0 : (userSettingsDialog.width - userSettingsDialog.collapsePoint) * 0.4 + Nheko.paddingLarge
anchors.rightMargin: anchors.leftMargin
spacing: Nheko.paddingMedium
width: scroll.availableWidth
Repeater {
model: UserSettingsModel
delegate: GridLayout {
width: scroll.availableWidth
columns: collapsed? 1 : 2
rows: collapsed? 2: 1
required property var model
id: r
required property var model
columns: collapsed ? 1 : 2
rows: collapsed ? 2 : 1
width: scroll.availableWidth
Label {
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
color: palette.text
text: model.name
//Layout.column: 0
Layout.columnSpan: (model.type == UserSettingsModel.SectionTitle && !userSettingsDialog.collapsed) ? 2 : 1
Layout.fillWidth: true
//Layout.row: model.index
//Layout.minimumWidth: implicitWidth
Layout.leftMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium
Layout.topMargin: model.type == UserSettingsModel.SectionTitle ? Nheko.paddingLarge : 0
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: model.description ?? ""
ToolTip.visible: hovered.hovered && model.description
color: palette.text
font.pointSize: 1.1 * fontMetrics.font.pointSize
text: model.name
wrapMode: Text.Wrap
HoverHandler {
id: hovered
enabled: model.description ?? false
}
ToolTip.visible: hovered.hovered && model.description
ToolTip.text: model.description ?? ""
ToolTip.delay: Nheko.tooltipDelay
wrapMode: Text.Wrap
}
DelegateChooser {
id: chooser
roleValue: model.type
Layout.alignment: Qt.AlignRight
Layout.columnSpan: (model.type == UserSettingsModel.SectionTitle && !userSettingsDialog.collapsed) ? 2 : 1
Layout.fillWidth: model.type == UserSettingsModel.SectionTitle || model.type == UserSettingsModel.Options || model.type == UserSettingsModel.Number
Layout.maximumWidth: model.type == UserSettingsModel.SectionTitle ? Number.POSITIVE_INFINITY : 400
Layout.preferredHeight: child.height
Layout.preferredWidth: child.implicitWidth
Layout.maximumWidth: model.type == UserSettingsModel.SectionTitle ? Number.POSITIVE_INFINITY : 400
Layout.fillWidth: model.type == UserSettingsModel.SectionTitle || model.type == UserSettingsModel.Options || model.type == UserSettingsModel.Number
Layout.rightMargin: model.type == UserSettingsModel.SectionTitle ? 0 : Nheko.paddingMedium
roleValue: model.type
DelegateChoice {
roleValue: UserSettingsModel.Toggle
ToggleButton {
checked: model.value
onCheckedChanged: model.value = checked
enabled: model.enabled
onCheckedChanged: model.value = checked
}
}
DelegateChoice {
roleValue: UserSettingsModel.Options
ComboBox {
anchors.right: parent.right
model: r.model.values
currentIndex: r.model.value
implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted
model: r.model.values
width: Math.min(implicitWidth, scroll.availableWidth - Nheko.paddingMedium)
onCurrentIndexChanged: r.model.value = currentIndex
implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted
WheelHandler{} // suppress scrolling changing values
WheelHandler {
} // suppress scrolling changing values
}
}
DelegateChoice {
@ -111,14 +116,16 @@ Rectangle {
SpinBox {
anchors.right: parent.right
editable: true
from: model.valueLowerBound
to: model.valueUpperBound
stepSize: model.valueStep
to: model.valueUpperBound
value: model.value
onValueChanged: model.value = value
editable: true
WheelHandler{} // suppress scrolling changing values
WheelHandler {
} // suppress scrolling changing values
}
}
DelegateChoice {
@ -127,54 +134,56 @@ Rectangle {
SpinBox {
id: spinbox
readonly property double div: 100
readonly property int decimals: 2
readonly property double div: 100
property real realValue: value / div
anchors.right: parent.right
editable: true
from: model.valueLowerBound * div
to: model.valueUpperBound * div
stepSize: model.valueStep * div
textFromValue: function (value, locale) {
return Number(value / spinbox.div).toLocaleString(locale, 'f', spinbox.decimals);
}
to: model.valueUpperBound * div
value: model.value * div
onValueChanged: model.value = value/div
editable: true
property real realValue: value / div
validator: DoubleValidator {
bottom: Math.min(spinbox.from/spinbox.div, spinbox.to/spinbox.div)
top: Math.max(spinbox.from/spinbox.div, spinbox.to/spinbox.div)
valueFromText: function (text, locale) {
return Number.fromLocaleString(locale, text) * spinbox.div;
}
textFromValue: function(value, locale) {
return Number(value / spinbox.div).toLocaleString(locale, 'f', spinbox.decimals)
validator: DoubleValidator {
bottom: Math.min(spinbox.from / spinbox.div, spinbox.to / spinbox.div)
top: Math.max(spinbox.from / spinbox.div, spinbox.to / spinbox.div)
}
valueFromText: function(text, locale) {
return Number.fromLocaleString(locale, text) * spinbox.div
}
onValueChanged: model.value = value / div
WheelHandler{} // suppress scrolling changing values
WheelHandler {
} // suppress scrolling changing values
}
}
DelegateChoice {
roleValue: UserSettingsModel.ReadOnlyText
TextEdit {
color: palette.text
text: model.value
readOnly: true
text: model.value
textFormat: Text.PlainText
}
}
DelegateChoice {
roleValue: UserSettingsModel.SectionTitle
Item {
width: grid.width
height: fontMetrics.lineSpacing
width: grid.width
Rectangle {
anchors.topMargin: Nheko.paddingSmall
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: Nheko.paddingSmall
color: palette.buttonText
height: 1
}
@ -182,6 +191,7 @@ Rectangle {
}
DelegateChoice {
roleValue: UserSettingsModel.KeyStatus
Text {
color: model.good ? "green" : Nheko.theme.error
text: model.value ? qsTr("CACHED") : qsTr("NOT CACHED")
@ -189,34 +199,42 @@ Rectangle {
}
DelegateChoice {
roleValue: UserSettingsModel.SessionKeyImportExport
RowLayout {
Button {
text: qsTr("IMPORT")
onClicked: UserSettingsModel.importSessionKeys()
}
Button {
text: qsTr("EXPORT")
onClicked: UserSettingsModel.exportSessionKeys()
}
}
}
DelegateChoice {
roleValue: UserSettingsModel.XSignKeysRequestDownload
RowLayout {
Button {
text: qsTr("DOWNLOAD")
onClicked: UserSettingsModel.downloadCrossSigningSecrets()
}
Button {
text: qsTr("REQUEST")
onClicked: UserSettingsModel.requestCrossSigningSecrets()
}
}
}
DelegateChoice {
roleValue: UserSettingsModel.ConfigureHiddenEvents
Button {
text: qsTr("CONFIGURE")
onClicked: {
var dialog = hiddenEventsDialog.createObject();
dialog.show();
@ -226,15 +244,17 @@ Rectangle {
Component {
id: hiddenEventsDialog
HiddenEventsDialog {}
HiddenEventsDialog {
}
}
}
}
DelegateChoice {
roleValue: UserSettingsModel.ManageIgnoredUsers
Button {
text: qsTr("MANAGE")
onClicked: {
var dialog = ignoredUsersDialog.createObject();
dialog.show();
@ -244,11 +264,11 @@ Rectangle {
Component {
id: ignoredUsersDialog
IgnoredUsers {}
IgnoredUsers {
}
}
}
}
DelegateChoice {
Text {
text: model.value
@ -259,19 +279,18 @@ Rectangle {
}
}
}
ImageButton {
id: backButton
anchors.top: parent.top
ToolTip.text: qsTr("Back")
ToolTip.visible: hovered
anchors.left: parent.left
anchors.margins: Nheko.paddingMedium
width: Nheko.avatarSize
anchors.top: parent.top
height: Nheko.avatarSize
image: ":/icons/icons/ui/angle-arrow-left.svg"
ToolTip.visible: hovered
ToolTip.text: qsTr("Back")
width: Nheko.avatarSize
onClicked: mainWindow.pop()
}
}

@ -14,86 +14,83 @@ ColumnLayout {
Item {
Layout.fillHeight: true
}
Image {
Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/splash.png"
Layout.preferredHeight: 256
Layout.preferredWidth: 256
source: "qrc:/logos/splash.png"
}
Label {
Layout.margins: Nheko.paddingLarge
Layout.bottomMargin: 0
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 0
Layout.fillWidth: true
text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.")
Layout.margins: Nheko.paddingLarge
color: palette.text
font.pointSize: fontMetrics.font.pointSize*2
wrapMode: Text.Wrap
font.pointSize: fontMetrics.font.pointSize * 2
horizontalAlignment: Text.AlignHCenter
text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.")
wrapMode: Text.Wrap
}
Label {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
text: qsTr("Enjoy your stay!")
Layout.margins: Nheko.paddingLarge
color: palette.text
font.pointSize: fontMetrics.font.pointSize*1.5
wrapMode: Text.Wrap
font.pointSize: fontMetrics.font.pointSize * 1.5
horizontalAlignment: Text.AlignHCenter
text: qsTr("Enjoy your stay!")
wrapMode: Text.Wrap
}
RowLayout {
Item {
Layout.fillWidth: true
}
FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingLarge
text: qsTr("REGISTER")
onClicked: {
mainWindow.push(registerPage);
}
}
FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingLarge
text: qsTr("LOGIN")
onClicked: {
mainWindow.push(loginPage);
}
}
Item {
Layout.fillWidth: true
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingLarge
ToggleButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignRight
Layout.margins: Nheko.paddingLarge
checked: Settings.reducedMotion
onCheckedChanged: Settings.reducedMotion = checked
}
Label {
Layout.alignment: Qt.AlignLeft
Layout.margins: Nheko.paddingLarge
text: qsTr("Reduce animations")
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Nheko uses animations in several places to make stuff pretty. This allows you to turn those off if they make you feel unwell.")
ToolTip.visible: hovered.hovered
color: palette.text
text: qsTr("Reduce animations")
HoverHandler {
id: hovered
}
ToolTip.visible: hovered.hovered
ToolTip.text: qsTr("Nheko uses animations in several places to make stuff pretty. This allows you to turn those off if they make you feel unwell.")
ToolTip.delay: Nheko.tooltipDelay
}
}
Item {

@ -9,42 +9,39 @@ import im.nheko 1.0
Slider {
id: control
property color progressColor: palette.highlight
property bool alwaysShowSlider: true
property color progressColor: palette.highlight
property int sliderRadius: 16
value: 0
implicitHeight: sliderRadius
padding: 0
value: 0
background: Rectangle {
x: control.leftPadding + handle.width / 2
y: control.topPadding + control.availableHeight / 2 - height / 2
implicitWidth: 200
implicitHeight: control.sliderRadius / 4
width: control.availableWidth - handle.width
color: palette.buttonText
height: implicitHeight
implicitHeight: control.sliderRadius / 4
implicitWidth: 200
radius: height / 2
color: palette.buttonText
width: control.availableWidth - handle.width
x: control.leftPadding + handle.width / 2
y: control.topPadding + control.availableHeight / 2 - height / 2
Rectangle {
width: control.visualPosition * parent.width
height: parent.height
color: control.progressColor
height: parent.height
radius: 2
width: control.visualPosition * parent.width
}
}
handle: Rectangle {
x: control.leftPadding + control.visualPosition * background.width
y: control.topPadding + control.availableHeight / 2 - height / 2
implicitWidth: control.sliderRadius
border.color: control.progressColor
color: control.progressColor
implicitHeight: control.sliderRadius
implicitWidth: control.sliderRadius
radius: control.sliderRadius / 2
color: control.progressColor
visible: Settings.mobileMode || control.alwaysShowSlider || control.hovered || control.pressed
border.color: control.progressColor
x: control.leftPadding + control.visualPosition * background.width
y: control.topPadding + control.availableHeight / 2 - height / 2
}
}

@ -9,9 +9,9 @@ Item {
property color color: "#22000000"
property real maxRadius: Math.max(width, height)
readonly property real opacityAnimationDuration: 300
readonly property real radiusAnimationRate: 0.05
readonly property real radiusTailAnimationRate: 0.5
readonly property real opacityAnimationDuration: 300
property var rippleTarget: parent
anchors.fill: parent
@ -19,18 +19,13 @@ Item {
PointHandler {
id: ph
onGrabChanged: (_, point) => {
circle.centerX = point.position.x
circle.centerY = point.position.y
}
target: Rectangle {
id: backgroundLayer
parent: rippleTarget
anchors.fill: parent
color: "transparent"
clip: true
color: "transparent"
parent: rippleTarget
Rectangle {
id: circle
@ -38,15 +33,14 @@ Item {
property real centerX
property real centerY
color: ripple.color
height: radius * 2
radius: 0
state: ph.active ? "ACTIVE" : "NORMAL"
width: radius * 2
x: centerX - radius
y: centerY - radius
height: radius*2
width: radius*2
radius: 0
color: ripple.color
state: ph.active ? "ACTIVE" : "NORMAL"
states: [
State {
name: "NORMAL"
@ -63,26 +57,30 @@ Item {
SequentialAnimation {
//PropertyAction { target: circle; property: "centerX"; value: ph.point.position.x }
//PropertyAction { target: circle; property: "centerY"; value: ph.point.position.y }
PropertyAction { target: circle; property: "visible"; value: true }
PropertyAction { target: circle; property: "opacity"; value: 1 }
PropertyAction {
property: "visible"
target: circle
value: true
}
PropertyAction {
property: "opacity"
target: circle
value: 1
}
NumberAnimation {
id: radius_animation
target: circle
properties: "radius"
duration: ripple.maxRadius / ripple.radiusAnimationRate
from: 0
properties: "radius"
target: circle
to: ripple.maxRadius
duration: ripple.maxRadius / ripple.radiusAnimationRate
easing {
type: Easing.OutQuad
}
}
}
},
Transition {
from: "ACTIVE"
@ -93,37 +91,42 @@ Item {
NumberAnimation {
id: radius_tail_animation
target: circle
duration: ripple.maxRadius / ripple.radiusTailAnimationRate
properties: "radius"
target: circle
to: ripple.maxRadius
duration: ripple.maxRadius / ripple.radiusTailAnimationRate
easing {
type: Easing.Linear
}
}
NumberAnimation {
id: opacity_animation
target: circle
duration: ripple.opacityAnimationDuration
properties: "opacity"
target: circle
to: 0
duration: ripple.opacityAnimationDuration
easing {
type: Easing.InQuad
}
}
}
PropertyAction { target: circle; property: "visible"; value: false }
PropertyAction {
property: "visible"
target: circle
value: false
}
}
}
]
}
}
onGrabChanged: (_, point) => {
circle.centerX = point.position.x;
circle.centerY = point.position.y;
}
}
}

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

@ -9,15 +9,15 @@ import QtQuick.Effects
Item {
id: spinner
property int spacing: 0
property bool running: true
property var foreground: "#333"
readonly property int barCount: 6
readonly property real a: Math.PI / 6
readonly property var colors: ["#c0def5", "#87aade", "white"]
readonly property var anims: [anim1, anim2, anim3, anim4, anim5, anim6]
readonly property int pauseDuration: barCount * 150
readonly property int barCount: 6
readonly property var colors: ["#c0def5", "#87aade", "white"]
property var foreground: "#333"
readonly property int glowDuration: 300
readonly property int pauseDuration: barCount * 150
property bool running: true
property int spacing: 0
height: 40
width: barCount * (height * 0.375)
@ -25,131 +25,116 @@ Item {
Row {
id: row
transform: Matrix4x4 {
matrix: Qt.matrix4x4(Math.cos(spinner.a), -Math.sin(spinner.a), 0, 0, 0, Math.cos(spinner.a), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
}
Rectangle {
id: rect1
width: ((spinner.width / spinner.barCount) - (spinner.spacing)) * 1.5
height: spinner.height / 3.5
color: "white"
height: spinner.height / 3.5
width: ((spinner.width / spinner.barCount) - (spinner.spacing)) * 1.5
}
Rectangle {
id: rect2
width: (spinner.width / spinner.barCount) - spinner.spacing
height: spinner.height
color: spinner.colors[0]
height: spinner.height
width: (spinner.width / spinner.barCount) - spinner.spacing
}
Rectangle {
id: rect3
width: (spinner.width / spinner.barCount) - spinner.spacing
height: spinner.height
color: spinner.colors[1]
height: spinner.height
width: (spinner.width / spinner.barCount) - spinner.spacing
}
Rectangle {
id: rect4
width: (spinner.width / spinner.barCount) - spinner.spacing
height: spinner.height
color: spinner.colors[2]
height: spinner.height
width: (spinner.width / spinner.barCount) - spinner.spacing
}
Rectangle {
id: rect5
width: (spinner.width / (spinner.barCount + 1)) - spinner.spacing
height: spinner.height / 3.5
color: "white"
height: spinner.height / 3.5
width: (spinner.width / (spinner.barCount + 1)) - spinner.spacing
}
Rectangle {
id: rect6
width: (spinner.width / spinner.barCount) - spinner.spacing
height: spinner.height
color: "white"
height: spinner.height
width: (spinner.width / spinner.barCount) - spinner.spacing
}
BlinkAnimation {
id: anim1
target: rect1
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration
offset: 0 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect1
}
BlinkAnimation {
id: anim2
target: rect2
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration
offset: 1 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect2
}
BlinkAnimation {
id: anim3
target: rect3
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration
offset: 2 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect3
}
BlinkAnimation {
id: anim4
target: rect4
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration
offset: 3 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect4
}
BlinkAnimation {
id: anim5
target: rect5
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration
offset: 4 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect5
}
BlinkAnimation {
id: anim6
target: rect6
running: spinner.running
pauseDuration: spinner.pauseDuration
glowDuration: spinner.glowDuration
offset: 5 / spinner.barCount
pauseDuration: spinner.pauseDuration
running: spinner.running
target: rect6
}
transform: Matrix4x4 {
matrix: Qt.matrix4x4(Math.cos(spinner.a), -Math.sin(spinner.a), 0, 0, 0, Math.cos(spinner.a), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
}
}
MultiEffect {
anchors.fill: row
shadowBlur: 14
shadowEnabled: true
shadowColor: spinner.foreground
shadowEnabled: true
source: row
transform: Matrix4x4 {
matrix: Qt.matrix4x4(Math.cos(spinner.a), -Math.sin(spinner.a), 0, 0, 0, Math.cos(spinner.a), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
}
}
}

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

@ -5,27 +5,24 @@
import QtQuick
SequentialAnimation {
property alias target: numberAnimation.target
property alias glowDuration: numberAnimation.duration
property int pauseDuration: 150
property double offset: 0
property int pauseDuration: 150
property alias target: numberAnimation.target
loops: Animation.Infinite
PauseAnimation {
duration: pauseDuration * offset
}
NumberAnimation {
id: numberAnimation
property: "opacity"
from: 0
property: "opacity"
to: 1
}
PauseAnimation {
duration: pauseDuration * (1 - offset)
}
}

@ -14,27 +14,22 @@ Rectangle {
id: control
property alias desiredVolume: volumeSlider.desiredVolume
property var duration
property bool mediaLoaded: false
property var mediaState
property bool muted: false
property bool playingVideo: false
property var mediaState
property bool mediaLoaded: false
property var duration
property var positionValue: 0
property var position
property var positionValue: 0
property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.state == "shown"
signal playPauseActivated()
signal loadActivated()
function showControls() {
controlHideTimer.restart();
}
signal loadActivated
signal playPauseActivated
function durationToString(duration) {
function maybeZeroPrepend(time) {
return (time < 10) ? "0" + time.toString() : time.toString();
}
var totalSeconds = Math.floor(duration / 1000);
var seconds = totalSeconds % 60;
var minutes = (Math.floor(totalSeconds / 60)) % 60;
@ -45,16 +40,25 @@ Rectangle {
var hh = hours.toString();
if (hours < 1)
return mm + ":" + ss;
return hh + ":" + mm + ":" + ss;
}
function showControls() {
controlHideTimer.restart();
}
color: {
var wc = palette.alternateBase;
return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
}
opacity: control.shouldShowControls ? 1 : 0
height: controlLayout.implicitHeight
opacity: control.shouldShowControls ? 1 : 0
// Fade controls in/out
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
HoverHandler {
id: playerMouseArea
@ -63,41 +67,40 @@ Rectangle {
onHoveredChanged: showControls()
}
ColumnLayout {
id: controlLayout
enabled: control.shouldShowControls
spacing: 0
anchors.bottom: control.bottom
anchors.left: control.left
anchors.right: control.right
enabled: control.shouldShowControls
spacing: 0
NhekoSlider {
Layout.fillWidth: true
Layout.leftMargin: Nheko.paddingSmall
Layout.rightMargin: Nheko.paddingSmall
alwaysShowSlider: false
enabled: control.mediaLoaded
value: control.positionValue
onMoved: control.position = value
from: 0
to: control.duration
alwaysShowSlider: false
}
value: control.positionValue
onMoved: control.position = value
}
RowLayout {
Layout.fillWidth: true
Layout.margins: Nheko.paddingSmall
spacing: Nheko.paddingSmall
Layout.fillWidth: true
// Cache/Play/pause button
ImageButton {
id: playbackStateImage
Layout.alignment: Qt.AlignLeft
buttonTextColor: palette.text
Layout.preferredHeight: 24
Layout.preferredWidth: 24
buttonTextColor: palette.text
image: {
if (control.mediaLoaded) {
if (control.mediaState == MediaPlayer.PlayingState)
@ -108,38 +111,47 @@ Rectangle {
return ":/icons/icons/ui/download.svg";
}
}
onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated()
}
ImageButton {
id: volumeButton
Layout.alignment: Qt.AlignLeft
buttonTextColor: palette.text
Layout.preferredHeight: 24
Layout.preferredWidth: 24
buttonTextColor: palette.text
image: {
if (control.muted || control.desiredVolume <= 0)
return ":/icons/icons/ui/volume-off-indicator.svg";
else
return ":/icons/icons/ui/volume-up.svg";
}
onClicked: control.muted = !control.muted
}
NhekoSlider {
id: volumeSlider
property real desiredVolume: volumeSlider.value
state: ""
Layout.alignment: Qt.AlignLeft
Layout.preferredWidth: 0
opacity: 0
orientation: Qt.Horizontal
state: ""
value: 1
onDesiredVolumeChanged: {
control.muted = !(desiredVolume > 0);
states: State {
name: "shown"
when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed
PropertyChanges {
volumeSlider.implicitWidth: 100
}
PropertyChanges {
volumeSlider.opacity: 1
}
}
transitions: [
Transition {
@ -150,20 +162,16 @@ Rectangle {
PauseAnimation {
duration: 50
}
NumberAnimation {
duration: 100
properties: "opacity"
easing.type: Easing.InQuad
properties: "opacity"
}
}
NumberAnimation {
properties: "Layout.preferredWidth"
duration: 150
properties: "Layout.preferredWidth"
}
},
Transition {
from: "shown"
@ -173,54 +181,34 @@ Rectangle {
PauseAnimation {
duration: 100
}
ParallelAnimation {
NumberAnimation {
duration: 100
properties: "opacity"
easing.type: Easing.InQuad
properties: "opacity"
}
NumberAnimation {
properties: "Layout.preferredWidth"
duration: 150
properties: "Layout.preferredWidth"
}
}
}
}
]
states: State {
name: "shown"
when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed
PropertyChanges {
volumeSlider.implicitWidth: 100
}
PropertyChanges {
volumeSlider.opacity: 1
onDesiredVolumeChanged: {
control.muted = !(desiredVolume > 0);
}
}
}
Label {
Layout.alignment: Qt.AlignRight
text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration)
color: palette.text
text: (!control.mediaLoaded ? "-- " : durationToString(control.positionValue)) + " / " + durationToString(control.duration)
}
Item {
Layout.fillWidth: true
}
}
}
// For hiding controls on stationary cursor
@ -230,13 +218,4 @@ Rectangle {
interval: 1500 //ms
repeat: false
}
// Fade controls in/out
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
}

@ -9,51 +9,48 @@ import QtQuick.Layouts 1.2
import im.nheko 1.0
Rectangle {
visible: CallManager.isOnCall
color: callInviteBar.color
implicitHeight: visible ? rowLayout.height + 8 : 0
visible: CallManager.isOnCall
MouseArea {
anchors.fill: parent
onClicked: {
if (CallManager.callType != Voip.VOICE)
stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1;
}
}
RowLayout {
id: rowLayout
anchors.left: parent.left
anchors.leftMargin: 8
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 8
Avatar {
implicitWidth: Nheko.avatarSize
displayName: CallManager.callPartyDisplayName
implicitHeight: Nheko.avatarSize
implicitWidth: Nheko.avatarSize
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
userid: CallManager.callParty
displayName: CallManager.callPartyDisplayName
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
}
Label {
Layout.leftMargin: 8
color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1
text: CallManager.callPartyDisplayName
color: "#000000"
}
Image {
id: callTypeIcon
Layout.leftMargin: 4
Layout.preferredWidth: 24
Layout.preferredHeight: 24
Layout.preferredWidth: 24
}
Item {
states: [
State {
@ -63,7 +60,6 @@ Rectangle {
PropertyChanges {
callTypeIcon.source: "qrc:/icons/icons/ui/place-call.svg"
}
},
State {
name: "VIDEO"
@ -72,7 +68,6 @@ Rectangle {
PropertyChanges {
callTypeIcon.source: "qrc:/icons/icons/ui/video.svg"
}
},
State {
name: "SCREEN"
@ -81,18 +76,15 @@ Rectangle {
PropertyChanges {
callTypeIcon.source: "qrc:/icons/icons/ui/screen-share.svg"
}
}
]
}
Label {
id: callStateLabel
font.pointSize: fontMetrics.font.pointSize * 1.1
color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1
}
Item {
states: [
State {
@ -102,7 +94,6 @@ Rectangle {
PropertyChanges {
callStateLabel.text: qsTr("Calling...")
}
},
State {
name: "CONNECTING"
@ -111,7 +102,6 @@ Rectangle {
PropertyChanges {
callStateLabel.text: qsTr("Connecting...")
}
},
State {
name: "ANSWERSENT"
@ -120,7 +110,6 @@ Rectangle {
PropertyChanges {
callStateLabel.text: qsTr("Connecting...")
}
},
State {
name: "CONNECTED"
@ -129,15 +118,12 @@ Rectangle {
PropertyChanges {
callStateLabel.text: "00:00"
}
PropertyChanges {
callTimer.startTime: Math.floor((new Date()).getTime() / 1000)
}
PropertyChanges {
stackLayout.currentIndex: CallManager.callType != Voip.VOICE ? 1 : 0
}
},
State {
name: "DISCONNECTED"
@ -152,14 +138,12 @@ Rectangle {
// stackLayout.currentIndex: 0
//}
PropertyChanges {
target: stackLayout
currentIndex: 0 // qmllint disable Quick.property-changes-parsed
target: stackLayout
}
}
]
}
Timer {
id: callTimer
@ -170,8 +154,9 @@ Rectangle {
}
interval: 1000
running: CallManager.callState == Voip.CONNECTED
repeat: true
running: CallManager.callState == Voip.CONNECTED
onTriggered: {
var d = new Date();
let seconds = Math.floor(d.getTime() / 1000 - startTime);
@ -181,44 +166,40 @@ Rectangle {
callStateLabel.text = (h ? (pad(h) + ":") : "") + pad(m) + ":" + pad(s);
}
}
Label {
Layout.leftMargin: 16
visible: CallManager.callType == Voip.SCREEN && CallManager.callState == Voip.CONNECTED
text: qsTr("You are screen sharing")
font.pointSize: fontMetrics.font.pointSize * 1.1
color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1
text: qsTr("You are screen sharing")
visible: CallManager.callType == Voip.SCREEN && CallManager.callState == Voip.CONNECTED
}
Item {
Layout.fillWidth: true
}
ImageButton {
visible: CallManager.haveLocalPiP
Layout.preferredWidth: 24
Layout.preferredHeight: 24
Layout.preferredWidth: 24
ToolTip.text: qsTr("Hide/Show Picture-in-Picture")
ToolTip.visible: hovered
buttonTextColor: "#000000"
image: ":/icons/icons/ui/picture-in-picture.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Hide/Show Picture-in-Picture")
image: ":/icons/icons/ui/picture-in-picture.svg"
visible: CallManager.haveLocalPiP
onClicked: CallManager.toggleLocalPiP()
}
ImageButton {
Layout.leftMargin: 8
Layout.rightMargin: 16
Layout.preferredWidth: 24
Layout.preferredHeight: 24
Layout.preferredWidth: 24
Layout.rightMargin: 16
ToolTip.text: CallManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic")
ToolTip.visible: hovered
buttonTextColor: "#000000"
image: CallManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.svg" : ":/icons/icons/ui/microphone-mute.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: CallManager.isMicMuted ? qsTr("Unmute Mic") : qsTr("Mute Mic")
image: CallManager.isMicMuted ? ":/icons/icons/ui/microphone-unmute.svg" : ":/icons/icons/ui/microphone-mute.svg"
onClicked: CallManager.toggleMicMute()
}
}
}

@ -9,79 +9,70 @@ import im.nheko 1.0
Popup {
modal: true
background: Rectangle {
border.color: palette.windowText
color: palette.window
}
// only set the anchors on Qt 5.12 or higher
// see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
Component.onCompleted: {
if (anchors)
anchors.centerIn = parent;
}
ColumnLayout {
spacing: 16
ColumnLayout {
spacing: 8
Layout.topMargin: 8
Layout.leftMargin: 8
Layout.rightMargin: 8
Layout.topMargin: 8
spacing: 8
RowLayout {
Image {
Layout.preferredWidth: 22
Layout.preferredHeight: 22
Layout.preferredWidth: 22
source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + palette.windowText
}
ComboBox {
id: micCombo
Layout.fillWidth: true
model: CallManager.mics
}
}
RowLayout {
visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0
Image {
Layout.preferredWidth: 22
Layout.preferredHeight: 22
Layout.preferredWidth: 22
source: "image://colorimage/:/icons/icons/ui/video-call.svg?" + palette.windowText
}
ComboBox {
id: cameraCombo
Layout.fillWidth: true
model: CallManager.cameras
}
}
}
DialogButtonBox {
Layout.leftMargin: 128
standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
onAccepted: {
Settings.microphone = micCombo.currentText;
if (cameraCombo.visible)
Settings.camera = cameraCombo.currentText;
close();
}
onRejected: {
close();
}
}
}
background: Rectangle {
color: palette.window
border.color: palette.windowText
}
}

@ -12,51 +12,50 @@ Popup {
id: callInv
closePolicy: Popup.NoAutoClose
width: parent.width
height: parent.height
width: parent.width
background: Rectangle {
border.color: palette.windowText
color: palette.window
}
Component {
id: deviceError
DeviceError {
}
}
Connections {
function onNewInviteState() {
if (!CallManager.haveCallInvite)
close();
}
target: CallManager
}
ColumnLayout {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
Label {
Layout.alignment: Qt.AlignCenter
Layout.topMargin: callInv.parent.height / 25
Layout.fillWidth: true
text: CallManager.callPartyDisplayName
font.pointSize: fontMetrics.font.pointSize * 2
Layout.topMargin: callInv.parent.height / 25
color: palette.windowText
font.pointSize: fontMetrics.font.pointSize * 2
horizontalAlignment: Text.AlignHCenter
text: CallManager.callPartyDisplayName
}
Avatar {
Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: callInv.height / 5
Layout.preferredWidth: callInv.height / 5
displayName: CallManager.callPartyDisplayName
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
userid: CallManager.callParty
displayName: CallManager.callPartyDisplayName
}
ColumnLayout {
Layout.alignment: Qt.AlignCenter
Layout.bottomMargin: callInv.height / 25
@ -65,20 +64,17 @@ Popup {
property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg"
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: callInv.height / 10
Layout.preferredHeight: callInv.height / 10
Layout.preferredWidth: callInv.height / 10
source: "image://colorimage/" + image + "?" + palette.windowText
}
Label {
Layout.alignment: Qt.AlignCenter
text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
font.pointSize: fontMetrics.font.pointSize * 2
color: palette.windowText
font.pointSize: fontMetrics.font.pointSize * 2
text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
}
}
ColumnLayout {
id: deviceCombos
@ -91,41 +87,34 @@ Popup {
Layout.alignment: Qt.AlignCenter
Image {
Layout.preferredWidth: deviceCombos.imageSize
Layout.preferredHeight: deviceCombos.imageSize
Layout.preferredWidth: deviceCombos.imageSize
source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + palette.windowText
}
ComboBox {
id: micCombo
Layout.fillWidth: true
model: CallManager.mics
}
}
RowLayout {
visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0
Layout.alignment: Qt.AlignCenter
visible: CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0
Image {
Layout.preferredWidth: deviceCombos.imageSize
Layout.preferredHeight: deviceCombos.imageSize
Layout.preferredWidth: deviceCombos.imageSize
source: "image://colorimage/:/icons/icons/ui/video.svg?" + palette.windowText
}
ComboBox {
id: cameraCombo
Layout.fillWidth: true
model: CallManager.cameras
}
}
}
RowLayout {
id: buttonLayout
@ -148,60 +137,48 @@ Popup {
spacing: callInv.height / 6
RoundButton {
implicitWidth: buttonLayout.buttonSize
implicitHeight: buttonLayout.buttonSize
onClicked: {
CallManager.rejectInvite();
close();
}
implicitWidth: buttonLayout.buttonSize
background: Rectangle {
radius: buttonLayout.buttonSize / 2
color: "#ff0000"
radius: buttonLayout.buttonSize / 2
}
contentItem: Image {
source: "image://colorimage/:/icons/icons/ui/end-call.svg?#ffffff"
}
onClicked: {
CallManager.rejectInvite();
close();
}
}
RoundButton {
id: acceptButton
property string image: CallManager.callType == Voip.VIDEO ? ":/icons/icons/ui/video.svg" : ":/icons/icons/ui/place-call.svg"
implicitWidth: buttonLayout.buttonSize
implicitHeight: buttonLayout.buttonSize
onClicked: {
if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText;
if (cameraCombo.visible)
Settings.camera = cameraCombo.currentText;
CallManager.acceptInvite();
close();
}
}
implicitWidth: buttonLayout.buttonSize
background: Rectangle {
radius: buttonLayout.buttonSize / 2
color: "#00ff00"
radius: buttonLayout.buttonSize / 2
}
contentItem: Image {
source: "image://colorimage/" + acceptButton.image + "?#ffffff"
}
onClicked: {
if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText;
if (cameraCombo.visible)
Settings.camera = cameraCombo.currentText;
CallManager.acceptInvite();
close();
}
}
}
}
background: Rectangle {
color: palette.window
border.color: palette.windowText
}
}

@ -9,87 +9,80 @@ import QtQuick.Layouts
import im.nheko
Rectangle {
visible: CallManager.haveCallInvite && !Settings.mobileMode
color: "#2ECC71"
implicitHeight: visible ? rowLayout.height + 8 : 0
visible: CallManager.haveCallInvite && !Settings.mobileMode
Component {
id: devicesDialog
CallDevices {
}
}
Component {
id: deviceError
DeviceError {
}
}
RowLayout {
id: rowLayout
anchors.left: parent.left
anchors.leftMargin: 8
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 8
Avatar {
implicitWidth: Nheko.avatarSize
displayName: CallManager.callPartyDisplayName
implicitHeight: Nheko.avatarSize
implicitWidth: Nheko.avatarSize
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
userid: CallManager.callParty
displayName: CallManager.callPartyDisplayName
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
}
Label {
Layout.leftMargin: 8
color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1
text: CallManager.callPartyDisplayName
color: "#000000"
}
Image {
Layout.leftMargin: 4
Layout.preferredWidth: 24
Layout.preferredHeight: 24
Layout.preferredWidth: 24
source: CallManager.callType == Voip.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg"
}
Label {
color: "#000000"
font.pointSize: fontMetrics.font.pointSize * 1.1
text: CallManager.callType == Voip.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
color: "#000000"
}
Item {
Layout.fillWidth: true
}
ImageButton {
Layout.rightMargin: 16
Layout.preferredWidth: 20
Layout.preferredHeight: 20
Layout.preferredWidth: 20
Layout.rightMargin: 16
ToolTip.text: qsTr("Devices")
ToolTip.visible: hovered
buttonTextColor: "#000000"
image: ":/icons/icons/ui/settings.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Devices")
image: ":/icons/icons/ui/settings.svg"
onClicked: {
var dialog = devicesDialog.createObject(timelineRoot);
dialog.open();
timelineRoot.destroyOnClose(dialog);
}
}
Button {
Layout.rightMargin: 4
icon.source: CallManager.callType == Voip.VIDEO ? "qrc:/icons/icons/ui/video.svg" : "qrc:/icons/icons/ui/place-call.svg"
text: qsTr("Accept")
onClicked: {
if (CallManager.mics.length == 0) {
var dialog = deviceError.createObject(timelineRoot, {
@ -98,7 +91,7 @@ Rectangle {
});
dialog.open();
timelineRoot.destroyOnClose(dialog);
return ;
return;
} else if (!CallManager.mics.includes(Settings.microphone)) {
var dialog = deviceError.createObject(timelineRoot, {
"errorString": qsTr("Unknown microphone: %1").arg(Settings.microphone),
@ -106,7 +99,7 @@ Rectangle {
});
dialog.open();
timelineRoot.destroyOnClose(dialog);
return ;
return;
}
if (CallManager.callType == Voip.VIDEO && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) {
var dialog = deviceError.createObject(timelineRoot, {
@ -115,21 +108,19 @@ Rectangle {
});
dialog.open();
timelineRoot.destroyOnClose(dialog);
return ;
return;
}
CallManager.acceptInvite();
}
}
Button {
Layout.rightMargin: 16
icon.source: "qrc:/icons/icons/ui/end-call.svg"
text: qsTr("Decline")
onClicked: {
CallManager.rejectInvite();
}
}
}
}

@ -8,35 +8,33 @@ import QtQuick.Layouts 1.2
Popup {
id: r
property string errorString
property var image
modal: true
background: Rectangle {
border.color: palette.windowText
color: palette.window
}
// only set the anchors on Qt 5.12 or higher
// see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
Component.onCompleted: {
if (anchors)
anchors.centerIn = parent;
}
RowLayout {
Image {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
Layout.preferredWidth: 16
source: "image://colorimage/" + r.image + "?" + palette.windowText
}
Label {
text: r.errorString
color: palette.windowText
text: r.errorString
}
}
background: Rectangle {
color: palette.window
border.color: palette.windowText
}
}

@ -10,12 +10,17 @@ import im.nheko 1.0
Popup {
modal: true
background: Rectangle {
border.color: palette.windowText
color: palette.window
}
// only set the anchors on Qt 5.12 or higher
// see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
Component.onCompleted: {
if (anchors)
anchors.centerIn = parent;
}
Component {
@ -23,29 +28,24 @@ Popup {
DeviceError {
}
}
ColumnLayout {
id: columnLayout
spacing: 16
RowLayout {
Layout.topMargin: 8
Layout.leftMargin: 8
Layout.topMargin: 8
Label {
text: qsTr("Place a call to %1?").arg(room.roomName)
color: palette.windowText
text: qsTr("Place a call to %1?").arg(room.roomName)
}
Item {
Layout.fillWidth: true
}
}
RowLayout {
id: buttonLayout
@ -66,18 +66,19 @@ Popup {
Layout.rightMargin: 8
Avatar {
Layout.rightMargin: cameraCombo.visible ? 16 : 64
Layout.preferredWidth: Nheko.avatarSize
Layout.preferredHeight: Nheko.avatarSize
url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
Layout.preferredWidth: Nheko.avatarSize
Layout.rightMargin: cameraCombo.visible ? 16 : 64
displayName: room.roomName
roomid: room.roomId
url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
}
Button {
text: qsTr("Voice")
icon.source: "qrc:/icons/icons/ui/place-call.svg"
text: qsTr("Voice")
onClicked: {
if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText;
@ -86,11 +87,11 @@ Popup {
}
}
}
Button {
visible: CallManager.cameras.length > 0
text: qsTr("Video")
icon.source: "qrc:/icons/icons/ui/video.svg"
text: qsTr("Video")
visible: CallManager.cameras.length > 0
onClicked: {
if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText;
@ -100,15 +101,14 @@ Popup {
}
}
}
Button {
text: qsTr("Screen")
icon.source: "qrc:/icons/icons/ui/screen-share.svg"
text: qsTr("Screen")
onClicked: {
if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText;
Settings.camera = cameraCombo.currentText;
var dialog = screenShareDialog.createObject(timelineRoot);
dialog.open();
timelineRoot.destroyOnClose(dialog);
@ -116,67 +116,52 @@ Popup {
}
}
}
Button {
text: qsTr("Cancel")
onClicked: {
close();
}
}
}
ColumnLayout {
spacing: 8
RowLayout {
Layout.bottomMargin: cameraCombo.visible ? 0 : 8
Layout.leftMargin: 8
Layout.rightMargin: 8
Layout.bottomMargin: cameraCombo.visible ? 0 : 8
Image {
Layout.preferredWidth: 22
Layout.preferredHeight: 22
Layout.preferredWidth: 22
source: "image://colorimage/:/icons/icons/ui/microphone-unmute.svg?" + palette.windowText
}
ComboBox {
id: micCombo
Layout.fillWidth: true
model: CallManager.mics
}
}
RowLayout {
visible: CallManager.cameras.length > 0
Layout.bottomMargin: 8
Layout.leftMargin: 8
Layout.rightMargin: 8
Layout.bottomMargin: 8
visible: CallManager.cameras.length > 0
Image {
Layout.preferredWidth: 22
Layout.preferredHeight: 22
Layout.preferredWidth: 22
source: "image://colorimage/:/icons/icons/ui/video.svg?" + palette.windowText
}
ComboBox {
id: cameraCombo
Layout.fillWidth: true
model: CallManager.cameras
}
}
}
}
background: Rectangle {
color: palette.window
border.color: palette.windowText
}
}

@ -9,9 +9,13 @@ import QtQuick.Layouts
import im.nheko
Popup {
anchors.centerIn: parent
modal: true
anchors.centerIn: parent;
background: Rectangle {
border.color: palette.windowText
color: palette.window
}
Component.onCompleted: {
frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate);
@ -22,176 +26,151 @@ Popup {
ColumnLayout {
Label {
Layout.topMargin: 16
Layout.alignment: Qt.AlignLeft
Layout.bottomMargin: 16
Layout.leftMargin: 8
Layout.rightMargin: 8
Layout.alignment: Qt.AlignLeft
text: qsTr("Share desktop with %1?").arg(room.roomName)
Layout.topMargin: 16
color: palette.windowText
text: qsTr("Share desktop with %1?").arg(room.roomName)
}
RowLayout {
Layout.bottomMargin: 8
Layout.leftMargin: 8
Layout.rightMargin: 8
Layout.bottomMargin: 8
Label {
Layout.alignment: Qt.AlignLeft
text: qsTr("Method:")
color: palette.windowText
text: qsTr("Method:")
}
ComboBox {
id: screenshareType
Layout.fillWidth: true
model: CallManager.screenShareTypeList()
onCurrentIndexChanged: CallManager.setScreenShareType(currentIndex);
onCurrentIndexChanged: CallManager.setScreenShareType(currentIndex)
}
}
RowLayout {
Layout.bottomMargin: 8
Layout.leftMargin: 8
Layout.rightMargin: 8
Layout.bottomMargin: 8
Label {
Layout.alignment: Qt.AlignLeft
text: qsTr("Window:")
color: palette.windowText
text: qsTr("Window:")
}
ComboBox {
visible: CallManager.screenShareType == Voip.X11
id: windowCombo
Layout.fillWidth: true
model: CallManager.windowList()
visible: CallManager.screenShareType == Voip.X11
}
Button {
visible: CallManager.screenShareType == Voip.XDP
highlighted: !CallManager.screenShareReady
text: qsTr("Request screencast")
visible: CallManager.screenShareType == Voip.XDP
onClicked: {
Settings.screenShareHideCursor = hideCursorCheckBox.checked;
CallManager.setupScreenShareXDP();
}
}
}
RowLayout {
Layout.bottomMargin: 8
Layout.leftMargin: 8
Layout.rightMargin: 8
Layout.bottomMargin: 8
Label {
Layout.alignment: Qt.AlignLeft
text: qsTr("Frame rate:")
color: palette.windowText
text: qsTr("Frame rate:")
}
ComboBox {
id: frameRateCombo
Layout.fillWidth: true
model: ["25", "20", "15", "10", "5", "2", "1"]
}
}
GridLayout {
Layout.margins: 8
columns: 2
rowSpacing: 10
Layout.margins: 8
MatrixText {
text: qsTr("Include your camera picture-in-picture")
}
ToggleButton {
id: pipCheckBox
enabled: CallManager.cameras.length > 0
checked: CallManager.cameras.length > 0 && Settings.screenSharePiP
Layout.alignment: Qt.AlignRight
checked: CallManager.cameras.length > 0 && Settings.screenSharePiP
enabled: CallManager.cameras.length > 0
}
MatrixText {
text: qsTr("Request remote camera")
ToolTip.text: qsTr("View your callee's camera like a regular video call")
ToolTip.visible: hovered
text: qsTr("Request remote camera")
}
ToggleButton {
id: remoteVideoCheckBox
Layout.alignment: Qt.AlignRight
checked: Settings.screenShareRemoteVideo
ToolTip.text: qsTr("View your callee's camera like a regular video call")
ToolTip.visible: hovered
checked: Settings.screenShareRemoteVideo
}
MatrixText {
text: qsTr("Hide mouse cursor")
}
ToggleButton {
id: hideCursorCheckBox
Layout.alignment: Qt.AlignRight
checked: Settings.screenShareHideCursor
}
}
RowLayout {
Layout.margins: 8
Item {
Layout.fillWidth: true
}
Button {
visible: CallManager.screenShareReady
text: qsTr("Share")
icon.source: "qrc:/icons/icons/ui/screen-share.svg"
text: qsTr("Share")
visible: CallManager.screenShareReady
onClicked: {
Settings.screenShareFrameRate = frameRateCombo.currentText;
Settings.screenSharePiP = pipCheckBox.checked;
Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked;
Settings.screenShareHideCursor = hideCursorCheckBox.checked;
CallManager.sendInvite(room.roomId, Voip.SCREEN, windowCombo.currentIndex);
close();
}
}
Button {
visible: CallManager.screenShareReady
text: qsTr("Preview")
visible: CallManager.screenShareReady
onClicked: {
CallManager.previewWindow(windowCombo.currentIndex);
}
}
Button {
text: qsTr("Cancel")
onClicked: {
close();
}
}
}
}
background: Rectangle {
color: palette.window
border.color: palette.windowText
}
}

Loading…
Cancel
Save