Compare commits

...

57 Commits

Author SHA1 Message Date
Bhushan Shah
2839d44ea8 GIT_SILENT: bump version to 22.09 2022-09-27 20:53:11 +05:30
l10n daemon script
5f32ae79c1 GIT_SILENT Sync po/docbooks with svn 2022-09-27 01:50:19 +00:00
Tobias Fella
0fc94310c3 Implement reporting events
BUG: 458856
2022-09-26 14:38:45 +00:00
l10n daemon script
7f26651edf GIT_SILENT Sync po/docbooks with svn 2022-09-26 01:58:14 +00:00
l10n daemon script
36880d001c GIT_SILENT Sync po/docbooks with svn 2022-09-25 01:53:43 +00:00
Tobias Fella
e0e289d424 Fix dialogbuttonbox 2022-09-24 15:28:17 +02:00
Tobias Fella
b79956871f Fix build without E2EE 2022-09-24 15:27:19 +02:00
l10n daemon script
917f77152d GIT_SILENT Sync po/docbooks with svn 2022-09-24 01:53:35 +00:00
l10n daemon script
e28d1918f5 GIT_SILENT Sync po/docbooks with svn 2022-09-23 01:50:01 +00:00
Tobias Fella
149b11ba6f Set application domain before using any translated strings 2022-09-23 00:42:22 +02:00
Tobias Fella
c007961ef6 Fix some compilation warnings 2022-09-23 00:21:08 +02:00
James Graham
ef40f5a747 Fix Stickers
Fix so that stickers are rendered and that the context menu works for them.
2022-09-22 19:03:06 +00:00
Bharadwaj Raju
37780c2e3b Link Previews
Uses Matrix's preview API to generate embedded link previews.

Only title and description for now.

![image](/uploads/2c5d632480073fe54345cdbe22ea54dc/image.png)
2022-09-22 15:42:53 +00:00
James Graham
53b9f42399 Fix Slash Commands in the Middle of a Message
Make sure that a slash command only activates when the slash is at the beginning of the message.

BUG: 457474
2022-09-22 15:11:56 +00:00
l10n daemon script
d2ed1cfb2e GIT_SILENT Sync po/docbooks with svn 2022-09-22 01:51:02 +00:00
Carl Schwan
c7d4b1a529 Update Flatpak.cmake 2022-09-21 12:07:44 +00:00
Carl Schwan
a8045f2134 Install emoji fonts to correct location
See https://wiki.archlinux.org/title/font_configuration#Fontconfig_configuration
and https://invent.kde.org/network/neochat/-/issues/341#note_525953
2022-09-21 12:07:44 +00:00
Tobias Fella
b8262fef92 Implement device verification 2022-09-21 11:49:11 +00:00
l10n daemon script
3071901a47 GIT_SILENT Sync po/docbooks with svn 2022-09-21 01:54:16 +00:00
l10n daemon script
396260549a GIT_SILENT Sync po/docbooks with svn 2022-09-20 01:55:30 +00:00
l10n daemon script
50c2de8d52 GIT_SILENT Sync po/docbooks with svn 2022-09-19 01:52:03 +00:00
Ahmad Samir
d4e067d57c Use KDE_INSTALL_TARGETS_DEFAULT_ARGS
The KF_* variant is for KF repos

GIT_SILENT
2022-09-17 00:45:00 +02:00
l10n daemon script
3797b0129a GIT_SILENT Sync po/docbooks with svn 2022-09-15 01:50:41 +00:00
l10n daemon script
59b4146c63 GIT_SILENT Sync po/docbooks with svn 2022-09-14 01:50:01 +00:00
Bharadwaj Raju
c76524d540 Fix CI tripping on .reuse/dep5
The dep5 parser in the suse_tumbleweed_qt515 CI doesn't like duplicate Copyright entries.

cc @carlschwan
2022-09-13 13:40:54 +00:00
Bharadwaj Raju
10030efd08 Fix text selection with context menu
Make the context menu actually work with selected text in a message.
2022-09-13 10:04:45 +00:00
l10n daemon script
918bd5439c GIT_SILENT Sync po/docbooks with svn 2022-09-13 01:50:39 +00:00
Bharadwaj Raju
e3ff50bbe8 Use Kirigami LoadingPlaceholder everywhere
Replace the custom Placeholder + BusyIndicator with the new LoadingPlaceholder component.

Also remove the "Loading…" page title on first load, as that is redundant to the existing front-and-center "Loading…" message and hence doesn't look nice.
2022-09-12 20:41:23 +00:00
Bharadwaj Raju
76edc858aa Add proper tooltip delays
Split from !521
2022-09-12 20:35:40 +00:00
Bharadwaj Raju
2a2c117ac1 Use selected text colors from theme
When using Breeze light:

| Before | After |
| ------ | ------ |
| ![Screenshot_20220913_005141](/uploads/9b15c79acaaa92e23855def9ca6bc6ab/Screenshot_20220913_005141.png) | ![Screenshot_20220913_005049](/uploads/06cb614edae21f7ddb027bebaf732edc/Screenshot_20220913_005049.png) |
2022-09-12 20:34:47 +00:00
l10n daemon script
d3e4640af9 GIT_SILENT Sync po/docbooks with svn 2022-09-12 01:49:03 +00:00
James Graham
7e1f0f4ea7 PushRule RoomList Room Menu Option
Add an option to the roomlist room menu to set the room push rule override.

Rename default to "follow global setting" for clarity.
2022-09-11 10:29:05 +00:00
l10n daemon script
80bf279321 GIT_SILENT Sync po/docbooks with svn 2022-09-11 01:49:10 +00:00
l10n daemon script
6eb4b8c9d2 GIT_SILENT Sync po/docbooks with svn 2022-09-10 01:49:47 +00:00
James Graham
4bba505da6 Initial work to add push rule support
This commit adds the ability to set the master push rule and set push rules for individual rooms as per the matrix spec. See https://spec.matrix.org/v1.3/client-server-api/#push-rules.

The master push rule is just on/off and uses the existing notification setting in general setting to enable/disable the server default master push rule .m.rule.master.

For each room there is now a page in the room setting that allows the following to be set:
- Default
- All messages
- @mentions and keywords
- off

New room or override rules are added/removed to achieve this.

There is also functionality to check the master/room notification state whenever the setting menu is entered. This allows the status to be updated if changed in another client or get the initial state for a room as it isn't stored.

Note - There is currently no menu items in the room list for setting the room push rule settings. This will be added in a later commit, the aim is to focus on making sure the technical implementation is good for now.
2022-09-09 16:41:03 +00:00
l10n daemon script
c2fc4e44a7 GIT_SILENT Sync po/docbooks with svn 2022-09-09 01:54:13 +00:00
Tobias Fella
274bf824e3 Don't quote property names 2022-09-09 00:23:18 +02:00
Tobias Fella
d70a8a652a Always use system-style includes for libQuotient 2022-09-09 00:16:39 +02:00
Jan Bidler
8c436075d8 Changes room access into radio buttons 2022-09-08 17:31:37 +00:00
James Graham
828032ba06 RoomDrawer Cleanup
This started off as me just wanting to adjust the margin of the search bar but grew a touch ...

- Cleanup margins especially make the member search bar have the same padding as everything else
- Move room information title to header and align all buttons right.
- Make the room name actually bold
- Remove highlight from the user list item after the menu is closed
- Always use actual width so that the drawer isn't wider when modal
- Use control instead of pane for the search bar as the padding works more consistently
- Use BasicListItem instead of AbstractListItem for the member list as this has the layout predefined

![image](/uploads/ab2c4066479b0510bcb2fe6ae91f7bc3/image.png)
2022-09-08 17:30:58 +00:00
l10n daemon script
c30abfefa9 GIT_SILENT Sync po/docbooks with svn 2022-09-08 01:56:22 +00:00
Jan Bidler
5d4efad2f8 Make "show avatar" in settings localizable 2022-09-07 13:55:26 +00:00
l10n daemon script
3a391aafe8 GIT_SILENT Sync po/docbooks with svn 2022-09-07 01:53:26 +00:00
l10n daemon script
7b5b8197bc SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2022-09-07 01:48:36 +00:00
Jan Bidler
55659e488f unify custom emojis text 2022-09-06 19:42:53 +00:00
Jan Bidler
c2c235eff1 Use " in all i18n functions instead of ' 2022-09-06 19:42:53 +00:00
l10n daemon script
f6f4edec86 GIT_SILENT Sync po/docbooks with svn 2022-09-06 01:51:42 +00:00
Nicolas Fella
55847cb9cc Refactor window handling code
Currently when we want to show/raise the window in reaction to the tray icon/notification being clicked etc we do this by emitting a signal on the controller.
This is connected to in main.qml, which does some things, then calls back to controller to do more things.

This is quite convoluted. Instead introduce a new class WindowController that is responsible for all things window, in particular showing/raising and config saving
2022-09-05 19:27:55 +00:00
Nicolas Fella
736c4b02ed Don't call show() on visible windows
It messes with the geometry
2022-09-05 19:27:55 +00:00
Nicolas Fella
4cf5b516d0 Extract code for obtaining a window from the QML engine into a function 2022-09-05 19:27:55 +00:00
Nicolas Fella
c379a7fa27 Fix Android packaging
In the AndroidManifest we specify 'neochat' as the executable

That causes the lib to be used instead of the executable, which breaks things

Shuffle around the names so that the executable is picked
2022-09-05 18:42:27 +00:00
Tobias Fella
2318fb95d9 Adapt to libQuotient API changes 2022-09-05 20:37:55 +02:00
James Graham
14e57e7833 Fix show author after state regression
The avatar should be shown after a state message

Fixes network/neochat#553
2022-09-03 14:33:58 +00:00
l10n daemon script
8c9ea72a9b GIT_SILENT Sync po/docbooks with svn 2022-09-02 01:50:06 +00:00
l10n daemon script
efd3f2e8d0 GIT_SILENT Sync po/docbooks with svn 2022-08-30 01:49:54 +00:00
l10n daemon script
ea70782771 GIT_SILENT Sync po/docbooks with svn 2022-08-28 01:50:03 +00:00
James Graham
6cca3dbe3a Add the right click menu for image delegates in fullscreen mode
Add the right click menu for image delegates in fullscreen mode

