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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.12
@ -12,67 +10,18 @@ Container {
//Component.onCompleted: {
// parent.width = Qt.binding(function() { return calculatedWidth; })
//}
id: container
property bool singlePageMode: width < 800
property int splitterGrabMargin: Nheko.paddingSmall
property alias pageIndex: view.currentIndex
property Component handle
property Component handleToucharea
onSinglePageModeChanged: if (!singlePageMode) pageIndex = 0
Component.onCompleted: {
for (var i = 0; i < count - 1; i++) {
let handle_ = handle.createObject(contentChildren[i]);
let split_ = handleToucharea.createObject(contentChildren[i]);
contentChildren[i].width = Qt.binding(function() {
return split_.calculatedWidth;
});
contentChildren[i].splitterWidth = Qt.binding(function() {
return handle_.width;
});
}
contentChildren[count - 1].width = Qt.binding(function() {
if (container.singlePageMode) {
return container.width;
} else {
var w = container.width;
for (var i = 0; i < count - 1; i++) {
if (contentChildren[i].width)
w = w - contentChildren[i].width;
}
return w;
}
});
contentChildren[count - 1].splitterWidth = 0;
for (var i = 0; i < count; i++) {
contentChildren[i].height = Qt.binding(function() {
return container.height;
});
contentChildren[i].children[0].height = Qt.binding(function() {
return container.height;
});
}
}
handle: Rectangle {
z: 3
property Component handle: Rectangle {
anchors.right: parent.right
color: Nheko.theme.separator
height: container.height
width: visible ? 1 : 0
anchors.right: parent.right
z: 3
}
handleToucharea: Item {
property Component handleToucharea: Item {
id: splitter
property int minimumWidth: parent.minimumWidth
property int maximumWidth: parent.maximumWidth
property int collapsedWidth: parent.collapsedWidth
property bool collapsible: parent.collapsible
property int calculatedWidth: {
if (!visible)
return 0;
@ -81,6 +30,10 @@ Container {
else
return (collapsible && x < minimumWidth) ? collapsedWidth : x;
}
property int collapsedWidth: parent.collapsedWidth
property bool collapsible: parent.collapsible
property int maximumWidth: parent.maximumWidth
property int minimumWidth: parent.minimumWidth
enabled: !container.singlePageMode
height: container.height
@ -89,49 +42,82 @@ Container {
z: 3
NhekoCursorShape {
cursorShape: Qt.SizeHorCursor
height: parent.height
width: container.splitterGrabMargin * 2
x: -container.splitterGrabMargin
cursorShape: Qt.SizeHorCursor
}
DragHandler {
id: dragHandler
enabled: !container.singlePageMode
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 ? 200 : 0
highlightRangeMode: ListView.StrictlyEnforceRange
interactive: singlePageMode
highlightMoveDuration: container.singlePageMode ? 200 : 0
currentIndex: container.singlePageMode ? container.pageIndex : 0
boundsBehavior: Flickable.StopAtBounds
model: container.contentModel
orientation: ListView.Horizontal
snapMode: ListView.SnapOneItem
}
Component.onCompleted: {
for (var i = 0; i < count - 1; i++) {
let handle_ = handle.createObject(contentChildren[i]);
let split_ = handleToucharea.createObject(contentChildren[i]);
contentChildren[i].width = Qt.binding(function () {
return split_.calculatedWidth;
});
contentChildren[i].splitterWidth = Qt.binding(function () {
return handle_.width;
});
}
contentChildren[count - 1].width = Qt.binding(function () {
if (container.singlePageMode) {
return container.width;
} else {
var w = container.width;
for (var i = 0; i < count - 1; i++) {
if (contentChildren[i].width)
w = w - contentChildren[i].width;
}
return w;
}
});
contentChildren[count - 1].splitterWidth = 0;
for (var i = 0; i < count; i++) {
contentChildren[i].height = Qt.binding(function () {
return container.height;
});
contentChildren[i].children[0].height = Qt.binding(function () {
return container.height;
});
}
}
onSinglePageModeChanged: if (!singlePageMode)
pageIndex = 0
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10
@ -10,6 +8,7 @@ import im.nheko
Pane {
property string title: qsTr("Verification Code")
background: Rectangle {
color: timelineRoot.palette.window
}
@ -19,345 +18,345 @@ Pane {
spacing: 16
Label {
Layout.preferredWidth: 400
Layout.fillWidth: true
wrapMode: Text.Wrap
text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
Layout.preferredWidth: 400
color: timelineRoot.palette.text
text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
verticalAlignment: Text.AlignVCenter
wrapMode: Text.Wrap
}
Item {
Layout.fillHeight: true
}
Item { Layout.fillHeight: true; }
RowLayout {
id: emojis
property var mapping: [{
"number": 0,
"emoji": "🐶",
"description": "Dog",
"unicode": "U+1F436"
}, {
"number": 1,
"emoji": "🐱",
"description": "Cat",
"unicode": "U+1F431"
}, {
"number": 2,
"emoji": "🦁",
"description": "Lion",
"unicode": "U+1F981"
}, {
"number": 3,
"emoji": "🐎",
"description": "Horse",
"unicode": "U+1F40E"
}, {
"number": 4,
"emoji": "🦄",
"description": "Unicorn",
"unicode": "U+1F984"
}, {
"number": 5,
"emoji": "🐷",
"description": "Pig",
"unicode": "U+1F437"
}, {
"number": 6,
"emoji": "🐘",
"description": "Elephant",
"unicode": "U+1F418"
}, {
"number": 7,
"emoji": "🐰",
"description": "Rabbit",
"unicode": "U+1F430"
}, {
"number": 8,
"emoji": "🐼",
"description": "Panda",
"unicode": "U+1F43C"
}, {
"number": 9,
"emoji": "🐓",
"description": "Rooster",
"unicode": "U+1F413"
}, {
"number": 10,
"emoji": "🐧",
"description": "Penguin",
"unicode": "U+1F427"
}, {
"number": 11,
"emoji": "🐢",
"description": "Turtle",
"unicode": "U+1F422"
}, {
"number": 12,
"emoji": "🐟",
"description": "Fish",
"unicode": "U+1F41F"
}, {
"number": 13,
"emoji": "🐙",
"description": "Octopus",
"unicode": "U+1F419"
}, {
"number": 14,
"emoji": "🦋",
"description": "Butterfly",
"unicode": "U+1F98B"
}, {
"number": 15,
"emoji": "🌷",
"description": "Flower",
"unicode": "U+1F337"
}, {
"number": 16,
"emoji": "🌳",
"description": "Tree",
"unicode": "U+1F333"
}, {
"number": 17,
"emoji": "🌵",
"description": "Cactus",
"unicode": "U+1F335"
}, {
"number": 18,
"emoji": "🍄",
"description": "Mushroom",
"unicode": "U+1F344"
}, {
"number": 19,
"emoji": "🌏",
"description": "Globe",
"unicode": "U+1F30F"
}, {
"number": 20,
"emoji": "🌙",
"description": "Moon",
"unicode": "U+1F319"
}, {
"number": 21,
"emoji": "☁",
"description": "Cloud",
"unicode": "U+2601U+FE0F"
}, {
"number": 22,
"emoji": "🔥",
"description": "Fire",
"unicode": "U+1F525"
}, {
"number": 23,
"emoji": "🍌",
"description": "Banana",
"unicode": "U+1F34C"
}, {
"number": 24,
"emoji": "🍎",
"description": "Apple",
"unicode": "U+1F34E"
}, {
"number": 25,
"emoji": "🍓",
"description": "Strawberry",
"unicode": "U+1F353"
}, {
"number": 26,
"emoji": "🌽",
"description": "Corn",
"unicode": "U+1F33D"
}, {
"number": 27,
"emoji": "🍕",
"description": "Pizza",
"unicode": "U+1F355"
}, {
"number": 28,
"emoji": "🎂",
"description": "Cake",
"unicode": "U+1F382"
}, {
"number": 29,
"emoji": "❤",
"description": "Heart",
"unicode": "U+2764U+FE0F"
}, {
"number": 30,
"emoji": "😀",
"description": "Smiley",
"unicode": "U+1F600"
}, {
"number": 31,
"emoji": "🤖",
"description": "Robot",
"unicode": "U+1F916"
}, {
"number": 32,
"emoji": "🎩",
"description": "Hat",
"unicode": "U+1F3A9"
}, {
"number": 33,
"emoji": "👓",
"description": "Glasses",
"unicode": "U+1F453"
}, {
"number": 34,
"emoji": "🔧",
"description": "Spanner",
"unicode": "U+1F527"
}, {
"number": 35,
"emoji": "🎅",
"description": "Santa",
"unicode": "U+1F385"
}, {
"number": 36,
"emoji": "👍",
"description": "Thumbs Up",
"unicode": "U+1F44D"
}, {
"number": 37,
"emoji": "☂",
"description": "Umbrella",
"unicode": "U+2602U+FE0F"
}, {
"number": 38,
"emoji": "⌛",
"description": "Hourglass",
"unicode": "U+231B"
}, {
"number": 39,
"emoji": "⏰",
"description": "Clock",
"unicode": "U+23F0"
}, {
"number": 40,
"emoji": "🎁",
"description": "Gift",
"unicode": "U+1F381"
}, {
"number": 41,
"emoji": "💡",
"description": "Light Bulb",
"unicode": "U+1F4A1"
}, {
"number": 42,
"emoji": "📕",
"description": "Book",
"unicode": "U+1F4D5"
}, {
"number": 43,
"emoji": "✏",
"description": "Pencil",
"unicode": "U+270FU+FE0F"
}, {
"number": 44,
"emoji": "📎",
"description": "Paperclip",
"unicode": "U+1F4CE"
}, {
"number": 45,
"emoji": "✂",
"description": "Scissors",
"unicode": "U+2702U+FE0F"
}, {
"number": 46,
"emoji": "🔒",
"description": "Lock",
"unicode": "U+1F512"
}, {
"number": 47,
"emoji": "🔑",
"description": "Key",
"unicode": "U+1F511"
}, {
"number": 48,
"emoji": "🔨",
"description": "Hammer",
"unicode": "U+1F528"
}, {
"number": 49,
"emoji": "☎",
"description": "Telephone",
"unicode": "U+260EU+FE0F"
}, {
"number": 50,
"emoji": "🏁",
"description": "Flag",
"unicode": "U+1F3C1"
}, {
"number": 51,
"emoji": "🚂",
"description": "Train",
"unicode": "U+1F682"
}, {
"number": 52,
"emoji": "🚲",
"description": "Bicycle",
"unicode": "U+1F6B2"
}, {
"number": 53,
"emoji": "✈",
"description": "Aeroplane",
"unicode": "U+2708U+FE0F"
}, {
"number": 54,
"emoji": "🚀",
"description": "Rocket",
"unicode": "U+1F680"
}, {
"number": 55,
"emoji": "🏆",
"description": "Trophy",
"unicode": "U+1F3C6"
}, {
"number": 56,
"emoji": "⚽",
"description": "Ball",
"unicode": "U+26BD"
}, {
"number": 57,
"emoji": "🎸",
"description": "Guitar",
"unicode": "U+1F3B8"
}, {
"number": 58,
"emoji": "🎺",
"description": "Trumpet",
"unicode": "U+1F3BA"
}, {
"number": 59,
"emoji": "🔔",
"description": "Bell",
"unicode": "U+1F514"
}, {
"number": 60,
"emoji": "⚓",
"description": "Anchor",
"unicode": "U+2693"
}, {
"number": 61,
"emoji": "🎧",
"description": "Headphones",
"unicode": "U+1F3A7"
}, {
"number": 62,
"emoji": "📁",
"description": "Folder",
"unicode": "U+1F4C1"
}, {
"number": 63,
"emoji": "📌",
"description": "Pin",
"unicode": "U+1F4CC"
}]
"number": 0,
"emoji": "🐶",
"description": "Dog",
"unicode": "U+1F436"
}, {
"number": 1,
"emoji": "🐱",
"description": "Cat",
"unicode": "U+1F431"
}, {
"number": 2,
"emoji": "🦁",
"description": "Lion",
"unicode": "U+1F981"
}, {
"number": 3,
"emoji": "🐎",
"description": "Horse",
"unicode": "U+1F40E"
}, {
"number": 4,
"emoji": "🦄",
"description": "Unicorn",
"unicode": "U+1F984"
}, {
"number": 5,
"emoji": "🐷",
"description": "Pig",
"unicode": "U+1F437"
}, {
"number": 6,
"emoji": "🐘",
"description": "Elephant",
"unicode": "U+1F418"
}, {
"number": 7,
"emoji": "🐰",
"description": "Rabbit",
"unicode": "U+1F430"
}, {
"number": 8,
"emoji": "🐼",
"description": "Panda",
"unicode": "U+1F43C"
}, {
"number": 9,
"emoji": "🐓",
"description": "Rooster",
"unicode": "U+1F413"
}, {
"number": 10,
"emoji": "🐧",
"description": "Penguin",
"unicode": "U+1F427"
}, {
"number": 11,
"emoji": "🐢",
"description": "Turtle",
"unicode": "U+1F422"
}, {
"number": 12,
"emoji": "🐟",
"description": "Fish",
"unicode": "U+1F41F"
}, {
"number": 13,
"emoji": "🐙",
"description": "Octopus",
"unicode": "U+1F419"
}, {
"number": 14,
"emoji": "🦋",
"description": "Butterfly",
"unicode": "U+1F98B"
}, {
"number": 15,
"emoji": "🌷",
"description": "Flower",
"unicode": "U+1F337"
}, {
"number": 16,
"emoji": "🌳",
"description": "Tree",
"unicode": "U+1F333"
}, {
"number": 17,
"emoji": "🌵",
"description": "Cactus",
"unicode": "U+1F335"
}, {
"number": 18,
"emoji": "🍄",
"description": "Mushroom",
"unicode": "U+1F344"
}, {
"number": 19,
"emoji": "🌏",
"description": "Globe",
"unicode": "U+1F30F"
}, {
"number": 20,
"emoji": "🌙",
"description": "Moon",
"unicode": "U+1F319"
}, {
"number": 21,
"emoji": "☁",
"description": "Cloud",
"unicode": "U+2601U+FE0F"
}, {
"number": 22,
"emoji": "🔥",
"description": "Fire",
"unicode": "U+1F525"
}, {
"number": 23,
"emoji": "🍌",
"description": "Banana",
"unicode": "U+1F34C"
}, {
"number": 24,
"emoji": "🍎",
"description": "Apple",
"unicode": "U+1F34E"
}, {
"number": 25,
"emoji": "🍓",
"description": "Strawberry",
"unicode": "U+1F353"
}, {
"number": 26,
"emoji": "🌽",
"description": "Corn",
"unicode": "U+1F33D"
}, {
"number": 27,
"emoji": "🍕",
"description": "Pizza",
"unicode": "U+1F355"
}, {
"number": 28,
"emoji": "🎂",
"description": "Cake",
"unicode": "U+1F382"
}, {
"number": 29,
"emoji": "❤",
"description": "Heart",
"unicode": "U+2764U+FE0F"
}, {
"number": 30,
"emoji": "😀",
"description": "Smiley",
"unicode": "U+1F600"
}, {
"number": 31,
"emoji": "🤖",
"description": "Robot",
"unicode": "U+1F916"
}, {
"number": 32,
"emoji": "🎩",
"description": "Hat",
"unicode": "U+1F3A9"
}, {
"number": 33,
"emoji": "👓",
"description": "Glasses",
"unicode": "U+1F453"
}, {
"number": 34,
"emoji": "🔧",
"description": "Spanner",
"unicode": "U+1F527"
}, {
"number": 35,
"emoji": "🎅",
"description": "Santa",
"unicode": "U+1F385"
}, {
"number": 36,
"emoji": "👍",
"description": "Thumbs Up",
"unicode": "U+1F44D"
}, {
"number": 37,
"emoji": "☂",
"description": "Umbrella",
"unicode": "U+2602U+FE0F"
}, {
"number": 38,
"emoji": "⌛",
"description": "Hourglass",
"unicode": "U+231B"
}, {
"number": 39,
"emoji": "⏰",
"description": "Clock",
"unicode": "U+23F0"
}, {
"number": 40,
"emoji": "🎁",
"description": "Gift",
"unicode": "U+1F381"
}, {
"number": 41,
"emoji": "💡",
"description": "Light Bulb",
"unicode": "U+1F4A1"
}, {
"number": 42,
"emoji": "📕",
"description": "Book",
"unicode": "U+1F4D5"
}, {
"number": 43,
"emoji": "✏",
"description": "Pencil",
"unicode": "U+270FU+FE0F"
}, {
"number": 44,
"emoji": "📎",
"description": "Paperclip",
"unicode": "U+1F4CE"
}, {
"number": 45,
"emoji": "✂",
"description": "Scissors",
"unicode": "U+2702U+FE0F"
}, {
"number": 46,
"emoji": "🔒",
"description": "Lock",
"unicode": "U+1F512"
}, {
"number": 47,
"emoji": "🔑",
"description": "Key",
"unicode": "U+1F511"
}, {
"number": 48,
"emoji": "🔨",
"description": "Hammer",
"unicode": "U+1F528"
}, {
"number": 49,
"emoji": "☎",
"description": "Telephone",
"unicode": "U+260EU+FE0F"
}, {
"number": 50,
"emoji": "🏁",
"description": "Flag",
"unicode": "U+1F3C1"
}, {
"number": 51,
"emoji": "🚂",
"description": "Train",
"unicode": "U+1F682"
}, {
"number": 52,
"emoji": "🚲",
"description": "Bicycle",
"unicode": "U+1F6B2"
}, {
"number": 53,
"emoji": "✈",
"description": "Aeroplane",
"unicode": "U+2708U+FE0F"
}, {
"number": 54,
"emoji": "🚀",
"description": "Rocket",
"unicode": "U+1F680"
}, {
"number": 55,
"emoji": "🏆",
"description": "Trophy",
"unicode": "U+1F3C6"
}, {
"number": 56,
"emoji": "⚽",
"description": "Ball",
"unicode": "U+26BD"
}, {
"number": 57,
"emoji": "🎸",
"description": "Guitar",
"unicode": "U+1F3B8"
}, {
"number": 58,
"emoji": "🎺",
"description": "Trumpet",
"unicode": "U+1F3BA"
}, {
"number": 59,
"emoji": "🔔",
"description": "Bell",
"unicode": "U+1F514"
}, {
"number": 60,
"emoji": "⚓",
"description": "Anchor",
"unicode": "U+2693"
}, {
"number": 61,
"emoji": "🎧",
"description": "Headphones",
"unicode": "U+1F3A7"
}, {
"number": 62,
"emoji": "📁",
"description": "Folder",
"unicode": "U+1F4C1"
}, {
"number": 63,
"emoji": "📌",
"description": "Pin",
"unicode": "U+1F4CC"
}]
Layout.alignment: Qt.AlignHCenter
Repeater {
id: repeater
model: 7
delegate: Rectangle {
@ -376,49 +375,42 @@ Pane {
Label {
//height: font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter
text: col.emoji.emoji
font.pixelSize: Qt.application.font.pixelSize * 2
font.family: Settings.emojiFont
color: timelineRoot.palette.text
font.family: Settings.emojiFont
font.pixelSize: Qt.application.font.pixelSize * 2
text: col.emoji.emoji
}
Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
text: col.emoji.description
color: timelineRoot.palette.text
text: col.emoji.description
}
}
}
}
}
Item { Layout.fillHeight: true; }
Item {
Layout.fillHeight: true
}
RowLayout {
Button {
Layout.alignment: Qt.AlignLeft
text: qsTr("They do not match!")
onClicked: {
flow.cancel();
dialog.close();
}
}
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("They match!")
onClicked: flow.next()
}
}
}
}

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

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

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

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

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

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

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import ".."
import "../"
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
@ -11,117 +9,108 @@ import im.nheko
ApplicationWindow {
id: hiddenEventsDialog
property string roomid: ""
property string roomName: ""
property var onAccepted: undefined
property string roomName: ""
property string roomid: ""
modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowTitleHint
minimumWidth: 250
minimumHeight: 220
HiddenEvents {
id: hiddenEvents
roomid: hiddenEventsDialog.roomid
}
minimumWidth: 250
modality: Qt.NonModal
title: {
if (roomid) {
return qsTr("Hidden events for %1").arg(roomName);
}
else {
} else {
return qsTr("Hidden events");
}
}
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();
}
}

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

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import ".."
import "../"
import "../components"
import Qt.labs.platform 1.1
import QtQuick 2.12
@ -15,321 +13,293 @@ ApplicationWindow {
id: win
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
property SingleImagePackModel imagePack
property int currentImageIndex: -1
property SingleImagePackModel imagePack
readonly property int stickerDim: 128
readonly property int stickerDimPad: 128 + Nheko.paddingSmall
title: qsTr("Editing image pack")
height: 600
width: 600
palette: timelineRoot.palette
color: timelineRoot.palette.base
modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: 600
modality: Qt.WindowModal
palette: timelineRoot.palette
title: qsTr("Editing image pack")
width: 600
footer: DialogButtonBox {
id: buttons
standardButtons: DialogButtonBox.Save | DialogButtonBox.Cancel
onAccepted: {
imagePack.save();
win.close();
}
onRejected: win.close()
}
AdaptiveLayout {
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
//required property bool isSticker
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: timelineRoot.palette.window
required property string body
property color bubbleBackground: timelineRoot.palette.highlight
property color bubbleText: timelineRoot.palette.highlightedText
property color importantText: timelineRoot.palette.text
required property string shortCode
property color unimportantText: timelineRoot.palette.placeholderText
required property string url
avatarUrl: url
crop: false
selectedIndex: currentImageIndex
subtitle: body
title: shortCode
TapHandler {
onSingleTapped: currentImageIndex = -1
}
Rectangle {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
height: parent.height - Nheko.paddingSmall * 2
width: 3
color: timelineRoot.palette.highlight
onSingleTapped: currentImageIndex = index
}
}
footer: Button {
palette: timelineRoot.palette
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: timelineRoot.palette.window
property color importantText: timelineRoot.palette.text
property color unimportantText: timelineRoot.palette.placeholderText
property color bubbleBackground: timelineRoot.palette.highlight
property color bubbleText: timelineRoot.palette.highlightedText
required property string shortCode
required property string url
required property string body
title: shortCode
subtitle: body
avatarUrl: url
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: timelineRoot.palette.highlight
height: parent.height - Nheko.paddingSmall * 2
width: 3
}
}
}
}
AdaptiveLayoutElement {
id: packinfoC
Rectangle {
color: timelineRoot.palette.window
GridLayout {
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
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/")
crop: false
displayName: imagePack.packname
roomid: imagePack.statekey
height: 130
roomid: imagePack.statekey
url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/")
width: 130
crop: false
Layout.alignment: Qt.AlignHCenter
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"
crop: false
displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
roomid: displayName
height: 130
roomid: displayName
url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/") + "?scale"
width: 130
crop: false
Layout.alignment: Qt.AlignHCenter
}
MatrixTextField {
Layout.fillWidth: true
Layout.columnSpan: 2
Layout.fillWidth: true
label: qsTr("Shortcode")
text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode)
}
MatrixTextField {
id: bodyField
Layout.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()
}
}

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

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

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

