Compare commits
93 Commits
v22.06
...
work/nvrwh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27455626fc | ||
|
|
696b34e094 | ||
|
|
6ce74bbd0c | ||
|
|
3c7e85fbbf | ||
|
|
6c6e408497 | ||
|
|
1ac79273b6 | ||
|
|
d4d99284cc | ||
|
|
25226aa61f | ||
|
|
6748a2d21d | ||
|
|
fdb424e65e | ||
|
|
8cd0b12c4a | ||
|
|
d133c4fab7 | ||
|
|
2f8303348b | ||
|
|
7dfac8a9f7 | ||
|
|
3c98a8fac4 | ||
|
|
57b1bb659c | ||
|
|
da8c8c48bc | ||
|
|
07c5cd8016 | ||
|
|
a23ef130ca | ||
|
|
2f4116796a | ||
|
|
f004f9e3c8 | ||
|
|
9a3726f9ff | ||
|
|
91d1f6ffeb | ||
|
|
cd895f1b06 | ||
|
|
fead1a69b3 | ||
|
|
63af4cae77 | ||
|
|
89090690b4 | ||
|
|
6a16334eab | ||
|
|
5b05622058 | ||
|
|
1eb705c17f | ||
|
|
f9ad0e8426 | ||
|
|
ef1ff04ef2 | ||
|
|
9b54aff3d5 | ||
|
|
52a093d449 | ||
|
|
619369e148 | ||
|
|
e63a9a9be1 | ||
|
|
5c97e67404 | ||
|
|
84a265b7f9 | ||
|
|
61201a7097 | ||
|
|
b9630ad2f2 | ||
|
|
179a201113 | ||
|
|
6eef58e57d | ||
|
|
94f325609a | ||
|
|
a40ba493b6 | ||
|
|
916e7465f1 | ||
|
|
8257a9d65e | ||
|
|
a75048761b | ||
|
|
f21822aba7 | ||
|
|
75eb5a51af | ||
|
|
7e37c31011 | ||
|
|
f80039a5c4 | ||
|
|
400d86a1e9 | ||
|
|
b20b1c10d0 | ||
|
|
a37fd0713f | ||
|
|
b1581a54d1 | ||
|
|
ded60b906b | ||
|
|
6957dd0fa2 | ||
|
|
7de4014b28 | ||
|
|
63e7ec1bd7 | ||
|
|
f24428fab3 | ||
|
|
c3a5a767c2 | ||
|
|
5fb311b509 | ||
|
|
f0d832f756 | ||
|
|
a7c137ca39 | ||
|
|
a96e8958c9 | ||
|
|
7dc951d2cd | ||
|
|
c3ee277ede | ||
|
|
78d62e9376 | ||
|
|
11e9eaf3e9 | ||
|
|
4337d0d5d8 | ||
|
|
a07537367f | ||
|
|
51efecaa25 | ||
|
|
830a47c5ff | ||
|
|
24748d42d8 | ||
|
|
19fe439e95 | ||
|
|
2bcd7118f4 | ||
|
|
27e660178e | ||
|
|
e0df553a72 | ||
|
|
53f040cb28 | ||
|
|
28cc7cf616 | ||
|
|
d224df8aa2 | ||
|
|
1dff2b8273 | ||
|
|
722aa422e7 | ||
|
|
70de0dc624 | ||
|
|
9d804e6ea7 | ||
|
|
52d552650d | ||
|
|
0c5007fd56 | ||
|
|
af19829225 | ||
|
|
0be8828dd4 | ||
|
|
729b6bd354 | ||
|
|
ef10042179 | ||
|
|
846d430947 | ||
|
|
cce4a3ebdf |
157
.flatpak-manifest.json
Normal file
157
.flatpak-manifest.json
Normal file
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"id": "org.kde.neochat",
|
||||
"branch": "master",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "5.15-21.08",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"command": "neochat",
|
||||
"tags": [
|
||||
"nightly"
|
||||
],
|
||||
"desktop-file-name-suffix": " (Nightly)",
|
||||
"finish-args": [
|
||||
"--share=network",
|
||||
"--share=ipc",
|
||||
"--socket=x11",
|
||||
"--socket=wayland",
|
||||
"--device=dri",
|
||||
"--filesystem=xdg-download",
|
||||
"--talk-name=org.freedesktop.Notifications",
|
||||
"--talk-name=org.kde.kwalletd5",
|
||||
"--talk-name=org.kde.StatusNotifierWatcher",
|
||||
"--own-name=org.kde.StatusNotifierItem-2-2"
|
||||
],
|
||||
"modules": [
|
||||
{
|
||||
"name": "kquickimageeditor",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://invent.kde.org/libraries/kquickimageeditor"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "olm",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.matrix.org/matrix-org/olm.git",
|
||||
"tag": "3.2.10",
|
||||
"x-checker-data": {
|
||||
"type": "git",
|
||||
"tag-pattern": "^([\\d.]+)$"
|
||||
},
|
||||
"commit": "9908862979147a71dc6abaecd521be526ae77be1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "libsecret",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"-Dmanpage=false",
|
||||
"-Dvapi=false",
|
||||
"-Dgtk_doc=false",
|
||||
"-Dintrospection=false",
|
||||
"-Dgcrypt=false"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://download.gnome.org/sources/libsecret/0.20/libsecret-0.20.5.tar.xz",
|
||||
"sha256": "3fb3ce340fcd7db54d87c893e69bfc2b1f6e4d4b279065ffe66dac9f0fd12b4d",
|
||||
"x-checker-data": {
|
||||
"type": "gnome",
|
||||
"name": "libsecret",
|
||||
"stable-only": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "qtkeychain",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/frankosterfeld/qtkeychain/archive/v0.13.2.tar.gz",
|
||||
"sha256": "20beeb32de7c4eb0af9039b21e18370faf847ac8697ab3045906076afbc4caa5",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 4138,
|
||||
"stable-only": true,
|
||||
"url-template": "https://github.com/frankosterfeld/qtkeychain/archive/v$version.tar.gz"
|
||||
}
|
||||
}
|
||||
],
|
||||
"config-opts": [
|
||||
"-DCMAKE_INSTALL_LIBDIR=/app/lib",
|
||||
"-DLIB_INSTALL_DIR=/app/lib",
|
||||
"-DBUILD_TRANSLATIONS=NO"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "libQuotient",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/quotient-im/libQuotient.git",
|
||||
"branch": "dev"
|
||||
}
|
||||
],
|
||||
"config-opts": [
|
||||
"-DQuotient_ENABLE_E2EE=ON"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cmark",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/commonmark/cmark.git"
|
||||
}
|
||||
],
|
||||
"config-opts": [
|
||||
"-DCMARK_TESTS=OFF",
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DCMAKE_INSTALL_PREFIX=/app"
|
||||
],
|
||||
"builddir": true
|
||||
},
|
||||
{
|
||||
"name": "qcoro",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.4.0.tar.gz",
|
||||
"sha256": "0e68b3f0ce7bf521ffbdd731464d2d60d8d7a39a749b551ed26855a1707d86d1",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 236236,
|
||||
"stable-only": true,
|
||||
"url-template": "https://github.com/danvratil/qcoro/archive/refs/tags/v$version.tar.gz"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "neochat",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "dir",
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"config-opts": [
|
||||
"-DNEOCHAT_FLATPAK=ON"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,4 +5,8 @@ include:
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
|
||||
# TODO enable once we can have qt6 libQuotient on the CI
|
||||
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml
|
||||
|
||||
@@ -42,3 +42,7 @@ License: BSD-2-Clause
|
||||
Files: imports/NeoChat/Component/confetti.png imports/NeoChat/Component/glowdot.png
|
||||
Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: .flatpak-manifest.json
|
||||
Copyright: 2020-2022 Tobias Fella <fella@posteo.de>
|
||||
License: BSD-2-Clause
|
||||
|
||||
@@ -41,8 +41,8 @@ ecm_setup_version(${PROJECT_VERSION}
|
||||
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
|
||||
)
|
||||
|
||||
find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
|
||||
set_package_properties(Qt5 PROPERTIES
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
|
||||
set_package_properties(Qt${QT_MAJOR_VERSION} PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
)
|
||||
@@ -56,8 +56,8 @@ set_package_properties(KF5Kirigami2 PROPERTIES
|
||||
PURPOSE "Kirigami application UI framework"
|
||||
)
|
||||
|
||||
find_package(Qt5Keychain)
|
||||
set_package_properties(Qt5Keychain PROPERTIES
|
||||
find_package(Qt${QT_MAJOR_VERSION}Keychain)
|
||||
set_package_properties(Qt${QT_MAJOR_VERSION}Keychain PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Secure storage of account secrets"
|
||||
)
|
||||
@@ -69,11 +69,12 @@ if(ANDROID)
|
||||
PURPOSE "Encrypted communications"
|
||||
)
|
||||
else()
|
||||
find_package(Qt5 ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem Sonnet)
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem)
|
||||
set_package_properties(KF5QQC2DesktopStyle PROPERTIES
|
||||
TYPE RUNTIME
|
||||
)
|
||||
ecm_find_qmlmodule(org.kde.sonnet 1.0)
|
||||
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
|
||||
endif()
|
||||
|
||||
@@ -108,8 +109,8 @@ set_package_properties(KQuickImageEditor PROPERTIES
|
||||
PURPOSE "Add image editing capability to image attachments"
|
||||
)
|
||||
|
||||
find_package(QCoro5 COMPONENTS Core QUIET)
|
||||
if(NOT QCoro5_FOUND)
|
||||
find_package(QCoro${QT_MAJOR_VERSION} COMPONENTS Core QUIET)
|
||||
if(NOT QCoro${QT_MAJOR_VERSION}_FOUND)
|
||||
find_package(QCoro REQUIRED)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ and can also directly be downloaded from the [binary factory](https://binary-fac
|
||||
|
||||
Nightly builds for [Windows](https://binary-factory.kde.org/job/NeoChat_Nightly_win64/), [MacOS](https://binary-factory.kde.org/job/NeoChat_Nightly_macos/) and [AppImages](https://binary-factory.kde.org/job/NeoChat_Nightly_appimage/) can also be downloaded from the [binary factory](https://binary-factory.kde.org/search/?q=neochat).
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
@@ -55,7 +55,7 @@ We welcome contributions in this direction.
|
||||
|
||||
## Contact
|
||||
|
||||
You can reach the maintainers at #neochat:kde.org, if you are already on Matrix.
|
||||
You can reach the maintainers at [#neochat:kde.org](https://matrix.to/#/#neochat:kde.org), if you are already on Matrix.
|
||||
Development happens in http://invent.kde.org/network/neochat (not in GitHub).
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
@@ -14,14 +14,14 @@ import NeoChat.Page 1.0
|
||||
Loader {
|
||||
id: root
|
||||
|
||||
property var attachmentMimetype: FileType.mimeTypeForUrl(ChatBoxHelper.attachmentPath)
|
||||
property var attachmentMimetype: FileType.mimeTypeForUrl(chatBoxHelper.attachmentPath)
|
||||
readonly property bool hasImage: attachmentMimetype.valid && FileType.supportedImageFormats.includes(attachmentMimetype.preferredSuffix)
|
||||
|
||||
active: visible
|
||||
sourceComponent: Component {
|
||||
Pane {
|
||||
id: attachmentPane
|
||||
property string baseFileName: ChatBoxHelper.attachmentPath.toString().substring(ChatBoxHelper.attachmentPath.toString().lastIndexOf('/') + 1, ChatBoxHelper.attachmentPath.length)
|
||||
property string baseFileName: chatBoxHelper.attachmentPath.toString().substring(chatBoxHelper.attachmentPath.toString().lastIndexOf('/') + 1, chatBoxHelper.attachmentPath.length)
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
contentItem: Item {
|
||||
@@ -46,7 +46,7 @@ Loader {
|
||||
asynchronous: true
|
||||
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
|
||||
smooth: height == preferredHeight && parent.height == parent.implicitHeight // Don't smooth until height animation stops
|
||||
source: hasImage ? ChatBoxHelper.attachmentPath : ""
|
||||
source: hasImage ? chatBoxHelper.attachmentPath : ""
|
||||
visible: hasImage
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
@@ -162,14 +162,14 @@ Loader {
|
||||
Component {
|
||||
id: imageEditorPage
|
||||
ImageEditorPage {
|
||||
imagePath: ChatBoxHelper.attachmentPath
|
||||
imagePath: chatBoxHelper.attachmentPath
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
|
||||
imageEditor.newPathChanged.connect(function(newPath) {
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
ChatBoxHelper.attachmentPath = newPath;
|
||||
chatBoxHelper.attachmentPath = newPath;
|
||||
});
|
||||
}
|
||||
ToolTip.text: text
|
||||
@@ -180,7 +180,7 @@ Loader {
|
||||
icon.name: "dialog-cancel"
|
||||
text: i18n("Cancel")
|
||||
display: AbstractButton.IconOnly
|
||||
onClicked: ChatBoxHelper.clearAttachment();
|
||||
onClicked: chatBoxHelper.clearAttachment();
|
||||
ToolTip.text: text
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@ ToolBar {
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
|
||||
+ inputField.topPadding + inputField.bottomPadding
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: inputField.font
|
||||
@@ -94,11 +97,11 @@ ToolBar {
|
||||
//property int lineHeight: contentHeight / lineCount
|
||||
|
||||
text: inputFieldText
|
||||
placeholderText: currentRoom.usesEncryption ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : editEventId.length > 0 ? i18n("Edit Message") : i18n("Write your message...")
|
||||
placeholderText: readOnly ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : editEventId.length > 0 ? i18n("Edit Message") : currentRoom.usesEncryption ? i18n("Send an encrypted message…") : i18n("Send a message…")
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
horizontalAlignment: TextEdit.AlignLeft
|
||||
wrapMode: Text.Wrap
|
||||
readOnly: currentRoom.usesEncryption
|
||||
readOnly: currentRoom.usesEncryption && !Controller.encryptionSupported
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
Kirigami.Theme.inherit: false
|
||||
@@ -145,11 +148,7 @@ ToolBar {
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
switchRoomDown();
|
||||
} else if (event.key === Qt.Key_PageUp) {
|
||||
switchRoomUp();
|
||||
} else if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
|
||||
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
|
||||
chatBar.pasteImage();
|
||||
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
|
||||
replyPreviousUserMessage();
|
||||
@@ -278,14 +277,14 @@ ToolBar {
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: !ChatBoxHelper.isReplying && (!ChatBoxHelper.hasAttachment || uploadingBusySpinner.running)
|
||||
visible: !chatBoxHelper.isReplying && (!chatBoxHelper.hasAttachment || uploadingBusySpinner.running)
|
||||
implicitWidth: uploadButton.implicitWidth
|
||||
implicitHeight: uploadButton.implicitHeight
|
||||
ToolButton {
|
||||
id: uploadButton
|
||||
anchors.fill: parent
|
||||
// Matrix does not allow sending attachments in replies
|
||||
visible: !ChatBoxHelper.isReplying && !ChatBoxHelper.hasAttachment && !uploadingBusySpinner.running
|
||||
visible: !chatBoxHelper.isReplying && !chatBoxHelper.hasAttachment && !uploadingBusySpinner.running
|
||||
icon.name: "mail-attachment"
|
||||
text: i18n("Attach an image or file")
|
||||
display: AbstractButton.IconOnly
|
||||
@@ -297,7 +296,7 @@ ToolBar {
|
||||
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
||||
fileDialog.chosen.connect((path) => {
|
||||
if (!path) { return }
|
||||
ChatBoxHelper.attachmentPath = path;
|
||||
chatBoxHelper.attachmentPath = path;
|
||||
})
|
||||
fileDialog.open()
|
||||
}
|
||||
@@ -380,16 +379,16 @@ ToolBar {
|
||||
if (!Clipboard.saveImage(localPath)) {
|
||||
return;
|
||||
}
|
||||
ChatBoxHelper.attachmentPath = localPath;
|
||||
chatBoxHelper.attachmentPath = localPath;
|
||||
}
|
||||
|
||||
function postMessage() {
|
||||
checkForFancyEffectsReason();
|
||||
|
||||
if (ChatBoxHelper.hasAttachment) {
|
||||
if (chatBoxHelper.hasAttachment) {
|
||||
// send attachment but don't reset the text
|
||||
actionsHandler.postMessage("", ChatBoxHelper.attachmentPath,
|
||||
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, {}, this.customEmojiModel);
|
||||
actionsHandler.postMessage("", chatBoxHelper.attachmentPath,
|
||||
chatBoxHelper.replyEventId, chatBoxHelper.editEventId, {}, this.customEmojiModel);
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
messageSent();
|
||||
return;
|
||||
@@ -401,14 +400,12 @@ ToolBar {
|
||||
actionsHandler.postEdit(inputField.text);
|
||||
} else {
|
||||
// send normal message
|
||||
actionsHandler.postMessage(inputField.text.trim(), ChatBoxHelper.attachmentPath,
|
||||
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, userAutocompleted, this.customEmojiModel);
|
||||
actionsHandler.postMessage(inputField.text.trim(), chatBoxHelper.attachmentPath,
|
||||
chatBoxHelper.replyEventId, chatBoxHelper.editEventId, userAutocompleted, this.customEmojiModel);
|
||||
}
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
inputField.clear();
|
||||
inputField.text = Qt.binding(function() {
|
||||
return currentRoom ? currentRoom.cachedInput : "";
|
||||
});
|
||||
inputField.text = currentRoom ? currentRoom.cachedInput : "";
|
||||
messageSent()
|
||||
}
|
||||
|
||||
|
||||
@@ -127,8 +127,8 @@ Item {
|
||||
|
||||
ReplyPane {
|
||||
id: replyPane
|
||||
visible: ChatBoxHelper.isReplying || ChatBoxHelper.isEditing
|
||||
user: ChatBoxHelper.replyUser
|
||||
visible: chatBoxHelper.isReplying || chatBoxHelper.isEditing
|
||||
user: chatBoxHelper.replyUser
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: attachmentSeparator.top
|
||||
@@ -154,7 +154,7 @@ Item {
|
||||
|
||||
AttachmentPane {
|
||||
id: attachmentPane
|
||||
visible: ChatBoxHelper.hasAttachment
|
||||
visible: chatBoxHelper.hasAttachment
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: chatBarSeparator.top
|
||||
@@ -248,7 +248,7 @@ Item {
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ChatBoxHelper
|
||||
target: chatBoxHelper
|
||||
|
||||
function onShouldClearText() {
|
||||
root.inputFieldText = "";
|
||||
@@ -277,7 +277,7 @@ Item {
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
ChatBoxHelper.clear();
|
||||
chatBoxHelper.clear();
|
||||
chatBar.emojiPaneOpened = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ Popup {
|
||||
implicitHeight: Math.min(completionListView.contentHeight, Kirigami.Units.gridUnit * 10)
|
||||
|
||||
contentItem: ScrollView {
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ListView {
|
||||
id: completionListView
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.kde.neochat 1.0
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
readonly property bool isEdit: ChatBoxHelper.isEditing
|
||||
readonly property bool isEdit: chatBoxHelper.isEditing
|
||||
property var user: null
|
||||
property string avatarMediaUrl: user ? "image://mxc/" + user.avatarMediaId : ""
|
||||
|
||||
@@ -71,6 +71,10 @@ Loader {
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
TextArea {
|
||||
id: textArea
|
||||
leftPadding: 0
|
||||
@@ -79,7 +83,7 @@ Loader {
|
||||
bottomPadding: 0
|
||||
text: {
|
||||
const stylesheet = "<style> a{color:"+Kirigami.Theme.linkColor+";}.user-pill{}</style>";
|
||||
const content = ChatBoxHelper.isReplying ? ChatBoxHelper.replyEventContent : ChatBoxHelper.editContent;
|
||||
const content = chatBoxHelper.isReplying ? chatBoxHelper.replyEventContent : chatBoxHelper.editContent;
|
||||
return stylesheet + content;
|
||||
}
|
||||
selectByMouse: true
|
||||
@@ -102,7 +106,7 @@ Loader {
|
||||
text: i18n("Cancel")
|
||||
display: AbstractButton.IconOnly
|
||||
onClicked: {
|
||||
ChatBoxHelper.clearEditReply();
|
||||
chatBoxHelper.clear();
|
||||
root.replyCancelled();
|
||||
}
|
||||
ToolTip.text: text
|
||||
|
||||
@@ -20,7 +20,7 @@ LoginStep {
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onSsoUrlChanged() {
|
||||
Qt.openUrlExternally(LoginHelper.ssoUrl)
|
||||
UrlHelper.openUrl(LoginHelper.ssoUrl)
|
||||
}
|
||||
function onConnected() {
|
||||
processed("qrc:/imports/NeoChat/Component/Login/Loading.qml")
|
||||
|
||||
@@ -1,71 +1,116 @@
|
||||
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtMultimedia 5.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
import NeoChat.Menu.Timeline 1.0
|
||||
|
||||
TimelineContainer {
|
||||
id: audioDelegate
|
||||
|
||||
width: ListView.view.width
|
||||
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||
|
||||
onOpenContextMenu: openFileContext(model, audioDelegate)
|
||||
|
||||
readonly property bool downloaded: model.progressInfo && model.progressInfo.completed
|
||||
onDownloadedChanged: audio.play()
|
||||
|
||||
hoverComponent: hoverActions
|
||||
innerObject: Control {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: audioDelegate.bubbleMaxWidth
|
||||
Layout.maximumWidth: audioDelegate.contentMaxWidth
|
||||
|
||||
Audio {
|
||||
id: audio
|
||||
source: currentRoom.urlToMxcUrl(content.url)
|
||||
source: model.progressInfo.localPath
|
||||
autoLoad: false
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: openFileContext(model, parent)
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: openFileContext(model, parent)
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: "notDownloaded"
|
||||
when: !model.progressInfo.completed && !model.progressInfo.active
|
||||
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-start"
|
||||
onClicked: currentRoom.downloadFile(model.eventId)
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "downloading"
|
||||
when: model.progressInfo.active && !model.progressInfo.completed
|
||||
PropertyChanges {
|
||||
target: downloadBar
|
||||
visible: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-stop"
|
||||
onClicked: {
|
||||
currentRoom.cancelFileTransfer(model.eventId)
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "paused"
|
||||
when: model.progressInfo.completed && (audio.playbackState === Audio.StoppedState || audio.playbackState === Audio.PausedState)
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-start"
|
||||
onClicked: {
|
||||
audio.play()
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "playing"
|
||||
when: model.progressInfo.completed && audio.playbackState === Audio.PlayingState
|
||||
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
|
||||
icon.name: "media-playback-pause"
|
||||
|
||||
onClicked: audio.pause()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
RowLayout {
|
||||
ToolButton {
|
||||
icon.name: audio.playbackState == Audio.PlayingState ? "media-playback-pause" : "media-playback-start"
|
||||
|
||||
onClicked: {
|
||||
if (audio.playbackState == Audio.PlayingState) {
|
||||
audio.pause()
|
||||
} else {
|
||||
audio.play()
|
||||
}
|
||||
}
|
||||
id: playButton
|
||||
}
|
||||
Label {
|
||||
text: model.display
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
ProgressBar {
|
||||
id: downloadBar
|
||||
visible: false
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: model.content.info.size
|
||||
value: model.progressInfo.progress
|
||||
}
|
||||
RowLayout {
|
||||
visible: audio.hasAudio
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
// Server doesn't support seeking, so use ProgressBar instead of Slider :(
|
||||
ProgressBar {
|
||||
|
||||
Slider {
|
||||
from: 0
|
||||
to: audio.duration
|
||||
value: audio.position
|
||||
onMoved: audio.seek(value)
|
||||
}
|
||||
|
||||
Label {
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.kde.neochat 1.0
|
||||
|
||||
TimelineContainer {
|
||||
id: encryptedDelegate
|
||||
width: ListView.view.width
|
||||
|
||||
innerObject: TextEdit {
|
||||
text: i18n("This message is encrypted and the sender has not shared the key with this device.")
|
||||
@@ -20,7 +19,7 @@ TimelineContainer {
|
||||
readOnly: true
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.RichText
|
||||
Layout.maximumWidth: encryptedDelegate.bubbleMaxWidth
|
||||
Layout.maximumWidth: encryptedDelegate.contentMaxWidth
|
||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
@@ -16,11 +15,12 @@ import NeoChat.Menu.Timeline 1.0
|
||||
|
||||
TimelineContainer {
|
||||
id: fileDelegate
|
||||
width: ListView.view.width
|
||||
|
||||
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||
hoverComponent: hoverActions
|
||||
|
||||
onOpenContextMenu: openFileContext(model, fileDelegate)
|
||||
|
||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||
|
||||
function saveFileAs() {
|
||||
@@ -30,14 +30,14 @@ TimelineContainer {
|
||||
}
|
||||
|
||||
function openSavedFile() {
|
||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
||||
if (UrlHelper.openUrl(progressInfo.localPath)) return;
|
||||
if (UrlHelper.openUrl(progressInfo.localDir)) return;
|
||||
}
|
||||
|
||||
innerObject: RowLayout {
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: fileDelegate.bubbleMaxWidth
|
||||
Layout.maximumWidth: fileDelegate.contentMaxWidth
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
@@ -131,14 +131,5 @@ TimelineContainer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: openFileContext(model, parent)
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: openFileContext(model, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
@@ -16,11 +15,11 @@ import NeoChat.Menu.Timeline 1.0
|
||||
TimelineContainer {
|
||||
id: imageDelegate
|
||||
|
||||
width: ListView.view.width
|
||||
|
||||
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||
hoverComponent: hoverActions
|
||||
|
||||
onOpenContextMenu: openFileContext(model, imageDelegate)
|
||||
|
||||
property var content: model.content
|
||||
readonly property bool isAnimated: contentType === "image/gif"
|
||||
|
||||
@@ -35,8 +34,8 @@ TimelineContainer {
|
||||
innerObject: Image {
|
||||
id: img
|
||||
|
||||
Layout.maximumWidth: imageDelegate.bubbleMaxWidth
|
||||
Layout.maximumHeight: imageDelegate.bubbleMaxWidth / imageDelegate.info.w * imageDelegate.info.h
|
||||
Layout.maximumWidth: imageDelegate.contentMaxWidth
|
||||
Layout.maximumHeight: imageDelegate.contentMaxWidth / imageDelegate.info.w * imageDelegate.info.h
|
||||
Layout.preferredWidth: imageDelegate.info.w
|
||||
Layout.preferredHeight: imageDelegate.info.h
|
||||
source: model.mediaUrl
|
||||
@@ -86,11 +85,6 @@ TimelineContainer {
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: openFileContext(model, parent)
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: openFileContext(model, parent)
|
||||
@@ -115,8 +109,8 @@ TimelineContainer {
|
||||
}
|
||||
|
||||
function openSavedFile() {
|
||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
||||
if (UrlHelper.openUrl(progressInfo.localPath)) return;
|
||||
if (UrlHelper.openUrl(progressInfo.localDir)) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,24 +13,15 @@ import org.kde.neochat 1.0
|
||||
TimelineContainer {
|
||||
id: messageDelegate
|
||||
|
||||
width: ListView.view.width
|
||||
property bool isEmote: false
|
||||
onOpenContextMenu: openMessageContext(model, parent.selectedText, Controller.plainText(label.textDocument))
|
||||
|
||||
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||
hoverComponent: hoverActions
|
||||
|
||||
innerObject: RichLabel {
|
||||
id: label
|
||||
isEmote: messageDelegate.isEmote
|
||||
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: openMessageContext(model, parent.selectedText)
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: openMessageContext(model, parent.selectedText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,49 @@ import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
QQC2.ItemDelegate {
|
||||
id: readMarkerDelegate
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
topInset: Kirigami.Units.largeSpacing
|
||||
topPadding: Kirigami.Units.largeSpacing * 2
|
||||
width: ListView.view.width - Kirigami.Units.gridUnit
|
||||
x: Kirigami.Units.gridUnit / 2
|
||||
|
||||
// extraWidth defines how the delegate can grow after the listView gets very wide
|
||||
readonly property int extraWidth: messageListView.width >= Kirigami.Units.gridUnit * 46 ? Math.min((messageListView.width - Kirigami.Units.gridUnit * 46), Kirigami.Units.gridUnit * 20) : 0
|
||||
readonly property int delegateMaxWidth: Config.compactLayout ? messageListView.width - Kirigami.Units.largeSpacing * 2 : Math.min(messageListView.width - Kirigami.Units.largeSpacing * 2, Kirigami.Units.gridUnit * 40 + extraWidth)
|
||||
|
||||
width: delegateMaxWidth
|
||||
anchors.leftMargin: Kirigami.Units.largeSpacing
|
||||
anchors.rightMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
state: Config.compactLayout ? "alignLeft" : "alignCenter"
|
||||
// Align left when in compact mode and center when using bubbles
|
||||
states: [
|
||||
State {
|
||||
name: "alignLeft"
|
||||
AnchorChanges {
|
||||
target: readMarkerDelegate
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.left: parent ? parent.left : undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "alignCenter"
|
||||
AnchorChanges {
|
||||
target: readMarkerDelegate
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
anchors.left: undefined
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
AnchorAnimation {
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
contentItem: QQC2.Label {
|
||||
text: i18nc("Relative time since the room was last read", "Last read: %1", time)
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ MouseArea {
|
||||
id: replyButton
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: replyName.implicitHeight + (loader.item ? loader.item.height : 0) + Kirigami.Units.largeSpacing
|
||||
implicitWidth: Math.min(bubbleMaxWidth, Math.max((loader.item ? loader.item.width + Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
|
||||
implicitWidth: Math.min(contentMaxWidth, Math.max((loader.item ? loader.item.width + Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
|
||||
Component.onCompleted: {
|
||||
parent.Layout.fillWidth = true;
|
||||
parent.Layout.preferredWidth = Qt.binding(function() { return implicitWidth; })
|
||||
parent.Layout.maximumWidth = Qt.binding(function() { return bubbleMaxWidth + Kirigami.Units.largeSpacing * 2; })
|
||||
parent.Layout.maximumWidth = Qt.binding(function() { return contentMaxWidth + Kirigami.Units.largeSpacing * 2; })
|
||||
}
|
||||
Rectangle {
|
||||
id: replyLeftBorder
|
||||
@@ -79,7 +79,7 @@ MouseArea {
|
||||
id: replyText
|
||||
textMessage: reply.display
|
||||
textFormat: Text.RichText
|
||||
width: Math.min(implicitWidth, bubbleMaxWidth - Kirigami.Units.largeSpacing * 3)
|
||||
width: Math.min(implicitWidth, contentMaxWidth - Kirigami.Units.largeSpacing * 3)
|
||||
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ MouseArea {
|
||||
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
|
||||
source: "image://mxc/" + mediaId
|
||||
|
||||
width: bubbleMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - replyAvatar.width
|
||||
width: contentMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - replyAvatar.width
|
||||
height: reply.content.info.h / reply.content.info.w * width
|
||||
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ TextEdit {
|
||||
|
||||
Layout.fillWidth: Config.compactLayout
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
text: "<style>
|
||||
table {
|
||||
@@ -44,6 +44,10 @@ a{
|
||||
text-decoration: none;
|
||||
}
|
||||
" + (!spoilerRevealed ? "
|
||||
[data-mx-spoiler] a {
|
||||
color: transparent;
|
||||
background: " + Kirigami.Theme.textColor + ";
|
||||
}
|
||||
[data-mx-spoiler] {
|
||||
color: transparent;
|
||||
background: " + Kirigami.Theme.textColor + ";
|
||||
@@ -58,8 +62,11 @@ a{
|
||||
wrapMode: Text.Wrap
|
||||
textFormat: Text.RichText
|
||||
|
||||
onLinkActivated: RoomManager.openResource(link)
|
||||
onHoveredLinkChanged: if (hoveredLink.length > 0) {
|
||||
onLinkActivated: {
|
||||
spoilerRevealed = true
|
||||
RoomManager.openResource(link)
|
||||
}
|
||||
onHoveredLinkChanged: if (hoveredLink.length > 0 && hoveredLink !== "1") {
|
||||
applicationWindow().hoverLinkIndicator.text = hoveredLink;
|
||||
} else {
|
||||
applicationWindow().hoverLinkIndicator.text = "";
|
||||
|
||||
@@ -7,19 +7,53 @@ import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
Control {
|
||||
x: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing
|
||||
width: ListView.view.width - Kirigami.Units.largeSpacing - x
|
||||
id: stateDelegate
|
||||
// extraWidth defines how the delegate can grow after the listView gets very wide
|
||||
readonly property int extraWidth: messageListView.width >= Kirigami.Units.gridUnit * 46 ? Math.min((messageListView.width - Kirigami.Units.gridUnit * 46), Kirigami.Units.gridUnit * 20) : 0
|
||||
readonly property int delegateMaxWidth: Config.compactLayout ? messageListView.width: Math.min(messageListView.width, Kirigami.Units.gridUnit * 40 + extraWidth)
|
||||
|
||||
width: delegateMaxWidth
|
||||
// anchors.leftMargin: Kirigami.Units.largeSpacing
|
||||
// anchors.rightMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
state: Config.compactLayout ? "alignLeft" : "alignCenter"
|
||||
// Align left when in compact mode and center when using bubbles
|
||||
states: [
|
||||
State {
|
||||
name: "alignLeft"
|
||||
AnchorChanges {
|
||||
target: stateDelegate
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.left: parent ? parent.left : undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "alignCenter"
|
||||
AnchorChanges {
|
||||
target: stateDelegate
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
anchors.left: undefined
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
AnchorAnimation{duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic}
|
||||
}
|
||||
]
|
||||
|
||||
height: sectionDelegate.height + rowLayout.height
|
||||
SectionDelegate {
|
||||
id: sectionDelegate
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
visible: model.showSection
|
||||
height: visible ? implicitHeight : 0
|
||||
}
|
||||
@@ -27,8 +61,11 @@ Control {
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
height: label.contentHeight
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing + (Config.compactLayout ? Kirigami.Units.largeSpacing * 1.25 : 0)
|
||||
anchors.rightMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Avatar {
|
||||
id: icon
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.12
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
@@ -13,19 +12,24 @@ import NeoChat.Component 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
QQC2.ItemDelegate {
|
||||
id: messageDelegate
|
||||
id: timelineContainer
|
||||
default property alias innerObject : column.children
|
||||
// readonly property bool failed: marks == EventStatus.SendingFailed
|
||||
|
||||
property bool isEmote: false
|
||||
property bool cardBackground: true
|
||||
|
||||
readonly property int bubbleMaxWidth: Config.compactLayout && !Config.showAvatarInTimeline ? width - Kirigami.Units.largeSpacing * 4 : (Config.compactLayout ? width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 4 : Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 6, Kirigami.Units.gridUnit * 20))
|
||||
signal openContextMenu
|
||||
|
||||
// The bubble and delegate widths are allowed to grow once the ListView gets beyond a certain size
|
||||
// extraWidth defines this as the excess after a certain ListView width, capped to a max value
|
||||
readonly property int extraWidth: messageListView.width >= Kirigami.Units.gridUnit * 46 ? Math.min((messageListView.width - Kirigami.Units.gridUnit * 46), Kirigami.Units.gridUnit * 20) : 0
|
||||
readonly property int bubbleMaxWidth: Kirigami.Units.gridUnit * 20 + extraWidth * 0.5
|
||||
readonly property int delegateMaxWidth: Config.compactLayout ? messageListView.width : Math.min(messageListView.width, Kirigami.Units.gridUnit * 40 + extraWidth)
|
||||
readonly property int contentMaxWidth: Config.compactLayout ? width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) - Kirigami.Units.largeSpacing * 4 : Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 6, bubbleMaxWidth)
|
||||
|
||||
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight &&
|
||||
model.author.isLocalUser &&
|
||||
!applicationWindow().wideScreen &&
|
||||
!Config.compactLayout
|
||||
model.author.isLocalUser && !Config.compactLayout
|
||||
|
||||
signal openExternally()
|
||||
signal replyClicked(string eventID)
|
||||
@@ -38,7 +42,10 @@ QQC2.ItemDelegate {
|
||||
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
width: delegateMaxWidth
|
||||
height: sectionDelegate.height + Math.max(model.showAuthor ? avatar.height : 0, bubble.implicitHeight) + loader.height + (showAuthor ? Kirigami.Units.largeSpacing : 0)
|
||||
background: null
|
||||
|
||||
property Item hoverComponent
|
||||
|
||||
// show hover actions
|
||||
@@ -51,13 +58,39 @@ QQC2.ItemDelegate {
|
||||
// updates the global hover component to point to this delegate, and update its position
|
||||
function updateHoverComponent() {
|
||||
if (hoverComponent) {
|
||||
hoverComponent.delegate = timelineContainer
|
||||
hoverComponent.bubble = bubble
|
||||
hoverComponent.updateFunction = updateHoverComponent;
|
||||
hoverComponent.event = model
|
||||
}
|
||||
}
|
||||
|
||||
height: sectionDelegate.height + Math.max(model.showAuthor ? avatar.height : 0, bubble.implicitHeight) + loader.height + (showAuthor ? Kirigami.Units.largeSpacing : 0)
|
||||
state: Config.compactLayout ? "alignLeft" : "alignCenter"
|
||||
// Align left when in compact mode and center when using bubbles
|
||||
states: [
|
||||
State {
|
||||
name: "alignLeft"
|
||||
AnchorChanges {
|
||||
target: timelineContainer
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.left: parent ? parent.left : undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "alignCenter"
|
||||
AnchorChanges {
|
||||
target: timelineContainer
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
anchors.left: undefined
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
AnchorAnimation{duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic}
|
||||
}
|
||||
]
|
||||
|
||||
SectionDelegate {
|
||||
id: sectionDelegate
|
||||
@@ -111,18 +144,15 @@ QQC2.ItemDelegate {
|
||||
rightPadding: Config.compactLayout ? Kirigami.Units.largeSpacing : Kirigami.Units.smallSpacing
|
||||
hoverEnabled: true
|
||||
|
||||
// state: Config.compactLayout ? "compactLayout" : "default"
|
||||
state: showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
|
||||
|
||||
anchors {
|
||||
top: avatar.top
|
||||
leftMargin: Kirigami.Units.largeSpacing
|
||||
rightMargin: showUserMessageOnRight ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
|
||||
}
|
||||
// HACK: anchoring didn't reset anchors.right when switching from parent.right to undefined reliably
|
||||
width: Config.compactLayout ? messageDelegate.width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) + Kirigami.Units.largeSpacing * 2 : implicitWidth
|
||||
|
||||
width: Config.compactLayout ? timelineContainer.width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) + Kirigami.Units.largeSpacing * 2 : implicitWidth
|
||||
|
||||
state: showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
|
||||
// states for anchor animations on window resize
|
||||
// as setting anchors to undefined did not work reliably
|
||||
states: [
|
||||
@@ -160,7 +190,7 @@ QQC2.ItemDelegate {
|
||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.preferredWidth: nameLabel.implicitWidth + timeLabel.implicitWidth + Kirigami.Units.largeSpacing * 2
|
||||
Layout.maximumWidth: bubbleMaxWidth
|
||||
Layout.maximumWidth: contentMaxWidth
|
||||
implicitHeight: visible ? nameLabel.implicitHeight : 0
|
||||
|
||||
QQC2.Label {
|
||||
@@ -168,15 +198,13 @@ QQC2.ItemDelegate {
|
||||
topInset: 0
|
||||
|
||||
visible: model.showAuthor && !isEmote
|
||||
anchors.left: rowLayout.left
|
||||
anchors.right: timeLabel.left
|
||||
anchors.rightMargin: Kirigami.Units.smallSpacing
|
||||
width: Math.min(contentMaxWidth - timeLabel.width, implicitWidth)
|
||||
|
||||
text: visible ? author.displayName : ""
|
||||
textFormat: Text.PlainText
|
||||
font.weight: Font.Bold
|
||||
color: author.color
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
@@ -193,10 +221,18 @@ QQC2.ItemDelegate {
|
||||
}
|
||||
QQC2.Label {
|
||||
id: timeLabel
|
||||
anchors.right: rowLayout.right
|
||||
leftPadding: Kirigami.Units.largeSpacing
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
anchors.left: nameLabel.right
|
||||
visible: model.showAuthor && !isEmote
|
||||
text: visible ? time.toLocaleTimeString(Locale.ShortFormat) : ""
|
||||
text: visible ? time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat) : ""
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
QQC2.ToolTip.visible: hoverHandler.hovered
|
||||
QQC2.ToolTip.text: time.toLocaleString(Qt.locale(), Locale.LongFormat)
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
}
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
@@ -206,6 +242,7 @@ QQC2.ItemDelegate {
|
||||
visible: active
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Config.compactLayout ? 0 : Kirigami.Units.smallSpacing
|
||||
Layout.leftMargin: Config.compactLayout ? 0 : Kirigami.Units.largeSpacing
|
||||
|
||||
Connections {
|
||||
target: replyLoader.item
|
||||
@@ -218,7 +255,7 @@ QQC2.ItemDelegate {
|
||||
|
||||
background: Item {
|
||||
Rectangle {
|
||||
visible: messageDelegate.hovered
|
||||
visible: timelineContainer.hovered
|
||||
color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
anchors.fill: parent
|
||||
@@ -258,4 +295,14 @@ QQC2.ItemDelegate {
|
||||
visible: active
|
||||
sourceComponent: ReactionDelegate { }
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: timelineContainer.openContextMenu()
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: timelineContainer.openContextMenu()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import QtMultimedia 5.15
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
|
||||
@@ -18,8 +17,6 @@ import NeoChat.Menu.Timeline 1.0
|
||||
TimelineContainer {
|
||||
id: videoDelegate
|
||||
|
||||
width: ListView.view.width
|
||||
|
||||
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||
hoverComponent: hoverActions
|
||||
|
||||
@@ -29,6 +26,8 @@ TimelineContainer {
|
||||
property bool supportStreaming: true
|
||||
readonly property int maxWidth: 1000 // TODO messageListView.width
|
||||
|
||||
onOpenContextMenu: openFileContext(model, vid)
|
||||
|
||||
onDownloadedChanged: {
|
||||
if (downloaded) {
|
||||
vid.source = progressInfo.localPath
|
||||
@@ -43,7 +42,7 @@ TimelineContainer {
|
||||
innerObject: Video {
|
||||
id: vid
|
||||
|
||||
Layout.maximumWidth: videoDelegate.bubbleMaxWidth
|
||||
Layout.maximumWidth: videoDelegate.contentMaxWidth
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 15
|
||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
|
||||
@@ -124,16 +123,6 @@ TimelineContainer {
|
||||
videoDelegate.downloadAndPlay()
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: openFileContext(model, parent)
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: openFileContext(model, parent)
|
||||
}
|
||||
}
|
||||
|
||||
function downloadAndPlay() {
|
||||
|
||||
@@ -92,7 +92,7 @@ Labs.MenuBar {
|
||||
|
||||
Labs.MenuItem {
|
||||
text: i18nc("menu", "Matrix FAQ")
|
||||
onTriggered: Qt.openUrlExternally("https://matrix.org/faq/")
|
||||
onTriggered: UrlHelper.openUrl("https://matrix.org/faq/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,11 @@ Loader {
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Room settings")
|
||||
onTriggered: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
|
||||
MenuItem {
|
||||
|
||||
@@ -24,13 +24,13 @@ MessageDelegateContextMenu {
|
||||
icon.name: "document-open"
|
||||
onTriggered: {
|
||||
if (file.downloaded) {
|
||||
if (!Qt.openUrlExternally(progressInfo.localPath)) {
|
||||
Qt.openUrlExternally(progressInfo.localDir);
|
||||
if (!UrlHelper.openUrl(progressInfo.localPath)) {
|
||||
UrlHelper.openUrl(progressInfo.localDir);
|
||||
}
|
||||
} else {
|
||||
file.onDownloadedChanged.connect(function() {
|
||||
if (!Qt.openUrlExternally(progressInfo.localPath)) {
|
||||
Qt.openUrlExternally(progressInfo.localDir);
|
||||
if (!UrlHelper.openUrl(progressInfo.localPath)) {
|
||||
UrlHelper.openUrl(progressInfo.localDir);
|
||||
}
|
||||
});
|
||||
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||
@@ -50,7 +50,7 @@ MessageDelegateContextMenu {
|
||||
text: i18n("Reply")
|
||||
icon.name: "mail-replied-symbolic"
|
||||
onTriggered: {
|
||||
ChatBoxHelper.replyToMessage(eventId, message, author);
|
||||
chatBoxHelper.replyToMessage(eventId, message, author);
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
|
||||
@@ -20,6 +20,7 @@ Loader {
|
||||
property string formattedBody: ""
|
||||
required property string source
|
||||
property string selectedText: ""
|
||||
required property string plainMessage
|
||||
|
||||
property list<Kirigami.Action> nestedActions
|
||||
|
||||
@@ -27,13 +28,13 @@ Loader {
|
||||
Kirigami.Action {
|
||||
text: i18n("Edit")
|
||||
icon.name: "document-edit"
|
||||
onTriggered: ChatBoxHelper.edit(message, formattedBody, eventId);
|
||||
onTriggered: chatBoxHelper.edit(message, formattedBody, eventId);
|
||||
visible: eventType.length > 0 && author.id === Controller.activeConnection.localUserId && (eventType === "emote" || eventType === "message")
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Reply")
|
||||
icon.name: "mail-replied-symbolic"
|
||||
onTriggered: ChatBoxHelper.replyToMessage(eventId, message, author);
|
||||
onTriggered: chatBoxHelper.replyToMessage(eventId, message, author);
|
||||
},
|
||||
Kirigami.Action {
|
||||
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
|
||||
@@ -45,7 +46,7 @@ Loader {
|
||||
Kirigami.Action {
|
||||
text: i18n("Copy")
|
||||
icon.name: "edit-copy"
|
||||
onTriggered: Clipboard.saveText(loadRoot.selectedText === "" ? loadRoot.message : loadRoot.selectedText)
|
||||
onTriggered: Clipboard.saveText(loadRoot.selectedText === "" ? loadRoot.plainMessage : loadRoot.selectedText)
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("View Source")
|
||||
@@ -111,7 +112,7 @@ Loader {
|
||||
Instantiator {
|
||||
model: WebShortcutModel {
|
||||
id: webshortcutmodel
|
||||
selectedText: loadRoot.selectedText ? loadRoot.selectedText : loadRoot.message
|
||||
selectedText: loadRoot.selectedText ? loadRoot.selectedText : loadRoot.plainMessage
|
||||
onOpenUrl: RoomManager.visitNonMatrix(url)
|
||||
}
|
||||
delegate: QQC2.MenuItem {
|
||||
|
||||
@@ -22,6 +22,10 @@ Kirigami.Page {
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
contentWidth: availableWidth
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
TextArea {
|
||||
id: sourceTextArea
|
||||
text: sourceText
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
@@ -15,6 +15,95 @@ import NeoChat.Component 1.0
|
||||
import NeoChat.Menu 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
|
||||
header: ColumnLayout {
|
||||
visible: !page.collapsedMode
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 0
|
||||
|
||||
ListView {
|
||||
id: spaceList
|
||||
property string activeSpaceId: ''
|
||||
|
||||
orientation: Qt.Horizontal
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
clip:true
|
||||
visible: spaceList.count > 0
|
||||
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||
Layout.fillWidth: true
|
||||
|
||||
model: SortFilterSpaceListModel {
|
||||
id: sortFilterSpaceListModel
|
||||
sourceModel: RoomListModel {
|
||||
id: spaceListModel
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: SpaceHierarchyCache
|
||||
function onSpaceHierarchyChanged() {
|
||||
if (spaceList.activeSpaceId !== '') {
|
||||
sortFilterRoomListModel.activeSpaceRooms = SpaceHierarchyCache.getRoomListForSpace(spaceList.activeSpaceId, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header: QQC2.Control {
|
||||
contentItem: QQC2.RoundButton {
|
||||
id: homeButton
|
||||
flat: true
|
||||
padding: Kirigami.Units.gridUnit / 2
|
||||
icon.name: "home"
|
||||
text: i18nc('@action:button', 'Show All Rooms')
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
onClicked: {
|
||||
sortFilterRoomListModel.activeSpaceRooms = [];
|
||||
spaceList.activeSpaceId = '';
|
||||
listView.positionViewAtIndex(0, ListView.Beginning);
|
||||
}
|
||||
|
||||
QQC2.ToolTip {
|
||||
text: homeButton.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: QQC2.Control {
|
||||
required property string avatar
|
||||
required property var currentRoom
|
||||
required property int index
|
||||
required property string id
|
||||
implicitWidth: ListView.view.headerItem.implicitWidth
|
||||
implicitHeight: ListView.view.headerItem.implicitHeight
|
||||
|
||||
contentItem: Kirigami.Avatar {
|
||||
id: del
|
||||
|
||||
actions.main: Kirigami.Action {
|
||||
id: enterSpaceAction
|
||||
onTriggered: {
|
||||
spaceList.activeSpaceId = id;
|
||||
sortFilterRoomListModel.activeSpaceRooms = SpaceHierarchyCache.getRoomListForSpace(id, true);
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolTip {
|
||||
text: currentRoom.displayName
|
||||
}
|
||||
|
||||
source: avatar !== "" ? "image://mxc/" + avatar : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
id: page
|
||||
|
||||
title: i18n("Rooms")
|
||||
@@ -68,7 +157,6 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
@@ -100,6 +188,8 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
||||
@@ -233,6 +323,7 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
acceptedDevices: PointerDevice.Mouse
|
||||
onTapped: createRoomListContextMenu()
|
||||
}
|
||||
|
||||
@@ -247,15 +338,17 @@ Kirigami.ScrollablePage {
|
||||
|
||||
trailing: RowLayout {
|
||||
QQC2.Label {
|
||||
text: notificationCount
|
||||
visible: notificationCount > 0
|
||||
text: notificationCount > 0 ? notificationCount : "●"
|
||||
visible: unreadCount > 0
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
color: highlightCount > 0 ? "white" : Kirigami.Theme.textColor
|
||||
color: Kirigami.Theme.textColor
|
||||
Layout.minimumWidth: height
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
background: Rectangle {
|
||||
visible: notificationCount > 0
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
|
||||
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
|
||||
opacity: highlightCount > 0 ? 1 : 0.3
|
||||
radius: height / 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ Kirigami.ScrollablePage {
|
||||
|
||||
/// It's not readonly because of the seperate window view.
|
||||
property var currentRoom: RoomManager.currentRoom
|
||||
property bool loading: page.currentRoom === null || (messageListView.count === 0 && !page.currentRoom.allHistoryLoaded && !page.currentRoom.isInvite)
|
||||
/// Used to determine if scrolling to the bottom should mark the message as unread
|
||||
property bool hasScrolledUpBefore: false;
|
||||
|
||||
@@ -52,7 +53,26 @@ Kirigami.ScrollablePage {
|
||||
|
||||
onCurrentRoomChanged: {
|
||||
hasScrolledUpBefore = false;
|
||||
ChatBoxHelper.clearEditReply()
|
||||
chatBoxHelper.clearEditReply()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: messageEventModel
|
||||
function onRowsInserted() {
|
||||
markReadIfVisibleTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: markReadIfVisibleTimer
|
||||
interval: 1000
|
||||
onTriggered: {
|
||||
if (loading || !currentRoom.readMarkerLoaded || !applicationWindow().active) {
|
||||
restart()
|
||||
} else {
|
||||
markReadIfVisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActionsHandler {
|
||||
@@ -61,6 +81,10 @@ Kirigami.ScrollablePage {
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
|
||||
ChatBoxHelper {
|
||||
id: chatBoxHelper
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: applicationWindow().pageStack.get(0).forceActiveFocus()
|
||||
@@ -123,7 +147,7 @@ Kirigami.ScrollablePage {
|
||||
Kirigami.PlaceholderMessage {
|
||||
id: loadingIndicator
|
||||
anchors.centerIn: parent
|
||||
visible: page.currentRoom === null || (messageListView.count === 0 && !page.currentRoom.allHistoryLoaded && !page.currentRoom.isInvite)
|
||||
visible: loading
|
||||
text: i18n("Loading…")
|
||||
QQC2.BusyIndicator {
|
||||
running: loadingIndicator.visible
|
||||
@@ -147,9 +171,9 @@ Kirigami.ScrollablePage {
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_PageDown && (event.modifiers & Qt.ControlModifier)) {
|
||||
switchRoomUp();
|
||||
} else if (event.key === Qt.Key_PageUp && (event.modifiers & Qt.ControlModifier)) {
|
||||
switchRoomDown();
|
||||
} else if (event.key === Qt.Key_PageUp && (event.modifiers & Qt.ControlModifier)) {
|
||||
switchRoomUp();
|
||||
} else if (!(event.modifiers & Qt.ControlModifier) && event.key < Qt.Key_Escape) {
|
||||
event.accepted = true;
|
||||
chatBox.addText(event.text);
|
||||
@@ -242,7 +266,7 @@ Kirigami.ScrollablePage {
|
||||
fileDialog.chosen.connect(function(path) {
|
||||
if (!path) return
|
||||
|
||||
ChatBoxHelper.attachmentPath = path;
|
||||
chatBoxHelper.attachmentPath = path;
|
||||
})
|
||||
|
||||
fileDialog.open()
|
||||
@@ -264,7 +288,7 @@ Kirigami.ScrollablePage {
|
||||
if (!Clipboard.saveImage(localPath)) {
|
||||
return;
|
||||
}
|
||||
ChatBoxHelper.attachmentPath = localPath;
|
||||
chatBoxHelper.attachmentPath = localPath;
|
||||
attachDialog.close();
|
||||
}
|
||||
}
|
||||
@@ -339,7 +363,7 @@ Kirigami.ScrollablePage {
|
||||
DropArea {
|
||||
id: dropAreaFile
|
||||
anchors.fill: parent
|
||||
onDropped: ChatBoxHelper.attachmentPath = drop.urls[0]
|
||||
onDropped: chatBoxHelper.attachmentPath = drop.urls[0]
|
||||
}
|
||||
|
||||
QQC2.Pane {
|
||||
@@ -388,7 +412,7 @@ Kirigami.ScrollablePage {
|
||||
currentRoom.usersTyping.length,
|
||||
currentRoom.usersTyping.map(user => user.displayName).join(", ")
|
||||
) : ""
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
height: visible ? implicitHeight : 0
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
@@ -408,7 +432,9 @@ Kirigami.ScrollablePage {
|
||||
Item {
|
||||
id: hoverActions
|
||||
property var event: null
|
||||
property bool showEdit: event && (event.author.id === Controller.activeConnection.localUserId && (event.eventType === "emote" || event.eventType === "message"))
|
||||
property bool userMsg: event && event.author.id === Controller.activeConnection.localUserId
|
||||
property bool showEdit: event && (userMsg && (event.eventType === "emote" || event.eventType === "message"))
|
||||
property var delegate: null
|
||||
property var bubble: null
|
||||
property var hovered: bubble && bubble.hovered
|
||||
property var visibleDelayed: (hovered || hoverHandler.hovered) && !Kirigami.Settings.isMobile
|
||||
@@ -424,7 +450,9 @@ Kirigami.ScrollablePage {
|
||||
interval: 200
|
||||
onTriggered: hoverActions.visible = hoverActions.visibleDelayed;
|
||||
}
|
||||
x: bubble ? (bubble.x + Kirigami.Units.largeSpacing + Math.max(bubble.width - childWidth, 0) - (Config.compactLayout ? Kirigami.Units.gridUnit * 3 : 0)) : 0
|
||||
|
||||
property int childOffset: userMsg && Config.showLocalMessagesOnRight && !Config.compactLayout ? (bubble ? bubble.width : 0) - childWidth : Math.max((bubble ? bubble.width : 0) - childWidth, 0)
|
||||
x: delegate && bubble ? (delegate.x + bubble.x + Kirigami.Units.largeSpacing + childOffset - (Config.compactLayout ? Kirigami.Units.gridUnit * 3 : 0)) : 0
|
||||
y: bubble ? bubble.mapToItem(parent, 0, 0).y - hoverActions.childHeight + Kirigami.Units.smallSpacing: 0;
|
||||
visible: false
|
||||
|
||||
@@ -462,7 +490,7 @@ Kirigami.ScrollablePage {
|
||||
icon.name: "document-edit"
|
||||
onClicked: {
|
||||
if (hoverActions.showEdit) {
|
||||
ChatBoxHelper.edit(hoverActions.event.message, hoverActions.event.formattedBody, hoverActions.event.eventId)
|
||||
chatBoxHelper.edit(hoverActions.event.message, hoverActions.event.formattedBody, hoverActions.event.eventId)
|
||||
}
|
||||
chatBox.focusInputField();
|
||||
}
|
||||
@@ -472,7 +500,7 @@ Kirigami.ScrollablePage {
|
||||
QQC2.ToolTip.visible: hovered
|
||||
icon.name: "mail-replied-symbolic"
|
||||
onClicked: {
|
||||
ChatBoxHelper.replyToMessage(hoverActions.event.eventId, hoverActions.event.message, hoverActions.event.author);
|
||||
chatBoxHelper.replyToMessage(hoverActions.event.eventId, hoverActions.event.message, hoverActions.event.author);
|
||||
chatBox.focusInputField();
|
||||
}
|
||||
}
|
||||
@@ -492,14 +520,14 @@ Kirigami.ScrollablePage {
|
||||
onEditLastUserMessage: {
|
||||
const targetMessage = messageEventModel.getLastLocalUserMessageEventId();
|
||||
if (targetMessage) {
|
||||
ChatBoxHelper.edit(targetMessage["message"], targetMessage["formattedBody"], targetMessage["event_id"]);
|
||||
chatBoxHelper.edit(targetMessage["message"], targetMessage["formattedBody"], targetMessage["event_id"]);
|
||||
chatBox.focusInputField();
|
||||
}
|
||||
}
|
||||
onReplyPreviousUserMessage: {
|
||||
const replyResponse = messageEventModel.getLatestMessageFromIndex(0);
|
||||
if (replyResponse && replyResponse["event_id"]) {
|
||||
ChatBoxHelper.replyToMessage(replyResponse["event_id"], replyResponse["message"], replyResponse["sender_id"]);
|
||||
chatBoxHelper.replyToMessage(replyResponse["event_id"], replyResponse["message"], replyResponse["sender_id"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -588,6 +616,14 @@ Kirigami.ScrollablePage {
|
||||
return index;
|
||||
}
|
||||
|
||||
// Mark all messages as read if all unread messages are visible to the user
|
||||
function markReadIfVisible() {
|
||||
let readMarkerRow = eventToIndex(currentRoom.readMarkerEventId)
|
||||
if (readMarkerRow > 0 && readMarkerRow < firstVisibleIndex()) {
|
||||
currentRoom.markAllMessagesAsRead()
|
||||
}
|
||||
}
|
||||
|
||||
/// Open message context dialog for file and videos
|
||||
function openFileContext(event, file) {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(page, {
|
||||
@@ -598,12 +634,13 @@ Kirigami.ScrollablePage {
|
||||
file: file,
|
||||
mimeType: event.mimeType,
|
||||
progressInfo: event.progressInfo,
|
||||
plainMessage: event.message,
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
/// Open context menu for normal message
|
||||
function openMessageContext(event, selectedText) {
|
||||
function openMessageContext(event, selectedText, plainMessage) {
|
||||
const contextMenu = messageDelegateContextMenu.createObject(page, {
|
||||
selectedText: selectedText,
|
||||
author: event.author,
|
||||
@@ -611,7 +648,8 @@ Kirigami.ScrollablePage {
|
||||
eventId: event.eventId,
|
||||
formattedBody: event.formattedBody,
|
||||
source: event.source,
|
||||
eventType: event.eventType
|
||||
eventType: event.eventType,
|
||||
plainMessage: plainMessage,
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ Kirigami.OverlayDrawer {
|
||||
wrapMode: Text.WordWrap
|
||||
selectByMouse: true
|
||||
color: Kirigami.Theme.textColor
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
onLinkActivated: UrlHelper.openUrl(link)
|
||||
readOnly: true
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -217,6 +217,9 @@ Kirigami.OverlayDrawer {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
ListView {
|
||||
id: userListView
|
||||
clip: true
|
||||
@@ -296,7 +299,9 @@ Kirigami.OverlayDrawer {
|
||||
}
|
||||
|
||||
onRoomChanged: {
|
||||
loader.item.userSearchText = ""
|
||||
if (loader.active) {
|
||||
loader.item.userSearchText = ""
|
||||
}
|
||||
if (room == null) {
|
||||
close()
|
||||
}
|
||||
|
||||
130
imports/NeoChat/Settings/AccountEditorPage.qml
Normal file
130
imports/NeoChat/Settings/AccountEditorPage.qml
Normal file
@@ -0,0 +1,130 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
title: i18n("Edit Account")
|
||||
property var connection
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.FormLayout {
|
||||
RowLayout {
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
source: root.connection && root.connection.localUser.avatarMediaId ? ("image://mxc/" + root.connection.localUser.avatarMediaId) : ""
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
property var fileDialog: null;
|
||||
onClicked: {
|
||||
if (fileDialog != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileDialog = openFileDialog.createObject(Controls.ApplicationWindow.Overlay)
|
||||
|
||||
fileDialog.chosen.connect(function(receivedSource) {
|
||||
mouseArea.fileDialog = null;
|
||||
if (!receivedSource) {
|
||||
return;
|
||||
}
|
||||
parent.source = receivedSource;
|
||||
});
|
||||
fileDialog.onRejected.connect(function() {
|
||||
mouseArea.fileDialog = null;
|
||||
});
|
||||
fileDialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
Controls.Button {
|
||||
visible: avatar.source.toString().length !== 0
|
||||
icon.name: "edit-clear"
|
||||
|
||||
onClicked: avatar.source = ""
|
||||
}
|
||||
Kirigami.FormData.label: i18n("Avatar:")
|
||||
}
|
||||
Controls.TextField {
|
||||
id: name
|
||||
text: root.connection ? root.connection.localUser.displayName : ""
|
||||
Kirigami.FormData.label: i18n("Name:")
|
||||
}
|
||||
Controls.TextField {
|
||||
id: accountLabel
|
||||
text: root.connection ? root.connection.localUser.accountLabel : ""
|
||||
Kirigami.FormData.label: i18n("Label:")
|
||||
}
|
||||
Controls.TextField {
|
||||
id: currentPassword
|
||||
Kirigami.FormData.label: i18n("Current Password:")
|
||||
enabled: roto.connection !== undefined && root.connection.canChangePassword !== false
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
Controls.TextField {
|
||||
id: newPassword
|
||||
Kirigami.FormData.label: i18n("New Password:")
|
||||
enabled: root.connection !== undefined && root.connection.canChangePassword !== false
|
||||
echoMode: TextInput.Password
|
||||
|
||||
}
|
||||
Controls.TextField {
|
||||
id: confirmPassword
|
||||
Kirigami.FormData.label: i18n("Confirm new Password:")
|
||||
enabled: root.connection !== undefined && root.connection.canChangePassword !== false
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Controls.Button {
|
||||
text: i18n("Save")
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
onClicked: {
|
||||
if (!Controller.setAvatar(root.connection, avatar.source)) {
|
||||
showPassiveNotification("The Avatar could not be set");
|
||||
}
|
||||
if (root.connection.localUser.displayName !== name.text) {
|
||||
root.connection.localUser.rename(name.text);
|
||||
}
|
||||
if (root.connection.localUser.accountLabel !== accountLabel.text) {
|
||||
root.connection.localUser.setAccountLabel(accountLabel.text);
|
||||
}
|
||||
if(currentPassword.text !== "" && newPassword.text !== "" && confirmPassword.text !== "") {
|
||||
if(newPassword.text === confirmPassword.text) {
|
||||
Controller.changePassword(root.connection, currentPassword.text, newPassword.text);
|
||||
} else {
|
||||
showPassiveNotification(i18n("Passwords do not match"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
Controls.Button {
|
||||
text: i18n("Cancel")
|
||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
onClicked: root.closeDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ Kirigami.ScrollablePage {
|
||||
|
||||
ListView {
|
||||
model: AccountRegistry
|
||||
anchors.fill: parent
|
||||
delegate: Kirigami.BasicListItem {
|
||||
text: model.connection.localUser.displayName
|
||||
labelItem.textFormat: Text.PlainText
|
||||
@@ -30,31 +31,39 @@ Kirigami.ScrollablePage {
|
||||
icon: model.connection.localUser.avatarMediaId ? ("image://mxc/" + model.connection.localUser.avatarMediaId) : "im-user"
|
||||
|
||||
onClicked: {
|
||||
Controller.activeConnection = model.connection
|
||||
pageStack.layers.pop()
|
||||
Controller.activeConnection = model.connection;
|
||||
pageStack.layers.pop();
|
||||
}
|
||||
|
||||
trailing: RowLayout {
|
||||
Controls.ToolButton {
|
||||
display: Controls.AbstractButton.IconOnly
|
||||
Controls.ToolTip {
|
||||
text: parent.action.text
|
||||
}
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Edit this account")
|
||||
iconName: "document-edit"
|
||||
onTriggered: {
|
||||
userEditSheet.connection = model.connection
|
||||
userEditSheet.open()
|
||||
}
|
||||
onTriggered: pageSettingStack.pushDialogLayer(Qt.resolvedUrl('./AccountEditorPage.qml'), {
|
||||
connection: model.connection
|
||||
}, {
|
||||
title: i18n('Account editor')
|
||||
});
|
||||
}
|
||||
}
|
||||
Controls.ToolButton {
|
||||
display: Controls.AbstractButton.IconOnly
|
||||
Controls.ToolTip {
|
||||
text: parent.action.text
|
||||
}
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Logout")
|
||||
iconName: "im-kick-user"
|
||||
onTriggered: {
|
||||
Controller.logout(model.connection, true)
|
||||
if(Controller.accountCount === 1)
|
||||
pageStack.layers.pop()
|
||||
Controller.logout(model.connection, true);
|
||||
if (Controller.accountCount === 1) {
|
||||
pageStack.layers.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,6 +85,7 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Controller
|
||||
function onConnectionAdded() {
|
||||
@@ -83,134 +93,21 @@ Kirigami.ScrollablePage {
|
||||
pageStack.layers.pop()
|
||||
}
|
||||
function onPasswordStatus(status) {
|
||||
if(status == Controller.Success)
|
||||
showPassiveNotification(i18n("Password changed successfully"))
|
||||
else if(status == Controller.Wrong)
|
||||
showPassiveNotification(i18n("Wrong password entered"))
|
||||
else
|
||||
showPassiveNotification(i18n("Unknown problem while trying to change password"))
|
||||
if (status === Controller.Success) {
|
||||
showPassiveNotification(i18n("Password changed successfully"));
|
||||
} else if (status === Controller.Wrong) {
|
||||
showPassiveNotification(i18n("Wrong password entered"));
|
||||
} else {
|
||||
showPassiveNotification(i18n("Unknown problem while trying to change password"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
property Component openFileDialog: Component {
|
||||
id: openFileDialog
|
||||
|
||||
OpenFileDialog {
|
||||
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: userEditSheet
|
||||
|
||||
property var connection
|
||||
|
||||
title: i18n("Edit Account")
|
||||
|
||||
Kirigami.FormLayout {
|
||||
RowLayout {
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
source: userEditSheet.connection && userEditSheet.connection.localUser.avatarMediaId ? ("image://mxc/" + userEditSheet.connection.localUser.avatarMediaId) : ""
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
property var fileDialog: null;
|
||||
onClicked: {
|
||||
if (fileDialog != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileDialog = openFileDialog.createObject(Controls.ApplicationWindow.Overlay)
|
||||
|
||||
fileDialog.chosen.connect(function(receivedSource) {
|
||||
mouseArea.fileDialog = null;
|
||||
if (!receivedSource) {
|
||||
return;
|
||||
}
|
||||
parent.source = receivedSource;
|
||||
});
|
||||
fileDialog.onRejected.connect(function() {
|
||||
mouseArea.fileDialog = null;
|
||||
});
|
||||
fileDialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
Controls.Button {
|
||||
visible: avatar.source.toString().length !== 0
|
||||
icon.name: "edit-clear"
|
||||
|
||||
onClicked: avatar.source = ""
|
||||
}
|
||||
Kirigami.FormData.label: i18n("Avatar:")
|
||||
}
|
||||
Controls.TextField {
|
||||
id: name
|
||||
text: userEditSheet.connection ? userEditSheet.connection.localUser.displayName : ""
|
||||
Kirigami.FormData.label: i18n("Name:")
|
||||
}
|
||||
Controls.TextField {
|
||||
id: accountLabel
|
||||
text: userEditSheet.connection ? userEditSheet.connection.localUser.accountLabel : ""
|
||||
Kirigami.FormData.label: i18n("Label:")
|
||||
}
|
||||
Controls.TextField {
|
||||
id: currentPassword
|
||||
Kirigami.FormData.label: i18n("Current Password:")
|
||||
enabled: userEditSheet.connection !== undefined && userEditSheet.connection.canChangePassword !== false
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
Controls.TextField {
|
||||
id: newPassword
|
||||
Kirigami.FormData.label: i18n("New Password:")
|
||||
enabled: userEditSheet.connection !== undefined && userEditSheet.connection.canChangePassword !== false
|
||||
echoMode: TextInput.Password
|
||||
|
||||
}
|
||||
Controls.TextField {
|
||||
id: confirmPassword
|
||||
Kirigami.FormData.label: i18n("Confirm new Password:")
|
||||
enabled: userEditSheet.connection !== undefined && userEditSheet.connection.canChangePassword !== false
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Controls.Button {
|
||||
text: i18n("Save")
|
||||
onClicked: {
|
||||
if(!Controller.setAvatar(userEditSheet.connection, avatar.source))
|
||||
showPassiveNotification("The Avatar could not be set")
|
||||
if(userEditSheet.connection.localUser.displayName !== name.text)
|
||||
userEditSheet.connection.localUser.rename(name.text)
|
||||
if(userEditSheet.connection.localUser.accountLabel !== accountLabel.text)
|
||||
userEditSheet.connection.localUser.setAccountLabel(accountLabel.text)
|
||||
if(currentPassword.text !== "" && newPassword.text !== "" && confirmPassword.text !== "") {
|
||||
if(newPassword.text === confirmPassword.text) {
|
||||
Controller.changePassword(userEditSheet.connection, currentPassword.text, newPassword.text)
|
||||
} else {
|
||||
showPassiveNotification(i18n("Passwords do not match"))
|
||||
return
|
||||
}
|
||||
}
|
||||
userEditSheet.close()
|
||||
currentPassword.text = ""
|
||||
newPassword.text = ""
|
||||
confirmPassword.text = ""
|
||||
}
|
||||
}
|
||||
Controls.Button {
|
||||
text: i18n("Cancel")
|
||||
onClicked: {
|
||||
userEditSheet.close()
|
||||
avatar.source = userEditSheet.connection.localUser.avatarMediaId ? ("image://mxc/" + userEditSheet.connection.localUser.avatarMediaId) : ""
|
||||
currentPassword.text = ""
|
||||
newPassword.text = ""
|
||||
confirmPassword.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ Kirigami.ScrollablePage {
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Show your messages on the right")
|
||||
checked: Config.showLocalMessagesOnRight
|
||||
enabled: !Config.isShowLocalMessagesOnRightImmutable
|
||||
enabled: !Config.isShowLocalMessagesOnRightImmutable && !Config.compactLayout
|
||||
onToggled: {
|
||||
Config.showLocalMessagesOnRight = checked
|
||||
Config.save()
|
||||
|
||||
@@ -17,8 +17,10 @@ Kirigami.ScrollablePage {
|
||||
id: devices
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
visible: parent.model.count === 0 // We can assume 0 means loading since there is at least one device
|
||||
visible: parent.count === 0 // We can assume 0 means loading since there is at least one device
|
||||
anchors.centerIn: parent
|
||||
text: i18n("Loading…")
|
||||
Controls.BusyIndicator {
|
||||
|
||||
@@ -19,9 +19,10 @@ Kirigami.ScrollablePage {
|
||||
title: i18nc('@title:window', 'Custom Emojis')
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
|
||||
model: CustomEmojiModel {
|
||||
id: emojiModel
|
||||
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
|
||||
@@ -102,7 +103,6 @@ Kirigami.ScrollablePage {
|
||||
this.fileDialog = null
|
||||
})
|
||||
this.fileDialog.onRejected.connect(() => {
|
||||
rej()
|
||||
this.fileDialog = null
|
||||
})
|
||||
this.fileDialog.open()
|
||||
|
||||
@@ -14,6 +14,7 @@ Kirigami.ScrollablePage {
|
||||
title: i18nc('@title:window', 'General')
|
||||
ColumnLayout {
|
||||
Kirigami.FormLayout {
|
||||
Layout.fillWidth: true
|
||||
QQC2.CheckBox {
|
||||
Kirigami.FormData.label: i18n("General settings:")
|
||||
text: i18n("Close to system tray")
|
||||
@@ -96,19 +97,16 @@ Kirigami.ScrollablePage {
|
||||
QQC2.CheckBox {
|
||||
id: quickEditCheckbox
|
||||
Layout.maximumWidth: parent.width
|
||||
contentItem: QQC2.Label {
|
||||
text: i18n("Use s/text/replacement syntax to edit your last message")
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: quickEditCheckbox.indicator.width + quickEditCheckbox.spacing
|
||||
wrapMode: QQC2.Label.Wrap
|
||||
}
|
||||
text: i18n("Use s/text/replacement syntax to edit your last message")
|
||||
checked: Config.allowQuickEdit
|
||||
enabled: !Config.isAllowQuickEditImmutable
|
||||
onToggled: {
|
||||
Config.allowQuickEdit = checked
|
||||
Config.save()
|
||||
}
|
||||
|
||||
// TODO KF5.97 remove this line
|
||||
Component.onCompleted: this.contentItem.wrap = QQC2.Label.Wrap
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Send Typing Notifications")
|
||||
@@ -121,12 +119,16 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Automatically hide/unhide the room information when resizing the window")
|
||||
Layout.maximumWidth: parent.width
|
||||
checked: Config.autoRoomInfoDrawer
|
||||
enabled: !Config.isAutoRoomInfoDrawerImmutable
|
||||
onToggled: {
|
||||
Config.autoRoomInfoDrawer = checked
|
||||
Config.save()
|
||||
}
|
||||
|
||||
// TODO KF5.97 remove this line
|
||||
Component.onCompleted: this.contentItem.wrap = QQC2.Label.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,10 @@ Kirigami.Page {
|
||||
Layout.fillHeight: true
|
||||
enabled: autodetectLanguageCheckbox.checked
|
||||
Component.onCompleted: background.visible = wideMode
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
ListView {
|
||||
clip: true
|
||||
model: settings.dictionaryModel
|
||||
@@ -254,6 +258,10 @@ Kirigami.Page {
|
||||
}
|
||||
QQC2.ScrollView {
|
||||
anchors.fill: parent
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
ListView {
|
||||
model: settings.currentIgnoreList
|
||||
delegate: Kirigami.BasicListItem {
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<summary xml:lang="ar">عميل لماتركس، ميفاق الاتصال اللامركزي</summary>
|
||||
<summary xml:lang="az">Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu</summary>
|
||||
<summary xml:lang="ca">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="ca-valencia">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="ca-valencia">Un client per a Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix</summary>
|
||||
<summary xml:lang="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll</summary>
|
||||
<summary xml:lang="en-GB">A client for matrix, the decentralised communication protocol</summary>
|
||||
@@ -102,7 +102,7 @@
|
||||
<p xml:lang="ar">ماتريكس هو بروتوكول اتصال لامركزي ، يعيد المستخدم إلى السيطرة. يطبق نيوتشات حاليًا جزءًا كبيرًا من الميفاق باستثناء الدردشات المشفرة ودردشة الفيديو.</p>
|
||||
<p xml:lang="az">Matrix, istifadəçini nəzarətdə saxlayan, mərkəzləşməmişi rabitə protokoludur. NeoChat, söhbətin və video əlaqəsinin şifrələnməsindən başqa bir çox protokolları həyata keçirə bilir.</p>
|
||||
<p xml:lang="ca">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment NeoChat implementa una gran part del protocol amb l'excepció dels chats encriptats i els chats de vídeo.</p>
|
||||
<p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="de">Matrix ist ein dezentralisiertes Kommunikationsprotokoll, das dem Benutzer wieder die Kontrolle zurückgibt. Derzeit implementiert NeoChat einen großen Teil des Protokolls mit der Ausnahme von verschlüsselten Chats und Video-Chat.</p>
|
||||
<p xml:lang="en-GB">Matrix is a decentralised communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p>
|
||||
<p xml:lang="es">Matrix es un protocolo de comunicaciones descentralizado, que devuelve el control al usuario. En la actualidad, NeoChat implementa gran parte del protocolo con la excepción de chats cifrados y chats de vídeo.</p>
|
||||
@@ -129,7 +129,7 @@
|
||||
<p xml:lang="ar">يعمل نيوتشات على كل من الأجهزة المحمولة وسطح المكتب مع توفير تجربة مستخدم متسقة.</p>
|
||||
<p xml:lang="az">Vahid istifadəçi interfeysi ilə təmin olunan NeoChat, həm mobil telefonda həm də kompyuterlərdə işləyir.</p>
|
||||
<p xml:lang="ca">El NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat funciona en els mòbils i en l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="de">NeoChat funktioniert sowohl auf dem Mobiltelefon als auch auf dem Arbeitsfläche und bietet ein einheitliches Benutzererlebnis. </p>
|
||||
<p xml:lang="en-GB">NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
|
||||
<p xml:lang="es">NeoChat funciona en móviles y en el escritorio a la vez que proporciona una experiencia de usuario consistente.</p>
|
||||
|
||||
486
po/ar/neochat.po
486
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
490
po/az/neochat.po
490
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
483
po/ca/neochat.po
483
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
490
po/cs/neochat.po
490
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
485
po/da/neochat.po
485
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
656
po/de/neochat.po
656
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
488
po/es/neochat.po
488
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
488
po/eu/neochat.po
488
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
499
po/fi/neochat.po
499
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
488
po/fr/neochat.po
488
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
488
po/hu/neochat.po
488
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
493
po/ia/neochat.po
493
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
509
po/id/neochat.po
509
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
488
po/it/neochat.po
488
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
479
po/ja/neochat.po
479
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
493
po/ko/neochat.po
493
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
488
po/nl/neochat.po
488
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
355
po/nn/neochat.po
355
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
488
po/pa/neochat.po
488
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
491
po/pl/neochat.po
491
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
484
po/pt/neochat.po
484
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
485
po/ru/neochat.po
485
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
488
po/sk/neochat.po
488
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
488
po/sl/neochat.po
488
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
491
po/sv/neochat.po
491
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
511
po/ta/neochat.po
511
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
498
po/tr/neochat.po
498
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/uk/neochat.po
486
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -407,7 +407,7 @@ Kirigami.ApplicationWindow {
|
||||
}
|
||||
footer: QQC2.Button {
|
||||
text: i18n("Open")
|
||||
onClicked: Qt.openUrlExternally(consentSheet.url)
|
||||
onClicked: UrlHelper.openUrl(consentSheet.url)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
res.qrc
1
res.qrc
@@ -80,6 +80,7 @@
|
||||
<file>imports/NeoChat/Settings/Emoticons.qml</file>
|
||||
<file>imports/NeoChat/Settings/AppearanceSettingsPage.qml</file>
|
||||
<file>imports/NeoChat/Settings/AccountsPage.qml</file>
|
||||
<file>imports/NeoChat/Settings/AccountEditorPage.qml</file>
|
||||
<file>imports/NeoChat/Settings/DevicesPage.qml</file>
|
||||
<file>imports/NeoChat/Settings/About.qml</file>
|
||||
<file>imports/NeoChat/Settings/SonnetConfigPage.qml</file>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
add_executable(neochat
|
||||
add_library(neochat STATIC
|
||||
controller.cpp
|
||||
actionshandler.cpp
|
||||
emojimodel.cpp
|
||||
@@ -14,6 +14,8 @@ add_executable(neochat
|
||||
messageeventmodel.cpp
|
||||
messagefiltermodel.cpp
|
||||
roomlistmodel.cpp
|
||||
sortfilterspacelistmodel.cpp
|
||||
spacehierarchycache.cpp
|
||||
roommanager.cpp
|
||||
neochatroom.cpp
|
||||
neochatuser.cpp
|
||||
@@ -21,7 +23,6 @@ add_executable(neochat
|
||||
publicroomlistmodel.cpp
|
||||
userdirectorylistmodel.cpp
|
||||
utils.cpp
|
||||
main.cpp
|
||||
notificationsmanager.cpp
|
||||
sortfilterroomlistmodel.cpp
|
||||
chatdocumenthandler.cpp
|
||||
@@ -36,11 +37,22 @@ add_executable(neochat
|
||||
blurhashimageprovider.cpp
|
||||
joinrulesevent.cpp
|
||||
collapsestateproxymodel.cpp
|
||||
urlhelper.cpp
|
||||
)
|
||||
|
||||
add_executable(neochat-app
|
||||
main.cpp
|
||||
../res.qrc
|
||||
)
|
||||
|
||||
target_include_directories(neochat-app PRIVATE ${CMAKE_BINARY_DIR})
|
||||
|
||||
target_link_libraries(neochat-app PRIVATE
|
||||
neochat
|
||||
)
|
||||
|
||||
if(Quotient_VERSION_MINOR GREATER 6)
|
||||
target_compile_definitions(neochat PRIVATE QUOTIENT_07)
|
||||
target_compile_definitions(neochat PUBLIC QUOTIENT_07)
|
||||
else()
|
||||
target_sources(neochat PRIVATE neochataccountregistry.cpp)
|
||||
endif()
|
||||
@@ -56,34 +68,37 @@ if(NOT ANDROID)
|
||||
else()
|
||||
target_sources(neochat PRIVATE trayicon.cpp)
|
||||
endif()
|
||||
target_link_libraries(neochat PRIVATE KF5::ConfigWidgets KF5::WindowSystem KF5::SonnetCore)
|
||||
target_compile_definitions(neochat PRIVATE -DHAVE_COLORSCHEME)
|
||||
target_compile_definitions(neochat PRIVATE -DHAVE_WINDOWSYSTEM)
|
||||
target_link_libraries(neochat PUBLIC KF5::ConfigWidgets KF5::WindowSystem)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_COLORSCHEME)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM)
|
||||
endif()
|
||||
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
target_sources(neochat PRIVATE ../res_desktop.qrc runner.cpp)
|
||||
target_compile_definitions(neochat PRIVATE -DHAVE_RUNNER)
|
||||
target_sources(neochat-app PRIVATE ../res_desktop.qrc)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
|
||||
target_sources(neochat PRIVATE runner.cpp)
|
||||
else()
|
||||
target_sources(neochat PRIVATE ../res_android.qrc)
|
||||
target_sources(neochat-app PRIVATE ../res_android.qrc)
|
||||
endif()
|
||||
|
||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
||||
target_link_libraries(neochat PRIVATE Qt::Quick Qt::Qml Qt::Gui Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES})
|
||||
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES})
|
||||
if(TARGET QCoro5::Coro)
|
||||
target_link_libraries(neochat PRIVATE QCoro5::Coro)
|
||||
target_link_libraries(neochat PUBLIC QCoro5::Coro)
|
||||
else()
|
||||
target_link_libraries(neochat PRIVATE QCoro::QCoro)
|
||||
target_link_libraries(neochat PUBLIC QCoro::QCoro)
|
||||
endif()
|
||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||
|
||||
if(NEOCHAT_FLATPAK)
|
||||
target_compile_definitions(neochat PRIVATE NEOCHAT_FLATPAK)
|
||||
target_compile_definitions(neochat PUBLIC NEOCHAT_FLATPAK)
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
target_sources(neochat PRIVATE notifyrc.qrc)
|
||||
target_link_libraries(neochat PRIVATE Qt5::Svg OpenSSL::SSL)
|
||||
target_link_libraries(neochat PRIVATE Qt::Svg OpenSSL::SSL)
|
||||
target_sources(neochat-app PRIVATE notifyrc.qrc)
|
||||
target_link_libraries(neochat PUBLIC Qt::Svg OpenSSL::SSL)
|
||||
kirigami_package_breeze_icons(ICONS
|
||||
"arrow-down"
|
||||
"help-about"
|
||||
@@ -125,20 +140,22 @@ if(ANDROID)
|
||||
"preferences-desktop-theme-global"
|
||||
)
|
||||
else()
|
||||
target_link_libraries(neochat PRIVATE Qt5::Widgets KF5::KIOWidgets)
|
||||
target_link_libraries(neochat PUBLIC Qt::Widgets KF5::KIOWidgets)
|
||||
install(FILES neochat.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
|
||||
endif()
|
||||
|
||||
set_target_properties(neochat-app PROPERTIES OUTPUT_NAME "neochat")
|
||||
|
||||
if(TARGET KF5::DBusAddons)
|
||||
target_link_libraries(neochat PRIVATE KF5::DBusAddons)
|
||||
target_compile_definitions(neochat PRIVATE -DHAVE_KDBUSADDONS)
|
||||
target_link_libraries(neochat PUBLIC KF5::DBusAddons)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
|
||||
endif()
|
||||
|
||||
if (TARGET KF5::KIOWidgets)
|
||||
target_compile_definitions(neochat PRIVATE -DHAVE_KIO)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
|
||||
endif()
|
||||
|
||||
install(TARGETS neochat ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
install(TARGETS neochat-app ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
|
||||
|
||||
@@ -131,8 +131,8 @@ void ChatBoxHelper::clear()
|
||||
setEditContent(QString());
|
||||
setReplyEventId(QString());
|
||||
setReplyEventContent(QString());
|
||||
setAttachmentPath(QString());
|
||||
setReplyUser(QVariant());
|
||||
setAttachmentPath(QString());
|
||||
}
|
||||
|
||||
void ChatBoxHelper::edit(const QString &message, const QString &formattedBody, const QString &eventId)
|
||||
@@ -149,6 +149,7 @@ void ChatBoxHelper::clearEditReply()
|
||||
setReplyEventId(QString());
|
||||
setReplyEventContent(QString());
|
||||
setReplyUser(QVariant());
|
||||
setAttachmentPath(QString());
|
||||
Q_EMIT shouldClearText();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
|
||||
#include <QFont>
|
||||
#include <QObject>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QTextCursor>
|
||||
#include <QUrl>
|
||||
|
||||
class QTextDocument;
|
||||
class QQuickTextDocument;
|
||||
class NeoChatRoom;
|
||||
class Controller;
|
||||
|
||||
|
||||
@@ -21,14 +21,15 @@
|
||||
#include <QFileInfo>
|
||||
#include <QGuiApplication>
|
||||
#include <QMovie>
|
||||
#include <QNetworkConfigurationManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QPixmap>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QQuickWindow>
|
||||
#include <QStandardPaths>
|
||||
#include <QStringBuilder>
|
||||
#include <QSysInfo>
|
||||
#include <QTextDocument>
|
||||
#include <QTimer>
|
||||
#include <utility>
|
||||
|
||||
@@ -52,6 +53,7 @@
|
||||
#include "roommanager.h"
|
||||
#include "settings.h"
|
||||
#include "utils.h"
|
||||
#include <qt_connection_util.h>
|
||||
|
||||
#include <KStandardShortcut>
|
||||
|
||||
@@ -123,13 +125,6 @@ Controller::Controller(QObject *parent)
|
||||
#endif
|
||||
}
|
||||
|
||||
Controller::~Controller()
|
||||
{
|
||||
for (auto c : AccountRegistry::instance().accounts()) {
|
||||
c->saveState();
|
||||
}
|
||||
}
|
||||
|
||||
Controller &Controller::instance()
|
||||
{
|
||||
static Controller _instance;
|
||||
@@ -701,3 +696,17 @@ bool Controller::hasWindowSystem() const
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString Controller::plainText(QQuickTextDocument *document) const
|
||||
{
|
||||
return document->textDocument()->toPlainText();
|
||||
}
|
||||
|
||||
bool Controller::encryptionSupported() const
|
||||
{
|
||||
#ifdef QUOTIENT_07
|
||||
return Quotient::encryptionSupported();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ class NeoChatRoom;
|
||||
class NeoChatUser;
|
||||
class TrayIcon;
|
||||
class QQuickWindow;
|
||||
class QQuickTextDocument;
|
||||
|
||||
namespace QKeychain
|
||||
{
|
||||
@@ -40,6 +41,7 @@ class Controller : public QObject
|
||||
Q_PROPERTY(bool supportSystemTray READ supportSystemTray CONSTANT)
|
||||
Q_PROPERTY(bool hasWindowSystem READ hasWindowSystem CONSTANT)
|
||||
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
|
||||
Q_PROPERTY(bool encryptionSupported READ encryptionSupported CONSTANT)
|
||||
|
||||
public:
|
||||
static Controller &instance();
|
||||
@@ -94,10 +96,11 @@ public:
|
||||
|
||||
Q_INVOKABLE void setBlur(QQuickItem *item, bool blur);
|
||||
Q_INVOKABLE void raiseWindow(QWindow *window);
|
||||
Q_INVOKABLE QString plainText(QQuickTextDocument *document) const;
|
||||
bool encryptionSupported() const;
|
||||
|
||||
private:
|
||||
explicit Controller(QObject *parent = nullptr);
|
||||
~Controller() override;
|
||||
|
||||
QPointer<Connection> m_connection;
|
||||
bool m_busy = false;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "controller.h"
|
||||
|
||||
#include <QUrl>
|
||||
#include <qt_connection_util.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
|
||||
12
src/main.cpp
12
src/main.cpp
@@ -69,6 +69,9 @@
|
||||
#include "roomlistmodel.h"
|
||||
#include "roommanager.h"
|
||||
#include "sortfilterroomlistmodel.h"
|
||||
#include "sortfilterspacelistmodel.h"
|
||||
#include "spacehierarchycache.h"
|
||||
#include "urlhelper.h"
|
||||
#include "userdirectorylistmodel.h"
|
||||
#include "userlistmodel.h"
|
||||
#include "webshortcutmodel.h"
|
||||
@@ -175,6 +178,8 @@ int main(int argc, char *argv[])
|
||||
|
||||
Login *login = new Login();
|
||||
ChatBoxHelper chatBoxHelper;
|
||||
UrlHelper urlHelper;
|
||||
SpaceHierarchyCache spaceHierarchyCache;
|
||||
|
||||
#ifdef HAVE_COLORSCHEME
|
||||
ColorSchemer colorScheme;
|
||||
@@ -190,11 +195,13 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "RoomManager", &RoomManager::instance());
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "FileType", &fileTypeSingleton);
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login);
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "ChatBoxHelper", &chatBoxHelper);
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "UrlHelper", &urlHelper);
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "EmojiModel", new EmojiModel(&app));
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "CommandModel", new CommandModel(&app));
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "AccountRegistry", &Quotient::AccountRegistry::instance());
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "SpaceHierarchyCache", &spaceHierarchyCache);
|
||||
qmlRegisterType<ActionsHandler>("org.kde.neochat", 1, 0, "ActionsHandler");
|
||||
qmlRegisterType<ChatBoxHelper>("org.kde.neochat", 1, 0, "ChatBoxHelper");
|
||||
qmlRegisterType<ChatDocumentHandler>("org.kde.neochat", 1, 0, "ChatDocumentHandler");
|
||||
qmlRegisterType<RoomListModel>("org.kde.neochat", 1, 0, "RoomListModel");
|
||||
qmlRegisterType<KWebShortcutModel>("org.kde.neochat", 1, 0, "WebShortcutModel");
|
||||
@@ -206,6 +213,7 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel");
|
||||
qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
|
||||
qmlRegisterType<SortFilterRoomListModel>("org.kde.neochat", 1, 0, "SortFilterRoomListModel");
|
||||
qmlRegisterType<SortFilterSpaceListModel>("org.kde.neochat", 1, 0, "SortFilterSpaceListModel");
|
||||
qmlRegisterType<DevicesModel>("org.kde.neochat", 1, 0, "DevicesModel");
|
||||
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
||||
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");
|
||||
@@ -228,7 +236,9 @@ int main(int argc, char *argv[])
|
||||
});
|
||||
#endif
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
qRegisterMetaTypeStreamOperators<Emoji>();
|
||||
#endif
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
||||
|
||||
@@ -193,6 +193,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
}
|
||||
refreshEventRoles(eventId, {ReactionRole, Qt::DisplayRole});
|
||||
});
|
||||
connect(m_currentRoom, &Room::newFileTransfer, this, &MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom, &Room::fileTransferCompleted, this, &MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom, &Room::fileTransferFailed, this, &MessageEventModel::refreshEvent);
|
||||
@@ -714,9 +715,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
for (auto r = row + 1; r < rowCount(); ++r) {
|
||||
auto i = index(r);
|
||||
if (data(i, SpecialMarksRole) != EventStatus::Hidden) {
|
||||
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, EventTypeRole) != data(idx, EventTypeRole)
|
||||
|| data(idx, TimeRole).toDateTime().msecsTo(data(i, TimeRole).toDateTime()) > 600000
|
||||
|| data(idx, TimeRole).toDateTime().toLocalTime().date().day() != data(i, TimeRole).toDateTime().toLocalTime().date().day();
|
||||
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000
|
||||
|| data(i, TimeRole).toDateTime().toLocalTime().date().day() != data(idx, TimeRole).toDateTime().toLocalTime().date().day();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,16 @@
|
||||
#include <cmark.h>
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QImageReader>
|
||||
#include <QMetaObject>
|
||||
#include <QMimeDatabase>
|
||||
#include <QTextDocument>
|
||||
#include <functional>
|
||||
|
||||
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <QMediaMetaData>
|
||||
#include <QMediaPlayer>
|
||||
#endif
|
||||
|
||||
#include <qcoro/qcorosignal.h>
|
||||
#include <qcoro/task.h>
|
||||
|
||||
@@ -26,6 +30,7 @@
|
||||
#include "events/accountdataevents.h"
|
||||
#include "events/reactionevent.h"
|
||||
#include "events/roomcanonicalaliasevent.h"
|
||||
#include "events/roomcreateevent.h"
|
||||
#include "events/roommessageevent.h"
|
||||
#include "events/roompowerlevelsevent.h"
|
||||
#include "events/typingevent.h"
|
||||
@@ -35,6 +40,8 @@
|
||||
#include "stickerevent.h"
|
||||
#include "user.h"
|
||||
#include "utils.h"
|
||||
#include <events/eventcontent.h>
|
||||
#include <qt_connection_util.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
@@ -76,12 +83,38 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
}
|
||||
|
||||
void NeoChatRoom::uploadFile(const QUrl &url, const QString &body)
|
||||
{
|
||||
doUploadFile(url, body);
|
||||
}
|
||||
|
||||
QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body)
|
||||
{
|
||||
if (url.isEmpty()) {
|
||||
return;
|
||||
co_return;
|
||||
}
|
||||
|
||||
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
|
||||
auto mime = QMimeDatabase().mimeTypeForUrl(url);
|
||||
QFileInfo fileInfo(url.toLocalFile());
|
||||
EventContent::TypedBase *content;
|
||||
if (mime.name().startsWith("image/")) {
|
||||
QImage image;
|
||||
content = new EventContent::ImageContent(url, fileInfo.size(), mime, image.size(), fileInfo.fileName());
|
||||
} else if (mime.name().startsWith("audio/")) {
|
||||
content = new EventContent::AudioContent(url, fileInfo.size(), mime, fileInfo.fileName());
|
||||
} else if (mime.name().startsWith("video/")) {
|
||||
QMediaPlayer player;
|
||||
player.setSource(url);
|
||||
co_await qCoro(&player, &QMediaPlayer::mediaStatusChanged);
|
||||
auto resolution = player.metaData().value(QMediaMetaData::Resolution).toSize();
|
||||
content = new EventContent::VideoContent(url, fileInfo.size(), mime, resolution, fileInfo.fileName());
|
||||
} else {
|
||||
content = new EventContent::FileContent(url, fileInfo.size(), mime, fileInfo.fileName());
|
||||
}
|
||||
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, content);
|
||||
#else
|
||||
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, url, false);
|
||||
#endif
|
||||
setHasFileUploading(true);
|
||||
#ifdef QUOTIENT_07
|
||||
connect(this, &Room::fileTransferCompleted, [this, txnId](const QString &id, FileSourceInfo) {
|
||||
@@ -602,7 +635,7 @@ void NeoChatRoom::removeLocalAlias(const QString &alias)
|
||||
QString NeoChatRoom::markdownToHTML(const QString &markdown)
|
||||
{
|
||||
const auto str = markdown.toUtf8();
|
||||
char *tmp_buf = cmark_markdown_to_html(str.constData(), str.size(), CMARK_OPT_DEFAULT);
|
||||
char *tmp_buf = cmark_markdown_to_html(str.constData(), str.size(), CMARK_OPT_HARDBREAKS);
|
||||
|
||||
const std::string html(tmp_buf);
|
||||
|
||||
@@ -610,11 +643,9 @@ QString NeoChatRoom::markdownToHTML(const QString &markdown)
|
||||
|
||||
auto result = QString::fromStdString(html).trimmed();
|
||||
|
||||
result.replace("<!-- raw HTML omitted -->", "<br />");
|
||||
result.replace(QRegularExpression("(<br />)*$"), "");
|
||||
result.replace("<!-- raw HTML omitted -->", "");
|
||||
result.replace("<p>", "");
|
||||
result.replace("</p>", "");
|
||||
result.replace("\n", "<br>");
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -840,3 +871,17 @@ void NeoChatRoom::clearInvitationNotification()
|
||||
{
|
||||
NotificationsManager::instance().clearInvitationNotification(id());
|
||||
}
|
||||
|
||||
bool NeoChatRoom::isSpace()
|
||||
{
|
||||
const auto creationEvent = this->creation();
|
||||
if (!creationEvent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
return creationEvent->roomType() == RoomType::Space;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ public:
|
||||
/// \see lastEventToString
|
||||
[[nodiscard]] QString subtitleText();
|
||||
|
||||
[[nodiscard]] bool isSpace();
|
||||
|
||||
bool isEventHighlighted(const Quotient::RoomEvent *e) const;
|
||||
|
||||
[[nodiscard]] QString joinRule() const;
|
||||
@@ -151,6 +153,7 @@ private:
|
||||
|
||||
static QString markdownToHTML(const QString &markdown);
|
||||
QCoro::Task<void> doDeleteMessagesByUser(const QString &user);
|
||||
QCoro::Task<void> doUploadFile(QUrl url, QString body = QString());
|
||||
|
||||
private Q_SLOTS:
|
||||
void countChanged();
|
||||
|
||||
@@ -408,6 +408,10 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
if (role == IdRole) {
|
||||
return room->id();
|
||||
}
|
||||
if (role == IsSpaceRole) {
|
||||
return room->isSpace();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
@@ -439,6 +443,8 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
|
||||
roles[CurrentRoomRole] = "currentRoom";
|
||||
roles[CategoryVisibleRole] = "categoryVisible";
|
||||
roles[SubtitleTextRole] = "subtitleText";
|
||||
roles[IsSpaceRole] = "isSpace";
|
||||
roles[IdRole] = "id";
|
||||
return roles;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
SubtitleTextRole,
|
||||
AvatarImageRole,
|
||||
IdRole,
|
||||
IsSpaceRole,
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
|
||||
@@ -99,6 +100,7 @@ private:
|
||||
QMap<int, bool> m_categoryVisibility;
|
||||
|
||||
int m_notificationCount = 0;
|
||||
QString m_activeSpaceId = "";
|
||||
|
||||
void connectRoomSignals(NeoChatRoom *room);
|
||||
void handleNotifications();
|
||||
|
||||
@@ -11,8 +11,13 @@
|
||||
#include <QDesktopServices>
|
||||
#include <QStandardPaths>
|
||||
#include <csapi/joining.h>
|
||||
#include <qt_connection_util.h>
|
||||
#include <utility>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <KIO/OpenUrlJob>
|
||||
#endif
|
||||
|
||||
RoomManager::RoomManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_currentRoom(nullptr)
|
||||
@@ -191,9 +196,19 @@ void RoomManager::joinRoom(Quotient::Connection *account, const QString &roomAli
|
||||
|
||||
bool RoomManager::visitNonMatrix(const QUrl &url)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (!QDesktopServices::openUrl(url)) {
|
||||
Q_EMIT warning(i18n("No application for the link"), i18n("Your operating system could not find an application for the link."));
|
||||
}
|
||||
#else
|
||||
auto *job = new KIO::OpenUrlJob(url);
|
||||
connect(job, &KJob::finished, this, [this](KJob *job) {
|
||||
if (job->error()) {
|
||||
Q_EMIT warning(i18n("Could not open URL"), job->errorString());
|
||||
}
|
||||
});
|
||||
job->start();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,23 @@ QString SortFilterRoomListModel::filterText() const
|
||||
bool SortFilterRoomListModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent);
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::NameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != "upgraded";
|
||||
|
||||
bool acceptRoom = sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::NameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != "upgraded"
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool() == false;
|
||||
|
||||
if (m_activeSpaceRooms.empty())
|
||||
return acceptRoom;
|
||||
else
|
||||
return std::find(m_activeSpaceRooms.begin(),
|
||||
m_activeSpaceRooms.end(),
|
||||
sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IdRole).toString())
|
||||
!= m_activeSpaceRooms.end()
|
||||
&& acceptRoom;
|
||||
}
|
||||
|
||||
void SortFilterRoomListModel::setActiveSpaceRooms(QVector<QString> activeSpaceRooms)
|
||||
{
|
||||
this->m_activeSpaceRooms = activeSpaceRooms;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class SortFilterRoomListModel : public QSortFilterProxyModel
|
||||
|
||||
Q_PROPERTY(RoomSortOrder roomSortOrder READ roomSortOrder WRITE setRoomSortOrder NOTIFY roomSortOrderChanged)
|
||||
Q_PROPERTY(QString filterText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
Q_PROPERTY(QVector<QString> activeSpaceRooms WRITE setActiveSpaceRooms)
|
||||
|
||||
public:
|
||||
enum RoomSortOrder {
|
||||
@@ -30,6 +31,8 @@ public:
|
||||
|
||||
[[nodiscard]] bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||
|
||||
Q_INVOKABLE void setActiveSpaceRooms(QVector<QString> activeSpaceRooms);
|
||||
|
||||
protected:
|
||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
@@ -40,4 +43,5 @@ Q_SIGNALS:
|
||||
private:
|
||||
RoomSortOrder m_sortOrder = Categories;
|
||||
QString m_filterText;
|
||||
QVector<QString> m_activeSpaceRooms;
|
||||
};
|
||||
|
||||
28
src/sortfilterspacelistmodel.cpp
Normal file
28
src/sortfilterspacelistmodel.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
// SPDX-FileCopyrightText: 2022 Snehit Sah <hi@snehit.dev>
|
||||
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
#include "sortfilterspacelistmodel.h"
|
||||
|
||||
#include "roomlistmodel.h"
|
||||
|
||||
SortFilterSpaceListModel::SortFilterSpaceListModel(QObject *parent)
|
||||
: QSortFilterProxyModel{parent}
|
||||
{
|
||||
setSortRole(RoomListModel::IdRole);
|
||||
sort(0);
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
bool SortFilterSpaceListModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent);
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool()
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != "upgraded";
|
||||
}
|
||||
|
||||
bool SortFilterSpaceListModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
|
||||
{
|
||||
const auto idLeft = sourceModel()->data(source_left, RoomListModel::IdRole).toString();
|
||||
const auto idRight = sourceModel()->data(source_right, RoomListModel::IdRole).toString();
|
||||
return idLeft < idRight;
|
||||
}
|
||||
24
src/sortfilterspacelistmodel.h
Normal file
24
src/sortfilterspacelistmodel.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// SPDX-FileCopyrightText: 2022 Snehit Sah <hi@snehit.dev>
|
||||
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class SortFilterSpaceListModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SortFilterSpaceListModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] QString activeSpaceId() const;
|
||||
|
||||
[[nodiscard]] bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void activeSpaceIdChanged(QString &activeSpaceId);
|
||||
|
||||
protected:
|
||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
};
|
||||
75
src/spacehierarchycache.cpp
Normal file
75
src/spacehierarchycache.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: 2022 Snehit Sah <hi@snehit.dev>
|
||||
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
#include "spacehierarchycache.h"
|
||||
|
||||
#include "controller.h"
|
||||
#ifdef QUOTIENT_07
|
||||
#include "csapi/space_hierarchy.h"
|
||||
#endif
|
||||
#include "neochatroom.h"
|
||||
|
||||
SpaceHierarchyCache::SpaceHierarchyCache(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
cacheSpaceHierarchy();
|
||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this]() {
|
||||
cacheSpaceHierarchy();
|
||||
});
|
||||
}
|
||||
|
||||
void SpaceHierarchyCache::cacheSpaceHierarchy()
|
||||
{
|
||||
#ifdef QUOTIENT_07
|
||||
auto connection = Controller::instance().activeConnection();
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto roomList = connection->allRooms();
|
||||
for (const auto &room : roomList) {
|
||||
const auto neoChatRoom = static_cast<NeoChatRoom *>(room);
|
||||
if (neoChatRoom->isSpace()) {
|
||||
populateSpaceHierarchy(neoChatRoom->id());
|
||||
} else {
|
||||
connect(neoChatRoom, &Room::baseStateLoaded, neoChatRoom, [this, neoChatRoom]() {
|
||||
if (neoChatRoom->isSpace()) {
|
||||
populateSpaceHierarchy(neoChatRoom->id());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SpaceHierarchyCache::populateSpaceHierarchy(const QString &spaceId)
|
||||
{
|
||||
auto connection = Controller::instance().activeConnection();
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
#ifdef QUOTIENT_07
|
||||
GetSpaceHierarchyJob *job = connection->callApi<GetSpaceHierarchyJob>(spaceId);
|
||||
|
||||
connect(job, &BaseJob::success, this, [this, job, spaceId]() {
|
||||
const auto rooms = job->rooms();
|
||||
QVector<QString> roomList;
|
||||
for (unsigned long i = 0; i < rooms.size(); ++i) {
|
||||
for (const auto &state : rooms[i].childrenState) {
|
||||
roomList.push_back(state->stateKey());
|
||||
}
|
||||
roomList.push_back(rooms.at(i).roomId);
|
||||
}
|
||||
m_spaceHierarchy.insert(spaceId, roomList);
|
||||
Q_EMIT spaceHierarchyChanged();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
QVector<QString> SpaceHierarchyCache::getRoomListForSpace(const QString &spaceId, bool updateCache)
|
||||
{
|
||||
if (updateCache) {
|
||||
populateSpaceHierarchy(spaceId);
|
||||
}
|
||||
return m_spaceHierarchy[spaceId];
|
||||
}
|
||||
28
src/spacehierarchycache.h
Normal file
28
src/spacehierarchycache.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// SPDX-FileCopyrightText: 2022 Snehit Sah <hi@snehit.dev>
|
||||
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
class SpaceHierarchyCache : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SpaceHierarchyCache(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] Q_INVOKABLE QVector<QString> getRoomListForSpace(const QString &spaceId, bool updateCache);
|
||||
|
||||
Q_SIGNALS:
|
||||
void spaceHierarchyChanged();
|
||||
|
||||
private:
|
||||
QVector<QString> m_activeSpaceRooms;
|
||||
QHash<QString, QVector<QString>> m_spaceHierarchy;
|
||||
void cacheSpaceHierarchy();
|
||||
void populateSpaceHierarchy(const QString &spaceId);
|
||||
};
|
||||
24
src/urlhelper.cpp
Normal file
24
src/urlhelper.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
// SPDX-FileCopyrightText: 2022 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "urlhelper.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include <QDesktopServices>
|
||||
#else
|
||||
#include <KIO/OpenUrlJob>
|
||||
#endif
|
||||
|
||||
// QDesktopServices::openUrl doesn't support XDG activation yet, OpenUrlJob does
|
||||
// On Android XDG activation is not relevant, so use QDesktopServices::openUrl to avoid the heavy KIO dependency
|
||||
void UrlHelper::openUrl(const QUrl &url)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
QDesktopServices::openUrl(url);
|
||||
#else
|
||||
auto *job = new KIO::OpenUrlJob(url);
|
||||
job->start();
|
||||
#endif
|
||||
}
|
||||
13
src/urlhelper.h
Normal file
13
src/urlhelper.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// SPDX-FileCopyrightText: 2022 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class UrlHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE void openUrl(const QUrl &url);
|
||||
};
|
||||
Reference in New Issue
Block a user