diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml
index 333fb11d..00fc3216 100644
--- a/resources/qml/Completer.qml
+++ b/resources/qml/Completer.qml
@@ -70,7 +70,7 @@ Popup {
onCompleterNameChanged: {
if (completerName) {
if (completerName == "user")
- completer = TimelineManager.completerFor(completerName, room.roomId());
+ completer = TimelineManager.completerFor(completerName, room.roomId);
else
completer = TimelineManager.completerFor(completerName);
completer.setSearchString("");
diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml
new file mode 100644
index 00000000..50287ad5
--- /dev/null
+++ b/resources/qml/InviteDialog.qml
@@ -0,0 +1,159 @@
+// SPDX-FileCopyrightText: 2021 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 1.0
+
+ApplicationWindow {
+ id: inviteDialogRoot
+
+ property string roomId
+ property string plainRoomName
+ property InviteesModel invitees
+
+ function addInvite() {
+ if (inviteeEntry.isValidMxid) {
+ invitees.addUser(inviteeEntry.text);
+ inviteeEntry.clear();
+ }
+ }
+
+ function cleanUpAndClose() {
+ if (inviteeEntry.isValidMxid)
+ addInvite();
+
+ invitees.accept();
+ close();
+ }
+
+ title: qsTr("Invite users to %1").arg(plainRoomName)
+ x: MainWindow.x + (MainWindow.width / 2) - (width / 2)
+ y: MainWindow.y + (MainWindow.height / 2) - (height / 2)
+ height: 380
+ width: 340
+ palette: Nheko.colors
+ color: Nheko.colors.window
+
+ 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: Nheko.colors.text
+ }
+
+ RowLayout {
+ spacing: Nheko.paddingMedium
+
+ MatrixTextField {
+ id: inviteeEntry
+
+ property bool isValidMxid: text.match("@.+?:.{3,}")
+
+ backgroundColor: Nheko.colors.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();
+
+ }
+ }
+
+ Button {
+ text: qsTr("Add")
+ enabled: inviteeEntry.isValidMxid
+ onClicked: addInvite()
+ }
+
+ }
+
+ ListView {
+ id: inviteesList
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ model: invitees
+
+ delegate: RowLayout {
+ spacing: Nheko.paddingMedium
+
+ Avatar {
+ width: Nheko.avatarSize
+ height: Nheko.avatarSize
+ userid: model.mxid
+ url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
+ displayName: model.displayName
+ onClicked: TimelineManager.openGlobalUserProfile(model.mxid)
+ }
+
+ ColumnLayout {
+ spacing: Nheko.paddingSmall
+
+ Label {
+ text: model.displayName
+ color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window)
+ font.pointSize: fontMetrics.font.pointSize
+ }
+
+ Label {
+ text: model.mxid
+ color: Nheko.colors.buttonText
+ font.pointSize: fontMetrics.font.pointSize * 0.9
+ }
+
+ Item {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ footer: DialogButtonBox {
+ id: buttons
+
+ Button {
+ text: qsTr("Invite")
+ DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
+ enabled: invitees.count > 0
+ onClicked: cleanUpAndClose()
+ }
+
+ Button {
+ text: qsTr("Cancel")
+ DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole
+ onClicked: inviteDialogRoot.close()
+ }
+
+ }
+
+}
diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml
index 9129b154..35e5f7e7 100644
--- a/resources/qml/MatrixText.qml
+++ b/resources/qml/MatrixText.qml
@@ -8,6 +8,7 @@ import im.nheko 1.0
TextEdit {
id: r
+
textFormat: TextEdit.RichText
readOnly: true
focus: false
@@ -19,14 +20,13 @@ TextEdit {
onLinkActivated: Nheko.openLink(link)
ToolTip.visible: hoveredLink
ToolTip.text: hoveredLink
+ Component.onCompleted: {
+ TimelineManager.fixImageRendering(r.textDocument, r);
+ }
CursorShape {
anchors.fill: parent
cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
- Component.onCompleted: {
- TimelineManager.fixImageRendering(r.textDocument, r)
- }
-
}
diff --git a/resources/qml/MatrixTextField.qml b/resources/qml/MatrixTextField.qml
index 3c660bac..80732b27 100644
--- a/resources/qml/MatrixTextField.qml
+++ b/resources/qml/MatrixTextField.qml
@@ -10,6 +10,8 @@ import im.nheko 1.0
TextField {
id: input
+ property alias backgroundColor: backgroundRect.color
+
palette: Nheko.colors
color: Nheko.colors.text
@@ -62,6 +64,8 @@ TextField {
}
background: Rectangle {
+ id: backgroundRect
+
color: Nheko.colors.base
}
diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml
index 24f9b0e8..58d71a4e 100644
--- a/resources/qml/MessageInput.qml
+++ b/resources/qml/MessageInput.qml
@@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
+import "./emoji"
import "./voip"
import QtQuick 2.12
import QtQuick.Controls 2.3
@@ -87,7 +88,7 @@ Rectangle {
Layout.alignment: Qt.AlignBottom // | Qt.AlignHCenter
Layout.maximumHeight: Window.height / 4
Layout.minimumHeight: Settings.fontSize
- implicitWidth: inputBar.width - 4 * (22 + 16) - 24
+ implicitWidth: inputBar.width - 5 * (22 + 16) - 24
TextArea {
id: messageInput
@@ -320,6 +321,30 @@ Rectangle {
}
ImageButton {
+ id: stickerButton
+
+ Layout.alignment: Qt.AlignRight | Qt.AlignBottom
+ Layout.margins: 8
+ hoverEnabled: true
+ width: 22
+ height: 22
+ 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();
+ })
+
+ StickerPicker {
+ id: stickerPopup
+
+ colors: Nheko.colors
+ }
+
+ }
+
+ ImageButton {
id: emojiButton
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index 33dff122..50cbd371 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -92,16 +92,20 @@ ScrollView {
}
}
- EmojiButton {
+ ImageButton {
id: reactButton
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
width: 16
hoverEnabled: true
+ image: ":/icons/icons/ui/smile.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("React")
- emojiPicker: emojiPopup
- event_id: row.model ? row.model.eventId : ""
+ onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, function(emoji) {
+ var event_id = row.model ? row.model.eventId : "";
+ room.input.reaction(event_id, emoji);
+ TimelineManager.focusMessageInput();
+ })
}
ImageButton {
@@ -337,6 +341,7 @@ ScrollView {
required property var timestamp
required property int status
required property int index
+ required property int relatedEventCacheBuster
required property string previousMessageUserId
required property string day
required property string previousMessageDay
@@ -442,6 +447,7 @@ ScrollView {
trustlevel: wrapper.trustlevel
timestamp: wrapper.timestamp
status: wrapper.status
+ relatedEventCacheBuster: wrapper.relatedEventCacheBuster
y: section.visible && section.active ? section.y + section.height : 0
HoverHandler {
@@ -471,12 +477,23 @@ ScrollView {
}
- footer: Spinner {
+ footer: Item {
anchors.horizontalCenter: parent.horizontalCenter
- running: chat.model && chat.model.paginationInProgress
- foreground: Nheko.colors.mid
+ anchors.margins: Nheko.paddingLarge
visible: chat.model && chat.model.paginationInProgress
- z: 3
+ // hacky, but works
+ height: loadingSpinner.height + 2 * Nheko.paddingLarge
+
+ Spinner {
+ id: loadingSpinner
+
+ anchors.centerIn: parent
+ anchors.margins: Nheko.paddingLarge
+ running: chat.model && chat.model.paginationInProgress
+ foreground: Nheko.colors.mid
+ z: 3
+ }
+
}
}
diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index a1ce8d7e..2be5fe92 100644
--- a/resources/qml/RoomList.qml
+++ b/resources/qml/RoomList.qml
@@ -33,8 +33,8 @@ Page {
Connections {
onActiveTimelineChanged: {
- roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId()), ListView.Contain);
- console.log("Test" + Rooms.currentRoom.roomId() + " " + Rooms.roomidToIndex(Rooms.currentRoom.roomId()));
+ roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId), ListView.Contain);
+ console.log("Test" + Rooms.currentRoom.roomId + " " + Rooms.roomidToIndex(Rooms.currentRoom.roomId));
}
target: TimelineManager
}
@@ -61,9 +61,19 @@ Page {
}
}
+ Platform.MessageDialog {
+ id: leaveRoomDialog
+
+ title: qsTr("Leave Room")
+ text: qsTr("Are you sure you want to leave this room?")
+ modality: Qt.Modal
+ onAccepted: Rooms.leave(roomContextMenu.roomid)
+ buttons: Dialog.Ok | Dialog.Cancel
+ }
+
Platform.MenuItem {
text: qsTr("Leave room")
- onTriggered: Rooms.leave(roomContextMenu.roomid)
+ onTriggered: leaveRoomDialog.open()
}
Platform.MenuSeparator {
@@ -133,7 +143,7 @@ Page {
states: [
State {
name: "highlight"
- when: hovered.hovered && !((Rooms.currentRoom && roomId == Rooms.currentRoom.roomId()) || Rooms.currentRoomPreview.roomid == roomId)
+ when: hovered.hovered && !((Rooms.currentRoom && roomId == Rooms.currentRoom.roomId) || Rooms.currentRoomPreview.roomid == roomId)
PropertyChanges {
target: roomItem
@@ -147,7 +157,7 @@ Page {
},
State {
name: "selected"
- when: (Rooms.currentRoom && roomId == Rooms.currentRoom.roomId()) || Rooms.currentRoomPreview.roomid == roomId
+ when: (Rooms.currentRoom && roomId == Rooms.currentRoom.roomId) || Rooms.currentRoomPreview.roomid == roomId
PropertyChanges {
target: roomItem
diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml
new file mode 100644
index 00000000..641a08be
--- /dev/null
+++ b/resources/qml/RoomMembers.qml
@@ -0,0 +1,148 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import "./ui"
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Window 2.12
+import im.nheko 1.0
+
+ApplicationWindow {
+ id: roomMembersRoot
+
+ property MemberList members
+
+ title: qsTr("Members of %1").arg(members.roomName)
+ x: MainWindow.x + (MainWindow.width / 2) - (width / 2)
+ y: MainWindow.y + (MainWindow.height / 2) - (height / 2)
+ height: 650
+ width: 420
+ minimumHeight: 420
+ palette: Nheko.colors
+ color: Nheko.colors.window
+
+ Shortcut {
+ sequence: StandardKey.Cancel
+ onActivated: roomMembersRoot.close()
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: Nheko.paddingMedium
+ spacing: Nheko.paddingMedium
+
+ Avatar {
+ id: roomAvatar
+
+ width: 130
+ height: width
+ displayName: members.roomName
+ Layout.alignment: Qt.AlignHCenter
+ url: members.avatarUrl.replace("mxc://", "image://MxcImage/")
+ onClicked: TimelineManager.openRoomSettings(members.roomId)
+ }
+
+ ElidedLabel {
+ font.pixelSize: fontMetrics.font.pixelSize * 2
+ fullText: qsTr("%n people in %1", "Summary above list of members", members.memberCount).arg(members.roomName)
+ Layout.alignment: Qt.AlignHCenter
+ elideWidth: parent.width - Nheko.paddingMedium
+ }
+
+ ImageButton {
+ Layout.alignment: Qt.AlignHCenter
+ image: ":/icons/icons/ui/add-square-button.png"
+ hoverEnabled: true
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Invite more people")
+ onClicked: TimelineManager.openInviteUsers(members.roomId)
+ }
+
+ ScrollView {
+ palette: Nheko.colors
+ padding: Nheko.paddingMedium
+ ScrollBar.horizontal.visible: false
+ Layout.fillHeight: true
+ Layout.minimumHeight: 200
+ Layout.fillWidth: true
+
+ ListView {
+ id: memberList
+
+ clip: true
+ spacing: Nheko.paddingMedium
+ boundsBehavior: Flickable.StopAtBounds
+ model: members
+
+ ScrollHelper {
+ flickable: parent
+ anchors.fill: parent
+ enabled: !Settings.mobileMode
+ }
+
+ delegate: RowLayout {
+ spacing: Nheko.paddingMedium
+
+ Avatar {
+ width: Nheko.avatarSize
+ height: Nheko.avatarSize
+ userid: model.mxid
+ url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
+ displayName: model.displayName
+ onClicked: Rooms.currentRoom.openUserProfile(model.mxid)
+ }
+
+ ColumnLayout {
+ spacing: Nheko.paddingSmall
+
+ Label {
+ text: model.displayName
+ color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window)
+ font.pointSize: fontMetrics.font.pointSize
+ }
+
+ Label {
+ text: model.mxid
+ color: Nheko.colors.buttonText
+ font.pointSize: fontMetrics.font.pointSize * 0.9
+ }
+
+ Item {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+
+ }
+
+ }
+
+ footer: Item {
+ width: parent.width
+ visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers
+ // use the default height if it's visible, otherwise no height at all
+ height: membersLoadingSpinner.height
+ anchors.margins: Nheko.paddingMedium
+
+ Spinner {
+ id: membersLoadingSpinner
+
+ anchors.centerIn: parent
+ height: visible ? 35 : 0
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ footer: DialogButtonBox {
+ standardButtons: DialogButtonBox.Ok
+ onAccepted: roomMembersRoot.close()
+ }
+
+}
diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml
index 4b06401a..b8e527a5 100644
--- a/resources/qml/RoomSettings.qml
+++ b/resources/qml/RoomSettings.qml
@@ -4,7 +4,7 @@
import "./ui"
import Qt.labs.platform 1.1 as Platform
-import QtQuick 2.9
+import QtQuick 2.15
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtQuick.Window 2.3
@@ -98,13 +98,23 @@ ApplicationWindow {
MatrixText {
text: roomSettings.roomName
- font.pixelSize: 24
+ font.pixelSize: fontMetrics.font.pixelSize * 2
Layout.alignment: Qt.AlignHCenter
}
MatrixText {
text: qsTr("%1 member(s)").arg(roomSettings.memberCount)
Layout.alignment: Qt.AlignHCenter
+
+ TapHandler {
+ onTapped: TimelineManager.openRoomMembers(roomSettings.roomId)
+ }
+
+ CursorShape {
+ cursorShape: Qt.PointingHandCursor
+ anchors.fill: parent
+ }
+
}
}
@@ -209,7 +219,7 @@ ApplicationWindow {
title: qsTr("End-to-End Encryption")
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
+ modality: Qt.Modal
onAccepted: {
if (roomSettings.isEncryptionEnabled)
return ;
@@ -222,6 +232,17 @@ ApplicationWindow {
buttons: Dialog.Ok | Dialog.Cancel
}
+ MatrixText {
+ text: qsTr("Sticker & Emote Settings")
+ }
+
+ Button {
+ text: qsTr("Change")
+ ToolTip.text: qsTr("Change what packs are enabled, remove packs or create new ones")
+ onClicked: TimelineManager.openImagePackSettings(roomSettings.roomId)
+ Layout.alignment: Qt.AlignRight
+ }
+
Item {
// for adding extra space between sections
Layout.fillWidth: true
@@ -247,7 +268,7 @@ ApplicationWindow {
MatrixText {
text: roomSettings.roomId
- font.pixelSize: 14
+ font.pixelSize: fontMetrics.font.pixelSize * 1.2
Layout.alignment: Qt.AlignRight
}
@@ -257,16 +278,16 @@ ApplicationWindow {
MatrixText {
text: roomSettings.roomVersion
- font.pixelSize: 14
+ font.pixelSize: fontMetrics.font.pixelSize * 1.2
Layout.alignment: Qt.AlignRight
}
}
- Button {
- Layout.alignment: Qt.AlignRight
- text: qsTr("OK")
- onClicked: close()
+ DialogButtonBox {
+ Layout.fillWidth: true
+ standardButtons: DialogButtonBox.Ok
+ onAccepted: close()
}
}
diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index 5316e20d..1793d9bc 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -4,6 +4,7 @@
import "./delegates"
import "./device-verification"
+import "./dialogs"
import "./emoji"
import "./voip"
import Qt.labs.platform 1.1 as Platform
@@ -48,6 +49,14 @@ Page {
}
Component {
+ id: roomMembersComponent
+
+ RoomMembers {
+ }
+
+ }
+
+ Component {
id: mobileCallInviteDialog
CallInvite {
@@ -63,6 +72,30 @@ Page {
}
+ Component {
+ id: deviceVerificationDialog
+
+ DeviceVerification {
+ }
+
+ }
+
+ Component {
+ id: inviteDialog
+
+ InviteDialog {
+ }
+
+ }
+
+ Component {
+ id: packSettingsComponent
+
+ ImagePackSettingsDialog {
+ }
+
+ }
+
Shortcut {
sequence: "Ctrl+K"
onActivated: {
@@ -82,14 +115,6 @@ Page {
onActivated: Rooms.previousRoom()
}
- Component {
- id: deviceVerificationDialog
-
- DeviceVerification {
- }
-
- }
-
Connections {
target: TimelineManager
onNewDeviceVerificationRequest: {
@@ -104,6 +129,12 @@ Page {
});
userProfile.show();
}
+ onShowImagePackSettings: {
+ var packSet = packSettingsComponent.createObject(timelineRoot, {
+ "packlist": packlist
+ });
+ packSet.show();
+ }
}
Connections {
@@ -116,6 +147,31 @@ Page {
}
}
+ Connections {
+ target: TimelineManager
+ onOpenRoomMembersDialog: {
+ var membersDialog = roomMembersComponent.createObject(timelineRoot, {
+ "members": members,
+ "roomName": Rooms.currentRoom.roomName
+ });
+ membersDialog.show();
+ }
+ onOpenRoomSettingsDialog: {
+ var roomSettings = roomSettingsComponent.createObject(timelineRoot, {
+ "roomSettings": settings
+ });
+ roomSettings.show();
+ }
+ onOpenInviteUsersDialog: {
+ var dialog = inviteDialog.createObject(timelineRoot, {
+ "roomId": Rooms.currentRoom.roomId,
+ "plainRoomName": Rooms.currentRoom.plainRoomName,
+ "invitees": invitees
+ });
+ dialog.show();
+ }
+ }
+
ChatPage {
anchors.fill: parent
}
diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
index 58e367a0..755ab503 100644
--- a/resources/qml/TimelineRow.qml
+++ b/resources/qml/TimelineRow.qml
@@ -40,6 +40,7 @@ Item {
required property int trustlevel
required property var timestamp
required property int status
+ required property int relatedEventCacheBuster
anchors.left: parent.left
anchors.right: parent.right
@@ -86,29 +87,30 @@ Item {
// fancy reply, if this is a reply
Reply {
function fromModel(role) {
- return replyTo != "" ? room.dataById(replyTo, role) : null;
+ return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null;
}
visible: replyTo
- userColor: TimelineManager.userColor(userId, Nheko.colors.base)
- blurhash: fromModel(Room.Blurhash) ?? ""
- body: fromModel(Room.Body) ?? ""
- formattedBody: fromModel(Room.FormattedBody) ?? ""
+ userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, Nheko.colors.base)
+ blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? ""
+ body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? ""
+ formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? ""
eventId: fromModel(Room.EventId) ?? ""
- filename: fromModel(Room.Filename) ?? ""
- filesize: fromModel(Room.Filesize) ?? ""
- proportionalHeight: fromModel(Room.ProportionalHeight) ?? 1
- type: fromModel(Room.Type) ?? MtxEvent.UnknownMessage
- typeString: fromModel(Room.TypeString) ?? ""
- url: fromModel(Room.Url) ?? ""
- originalWidth: fromModel(Room.OriginalWidth) ?? 0
- isOnlyEmoji: fromModel(Room.IsOnlyEmoji) ?? false
- userId: fromModel(Room.UserId) ?? ""
- userName: fromModel(Room.UserName) ?? ""
- thumbnailUrl: fromModel(Room.ThumbnailUrl) ?? ""
- roomTopic: fromModel(Room.RoomTopic) ?? ""
- roomName: fromModel(Room.RoomName) ?? ""
- callType: fromModel(Room.CallType) ?? ""
+ filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? ""
+ filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? ""
+ proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1
+ 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
+ userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? ""
+ userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? ""
+ thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? ""
+ roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
+ roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
+ callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? ""
+ relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
}
// actual message content
@@ -134,6 +136,7 @@ Item {
roomTopic: r.roomTopic
roomName: r.roomName
callType: r.callType
+ relatedEventCacheBuster: r.relatedEventCacheBuster
isReply: false
}
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 148a5817..c5cc69a6 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -246,17 +246,7 @@ Item {
NhekoDropArea {
anchors.fill: parent
- roomid: room ? room.roomId() : ""
- }
-
- Connections {
- target: room
- onOpenRoomSettingsDialog: {
- var roomSettings = roomSettingsComponent.createObject(timelineRoot, {
- "roomSettings": settings
- });
- roomSettings.show();
- }
+ roomid: room ? room.roomId : ""
}
}
diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml
index 58aba0c7..8543d02a 100644
--- a/resources/qml/TopBar.qml
+++ b/resources/qml/TopBar.qml
@@ -24,7 +24,7 @@ Rectangle {
TapHandler {
onSingleTapped: {
if (room)
- room.openRoomSettings();
+ TimelineManager.openRoomSettings(room.roomId);
eventPoint.accepted = true;
}
@@ -66,7 +66,7 @@ Rectangle {
displayName: roomName
onClicked: {
if (room)
- room.openRoomSettings();
+ TimelineManager.openRoomSettings(room.roomId);
}
}
@@ -111,22 +111,22 @@ Rectangle {
Platform.MenuItem {
visible: room ? room.permissions.canInvite() : false
text: qsTr("Invite users")
- onTriggered: TimelineManager.openInviteUsersDialog()
+ onTriggered: TimelineManager.openInviteUsers(room.roomId)
}
Platform.MenuItem {
text: qsTr("Members")
- onTriggered: TimelineManager.openMemberListDialog(room.roomId())
+ onTriggered: TimelineManager.openRoomMembers(room.roomId)
}
Platform.MenuItem {
text: qsTr("Leave room")
- onTriggered: TimelineManager.openLeaveRoomDialog(room.roomId())
+ onTriggered: TimelineManager.openLeaveRoomDialog(room.roomId)
}
Platform.MenuItem {
text: qsTr("Settings")
- onTriggered: room.openRoomSettings()
+ onTriggered: TimelineManager.openRoomSettings(room.roomId)
}
}
diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml
index 0b060629..a98c2a8b 100644
--- a/resources/qml/delegates/MessageDelegate.qml
+++ b/resources/qml/delegates/MessageDelegate.qml
@@ -29,6 +29,7 @@ Item {
required property string roomTopic
required property string roomName
required property string callType
+ required property int relatedEventCacheBuster
height: chooser.childrenRect.height
@@ -231,7 +232,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
- formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId())
+ formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId)
}
}
@@ -301,7 +302,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
- formatted: room.formatPowerLevelEvent(d.eventId)
+ formatted: d.relatedEventCacheBuster, room.formatPowerLevelEvent(d.eventId)
}
}
@@ -313,7 +314,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
- formatted: room.formatJoinRuleEvent(d.eventId)
+ formatted: d.relatedEventCacheBuster, room.formatJoinRuleEvent(d.eventId)
}
}
@@ -325,7 +326,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
- formatted: room.formatHistoryVisibilityEvent(d.eventId)
+ formatted: d.relatedEventCacheBuster, room.formatHistoryVisibilityEvent(d.eventId)
}
}
@@ -337,7 +338,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
- formatted: room.formatGuestAccessEvent(d.eventId)
+ formatted: d.relatedEventCacheBuster, room.formatGuestAccessEvent(d.eventId)
}
}
@@ -349,7 +350,7 @@ Item {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
- formatted: room.formatMemberEvent(d.eventId)
+ formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId)
}
}
diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml
index 3a188d78..75e3d617 100644
--- a/resources/qml/delegates/Reply.qml
+++ b/resources/qml/delegates/Reply.qml
@@ -30,6 +30,7 @@ Item {
property string roomTopic
property string roomName
property string callType
+ property int relatedEventCacheBuster
width: parent.width
height: replyContainer.height
@@ -95,6 +96,7 @@ Item {
roomTopic: r.roomTopic
roomName: r.roomName
callType: r.callType
+ relatedEventCacheBuster: r.relatedEventCacheBuster
enabled: false
width: parent.width
isReply: true
diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml
new file mode 100644
index 00000000..c4b4a885
--- /dev/null
+++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml
@@ -0,0 +1,309 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import "../components"
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import im.nheko 1.0
+
+ApplicationWindow {
+ id: win
+
+ property ImagePackListModel packlist
+ property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
+ property SingleImagePackModel currentPack: packlist.packAt(currentPackIndex)
+ property int currentPackIndex: 0
+ readonly property int stickerDim: 128
+ readonly property int stickerDimPad: 128 + Nheko.paddingSmall
+
+ title: qsTr("Image pack settings")
+ x: MainWindow.x + (MainWindow.width / 2) - (width / 2)
+ y: MainWindow.y + (MainWindow.height / 2) - (height / 2)
+ height: 400
+ width: 600
+ palette: Nheko.colors
+ color: Nheko.colors.base
+ modality: Qt.NonModal
+ flags: Qt.Dialog
+
+ AdaptiveLayout {
+ id: adaptiveView
+
+ anchors.fill: parent
+ singlePageMode: false
+ pageIndex: 0
+
+ AdaptiveLayoutElement {
+ id: packlistC
+
+ visible: Settings.groupView
+ minimumWidth: 200
+ collapsedWidth: 200
+ preferredWidth: 300
+ maximumWidth: 300
+
+ ListView {
+ model: packlist
+ clip: true
+
+ ScrollHelper {
+ flickable: parent
+ anchors.fill: parent
+ enabled: !Settings.mobileMode
+ }
+
+ delegate: Rectangle {
+ id: packItem
+
+ property color background: Nheko.colors.window
+ property color importantText: Nheko.colors.text
+ property color unimportantText: Nheko.colors.buttonText
+ property color bubbleBackground: Nheko.colors.highlight
+ property color bubbleText: Nheko.colors.highlightedText
+ required property string displayName
+ required property string avatarUrl
+ required property bool fromAccountData
+ required property bool fromCurrentRoom
+ required property int index
+
+ color: background
+ height: avatarSize + 2 * Nheko.paddingMedium
+ width: ListView.view.width
+ state: "normal"
+ states: [
+ State {
+ name: "highlight"
+ when: hovered.hovered && !(index == currentPackIndex)
+
+ PropertyChanges {
+ target: packItem
+ background: Nheko.colors.dark
+ importantText: Nheko.colors.brightText
+ unimportantText: Nheko.colors.brightText
+ bubbleBackground: Nheko.colors.highlight
+ bubbleText: Nheko.colors.highlightedText
+ }
+
+ },
+ State {
+ name: "selected"
+ when: index == currentPackIndex
+
+ PropertyChanges {
+ target: packItem
+ background: Nheko.colors.highlight
+ importantText: Nheko.colors.highlightedText
+ unimportantText: Nheko.colors.highlightedText
+ bubbleBackground: Nheko.colors.highlightedText
+ bubbleText: Nheko.colors.highlight
+ }
+
+ }
+ ]
+
+ TapHandler {
+ margin: -Nheko.paddingSmall
+ onSingleTapped: currentPackIndex = index
+ }
+
+ HoverHandler {
+ id: hovered
+ }
+
+ RowLayout {
+ spacing: Nheko.paddingMedium
+ anchors.fill: parent
+ anchors.margins: Nheko.paddingMedium
+
+ Avatar {
+ // In the future we could show an online indicator by setting the userid for the avatar
+ //userid: Nheko.currentUser.userid
+
+ id: avatar
+
+ enabled: false
+ Layout.alignment: Qt.AlignVCenter
+ height: avatarSize
+ width: avatarSize
+ url: avatarUrl.replace("mxc://", "image://MxcImage/")
+ displayName: packItem.displayName
+ }
+
+ 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
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 0
+
+ ElidedLabel {
+ Layout.alignment: Qt.AlignBottom
+ color: packItem.importantText
+ elideWidth: textContent.width - Nheko.paddingMedium
+ fullText: displayName
+ textFormat: Text.PlainText
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 0
+
+ ElidedLabel {
+ color: packItem.unimportantText
+ font.pixelSize: fontMetrics.font.pixelSize * 0.9
+ elideWidth: textContent.width - Nheko.paddingSmall
+ fullText: {
+ if (fromAccountData)
+ return qsTr("Private pack");
+ else if (fromCurrentRoom)
+ return qsTr("Pack from this room");
+ else
+ return qsTr("Globally enabled pack");
+ }
+ textFormat: Text.PlainText
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ AdaptiveLayoutElement {
+ id: packinfoC
+
+ Rectangle {
+ color: Nheko.colors.window
+
+ ColumnLayout {
+ id: packinfo
+
+ property string packName: currentPack ? currentPack.packname : ""
+ property string avatarUrl: currentPack ? currentPack.avatarUrl : ""
+
+ anchors.fill: parent
+ anchors.margins: Nheko.paddingLarge
+ spacing: Nheko.paddingLarge
+
+ Avatar {
+ url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/")
+ displayName: packinfo.packName
+ height: 100
+ width: 100
+ Layout.alignment: Qt.AlignHCenter
+ enabled: false
+ }
+
+ MatrixText {
+ text: packinfo.packName
+ font.pixelSize: 24
+ Layout.alignment: Qt.AlignHCenter
+ }
+
+ GridLayout {
+ Layout.alignment: Qt.AlignHCenter
+ visible: currentPack && currentPack.roomid != ""
+ columns: 2
+ rowSpacing: Nheko.paddingMedium
+
+ MatrixText {
+ text: qsTr("Enable globally")
+ }
+
+ ToggleButton {
+ ToolTip.text: qsTr("Enables this pack to be used in all rooms")
+ checked: currentPack ? currentPack.isGloballyEnabled : false
+ onClicked: currentPack.isGloballyEnabled = !currentPack.isGloballyEnabled
+ Layout.alignment: Qt.AlignRight
+ }
+
+ }
+
+ GridView {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ model: currentPack
+ cellWidth: stickerDimPad
+ cellHeight: stickerDimPad
+ boundsBehavior: Flickable.StopAtBounds
+ clip: true
+ currentIndex: -1 // prevent sorting from stealing focus
+ cacheBuffer: 500
+
+ ScrollHelper {
+ flickable: parent
+ anchors.fill: parent
+ enabled: !Settings.mobileMode
+ }
+
+ // 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/")
+ fillMode: Image.PreserveAspectFit
+ }
+
+ background: Rectangle {
+ anchors.fill: parent
+ color: hovered ? Nheko.colors.highlight : 'transparent'
+ radius: 5
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ footer: DialogButtonBox {
+ id: buttons
+
+ Button {
+ text: qsTr("Close")
+ DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
+ onClicked: win.close()
+ }
+
+ }
+
+}
diff --git a/resources/qml/emoji/EmojiButton.qml b/resources/qml/emoji/EmojiButton.qml
deleted file mode 100644
index 5f4d23d3..00000000
--- a/resources/qml/emoji/EmojiButton.qml
+++ /dev/null
@@ -1,23 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-import "../"
-import QtQuick 2.10
-import QtQuick.Controls 2.1
-import im.nheko 1.0
-import im.nheko.EmojiModel 1.0
-
-ImageButton {
- id: emojiButton
-
- property var colors: currentActivePalette
- property var emojiPicker
- property string event_id
-
- image: ":/icons/icons/ui/smile.png"
- onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, function(emoji) {
- room.input.reaction(event_id, emoji);
- TimelineManager.focusMessageInput();
- })
-}
diff --git a/resources/qml/emoji/EmojiPicker.qml b/resources/qml/emoji/EmojiPicker.qml
index 6f10a230..354e340c 100644
--- a/resources/qml/emoji/EmojiPicker.qml
+++ b/resources/qml/emoji/EmojiPicker.qml
@@ -130,6 +130,7 @@ Menu {
boundsBehavior: Flickable.StopAtBounds
clip: true
currentIndex: -1 // prevent sorting from stealing focus
+ cacheBuffer: 500
// Individual emoji
delegate: AbstractButton {
diff --git a/resources/qml/emoji/StickerPicker.qml b/resources/qml/emoji/StickerPicker.qml
new file mode 100644
index 00000000..3731a948
--- /dev/null
+++ b/resources/qml/emoji/StickerPicker.qml
@@ -0,0 +1,180 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import "../"
+import QtGraphicalEffects 1.0
+import QtQuick 2.9
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.3
+import im.nheko 1.0
+import im.nheko.EmojiModel 1.0
+
+Menu {
+ id: stickerPopup
+
+ property var callback
+ property var colors
+ property string roomid
+ property alias model: gridView.model
+ property var textArea
+ property real highlightHue: Nheko.colors.highlight.hslHue
+ property real highlightSat: Nheko.colors.highlight.hslSaturation
+ property real highlightLight: Nheko.colors.highlight.hslLightness
+ readonly property int stickerDim: 128
+ readonly property int stickerDimPad: 128 + Nheko.paddingSmall
+ readonly property int stickersPerRow: 3
+
+ function show(showAt, roomid_, callback) {
+ console.debug("Showing sticker picker");
+ roomid = roomid_;
+ stickerPopup.callback = callback;
+ popup(showAt ? showAt : null);
+ }
+
+ margins: 0
+ bottomPadding: 1
+ leftPadding: 1
+ rightPadding: 1
+ modal: true
+ focus: true
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
+ width: stickersPerRow * stickerDimPad + 20
+
+ Rectangle {
+ color: Nheko.colors.window
+ height: columnView.implicitHeight + 4
+ width: stickersPerRow * stickerDimPad + 20
+
+ ColumnLayout {
+ id: columnView
+
+ spacing: 0
+ anchors.leftMargin: 3
+ anchors.rightMargin: 3
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.topMargin: 2
+
+ // Search field
+ TextField {
+ id: emojiSearch
+
+ Layout.topMargin: 3
+ Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - 6
+ palette: Nheko.colors
+ background: null
+ placeholderTextColor: Nheko.colors.buttonText
+ color: Nheko.colors.text
+ placeholderText: qsTr("Search")
+ selectByMouse: true
+ rightPadding: clearSearch.width
+ onTextChanged: searchTimer.restart()
+ onVisibleChanged: {
+ if (visible)
+ forceActiveFocus();
+
+ }
+
+ 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.png?" + (clearSearch.hovered ? Nheko.colors.highlight : Nheko.colors.buttonText)
+ focusPolicy: Qt.NoFocus
+ onClicked: emojiSearch.clear()
+ hoverEnabled: true
+ background: null
+
+ anchors {
+ verticalCenter: parent.verticalCenter
+ right: parent.right
+ }
+ // clear the default hover effects.
+
+ Image {
+ height: parent.height - 2 * Nheko.paddingSmall
+ width: height
+ source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? Nheko.colors.highlight : Nheko.colors.buttonText)
+
+ anchors {
+ verticalCenter: parent.verticalCenter
+ right: parent.right
+ margins: Nheko.paddingSmall
+ }
+
+ }
+
+ }
+
+ }
+
+ // emoji grid
+ GridView {
+ id: gridView
+
+ model: roomid ? TimelineManager.completerFor("stickers", roomid) : null
+ Layout.preferredHeight: cellHeight * 3.5
+ Layout.preferredWidth: stickersPerRow * stickerDimPad + 20
+ Layout.leftMargin: 4
+ cellWidth: stickerDimPad
+ cellHeight: stickerDimPad
+ boundsBehavior: Flickable.StopAtBounds
+ clip: true
+ currentIndex: -1 // prevent sorting from stealing focus
+ cacheBuffer: 500
+
+ ScrollHelper {
+ flickable: parent
+ anchors.fill: parent
+ enabled: !Settings.mobileMode
+ }
+
+ // 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/")
+ fillMode: Image.PreserveAspectFit
+ }
+
+ background: Rectangle {
+ anchors.fill: parent
+ color: hovered ? Nheko.colors.highlight : 'transparent'
+ radius: 5
+ }
+
+ }
+
+ ScrollBar.vertical: ScrollBar {
+ id: emojiScroll
+ }
+
+ }
+
+ }
+
+ }
+
+}
diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml
index 5f564853..97932cc9 100644
--- a/resources/qml/voip/PlaceCall.qml
+++ b/resources/qml/voip/PlaceCall.qml
@@ -88,7 +88,7 @@ Popup {
onClicked: {
if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText;
- CallManager.sendInvite(room.roomId(), CallType.VOICE);
+ CallManager.sendInvite(room.roomId, CallType.VOICE);
close();
}
}
@@ -102,7 +102,7 @@ Popup {
if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText;
Settings.camera = cameraCombo.currentText;
- CallManager.sendInvite(room.roomId(), CallType.VIDEO);
+ CallManager.sendInvite(room.roomId, CallType.VIDEO);
close();
}
}
diff --git a/resources/qml/voip/ScreenShare.qml b/resources/qml/voip/ScreenShare.qml
index a10057b2..8cd43b1c 100644
--- a/resources/qml/voip/ScreenShare.qml
+++ b/resources/qml/voip/ScreenShare.qml
@@ -136,7 +136,7 @@ Popup {
Settings.screenSharePiP = pipCheckBox.checked;
Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked;
Settings.screenShareHideCursor = hideCursorCheckBox.checked;
- CallManager.sendInvite(room.roomId(), CallType.SCREEN, windowCombo.currentIndex);
+ CallManager.sendInvite(room.roomId, CallType.SCREEN, windowCombo.currentIndex);
close();
}
}
|