@ -1,9 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import ".."
import "../"
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
@ -11,64 +9,57 @@ import im.nheko
ApplicationWindow {
id: joinRoomRoot
title: qsTr("Join room")
modality: Qt.WindowModal
color: timelineRoot.palette.window
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
height: fontMetrics.lineSpacing * 7
modality: Qt.WindowModal
palette: timelineRoot.palette
color: timelineRoot.palette.window
title: qsTr("Join room")
width: 350
height: fontMetrics.lineSpacing * 7
footer: DialogButtonBox {
id: dbb
standardButtons: DialogButtonBox.Cancel
onAccepted: {
Nheko.joinRoom(input.text);
joinRoomRoot.close();
}
onRejected: {
joinRoomRoot.close();
}
Button {
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: input.text.match("#.+?:.{3,}")
text: "Join"
}
}
Shortcut {
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: timelineRoot.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: "Join"
enabled: input.text.match("#.+?:.{3,}")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
}
}

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

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import Qt.labs.platform 1.1 as P
import QtQuick 2.15
import QtQuick.Controls 2.15
@ -10,11 +8,11 @@ import im.nheko
P.MessageDialog {
id: logoutRoot
title: qsTr("Log out")
text: CallManager.isOnCall ? qsTr("A call is in progress. Log out?") : qsTr("Are you sure you want to log out?")
modality: Qt.WindowModal
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
buttons: P.MessageDialog.Ok | P.MessageDialog.Cancel
flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
modality: Qt.WindowModal
text: CallManager.isOnCall ? qsTr("A call is in progress. Log out?") : qsTr("Are you sure you want to log out?")
title: qsTr("Log out")
onAccepted: Nheko.logout()
}

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,7 +1,5 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15
import im.nheko
@ -9,8 +7,8 @@ import im.nheko
Popup {
id: snackbar
property var messages: []
property string currentMessage: ""
property var messages: []
function showNotification(msg) {
messages.push(msg);
@ -21,78 +19,75 @@ Popup {
}
}
Timer {
id: dismissTimer
interval: 10000
onTriggered: snackbar.close()
}
onAboutToHide: {
messages.shift();
}
onClosed: {
if (messages.length > 0) {
currentMessage = messages[0];
open();
dismissTimer.restart();
}
}
parent: Overlay.overlay
opacity: 0
y: -100
x: (parent.width - width)/2
padding: Nheko.paddingLarge
contentItem: Label {
color: timelineRoot.palette.light
width: Math.max(Overlay.overlay? Overlay.overlay.width/2 : 0, 400)
text: snackbar.currentMessage
font.bold: true
}
parent: Overlay.overlay
x: (parent.width - width) / 2
y: -100
background: Rectangle {
radius: Nheko.paddingLarge
color: timelineRoot.palette.dark
opacity: 0.8
radius: Nheko.paddingLarge
}
contentItem: Label {
color: timelineRoot.palette.light
font.bold: true
text: snackbar.currentMessage
width: Math.max(Overlay.overlay ? Overlay.overlay.width / 2 : 0, 400)
}
enter: Transition {
NumberAnimation {
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()
}
}

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save