BUG: 455147
2022-08-27 11:52:45 +00:00
120 changed files with 20917 additions and 7600 deletions

View File

@@ -31,8 +31,7 @@ Copyright: 2021 Carl Schwan <carlschwan@kde.org>
License: BSD-2-Clause
Files: src/neochatconfig.kcfg
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>
Copyright: 2020-2021 Tobias Fella <fella@posteo.de>
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <fella@posteo.de>
License: BSD-2-Clause
Files: src/neochat.notifyrc

View File

@@ -7,9 +7,9 @@
cmake_minimum_required(VERSION 3.16)
project(NeoChat)
set(PROJECT_VERSION "22.06")
set(PROJECT_VERSION "22.09")
set(KF5_MIN_VERSION "5.88.0")
set(KF5_MIN_VERSION "5.91.0")
set(QT_MIN_VERSION "5.15.2")
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
@@ -25,12 +25,14 @@ include(FeatureSummary)
include(ECMSetupVersion)
include(KDEInstallDirs)
include(ECMFindQmlModule)
include(KDEClangFormat)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMAddAppIcon)
include(KDEGitCommitHooks)
include(ECMCheckOutboundLicense)
if (NOT ANDROID)
include(KDEClangFormat)
endif()
if(NEOCHAT_FLATPAK)
include(cmake/Flatpak.cmake)
@@ -137,11 +139,12 @@ add_subdirectory(src)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
if (NOT ANDROID)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
endif()
file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.h *.qml)
# CI installs dependency headers to _install and _build, which break the reuse check
# Fixes the test by excluding this directory

View File

@@ -21,7 +21,7 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="neochat"/>
<meta-data android:name="android.app.lib_name" android:value="neochat-app"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>

View File

@@ -9,6 +9,6 @@ install(
FILES
${CMAKE_CURRENT_SOURCE_DIR}/cmake/Flatpak/99-noto-mono-color-emoji.conf
DESTINATION
${CMAKE_INSTALL_SYSCONFDIR}/fonts/conf.d/
${CMAKE_INSTALL_SYSCONFDIR}/fonts/local.conf
)

View File

@@ -14,6 +14,7 @@ ApplicationWindow {
property string blurhash: ""
property int imageWidth: -1
property int imageHeight: -1
property var modelData
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
@@ -52,6 +53,24 @@ ApplicationWindow {
source: root.blurhash !== "" ? ("image://blurhash/" + root.blurhash) : ""
visible: root.blurhash !== "" && parent.status !== Image.Ready
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: {
const contextMenu = fileDelegateContextMenu.createObject(parent, {
author: modelData.author,
message: modelData.message,
eventId: modelData.eventId,
source: modelData.source,
file: root.parent,
mimeType: modelData.mimeType,
progressInfo: modelData.progressInfo,
plainMessage: modelData.message,
});
contextMenu.closeFullscreen.connect(root.destroy)
contextMenu.open();
}
}
}
Button {

View File

@@ -14,6 +14,8 @@ TimelineContainer {
innerObject: TextEdit {
text: i18n("This message is encrypted and the sender has not shared the key with this device.")
color: Kirigami.Theme.disabledTextColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
font.pointSize: Kirigami.Theme.defaultFont.pointSize
selectByMouse: !Kirigami.Settings.isMobile
readOnly: true

View File

@@ -42,9 +42,7 @@ DelegateChooser {
DelegateChoice {
roleValue: "sticker"
delegate: ImageDelegate {
cardBackground: false
}
delegate: ImageDelegate {}
}
DelegateChoice {

View File

@@ -53,6 +53,7 @@ TimelineContainer {
icon.name: "document-open"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
onClicked: openSavedFile()
}
@@ -70,6 +71,7 @@ TimelineContainer {
icon.name: "media-playback-stop"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
onClicked: currentRoom.cancelFileTransfer(eventId)
}
},

View File

@@ -91,10 +91,11 @@ TimelineContainer {
onTapped: {
fullScreenImage.createObject(parent, {
filename: eventId,
source: model.mediaUrl,
source: mediaUrl,
blurhash: model.content.info["xyz.amorgan.blurhash"],
imageWidth: content.info.w,
imageHeight: content.info.h
imageHeight: content.info.h,
modelData: model
}).showFullScreen();
}
}

View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2022 Bharadwaj Raju <bharadwaj.raju777@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
RowLayout {
id: row
property var links: model.display.match(/(\bhttps?:\/\/[^\s\<\>\"\']*[^\s\<\>\"\'])/g)
// don't show previews for room links or user mentions
.filter(link => !link.includes("https://matrix.to"))
// remove ending fullstops and commas
.map(link => (link.length && [".", ","].includes(link[link.length-1])) ? link.substring(0, link.length-1) : link)
LinkPreviewer {
id: lp
url: links[0]
}
visible: lp.loaded && lp.title
Rectangle {
Layout.fillHeight: true
width: Kirigami.Units.smallSpacing
visible: lp.loaded && lp.title
color: Kirigami.Theme.highlightColor
}
Image {
visible: lp.imageSource
Layout.maximumHeight: Kirigami.Units.gridUnit * 5
Layout.maximumWidth: Kirigami.Units.gridUnit * 5
source: lp.imageSource.replace("mxc://", "image://mxc/")
fillMode: Image.PreserveAspectFit
}
ColumnLayout {
id: column
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
Layout.fillWidth: true
level: 4
wrapMode: Text.Wrap
textFormat: Text.RichText
text: "<style>
a {
text-decoration: none;
}
</style>
<a href=\"" + links[0] + "\">" + lp.title.replace("&ndash;", "—") + "</a>"
visible: lp.loaded
onLinkActivated: RoomManager.openResource(link)
}
Label {
text: lp.description
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
Layout.fillWidth: true
wrapMode: Text.Wrap
visible: lp.loaded && lp.description
}
}
}

View File

@@ -14,14 +14,27 @@ TimelineContainer {
id: messageDelegate
property bool isEmote: false
onOpenContextMenu: openMessageContext(model, parent.selectedText, Controller.plainText(label.textDocument))
onOpenContextMenu: openMessageContext(model, label.selectedText, Controller.plainText(label.textDocument))
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
innerObject: RichLabel {
id: label
isEmote: messageDelegate.isEmote
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
innerObject: ColumnLayout {
RichLabel {
id: label
isEmote: messageDelegate.isEmote
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
}
Loader {
id: linkPreviewLoader
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
height: active ? item.implicitHeight : 0
active: !currentRoom.usesEncryption && model.display && model.display.includes("http")
visible: active
sourceComponent: LinkPreviewDelegate {
anchors.verticalCenter: parent.verticalCenter
}
}
}
}

View File

@@ -24,6 +24,8 @@ TextEdit {
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
persistentSelection: true
text: "<style>
table {
width:100%;
@@ -56,6 +58,8 @@ a{
</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + textMessage + (isEdited ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + "<span style='font-size: " + Kirigami.Theme.defaultFont.pixelSize +"px'>" + i18n(" (edited)") + "</span>") : "")
color: Kirigami.Theme.textColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
font.pointSize: model.reply === undefined && isEmoji.test(model.display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
selectByMouse: !Kirigami.Settings.isMobile
readOnly: true

View File

@@ -229,6 +229,7 @@ QQC2.ItemDelegate {
color: Kirigami.Theme.disabledTextColor
QQC2.ToolTip.visible: hoverHandler.hovered
QQC2.ToolTip.text: time.toLocaleString(Qt.locale(), Locale.LongFormat)
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
HoverHandler {
id: hoverHandler

View File

@@ -12,3 +12,4 @@ EncryptedDelegate 1.0 EncryptedDelegate.qml
EventDelegate 1.0 EventDelegate.qml
MessageDelegate 1.0 MessageDelegate.qml
ReadMarkerDelegate 1.0 ReadMarkerDelegate.qml
LinkPreviewDelegate 1.0 LinkPreviewDelegate.qml

View File

@@ -0,0 +1,39 @@
// 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 as QQC2
import QtQml 2.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Column {
id: emojiItem
property string emoji
property string description
QQC2.Label {
id: emojiLabel
x: 0
y: 0
width: parent.width
height: parent.height * 0.75
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: emojiItem.emoji
font.family: "emoji"
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 4
}
QQC2.Label {
x: 0
y: parent.height * 0.75
width: parent.width
height: parent.height * 0.25
text: emojiItem.description
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}

View File

@@ -0,0 +1,25 @@
// 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 as QQC2
import QtQml 2.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Row {
id: emojiRow
property alias model: repeater.model
anchors.horizontalCenter: parent.horizontalCenter
Repeater {
id: repeater
delegate: EmojiItem {
emoji: modelData.emoji
description: modelData.description
width: emojiRow.height
height: width
}
}
}

View File

@@ -0,0 +1,50 @@
// 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 as QQC2
import QtQml 2.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Column {
id: emojiSas
required property var model
signal accept()
signal reject()
visible: dialog.session.state === KeyVerificationSession.WAITINGFORVERIFICATION
anchors.centerIn: parent
spacing: Kirigami.Units.largeSpacing
QQC2.Label {
text: i18n("Confirm the emoji below are displayed on both devices, in the same order.")
}
EmojiRow {
anchors.horizontalCenter: parent.horizontalCenter
height: Kirigami.Units.gridUnit * 4
model: emojiSas.model.slice(0, 4)
}
EmojiRow {
anchors.horizontalCenter: parent.horizontalCenter
height: Kirigami.Units.gridUnit * 4
model: emojiSas.model.slice(4, 7)
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
QQC2.Button {
anchors.bottom: parent.bottom
text: i18n("They match")
icon.name: "dialog-ok"
onClicked: emojiSas.accept()
}
QQC2.Button {
anchors.bottom: parent.bottom
text: i18n("They don't match")
icon.name: "dialog-cancel"
onClicked: emojiSas.reject()
}
}
}

View File

@@ -0,0 +1,86 @@
// 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 as QQC2
import QtQuick.Layouts 1.15
import QtQml 2.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Kirigami.Page {
id: dialog
title: i18n("Session Verification")
required property var session
Item {
anchors.fill: parent
VerificationCanceled {
visible: dialog.session.state === KeyVerificationSession.CANCELED
anchors.centerIn: parent
reason: dialog.session.error
}
EmojiSas {
anchors.centerIn: parent
visible: dialog.session.state === KeyVerificationSession.WAITINGFORVERIFICATION
model: dialog.session.sasEmojis
onReject: dialog.session.cancelVerification(KeyVerificationSession.MISMATCHED_SAS)
onAccept: dialog.session.sendMac()
}
Message {
visible: dialog.session.state === KeyVerificationSession.WAITINGFORREADY
anchors.centerIn: parent
icon: "security-medium-symbolic"
text: i18n("Waiting for device to accept verification.")
}
Message {
visible: dialog.session.state === KeyVerificationSession.INCOMING
anchors.centerIn: parent
icon: "security-medium-symbolic"
text: i18n("Incoming key verification request from device **%1**", dialog.session.remoteDeviceId)
}
Message {
visible: dialog.session.state === KeyVerificationSession.WAITINGFORMAC
anchors.centerIn: parent
icon: "security-medium-symbolic"
text: i18n("Waiting for other party to verify.")
}
Kirigami.BasicListItem {
id: emojiVerification
text: "Emoji Verification"
visible: dialog.session.state === KeyVerificationSession.READY
subtitle: i18n("Compare a set of emoji on both devices")
onClicked: {
dialog.session.sendStartSas()
}
}
Message {
visible: dialog.session.state === KeyVerificationSession.DONE
anchors.centerIn: parent
text: i18n("Successfully verified device **%1**", dialog.session.remoteDeviceId)
icon: "security-high"
}
}
footer: QQC2.ToolBar {
visible: dialog.session.state === KeyVerificationSession.INCOMING
QQC2.DialogButtonBox {
anchors.fill: parent
Item { Layout.fillWidth: true }
QQC2.Button {
text: i18n("Accept")
icon.name: "dialog-ok"
onClicked: dialog.session.sendReady()
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
}
QQC2.Button {
text: i18n("Decline")
icon.name: "dialog-cancel"
onClicked: dialog.session.cancelVerification("m.user", "Declined")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.CancelRole
}
}
}
}

View File

@@ -0,0 +1,27 @@
// 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 as QQC2
import QtQml 2.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Column {
id: message
required property string icon
required property string text
anchors.centerIn: parent
Kirigami.Icon {
width: Kirigami.Units.iconSizes.enormous
height: width
anchors.horizontalCenter: parent.horizontalCenter
source: message.icon
}
QQC2.Label {
text: message.text
textFormat: Text.MarkdownText
}
}

View File

@@ -0,0 +1,70 @@
// 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 as QQC2
import QtQml 2.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Message {
id: verificationCanceled
required property int reason
anchors.centerIn: parent
icon: "security-low"
text: {
switch(verificationCanceled.reason) {
case KeyVerificationSession.NONE:
return i18n("The session verification was canceled for unknown reason.");
case KeyVerificationSession.TIMEOUT:
return i18n("The session verification timed out.");
case KeyVerificationSession.REMOTE_TIMEOUT:
return i18n("The session verification timed out for remote party.");
case KeyVerificationSession.USER:
return i18n("You canceled the session verification.");
case KeyVerificationSession.REMOTE_USER:
return i18n("The remote party canceled the session verification.");
case KeyVerificationSession.UNEXPECTED_MESSAGE:
return i18n("The session verification was canceled because we received an unexpected message.");
case KeyVerificationSession.REMOTE_UNEXPECTED_MESSAGE:
return i18n("The remote party canceled the session verification because it received an unexpected message.");
case KeyVerificationSession.UNKNOWN_TRANSACTION:
return i18n("The session verification was canceled because it received a message for an unknown session.");
case KeyVerificationSession.REMOTE_UNKNOWN_TRANSACTION:
return i18n("The remote party canceled the session verification because it received a message for an unknown session.");
case KeyVerificationSession.UNKNOWN_METHOD:
return i18n("The session verification was canceled because NeoChat is unable to handle this verification method.");
case KeyVerificationSession.REMOTE_UNKNOWN_METHOD:
return i18n("The remote party canceled the session verification because it is unable to handle this verification method.");
case KeyVerificationSession.KEY_MISMATCH:
return i18n("The session verification was canceled because the keys are incorrect.");
case KeyVerificationSession.REMOTE_KEY_MISMATCH:
return i18n("The remote party canceled the session verification because the keys are incorrect.");
case KeyVerificationSession.USER_MISMATCH:
return i18n("The session verification was canceled because it verifies an unexpected user.");
case KeyVerificationSession.REMOTE_USER_MISMATCH:
return i18n("The remote party canceled the session verification because it verifies an unexpected user.");
case KeyVerificationSession.INVALID_MESSAGE:
return i18n("The session verification was canceled because we received an invalid message.");
case KeyVerificationSession.REMOTE_INVALID_MESSAGE:
return i18n("The remote party canceled the session verification because it received an invalid message.");
case KeyVerificationSession.SESSION_ACCEPTED:
return i18n("The session was accepted on a different device"); //TODO this should not be visible
case KeyVerificationSession.REMOTE_SESSION_ACCEPTED:
return i18n("The session was accepted on a different device"); //TODO neither should this
case KeyVerificationSession.MISMATCHED_COMMITMENT:
return i18n("The session verification was canceled because of a mismatched key.");
case KeyVerificationSession.REMOTE_MISMATCHED_COMMITMENT:
return i18n("The remote party canceled the session verification because of a mismatched key.");
case KeyVerificationSession.MISMATCHED_SAS:
return i18n("The session verification was canceled because the keys do not match.");
case KeyVerificationSession.REMOTE_MISMATCHED_SAS:
return i18n("The remote party canceled the session verification because the keys do not match.");
default:
return i18n("The session verification was canceled due to an unknown error.");
}
}
}

View File

@@ -0,0 +1,7 @@
module NeoChat.Dialog.KeyVerification
KeyVerificationDialog 1.0 KeyVerificationDialog.qml
Message 1.0 Message.qml
VerificationCanceled 1.0 VerificationCanceled.qml
EmojiItem 1.0 EmojiItem.qml
EmojiRow 1.0 EmojiRow.qml
EmojiSas 1.0 EmojiSas.qml

View File

@@ -14,6 +14,8 @@ import NeoChat.Component 1.0
Kirigami.OverlaySheet {
id: root
signal closed()
property var room
property var user
@@ -52,7 +54,7 @@ Kirigami.OverlaySheet {
onClicked: {
if (avatarMediaId) {
fullScreenImage.createObject(parent, {"filename": displayName, "source": room.urlToMxcUrl(avatarUrl)}).showFullScreen()
fullScreenImage.createObject(parent, {filename: displayName, source: room.urlToMxcUrl(avatarUrl)}).showFullScreen()
}
}
}
@@ -164,5 +166,11 @@ Kirigami.OverlaySheet {
FullScreenImage {}
}
}
onSheetOpenChanged: {
if (!sheetOpen) {
closed()
}
}
}

View File

@@ -7,3 +7,4 @@ OpenFileDialog 1.0 OpenFileDialog.qml
ImageClipboardDialog 1.0 ImageClipboardDialog.qml
StartChatDialog 1.0 StartChatDialog.qml
EmojiDialog 1.0 EmojiDialog.qml
KeyVerificationDialog 1.0 KeyVerificationDialog.qml

View File

@@ -45,7 +45,7 @@ Labs.MenuBar {
Labs.MenuItem {
text: i18nc("menu", "New Private Chat…")
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {"connection": Controller.activeConnection})
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {connection: Controller.activeConnection})
}
Labs.MenuItem {
text: i18nc("menu", "New Group…")
@@ -58,7 +58,7 @@ Labs.MenuBar {
}
Labs.MenuItem {
text: i18nc("menu", "Browse Chats…")
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {"connection": Controller.activeConnection})
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {connection: Controller.activeConnection})
}
}
EditMenu {

View File

@@ -24,7 +24,7 @@ Loader {
Menu {
MenuItem {
id: newWindow
text: i18n("Open in new window")
text: i18n("Open in New Window")
onTriggered: RoomManager.openWindow(room);
visible: !Kirigami.Settings.isMobile
}
@@ -49,7 +49,7 @@ Loader {
}
MenuItem {
text: i18nc("@action:inmenu", "Copy address to clipboard")
text: i18nc("@action:inmenu", "Copy Address to Clipboard")
onTriggered: if (room.canonicalAlias.length === 0) {
Clipboard.saveText(room.id)
} else {
@@ -57,8 +57,53 @@ Loader {
}
}
Menu {
title: i18n("Notification State")
MenuItem {
text: i18n("Follow Global Setting")
checkable: true
autoExclusive: true
checked: room.pushNotificationState === PushNotificationState.Default
enabled: room.pushNotificationState != PushNotificationState.Unknown
onTriggered: {
room.pushNotificationState = PushNotificationState.Default
}
}
MenuItem {
text: i18nc("As in 'notify for all messages'","All")
checkable: true
autoExclusive: true
checked: room.pushNotificationState === PushNotificationState.All
enabled: room.pushNotificationState != PushNotificationState.Unknown
onTriggered: {
room.pushNotificationState = PushNotificationState.All
}
}
MenuItem {
text: i18nc("As in 'notify when the user is mentioned or the message contains a set keyword'","@Mentions and Keywords")
checkable: true
autoExclusive: true
checked: room.pushNotificationState === PushNotificationState.MentionKeyword
enabled: room.pushNotificationState != PushNotificationState.Unknown
onTriggered: {
room.pushNotificationState = PushNotificationState.MentionKeyword
}
}
MenuItem {
text: i18nc("As in 'do not notify for any messages'","Off")
checkable: true
autoExclusive: true
checked: room.pushNotificationState === PushNotificationState.Mute
enabled: room.pushNotificationState != PushNotificationState.Unknown
onTriggered: {
room.pushNotificationState = PushNotificationState.Mute
}
}
}
MenuItem {
text: i18n("Room settings")
text: i18n("Room Settings")
onTriggered: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
}

View File

@@ -14,6 +14,8 @@ import NeoChat.Menu 1.0
MessageDelegateContextMenu {
id: root
signal closeFullscreen
required property var file
required property var progressInfo
required property string mimeType
@@ -51,6 +53,7 @@ MessageDelegateContextMenu {
icon.name: "mail-replied-symbolic"
onTriggered: {
chatBoxHelper.replyToMessage(eventId, message, author);
root.closeFullscreen()
}
},
Kirigami.Action {
@@ -60,8 +63,18 @@ MessageDelegateContextMenu {
icon.color: "red"
onTriggered: {
currentRoom.redactEvent(eventId);
root.closeFullscreen()
}
},
Kirigami.Action {
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
icon.name: "dialog-warning-symbolic"
visible: author.id !== currentRoom.localUser.id
onTriggered: applicationWindow().pageStack.pushDialogLayer("qrc:/imports/NeoChat/Menu/Timeline/ReportSheet.qml", {room: currentRoom, eventId: eventId}, {
title: i18nc("@title", "Report Message"),
width: Kirigami.Units.gridUnit * 25
})
},
Kirigami.Action {
text: i18n("View Source")
icon.name: "code-context"
@@ -72,6 +85,7 @@ MessageDelegateContextMenu {
title: i18n("Message Source"),
width: Kirigami.Units.gridUnit * 25
});
root.closeFullscreen()
}
}
]

View File

@@ -48,6 +48,15 @@ Loader {
icon.name: "edit-copy"
onTriggered: Clipboard.saveText(loadRoot.selectedText === "" ? loadRoot.plainMessage : loadRoot.selectedText)
},
Kirigami.Action {
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
icon.name: "dialog-warning-symbolic"
visible: author.id !== currentRoom.localUser.id
onTriggered: applicationWindow().pageStack.pushDialogLayer("qrc:/imports/NeoChat/Menu/Timeline/ReportSheet.qml", {room: currentRoom, eventId: eventId}, {
title: i18nc("@title", "Report Message"),
width: Kirigami.Units.gridUnit * 25
})
},
Kirigami.Action {
text: i18n("View Source")
icon.name: "code-context"

View File

@@ -0,0 +1,47 @@
// 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 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.20 as Kirigami
Kirigami.Page {
id: reportSheet
property var room
property string eventId
title: i18n("Report Message")
QQC2.TextArea {
id: reason
placeholderText: i18n("Reason for reporting this message")
anchors.fill: parent
wrapMode: TextEdit.Wrap
}
footer: QQC2.ToolBar {
QQC2.DialogButtonBox {
anchors.fill: parent
Item {
Layout.fillWidth: true
}
QQC2.Button {
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
icon.name: "dialog-warning-symbolic"
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
onClicked: {
reportSheet.room.reportEvent(eventId, reason.text)
reportSheet.closeDialog()
}
}
QQC2.Button {
text: i18nc("@action", "Cancel")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
onClicked: reportSheet.closeDialog()
}
}
}
}

View File

@@ -2,3 +2,4 @@ module NeoChat.Menu.Timeline
MessageDelegateContextMenu 1.0 MessageDelegateContextMenu.qml
FileDelegateContextMenu 1.0 FileDelegateContextMenu.qml
MessageSourceSheet 1.0 MessageSourceSheet.qml
ReportSheet 1.0 ReportSheet.qml

View File

@@ -3,18 +3,11 @@
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.12 as QQC2
import org.kde.kirigami 2.12 as Kirigami
import org.kde.kirigami 2.19 as Kirigami
Kirigami.Page {
title: i18n("Loading…")
Kirigami.PlaceholderMessage {
Kirigami.LoadingPlaceholder {
id: loadingIndicator
anchors.centerIn: parent
text: i18n("Loading…")
QQC2.BusyIndicator {
running: false
Layout.alignment: Qt.AlignHCenter
}
}
}

View File

@@ -58,7 +58,7 @@ Kirigami.ScrollablePage {
flat: true
padding: Kirigami.Units.gridUnit / 2
icon.name: "home"
text: i18nc('@action:button', 'Show All Rooms')
text: i18nc("@action:button", "Show All Rooms")
display: QQC2.AbstractButton.IconOnly
onClicked: {
@@ -368,7 +368,7 @@ Kirigami.ScrollablePage {
}
function createRoomListContextMenu() {
const menu = roomListContextMenu.createObject(page, {"room": currentRoom})
const menu = roomListContextMenu.createObject(page, {room: currentRoom})
configButton.visible = true
configButton.down = true
menu.closed.connect(function() {

View File

@@ -8,7 +8,7 @@ import QtQuick.Layouts 1.15
import Qt.labs.platform 1.1 as Platform
import Qt.labs.qmlmodels 1.0
import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigami 2.19 as Kirigami
import org.kde.kitemmodels 1.0
import org.kde.neochat 1.0
@@ -105,17 +105,17 @@ Kirigami.ScrollablePage {
function onShowMessage(messageType, message) {
page.header.contentItem.text = message;
page.header.contentItem.type = messageType === ActionsHandler.Error ? Kirigami.MessageType.Error : Kirigami.MessageType.Information;
page.header.contentItem.visible = true;
page.header.visible = true;
}
}
header: QQC2.Control {
height: visible ? implicitHeight : 0
visible: contentItem.visible
visible: false
padding: Kirigami.Units.smallSpacing
contentItem: Kirigami.InlineMessage {
showCloseButton: true
visible: false
visible: true
}
}
@@ -144,15 +144,10 @@ Kirigami.ScrollablePage {
}
}
Kirigami.PlaceholderMessage {
Kirigami.LoadingPlaceholder {
id: loadingIndicator
anchors.centerIn: parent
visible: loading
text: i18n("Loading…")
QQC2.BusyIndicator {
running: loadingIndicator.visible
Layout.alignment: Qt.AlignHCenter
}
}
focus: true
@@ -182,6 +177,15 @@ Kirigami.ScrollablePage {
}
}
Connections {
target: currentRoom
function onPositiveMessage(message) {
page.header.contentItem.text = message;
page.header.contentItem.type = Kirigami.MessageType.Positive;
page.header.visible = true;
}
}
// hover actions on a delegate, activated in TimelineContainer.qml
Connections {
target: page.flickable
@@ -454,6 +458,7 @@ Kirigami.ScrollablePage {
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
property var updateFunction
@@ -469,10 +474,23 @@ Kirigami.ScrollablePage {
id: hoverHandler
margin: Kirigami.Units.smallSpacing
}
Kirigami.Icon {
source: "security-high"
width: height
height: parent.height
visible: hoverActions.event.verified
HoverHandler {
id: hover
}
QQC2.ToolTip.text: i18n("This message was sent from a verified device")
QQC2.ToolTip.visible: hover.hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.Button {
QQC2.ToolTip.text: i18n("React")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
icon.name: "preferences-desktop-emoticons"
onClicked: emojiDialog.open();
EmojiDialog {
@@ -486,6 +504,7 @@ Kirigami.ScrollablePage {
QQC2.Button {
QQC2.ToolTip.text: i18n("Edit")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
visible: hoverActions.showEdit
icon.name: "document-edit"
onClicked: {
@@ -498,6 +517,7 @@ Kirigami.ScrollablePage {
QQC2.Button {
QQC2.ToolTip.text: i18n("Reply")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
icon.name: "mail-replied-symbolic"
onClicked: {
chatBoxHelper.replyToMessage(hoverActions.event.eventId, hoverActions.event.message, hoverActions.event.author);
@@ -571,7 +591,7 @@ Kirigami.ScrollablePage {
function warning(title, message) {
page.header.contentItem.text = `${title}<br />${message}`;
page.header.contentItem.type = Kirigami.MessageType.Warning;
page.header.contentItem.visible = true;
page.header.visible = true;
}
function showUserDetail(user) {

View File

@@ -18,7 +18,7 @@ Kirigami.OverlayDrawer {
id: roomDrawer
readonly property var room: RoomManager.currentRoom
width: modal ? undefined : actualWidth
width: actualWidth
readonly property int minWidth: Kirigami.Units.gridUnit * 15
readonly property int maxWidth: Kirigami.Units.gridUnit * 25
@@ -77,48 +77,70 @@ Kirigami.OverlayDrawer {
sourceComponent: ColumnLayout {
id: columnLayout
property alias userSearchText: userListSearchField.text
property alias highlightedUser: userListView.currentIndex
spacing: 0
Kirigami.AbstractApplicationHeader {
Layout.fillWidth: true
topPadding: Kirigami.Units.smallSpacing / 2;
bottomPadding: Kirigami.Units.smallSpacing / 2;
rightPadding: Kirigami.Units.smallSpacing
leftPadding: Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.largeSpacing
RowLayout {
anchors.fill: parent
spacing: 0
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
Layout.fillWidth: true
text: i18n("Room information")
level: 1
}
ToolButton {
id: inviteButton
Layout.alignment: Qt.AlignRight
icon.name: "list-add-user"
text: i18n("Invite")
text: i18n("Invite user to room")
display: AbstractButton.IconOnly
onClicked: {
applicationWindow().pageStack.layers.push("qrc:/imports/NeoChat/Page/InviteUserPage.qml", {room: room})
roomDrawer.close();
}
}
Item {
// HACK otherwise rating item is not right aligned
Layout.fillWidth: true
}
ToolTip {
text: inviteButton.text
}
}
ToolButton {
id: favouriteButton
Layout.alignment: Qt.AlignRight
icon.name: room && room.isFavourite ? "rating" : "rating-unrated"
checkable: true
checked: room && room.isFavourite
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
display: AbstractButton.IconOnly
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
ToolTip {
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
text: favouriteButton.text
}
}
ToolButton {
id: settingsButton
Layout.alignment: Qt.AlignRight
icon.name: 'settings-configure'
text: i18n("Room settings")
display: AbstractButton.IconOnly
onClicked: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
ToolTip {
text: i18n("Room settings")
text: settingsButton.text
}
}
}
@@ -127,14 +149,11 @@ Kirigami.OverlayDrawer {
ColumnLayout {
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
Kirigami.Heading {
text: i18n("Room information")
level: 3
}
spacing: Kirigami.Units.largeSpacing
RowLayout {
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.largeSpacing
Kirigami.Avatar {
@@ -151,10 +170,9 @@ Kirigami.OverlayDrawer {
spacing: 0
Kirigami.Heading {
Layout.maximumWidth: Kirigami.Units.gridUnit * 9
Layout.fillWidth: true
level: 1
font.bold: true
type: Kirigami.Heading.Type.Primary
wrapMode: Label.Wrap
text: room ? room.displayName : i18n("No name")
textFormat: Text.PlainText
@@ -165,6 +183,8 @@ Kirigami.OverlayDrawer {
wrapMode: Text.WordWrap
selectByMouse: true
color: Kirigami.Theme.textColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
readOnly: true
text: room && room.canonicalAlias ? room.canonicalAlias : i18n("No Canonical Alias")
}
@@ -179,6 +199,8 @@ Kirigami.OverlayDrawer {
wrapMode: Text.WordWrap
selectByMouse: true
color: Kirigami.Theme.textColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
onLinkActivated: UrlHelper.openUrl(link)
readOnly: true
MouseArea {
@@ -192,16 +214,23 @@ Kirigami.OverlayDrawer {
Kirigami.ListSectionHeader {
label: i18n("Members")
activeFocusOnTab: false
Label {
Layout.alignment: Qt.AlignRight
text: room ? i18np("%1 Member", "%1 Members", room.joinedCount) : i18n("No Member Count")
}
}
Pane {
padding: Kirigami.Units.smallSpacing
implicitWidth: parent.width
z: 2
Control {
Layout.fillWidth: true
// Note need to set padding individually to guarantee it will always work
// see note - https://doc.qt.io/qt-6/qml-qtquick-controls2-control.html#padding-prop
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.largeSpacing
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.inherit: false
@@ -209,6 +238,7 @@ Kirigami.OverlayDrawer {
}
contentItem: Kirigami.SearchField {
id: userListSearchField
onAccepted: sortedMessageEventModel.filterString = text;
}
}
@@ -223,8 +253,6 @@ Kirigami.OverlayDrawer {
ListView {
id: userListView
clip: true
headerPositioning: ListView.OverlayHeader
boundsBehavior: Flickable.DragOverBounds
activeFocusOnTab: true
model: KSortFilterProxyModel {
@@ -239,58 +267,50 @@ Kirigami.OverlayDrawer {
filterCaseSensitivity: Qt.CaseInsensitive
}
delegate: Kirigami.AbstractListItem {
width: userListView.width
delegate: Kirigami.BasicListItem {
id: userListItem
implicitHeight: Kirigami.Units.gridUnit * 2
z: 1
leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
contentItem: RowLayout {
Kirigami.Avatar {
Layout.preferredWidth: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
Layout.preferredHeight: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
visible: Config.showAvatarInRoomDrawer
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
source: avatar ? ("image://mxc/" + avatar) : ""
name: name
}
label: name
Label {
Layout.fillWidth: true
text: name
textFormat: Text.PlainText
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
Label {
visible: perm != UserType.Member
text: {
if (perm == UserType.Owner) {
return i18n("Owner")
}
if (perm == UserType.Admin) {
return i18n("Admin")
}
if (perm == UserType.Moderator) {
return i18n("Mod")
}
if (perm == UserType.Muted) {
return i18n("Muted")
}
return ""
}
color: Kirigami.Theme.disabledTextColor
font.pixelSize: 12
textFormat: Text.PlainText
wrapMode: Text.NoWrap
}
onClicked: {
const popup = userDetailDialog.createObject(ApplicationWindow.overlay, {room: room, user: user, displayName: name, avatarMediaId: avatar})
popup.closed.connect(function() {
userListItem.highlighted = false
})
popup.open()
}
action: Kirigami.Action {
onTriggered: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": room, "user": user, "displayName": name, "avatarMediaId": avatar}).open()
leading: Kirigami.Avatar {
implicitWidth: height
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
source: avatar ? ("image://mxc/" + avatar) : ""
name: name
}
trailing: Label {
visible: perm != UserType.Member
text: {
switch (perm) {
case UserType.Owner:
return i18n("Owner");
case UserType.Admin:
return i18n("Admin");
case UserType.Moderator:
return i18n("Mod");
case UserType.Muted:
return i18n("Muted");
default:
return "";
}
}
color: Kirigami.Theme.disabledTextColor
textFormat: Text.PlainText
wrapMode: Text.NoWrap
}
}
}
@@ -301,6 +321,7 @@ Kirigami.OverlayDrawer {
onRoomChanged: {
if (loader.active) {
loader.item.userSearchText = ""
loader.item.highlightedUser = -1
}
if (room == null) {
close()

View File

@@ -3,12 +3,12 @@
import QtQuick 2.15
import org.kde.kirigami 2.18 as Kirigami
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
Kirigami.CategorizedSettings {
id: root
property var room
objectName: "settingsPage"
actions: [
Kirigami.SettingAction {
@@ -30,6 +30,16 @@ Kirigami.CategorizedSettings {
room: root.room
}
}
},
Kirigami.SettingAction {
text: i18n("Notifications")
icon.name: "notifications"
page: Qt.resolvedUrl("PushNotification.qml")
initialProperties: {
return {
room: root.room
}
}
}
]
}

View File

@@ -21,7 +21,7 @@ Kirigami.ScrollablePage {
readonly property bool canChangeTopic: room.canSendState("m.room.topic")
readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias")
title: i18n('General')
title: i18n("General")
ColumnLayout {
Kirigami.FormLayout {

View File

@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
property var room
title: i18nc('@title:window', 'Notifications')
ColumnLayout {
Kirigami.FormLayout {
Layout.fillWidth: true
QQC2.RadioButton {
text: i18n("Follow global setting")
Kirigami.FormData.label: i18n("Room notifications setting:")
checked: room.pushNotificationState === PushNotificationState.Default
enabled: room.pushNotificationState != PushNotificationState.Unknown
onToggled: {
room.pushNotificationState = PushNotificationState.Default
}
}
QQC2.RadioButton {
text: i18nc("As in 'notify for all messages'","All")
checked: room.pushNotificationState === PushNotificationState.All
enabled: room.pushNotificationState != PushNotificationState.Unknown
onToggled: {
room.pushNotificationState = PushNotificationState.All
}
}
QQC2.RadioButton {
text: i18nc("As in 'notify when the user is mentioned or the message contains a set keyword'","@Mentions and Keywords")
checked: room.pushNotificationState === PushNotificationState.MentionKeyword
enabled: room.pushNotificationState != PushNotificationState.Unknown
onToggled: {
room.pushNotificationState = PushNotificationState.MentionKeyword
}
}
QQC2.RadioButton {
text: i18nc("As in 'do not notify for any messages'","Off")
checked: room.pushNotificationState === PushNotificationState.Mute
enabled: room.pushNotificationState != PushNotificationState.Unknown
onToggled: {
room.pushNotificationState = PushNotificationState.Mute
}
}
}
}
}

View File

@@ -16,13 +16,13 @@ Kirigami.ScrollablePage {
property var room
title: i18n('Security')
title: i18n("Security")
ColumnLayout {
Kirigami.FormLayout {
Layout.fillWidth: true
CheckBox {
RadioButton {
text: i18nc("@option:check", "Private (invite only)")
Kirigami.FormData.label: i18nc("@option:check", "Access:")
checked: room.joinRule === "invite"
@@ -32,7 +32,7 @@ Kirigami.ScrollablePage {
text: i18n("Only invited people can join.")
font: Kirigami.Theme.smallFont
}
CheckBox {
RadioButton {
text: i18nc("@option:check", "Space members")
checked: room.joinRule === "restricted"
enabled: false
@@ -41,7 +41,7 @@ Kirigami.ScrollablePage {
text: i18n("Anyone in a space can find and join.")
font: Kirigami.Theme.smallFont
}
CheckBox {
RadioButton {
text: i18nc("@option:check", "Public")
checked: room.joinRule === "public"
enabled: false

View File

@@ -7,6 +7,6 @@ import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
Kirigami.AboutPage {
title: i18nc('@title:window', 'About NeoChat')
title: i18nc("@title:window", "About NeoChat")
aboutData: Controller.aboutData
}

View File

@@ -47,7 +47,7 @@ Kirigami.ScrollablePage {
onTriggered: pageSettingStack.pushDialogLayer(Qt.resolvedUrl('./AccountEditorPage.qml'), {
connection: model.connection
}, {
title: i18n('Account editor')
title: i18n("Account editor")
});
}
}

View File

@@ -12,7 +12,7 @@ import org.kde.neochat 1.0
import NeoChat.Settings 1.0
Kirigami.ScrollablePage {
title: i18nc('@title:window', 'Appearance')
title: i18nc("@title:window", "Appearance")
ColumnLayout {
RowLayout {
Layout.alignment: Qt.AlignCenter
@@ -177,7 +177,7 @@ Kirigami.ScrollablePage {
Kirigami.FormLayout {
Layout.maximumWidth: parent.width
QQC2.CheckBox {
Kirigami.FormData.label: "Show Avatar:"
Kirigami.FormData.label: i18n("Show Avatar:")
text: i18n("In Chat")
checked: Config.showAvatarInTimeline
onToggled: {
@@ -237,6 +237,7 @@ Kirigami.ScrollablePage {
HoverHandler { id: sliderHover }
QQC2.ToolTip.visible: sliderHover.hovered && !enabled
QQC2.ToolTip.text: i18n("Only enabled if the transparent chat page is enabled.")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.Label {
text: Math.round(Config.transparency * 100) + "%"

View File

@@ -5,7 +5,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
@@ -19,13 +19,9 @@ Kirigami.ScrollablePage {
anchors.fill: parent
Kirigami.PlaceholderMessage {
Kirigami.LoadingPlaceholder {
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 {
running: parent.visible
}
}
delegate: Kirigami.BasicListItem {
@@ -45,6 +41,17 @@ Kirigami.ScrollablePage {
}
}
}
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
visible: Controller.encryptionSupported
action: Kirigami.Action {
text: i18n("Verify device")
iconName: "security-low-symbolic"
onTriggered: {
devices.connection.startKeyVerificationSession(model.id)
}
}
}
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
action: Kirigami.Action {

View File

@@ -16,7 +16,7 @@ import NeoChat.Component 1.0 as Components
import NeoChat.Dialog 1.0
Kirigami.ScrollablePage {
title: i18nc('@title:window', 'Custom Emojis')
title: i18nc("@title:window", "Custom Emojis")
ListView {
anchors.fill: parent

View File

@@ -11,7 +11,7 @@ import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
title: i18nc('@title:window', 'General')
title: i18nc("@title:window", "General")
ColumnLayout {
Kirigami.FormLayout {
Layout.fillWidth: true
@@ -46,6 +46,7 @@ Kirigami.ScrollablePage {
onToggled: {
Config.showNotifications = checked
Config.save()
NotificationsManager.globalNotificationsEnabled = checked
}
}
QQC2.CheckBox {

View File

@@ -3,7 +3,6 @@
import QtQuick 2.15
import org.kde.kirigami 2.18 as Kirigami
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
Kirigami.CategorizedSettings {
@@ -25,7 +24,7 @@ Kirigami.CategorizedSettings {
page: Qt.resolvedUrl("AccountsPage.qml")
},
Kirigami.SettingAction {
text: i18n("Custom Emoji")
text: i18n("Custom Emojis")
icon.name: "preferences-desktop-emoticons"
page: Qt.resolvedUrl("Emoticons.qml")
},

View File

@@ -47,7 +47,7 @@ Kirigami.Page {
dialog.close();
}
}
title: i18nc('@window:title', 'Spellchecking')
title: i18nc("@window:title", "Spellchecking")
QQC2.Dialog {
id: applyDialog

View File

@@ -206,6 +206,9 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="22.09" date="2022-09-27">
<url>https://www.plasma-mobile.org/2022/09/27/plasma-mobile-gear-22-09/</url>
</release>
<release version="22.06" date="2022-06-24">
<url>https://www.plasma-mobile.org/2022/06/28/plasma-mobile-gear-22-06/</url>
<description>

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

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

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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
import NeoChat.Page 1.0
import NeoChat.Panel 1.0
import NeoChat.Dialog.KeyVerification 1.0
Kirigami.ApplicationWindow {
id: root
@@ -51,7 +52,7 @@ Kirigami.ApplicationWindow {
Timer {
id: saveWindowGeometryTimer
interval: 1000
onTriggered: Controller.saveWindowGeometry(root)
onTriggered: Controller.saveWindowGeometry()
}
Connections {
@@ -59,7 +60,7 @@ Kirigami.ApplicationWindow {
enabled: false // Disable on startup to avoid writing wrong values if the window is hidden
target: root
function onClosing() { Controller.saveWindowGeometry(root); }
function onClosing() { Controller.saveWindowGeometry(); }
function onWidthChanged() { saveWindowGeometryTimer.restart(); }
function onHeightChanged() { saveWindowGeometryTimer.restart(); }
function onXChanged() { saveWindowGeometryTimer.restart(); }
@@ -106,7 +107,7 @@ Kirigami.ApplicationWindow {
function onOpenRoomInNewWindow(room) {
const secondayWindow = roomWindow.createObject(applicationWindow(), {currentRoom: room});
secondayWindow.width = root.width - pageStack.get(0).width;
secondayWiroomWindowndow.width = root.width - pageStack.get(0).width;
secondayWindow.show();
}
@@ -139,13 +140,6 @@ Kirigami.ApplicationWindow {
}
}
function showWindow() {
root.show()
root.raise()
root.requestActivate()
Controller.raiseWindow(root)
}
contextDrawer: RoomDrawer {
id: contextDrawer
modal: !root.wideScreen || !enabled
@@ -235,13 +229,13 @@ Kirigami.ApplicationWindow {
Kirigami.Action {
text: i18n("Explore rooms")
icon.name: "compass"
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {"connection": Controller.activeConnection})
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {connection: Controller.activeConnection})
enabled: pageStack.layers.currentItem.title !== i18n("Explore Rooms") && Controller.accountCount > 0
},
Kirigami.Action {
text: i18n("Start a Chat")
icon.name: "irc-join-channel"
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {"connection": Controller.activeConnection})
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {connection: Controller.activeConnection})
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
},
Kirigami.Action {
@@ -353,16 +347,6 @@ Kirigami.ApplicationWindow {
showPassiveNotification(i18n("%1: %2", error, detail));
}
function onShowWindow(token = null) {
root.showWindow()
if (token && KWindowSystem) {
KWindowSystem.setCurrentXdgActivationToken(basicNotification.xdgActivationToken)
KWindowSystem.activateWindow(root)
} else {
root.raise()
}
}
function onUserConsentRequired(url) {
consentSheet.url = url
consentSheet.open()
@@ -384,11 +368,23 @@ Kirigami.ApplicationWindow {
}
}
Component {
id: keyVerificationDialogComponent
KeyVerificationDialog { }
}
Connections {
target: Controller.activeConnection
function onDirectChatAvailable(directChat) {
RoomManager.enterRoom(Controller.activeConnection.room(directChat.id));
}
function onNewKeyVerificationSession(session) {
applicationWindow().pageStack.pushDialogLayer(keyVerificationDialogComponent, {
session: session,
}, {
title: i18nc("@title:window", "Session Verification")
});
}
}
Kirigami.OverlaySheet {

10
res.qrc
View File

@@ -15,6 +15,7 @@
<file>imports/NeoChat/Page/WelcomePage.qml</file>
<file>imports/NeoChat/RoomSettings/General.qml</file>
<file>imports/NeoChat/RoomSettings/Security.qml</file>
<file>imports/NeoChat/RoomSettings/PushNotification.qml</file>
<file>imports/NeoChat/RoomSettings/Categories.qml</file>
<file>imports/NeoChat/Component/qmldir</file>
<file>imports/NeoChat/Component/FullScreenImage.qml</file>
@@ -39,6 +40,7 @@
<file>imports/NeoChat/Component/Timeline/SectionDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/VideoDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/ReactionDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/LinkPreviewDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/AudioDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/FileDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/ImageDelegate.qml</file>
@@ -62,6 +64,13 @@
<file>imports/NeoChat/Dialog/CreateRoomDialog.qml</file>
<file>imports/NeoChat/Dialog/EmojiDialog.qml</file>
<file>imports/NeoChat/Dialog/OpenFileDialog.qml</file>
<file>imports/NeoChat/Dialog/KeyVerification/KeyVerificationDialog.qml</file>
<file>imports/NeoChat/Dialog/KeyVerification/Message.qml</file>
<file>imports/NeoChat/Dialog/KeyVerification/EmojiItem.qml</file>
<file>imports/NeoChat/Dialog/KeyVerification/EmojiRow.qml</file>
<file>imports/NeoChat/Dialog/KeyVerification/EmojiSas.qml</file>
<file>imports/NeoChat/Dialog/KeyVerification/VerificationCanceled.qml</file>
<file>imports/NeoChat/Dialog/KeyVerification/qmldir</file>
<file>imports/NeoChat/Menu/qmldir</file>
<file>imports/NeoChat/Menu/GlobalMenu.qml</file>
<file>imports/NeoChat/Menu/EditMenu.qml</file>
@@ -69,6 +78,7 @@
<file>imports/NeoChat/Menu/Timeline/MessageDelegateContextMenu.qml</file>
<file>imports/NeoChat/Menu/Timeline/FileDelegateContextMenu.qml</file>
<file>imports/NeoChat/Menu/Timeline/MessageSourceSheet.qml</file>
<file>imports/NeoChat/Menu/Timeline/ReportSheet.qml</file>
<file>imports/NeoChat/Menu/RoomListContextMenu.qml</file>
<file>qtquickcontrols2.conf</file>
<file>imports/NeoChat/Component/glowdot.png</file>

View File

@@ -38,6 +38,8 @@ add_library(neochat STATIC
joinrulesevent.cpp
collapsestateproxymodel.cpp
urlhelper.cpp
windowcontroller.cpp
linkpreviewer.cpp
)
add_executable(neochat-app
@@ -138,13 +140,16 @@ if(ANDROID)
"org.kde.neochat"
"preferences-system-users"
"preferences-desktop-theme-global"
"notifications"
)
else()
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(NOT ANDROID)
set_target_properties(neochat-app PROPERTIES OUTPUT_NAME "neochat")
endif()
if(TARGET KF5::DBusAddons)
target_link_libraries(neochat PUBLIC KF5::DBusAddons)
@@ -155,7 +160,7 @@ if (TARGET KF5::KIOWidgets)
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
endif()
install(TARGETS neochat-app ${KF5_INSTALL_TARGETS_DEFAULT_ARGS})
install(TARGETS neochat-app ${KDE_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)

View File

@@ -5,8 +5,8 @@
#include <QObject>
#include "connection.h"
#include "neochatroom.h"
#include <connection.h>
using namespace Quotient;

View File

@@ -159,7 +159,7 @@ QVariantMap ChatDocumentHandler::getAutocompletionInfo(bool isAutocompleting)
};
}
if (autoCompletePrefix.startsWith("/")) {
if (autoCompletePrefix.startsWith("/") && text.trimmed().length() <= 1) {
return QVariantMap{
{"keyword", autoCompletePrefix},
{"type", AutoCompletionType::Command},

View File

@@ -8,6 +8,7 @@
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent);
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventTypeRole)
!= QLatin1String("state") // If this is not a state, show it
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)

View File

@@ -12,7 +12,6 @@
#include <KWindowConfig>
#ifdef HAVE_WINDOWSYSTEM
#include <KWindowEffects>
#include <KWindowSystem>
#endif
#include <QAuthenticator>
@@ -41,19 +40,21 @@
#include "neochataccountregistry.h"
#endif
#include "csapi/account-data.h"
#include "csapi/content-repo.h"
#include "csapi/joining.h"
#include "csapi/logout.h"
#include "csapi/profile.h"
#include "events/eventcontent.h"
#include <csapi/account-data.h>
#include <csapi/content-repo.h>
#include <csapi/joining.h>
#include <csapi/logout.h>
#include <csapi/profile.h>
#include <events/eventcontent.h>
#include <qt_connection_util.h>
#include <settings.h>
#include "neochatconfig.h"
#include "neochatroom.h"
#include "neochatuser.h"
#include "roommanager.h"
#include "settings.h"
#include "utils.h"
#include <qt_connection_util.h>
#include "windowcontroller.h"
#include <KStandardShortcut>
@@ -131,6 +132,11 @@ Controller &Controller::instance()
return _instance;
}
void Controller::showWindow()
{
WindowController::instance().showAndRaiseWindow(QString());
}
inline QString accessTokenFileName(const AccountSettings &account)
{
QString fileName = account.userId();
@@ -568,7 +574,7 @@ void Controller::setActiveConnection(Connection *connection)
m_connection = connection;
if (connection != nullptr) {
NeoChatConfig::self()->setActiveConnection(connection->userId());
connect(connection, &Connection::networkError, this, [this](QString message, QString details, int retriesTaken, int nextRetryInMilliseconds) {
connect(connection, &Connection::networkError, this, [this]() {
if (!m_isOnline) {
return;
}
@@ -589,21 +595,9 @@ void Controller::setActiveConnection(Connection *connection)
Q_EMIT activeConnectionChanged();
}
void Controller::restoreWindowGeometry(QQuickWindow *window)
void Controller::saveWindowGeometry()
{
KConfig dataResource("data", KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
KConfigGroup windowGroup(&dataResource, "Window");
KWindowConfig::restoreWindowSize(window, windowGroup);
KWindowConfig::restoreWindowPosition(window, windowGroup);
}
void Controller::saveWindowGeometry(QQuickWindow *window)
{
KConfig dataResource("data", KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
KConfigGroup windowGroup(&dataResource, "Window");
KWindowConfig::saveWindowPosition(window, windowGroup);
KWindowConfig::saveWindowSize(window, windowGroup);
dataResource.sync();
WindowController::instance().saveGeometry();
}
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Omittable<QJsonObject> &auth)
@@ -681,13 +675,6 @@ void Controller::setBlur(QQuickItem *item, bool blur)
#endif
}
void Controller::raiseWindow(QWindow *window)
{
#ifdef HAVE_WINDOWSYSTEM
KWindowSystem::activateWindow(window->winId());
#endif
}
bool Controller::hasWindowSystem() const
{
#ifdef HAVE_WINDOWSYSTEM

View File

@@ -11,16 +11,16 @@
class QKeySequences;
#include "connection.h"
#include "csapi/list_public_rooms.h"
#include "room.h"
#include "settings.h"
#include "user.h"
#include <connection.h>
#include <csapi/list_public_rooms.h>
#include <room.h>
#include <settings.h>
#include <user.h>
class NeoChatRoom;
class NeoChatUser;
class TrayIcon;
class QQuickWindow;
class QWindow;
class QQuickTextDocument;
namespace QKeychain
@@ -95,7 +95,6 @@ public:
Q_INVOKABLE void openOrCreateDirectChat(NeoChatUser *user);
Q_INVOKABLE void setBlur(QQuickItem *item, bool blur);
Q_INVOKABLE void raiseWindow(QWindow *window);
Q_INVOKABLE QString plainText(QQuickTextDocument *document) const;
bool encryptionSupported() const;
@@ -118,6 +117,7 @@ private:
private Q_SLOTS:
void invokeLogin();
void showWindow();
Q_SIGNALS:
void busyChanged();
@@ -137,17 +137,19 @@ Q_SIGNALS:
void activeConnectionChanged();
void aboutDataChanged();
void passwordStatus(Controller::PasswordStatus _t1);
void showWindow();
void userConsentRequired(QUrl url);
void testConnectionResult(const QString &connection, bool usable);
void isOnlineChanged(bool isOnline);
void keyVerificationRequest(int timeLeft, Connection *connection, const QString &transactionId, const QString &deviceId);
void keyVerificationStart();
void keyVerificationAccept(const QString &commitment);
void keyVerificationKey(const QString &sas);
public Q_SLOTS:
void logout(Quotient::Connection *conn, bool serverSideLogout);
void changeAvatar(Quotient::Connection *conn, const QUrl &localFile);
static void markAllMessagesAsRead(Quotient::Connection *conn);
void restoreWindowGeometry(QQuickWindow *);
void saveWindowGeometry(QQuickWindow *);
void saveWindowGeometry();
};
// TODO libQuotient 0.7: Drop

View File

@@ -6,11 +6,15 @@
#include <csapi/device_management.h>
#include "controller.h"
#include <connection.h>
DevicesModel::DevicesModel(QObject *parent)
: QAbstractListModel(parent)
{
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &DevicesModel::fetchDevices);
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this]() {
DevicesModel::fetchDevices();
Q_EMIT connectionChanged();
});
fetchDevices();
}
@@ -94,3 +98,8 @@ void DevicesModel::setName(int index, const QString &name)
endResetModel();
});
}
Connection *DevicesModel::connection() const
{
return Controller::instance().activeConnection();
}

View File

@@ -9,10 +9,17 @@
using namespace Quotient;
namespace Quotient
{
class Connection;
}
class DevicesModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(Connection *connection READ connection NOTIFY connectionChanged)
public:
enum Roles {
Id,
@@ -31,6 +38,11 @@ public:
Q_INVOKABLE void logout(int index, const QString &password);
Q_INVOKABLE void setName(int index, const QString &name);
Connection *connection() const;
Q_SIGNALS:
void connectionChanged();
private:
void fetchDevices();
QVector<Quotient::Device> m_devices;

View File

@@ -8,17 +8,25 @@
namespace Quotient
{
#ifdef QUOTIENT_07
class JoinRulesEvent : public StateEvent
#else
class JoinRulesEvent : public StateEventBase
#endif
{
public:
#ifdef QUOTIENT_07
QUO_EVENT(JoinRulesEvent, "m.room.join_rules")
#else
DEFINE_EVENT_TYPEID("m.room.join_rules", JoinRulesEvent)
#endif
explicit JoinRulesEvent()
: StateEventBase(typeId(), matrixTypeId())
{
}
explicit JoinRulesEvent(const QJsonObject &obj)
#ifdef QUOTIENT_07
: StateEvent(obj)
#else
: StateEventBase(typeId(), obj)
#endif
{
}

64
src/linkpreviewer.cpp Normal file
View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2022 Bharadwaj Raju <bharadwaj.raju777@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
#include "linkpreviewer.h"
#include "controller.h"
LinkPreviewer::LinkPreviewer(QObject *parent)
: QObject(parent)
, m_loaded(false)
{
}
bool LinkPreviewer::loaded() const
{
return m_loaded;
}
QString LinkPreviewer::title() const
{
return m_title;
}
QString LinkPreviewer::description() const
{
return m_description;
}
QString LinkPreviewer::imageSource() const
{
return m_imageSource;
}
QUrl LinkPreviewer::url() const
{
return m_url;
}
void LinkPreviewer::setUrl(QUrl url)
{
if (url.scheme() == QStringLiteral("https")) {
m_loaded = false;
Q_EMIT loadedChanged();
m_url = url;
Q_EMIT urlChanged();
auto conn = Controller::instance().activeConnection();
GetUrlPreviewJob *job = conn->callApi<GetUrlPreviewJob>(m_url.toString());
connect(job, &BaseJob::success, this, [this, job]() {
const auto json = job->jsonData();
m_title = json["og:title"].toString();
m_description = json["og:description"].toString();
m_imageSource = json["og:image"].toString();
m_loaded = true;
Q_EMIT titleChanged();
Q_EMIT descriptionChanged();
Q_EMIT imageSourceChanged();
Q_EMIT loadedChanged();
});
}
}

46
src/linkpreviewer.h Normal file
View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2022 Bharadwaj Raju <bharadwaj.raju777@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <connection.h>
#include <csapi/content-repo.h>
#include <QObject>
using namespace Quotient;
class LinkPreviewer : public QObject
{
Q_OBJECT
Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged)
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
Q_PROPERTY(QString imageSource READ imageSource NOTIFY imageSourceChanged)
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
public:
explicit LinkPreviewer(QObject *parent = nullptr);
[[nodiscard]] bool loaded() const;
[[nodiscard]] QString title() const;
[[nodiscard]] QString description() const;
[[nodiscard]] QString imageSource() const;
[[nodiscard]] QUrl url() const;
void setUrl(QUrl);
private:
bool m_loaded;
QString m_title;
QString m_description;
QString m_imageSource;
QUrl m_url;
Q_SIGNALS:
void loadedChanged();
void titleChanged();
void descriptionChanged();
void imageSourceChanged();
void urlChanged();
};

View File

@@ -2,11 +2,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "login.h"
#include "connection.h"
#include <connection.h>
#include <qt_connection_util.h>
#include "controller.h"
#include <QUrl>
#include <qt_connection_util.h>
#include <KLocalizedString>

View File

@@ -5,8 +5,8 @@
#include <QObject>
#include "connection.h"
#include "csapi/wellknown.h"
#include <connection.h>
#include <csapi/wellknown.h>
using namespace Quotient;

View File

@@ -26,8 +26,8 @@
#include <KDBusService>
#endif
#ifdef HAVE_WINDOWSYSTEM
#include <kwindowsystem_version.h>
#include <KWindowSystem>
#include <kwindowsystem_version.h>
#endif
#include <KLocalizedContext>
#include <KLocalizedString>
@@ -36,11 +36,16 @@
#include "neochat-version.h"
#ifdef QUOTIENT_07
#include "accountregistry.h"
#include <accountregistry.h>
#else
#include "neochataccountregistry.h"
#endif
#include <csapi/joining.h>
#include <csapi/leaving.h>
#include <networkaccessmanager.h>
#include <room.h>
#include "actionshandler.h"
#include "blurhashimageprovider.h"
#include "chatboxhelper.h"
@@ -49,13 +54,12 @@
#include "collapsestateproxymodel.h"
#include "commandmodel.h"
#include "controller.h"
#include "csapi/joining.h"
#include "csapi/leaving.h"
#include "customemojimodel.h"
#include "devicesmodel.h"
#include "emojimodel.h"
#include "filetypesingleton.h"
#include "joinrulesevent.h"
#include "linkpreviewer.h"
#include "login.h"
#include "matriximageprovider.h"
#include "messageeventmodel.h"
@@ -63,7 +67,6 @@
#include "neochatconfig.h"
#include "neochatroom.h"
#include "neochatuser.h"
#include "networkaccessmanager.h"
#include "notificationsmanager.h"
#include "publicroomlistmodel.h"
#include "roomlistmodel.h"
@@ -75,7 +78,10 @@
#include "userdirectorylistmodel.h"
#include "userlistmodel.h"
#include "webshortcutmodel.h"
#include <room.h>
#include "windowcontroller.h"
#ifdef QUOTIENT_07
#include <keyverificationsession.h>
#endif
#ifdef HAVE_COLORSCHEME
#include "colorschemer.h"
#endif
@@ -95,20 +101,13 @@ class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
}
};
#ifdef HAVE_WINDOWSYSTEM
static void raiseWindow(QWindow *window)
static QWindow *windowFromEngine(QQmlApplicationEngine *engine)
{
#if KWINDOWSYSTEM_VERSION >= QT_VERSION_CHECK(5, 91, 0)
KWindowSystem::updateStartupId(window);
#else
if (KWindowSystem::isPlatformWayland()) {
KWindowSystem::setCurrentXdgActivationToken(qEnvironmentVariable("XDG_ACTIVATION_TOKEN"));
}
#endif
KWindowSystem::activateWindow(window->winId());
window->raise();
const auto rootObjects = engine->rootObjects();
auto *window = qobject_cast<QQuickWindow *>(rootObjects.first());
Q_ASSERT(window);
return window;
}
#endif
#ifdef Q_OS_ANDROID
Q_DECL_EXPORT
@@ -142,6 +141,7 @@ int main(int argc, char *argv[])
font.setPointSize(10);
app.setFont(font);
#endif
KLocalizedString::setApplicationDomain("neochat");
QGuiApplication::setOrganizationName("KDE");
@@ -165,7 +165,6 @@ int main(int argc, char *argv[])
KAboutData::setApplicationData(about);
QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("org.kde.neochat")));
#ifdef NEOCHAT_FLATPAK
// Copy over the included FontConfig configuration to the
// app's config dir:
@@ -190,6 +189,7 @@ int main(int argc, char *argv[])
#endif
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Controller", &Controller::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "NotificationsManager", &NotificationsManager::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Clipboard", &clipboard);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Config", config);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "RoomManager", &RoomManager::instance());
@@ -215,7 +215,9 @@ int main(int argc, char *argv[])
qmlRegisterType<SortFilterRoomListModel>("org.kde.neochat", 1, 0, "SortFilterRoomListModel");
qmlRegisterType<SortFilterSpaceListModel>("org.kde.neochat", 1, 0, "SortFilterSpaceListModel");
qmlRegisterType<DevicesModel>("org.kde.neochat", 1, 0, "DevicesModel");
qmlRegisterType<LinkPreviewer>("org.kde.neochat", 1, 0, "LinkPreviewer");
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");
qmlRegisterUncreatableType<UserType>("org.kde.neochat", 1, 0, "UserType", "ENUM");
@@ -229,11 +231,12 @@ int main(int argc, char *argv[])
qRegisterMetaType<NeoChatUser *>("NeoChatUser*");
qRegisterMetaType<GetRoomEventsJob *>("GetRoomEventsJob*");
qRegisterMetaType<QMimeType>("QMimeType");
#ifdef HAVE_WINDOWSYSTEM
qmlRegisterSingletonType<KWindowSystem>("org.kde.kwindowsystem.private", 1, 0, "KWindowSystem", [](QQmlEngine *, QJSEngine *) -> QObject * {
return KWindowSystem::self();
});
#ifdef QUOTIENT_07
#ifdef Quotient_E2EE_ENABLED
qRegisterMetaType<KeyVerificationSession *>("KeyVerificationSession*");
qmlRegisterUncreatableType<KeyVerificationSession>("org.kde.neochat", 1, 0, "KeyVerificationSession", {});
qRegisterMetaType<QVector<EmojiEntry>>("QVector<EmojiEntry>");
#endif
#endif
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
@@ -242,7 +245,6 @@ int main(int argc, char *argv[])
QQmlApplicationEngine engine;
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
KLocalizedString::setApplicationDomain("neochat");
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
@@ -282,16 +284,10 @@ int main(int argc, char *argv[])
[&engine](const QStringList &arguments, const QString &workingDirectory) {
Q_UNUSED(workingDirectory);
// Raise windows
const auto rootObjects = engine.rootObjects();
for (auto obj : rootObjects) {
auto view = qobject_cast<QQuickWindow *>(obj);
if (view) {
view->show();
raiseWindow(view);
return;
}
}
QWindow *window = windowFromEngine(&engine);
KWindowSystem::updateStartupId(window);
WindowController::instance().showAndRaiseWindow(QString());
// Open matrix uri
if (arguments.isEmpty()) {
@@ -304,15 +300,11 @@ int main(int argc, char *argv[])
}
});
#endif
const auto rootObjects = engine.rootObjects();
for (auto obj : rootObjects) {
auto view = qobject_cast<QQuickWindow *>(obj);
if (view) {
if (view->isVisible()) {
Controller::instance().restoreWindowGeometry(view);
}
break;
}
}
QWindow *window = windowFromEngine(&engine);
WindowController::instance().setWindow(window);
WindowController::instance().restoreGeometry();
return app.exec();
}

View File

@@ -55,6 +55,7 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
roles[FormattedBodyRole] = "formattedBody";
roles[AuthorIdRole] = "authorId";
roles[MediaUrlRole] = "mediaUrl";
roles[VerifiedRole] = "verified";
return roles;
}
@@ -541,12 +542,17 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == MimeTypeRole) {
auto e = eventCast<const RoomMessageEvent>(&evt);
if (!e || !e->hasFileContent()) {
return QVariant();
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
if (!e || !e->hasFileContent()) {
return QVariant();
}
return e->content()->fileInfo()->mimeType.name();
}
return e->content()->fileInfo()->mimeType.name();
if (auto e = eventCast<const StickerEvent>(&evt)) {
return e->image().mimeType.name();
}
}
if (role == SpecialMarksRole) {
@@ -715,7 +721,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, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, EventTypeRole) == "state"
|| data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000
|| data(i, TimeRole).toDateTime().toLocalTime().date().day() != data(idx, TimeRole).toDateTime().toLocalTime().date().day();
}
}
@@ -786,10 +793,38 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
return m_currentRoom->makeMediaUrl(e->id(), e->content()->originalJson["url"].toString());
}
}
// Requires https://github.com/quotient-im/libQuotient/pull/570
// if (auto e = eventCast<const StickerEvent>(&evt)) {
// return m_currentRoom->makeMediaUrl(e->id(), e->url());
// }
#endif
// Construct link in the same form as urlToDownload as that function doesn't work for stickers
if (auto e = eventCast<const StickerEvent>(&evt)) {
auto url = QUrl(m_currentRoom->connection()->homeserver().toString() + "/_matrix/media/r0/download/" + e->url().toString().remove("mxc://"));
QUrlQuery q(url.query());
q.addQueryItem("allow_remote", "true");
url.setQuery(q);
return url;
}
return m_currentRoom->urlToDownload(evt.id());
}
if (role == VerifiedRole) {
#ifdef QUOTIENT_07
#ifdef Quotient_E2EE_ENABLED
if (evt.originalEvent()) {
auto encrypted = dynamic_cast<const EncryptedEvent *>(evt.originalEvent());
Q_ASSERT(encrypted);
return m_currentRoom->connection()->isVerifiedSession(encrypted->sessionId());
}
return false;
#endif
#endif
}
return {};
}

View File

@@ -6,7 +6,7 @@
#include <QAbstractListModel>
#include "neochatroom.h"
#include "room.h"
#include <room.h>
class MessageEventModel : public QAbstractListModel
{
@@ -48,6 +48,7 @@ public:
// For debugging
EventResolvedTypeRole,
AuthorIdRole,
VerifiedRole,
LastRole, // Keep this last
};
Q_ENUM(EventRoles)

View File

@@ -146,6 +146,7 @@ Name[cs]=Nová pozvánka
Name[de]=Neue Einladung
Name[en_GB]=New Invitation
Name[es]=Nueva invitación
Name[eu]=Gonbidapen berria
Name[fi]=Uusi kutsu
Name[fr]=Nouvelle invitation
Name[ia]=Nove invitation
@@ -172,6 +173,7 @@ Comment[cs]=Máte novou pozvánku do místnosti
Comment[de]=Es gibt eine neue Einladung zu einem Raum
Comment[en_GB]=There is a new invitation to a room
Comment[es]=Hay una nueva invitación a una sala
Comment[eu]=Gela baterako gonbidapen berri bat dago
Comment[fi]=Uusi kutsu huoneeseen
Comment[fr]=Il y a une nouvelle invitation dans un salon.
Comment[ia]=Il ha un nove invitation a un sala

View File

@@ -4,7 +4,7 @@
#include "neochataccountregistry.h"
#include "connection.h"
#include <connection.h>
using namespace Quotient;

Some files were not shown because too many files have changed in this diff Show More