Compare commits
65 Commits
release/24
...
1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
143bd456de | ||
|
|
513de82515 | ||
|
|
581fd5f212 | ||
|
|
2452f630d0 | ||
|
|
2cdc37c3d5 | ||
|
|
5af99a872c | ||
|
|
5fe7ba478b | ||
|
|
f25bc6bac6 | ||
|
|
46ee015775 | ||
|
|
f93be04b2b | ||
|
|
f2d9ca13d8 | ||
|
|
eb04f4adf1 | ||
|
|
6b983332af | ||
|
|
4e197c3cc8 | ||
|
|
bfd6d2ffe2 | ||
|
|
421422edd0 | ||
|
|
0eda9608ec | ||
|
|
06fd8630d9 | ||
|
|
e7d8c4b69c | ||
|
|
44d4269978 | ||
|
|
759244b5d2 | ||
|
|
92a307747f | ||
|
|
627929203f | ||
|
|
0c449ab4bd | ||
|
|
45c35b3cbe | ||
|
|
ecd6a63564 | ||
|
|
b75bf3a75b | ||
|
|
43288db000 | ||
|
|
0d6c793a5e | ||
|
|
6daf184a60 | ||
|
|
93173e3f43 | ||
|
|
ec4aa320c1 | ||
|
|
079a1c8a34 | ||
|
|
e9bb0972a9 | ||
|
|
1717790096 | ||
|
|
893bc79f1e | ||
|
|
e87ae48f17 | ||
|
|
97bbcf3062 | ||
|
|
9f9498541a | ||
|
|
50a4d0a33f | ||
|
|
85b4d7d049 | ||
|
|
b574849df3 | ||
|
|
e802d7f805 | ||
|
|
99438011ca | ||
|
|
99ccfaf93e | ||
|
|
c56973763c | ||
|
|
c96109e9b7 | ||
|
|
3f01b3badf | ||
|
|
093412c788 | ||
|
|
c5ddb61981 | ||
|
|
ad4e52b20d | ||
|
|
5991d59ddd | ||
|
|
de49a26462 | ||
|
|
4924702c15 | ||
|
|
8060edd1c6 | ||
|
|
89bf5d3a31 | ||
|
|
60762b934c | ||
|
|
7729fec259 | ||
|
|
026769b07f | ||
|
|
5be14a4b8f | ||
|
|
0b70d2b33f | ||
|
|
dab77b8d07 | ||
|
|
2acdf61b16 | ||
|
|
defa3d4b77 | ||
|
|
be709a2732 |
@@ -20,22 +20,48 @@ include(KDEClangFormat)
|
||||
include(KDECMakeSettings)
|
||||
include(KDECompilerSettings NO_POLICY_SCOPE)
|
||||
|
||||
include(cmake/Flatpak.cmake)
|
||||
|
||||
# Fix a crash due to problems with quotient's event system. Can probably be removed once the reworked event system is in
|
||||
cmake_policy(SET CMP0063 OLD)
|
||||
|
||||
ecm_setup_version(0.1.0
|
||||
ecm_setup_version(1.0.1
|
||||
VARIABLE_PREFIX NEOCHAT
|
||||
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
|
||||
)
|
||||
|
||||
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Widgets Core Quick Gui QuickControls2 Multimedia Svg)
|
||||
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n Notifications Config CoreAddons)
|
||||
find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Widgets Core Quick Gui QuickControls2 Multimedia Svg)
|
||||
set_package_properties(Qt5 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
)
|
||||
find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons)
|
||||
set_package_properties(KF5 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
)
|
||||
set_package_properties(KF5Kirigami2 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Kirigami application UI framework"
|
||||
)
|
||||
|
||||
if(ANDROID)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(OpenSSL)
|
||||
set_package_properties(OpenSSL PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Encrypted communications"
|
||||
)
|
||||
else()
|
||||
find_package(Qt5Keychain REQUIRED)
|
||||
find_package(KF5DBusAddons ${KF5_MIN_VERSION} REQUIRED)
|
||||
find_package(Qt5Keychain)
|
||||
set_package_properties(Qt5Keychain PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Secure storage of account secrets"
|
||||
)
|
||||
find_package(KF5DBusAddons ${KF5_MIN_VERSION})
|
||||
set_package_properties(KF5DBusAddons PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "DBus convenience functions"
|
||||
)
|
||||
endif()
|
||||
|
||||
find_package(Quotient 0.6)
|
||||
@@ -55,9 +81,11 @@ set_package_properties(cmark PROPERTIES
|
||||
)
|
||||
|
||||
ecm_find_qmlmodule(org.kde.kquickimageeditor 1.0)
|
||||
ecm_find_qmlmodule(org.kde.kitemmodels 1.0)
|
||||
|
||||
find_package(KQuickImageEditor COMPONENTS)
|
||||
set_package_properties(KQuickImageEditor PROPERTIES
|
||||
TYPE REQUIRED
|
||||
DESCRIPTION "Simple image editor for QtQuick applications"
|
||||
URL "https://invent.kde.org/libraries/kquickimageeditor/"
|
||||
PURPOSE "Add image editing capability to image attachments"
|
||||
@@ -65,7 +93,7 @@ set_package_properties(KQuickImageEditor PROPERTIES
|
||||
|
||||
install(FILES org.kde.neochat.desktop DESTINATION ${KDE_INSTALL_APPDIR})
|
||||
install(FILES org.kde.neochat.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
|
||||
install(FILES neochat.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
|
||||
install(FILES org.kde.neochat.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
|
||||
install(FILES neochat.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR})
|
||||
|
||||
# add_definitions(-DQT_NO_KEYWORDS) Need to fix libQuotient first
|
||||
|
||||
16
cmake/Flatpak.cmake
Normal file
16
cmake/Flatpak.cmake
Normal file
@@ -0,0 +1,16 @@
|
||||
if(NOT NEOCHAT_FLATPAK)
|
||||
# Only include this if we build a Flatpak
|
||||
return()
|
||||
endif()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Include FontConfig config which uses the Emoji One font from the
|
||||
# KDE Flatpak SDK.
|
||||
install(
|
||||
FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cmake/Flatpak/99-noto-mono-color-emoji.conf
|
||||
DESTINATION
|
||||
${CMAKE_INSTALL_SYSCONFDIR}/fonts/conf.d/
|
||||
)
|
||||
|
||||
23
cmake/Flatpak/99-noto-mono-color-emoji.conf
Normal file
23
cmake/Flatpak/99-noto-mono-color-emoji.conf
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<alias>
|
||||
<family>serif</family>
|
||||
<prefer>
|
||||
<family>Emoji One</family>
|
||||
</prefer>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>sans-serif</family>
|
||||
<prefer>
|
||||
<family>Emoji One</family>
|
||||
</prefer>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>monospace</family>
|
||||
<prefer>
|
||||
<family>Emoji One</family>
|
||||
</prefer>
|
||||
</alias>
|
||||
</fontconfig>
|
||||
|
||||
@@ -89,8 +89,8 @@ ToolBar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
||||
|
||||
source: replyUser ? "image://mxc/" + replyUser.avatarMediaId: ""
|
||||
name: replyUser ? replyUser.displayName : i18n("No name")
|
||||
source: replyUser ? ("image://mxc/" + replyUser.avatarMediaId) : ""
|
||||
name: replyUser ? replyUser.name : i18n("No name")
|
||||
}
|
||||
|
||||
Label {
|
||||
@@ -133,8 +133,9 @@ ToolBar {
|
||||
keyNavigationWraps: true
|
||||
|
||||
delegate: Control {
|
||||
property string displayText: modelData.displayName ?? modelData.unicode
|
||||
property bool isEmoji: modelData.unicode != null
|
||||
readonly property string userId: modelData.id ?? ""
|
||||
readonly property string displayText: modelData.displayName ?? modelData.unicode
|
||||
readonly property bool isEmoji: modelData.unicode != null
|
||||
readonly property bool highlighted: autoCompleteListView.currentIndex == index
|
||||
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
@@ -156,7 +157,7 @@ ToolBar {
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
||||
source: modelData.avatarMediaId ? "image://mxc/" + modelData.avatarMediaId : ""
|
||||
source: modelData.avatarMediaId ? ("image://mxc/" + modelData.avatarMediaId) : ""
|
||||
color: modelData.color ? Qt.darker(modelData.color, 1.1) : null
|
||||
visible: !isEmoji
|
||||
}
|
||||
@@ -176,7 +177,7 @@ ToolBar {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
autoCompleteListView.currentIndex = index
|
||||
documentHandler.replaceAutoComplete(displayText)
|
||||
inputField.autoComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,7 +343,8 @@ ToolBar {
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
if (isAutoCompleting) {
|
||||
documentHandler.replaceAutoComplete(autoCompleteListView.currentItem.displayText)
|
||||
inputField.autoComplete();
|
||||
|
||||
isAutoCompleting = false;
|
||||
return;
|
||||
}
|
||||
@@ -356,7 +358,10 @@ ToolBar {
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: closeAll()
|
||||
Keys.onEscapePressed: {
|
||||
clearReply();
|
||||
closeAll();
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
@@ -397,7 +402,7 @@ ToolBar {
|
||||
autoAppeared = false;
|
||||
}
|
||||
|
||||
documentHandler.replaceAutoComplete(autoCompleteListView.currentItem.displayText)
|
||||
inputField.autoComplete();
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
@@ -433,11 +438,31 @@ ToolBar {
|
||||
autoCompleteEndPosition = cursorPosition
|
||||
}
|
||||
|
||||
// store each user we autoComplete here, this will be helpful later to generate
|
||||
// the matrix.to links.
|
||||
// This use an hack to define: https://doc.qt.io/qt-5/qml-var.html#property-value-initialization-semantics
|
||||
property var userAutocompleted: ({})
|
||||
|
||||
|
||||
function postMessage() {
|
||||
documentHandler.postMessage(inputField.text, attachmentPath, replyEventID);
|
||||
// Qt wraps lines so we need to use a small hack
|
||||
// to remove the wrapped lines but not break the empty
|
||||
// lines.
|
||||
documentHandler.postMessage(inputField.text.trim(), attachmentPath, replyEventID,
|
||||
inputField.userAutocompleted);
|
||||
clearAttachment();
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
clear();
|
||||
text = Qt.binding(function() {
|
||||
return currentRoom != null ? currentRoom.cachedInput : "";
|
||||
});
|
||||
}
|
||||
|
||||
function autoComplete() {
|
||||
documentHandler.replaceAutoComplete(autoCompleteListView.currentItem.displayText)
|
||||
if (!autoCompleteListView.currentItem.isEmoji) {
|
||||
inputField.userAutocompleted[autoCompleteListView.currentItem.displayText] = autoCompleteListView.currentItem.userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,6 +552,7 @@ ToolBar {
|
||||
|
||||
function clear() {
|
||||
inputField.clear()
|
||||
inputField.userAutocompleted = {};
|
||||
}
|
||||
|
||||
function clearReply() {
|
||||
|
||||
@@ -115,7 +115,7 @@ RowLayout {
|
||||
else
|
||||
{
|
||||
openOnFinished = true
|
||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtGraphicalEffects 1.0
|
||||
import Qt.labs.platform 1.0 as Platform
|
||||
import Qt.labs.platform 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
@@ -143,15 +143,21 @@ Image {
|
||||
}
|
||||
|
||||
function saveFileAs() {
|
||||
var folderDialog = openFolderDialog.createObject(ApplicationWindow.overlay)
|
||||
var dialog = fileDialog.createObject(ApplicationWindow.overlay)
|
||||
dialog.open()
|
||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
||||
}
|
||||
|
||||
folderDialog.chosen.connect(function(path) {
|
||||
if (!path) return
|
||||
Component {
|
||||
id: fileDialog
|
||||
|
||||
currentRoom.downloadFile(eventId, path + "/" + currentRoom.fileNameToDownload(eventId))
|
||||
})
|
||||
|
||||
folderDialog.open()
|
||||
FileDialog {
|
||||
fileMode: FileDialog.SaveFile
|
||||
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||
onAccepted: {
|
||||
currentRoom.downloadFile(eventId, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function downloadAndOpen()
|
||||
@@ -160,7 +166,7 @@ Image {
|
||||
else
|
||||
{
|
||||
openOnFinished = true
|
||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ RowLayout {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
visible: showAuthor && Config.showAvatarInTimeline
|
||||
name: author.displayName
|
||||
source: author.avatarMediaId ? "image://mxc/" + author.avatarMediaId : ""
|
||||
name: author.name
|
||||
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||
color: author.color
|
||||
|
||||
Component {
|
||||
@@ -64,6 +64,7 @@ RowLayout {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open()
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ QQC2.AbstractButton {
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
||||
Layout.alignment: Qt.AlignTop
|
||||
visible: Config.showAvatarInTimeline
|
||||
source: replyVisible && reply.author.avatarMediaId ? "image://mxc/" + reply.author.avatarMediaId : ""
|
||||
name: replyVisible ? reply.author.displayName : "H"
|
||||
source: replyVisible && reply.author.avatarMediaId ? ("image://mxc/" + reply.author.avatarMediaId) : ""
|
||||
name: replyVisible ? reply.author.name : "H"
|
||||
color: replyVisible ? reply.author.color : Kirigami.Theme.highlightColor
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ RowLayout {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||
|
||||
name: author.displayName
|
||||
source: author.avatarMediaId ? "image://mxc/" + author.avatarMediaId : ""
|
||||
name: author.name
|
||||
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||
color: author.color
|
||||
|
||||
Component {
|
||||
|
||||
@@ -15,7 +15,8 @@ TextEdit {
|
||||
|
||||
property bool isEmote: false
|
||||
|
||||
text: "<style>pre {white-space: pre-wrap} a{color: " + Kirigami.Theme.linkColor + ";} .user-pill{}</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + display
|
||||
text: "<style>pre {white-space: pre-wrap} a{color: " + Kirigami.Theme.linkColor + ";} .user-pill{}</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + display + (isEdited ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + i18n("(edited)") + "</span>") : "")
|
||||
|
||||
|
||||
color: Kirigami.Theme.textColor
|
||||
font.pointSize: isEmoji.test(display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
|
||||
|
||||
@@ -42,8 +42,8 @@ Kirigami.OverlaySheet {
|
||||
Layout.preferredHeight: 72
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
name: room.displayName
|
||||
source: room.avatarMediaId ? "image://mxc/" + room.avatarMediaId : ""
|
||||
name: room.name
|
||||
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -74,7 +74,7 @@ Kirigami.OverlaySheet {
|
||||
enabled: canChangeName
|
||||
}
|
||||
|
||||
TextField {
|
||||
TextArea {
|
||||
id: roomTopicField
|
||||
Layout.fillWidth: true
|
||||
text: room.topic
|
||||
|
||||
@@ -29,7 +29,11 @@ Kirigami.OverlaySheet {
|
||||
topPadding: 0
|
||||
|
||||
header: Kirigami.Heading {
|
||||
id: heading
|
||||
text: i18nc("Account detail dialog", "Account detail - %1", displayName)
|
||||
elide: Text.ElideRight
|
||||
QQC2.ToolTip.visible: truncated && hovered
|
||||
QQC2.ToolTip.text: text
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
@@ -47,7 +51,7 @@ Kirigami.OverlaySheet {
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
||||
|
||||
name: displayName
|
||||
source: avatarMediaId ? "image://mxc/" + avatarMediaId : ""
|
||||
source: avatarMediaId ? ("image://mxc/" + avatarMediaId) : ""
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -38,7 +38,7 @@ Kirigami.OverlaySheet {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
source: author.avatarMediaId ? "image://mxc/" + author.avatarMediaId : ""
|
||||
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||
Layout.alignment: Qt.AlignTop
|
||||
@@ -55,6 +55,18 @@ Kirigami.OverlaySheet {
|
||||
text: message
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
onLinkActivated: {
|
||||
if (link.startsWith("https://matrix.to/")) {
|
||||
var result = link.replace(/\?.*/, "").match("https://matrix.to/#/(!.*:.*)/(\\$.*:.*)")
|
||||
if (!result || result.length < 3) return
|
||||
if (result[1] != currentRoom.id) return
|
||||
if (!result[2]) return
|
||||
goToEvent(result[2])
|
||||
} else {
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ Kirigami.ScrollablePage {
|
||||
Layout.preferredWidth: height
|
||||
Layout.fillHeight: true
|
||||
|
||||
source: avatar ? "image://mxc/" + avatar : ""
|
||||
source: avatar ? ("image://mxc/" + avatar) : ""
|
||||
name: name
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.kde.kirigami 2.13 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Effect 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
@@ -117,7 +116,7 @@ Kirigami.ScrollablePage {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
||||
|
||||
source: model.avatar ? "image://mxc/" + model.avatar : ""
|
||||
source: model.avatar ? ("image://mxc/" + model.avatar) : ""
|
||||
name: name
|
||||
}
|
||||
ColumnLayout {
|
||||
|
||||
@@ -95,7 +95,7 @@ Kirigami.ScrollablePage {
|
||||
id: roomListItem
|
||||
property bool itemVisible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
|
||||
visible: itemVisible
|
||||
highlighted: roomManager.currentRoom && roomManager.currentRoom.name === name
|
||||
highlighted: roomManager.currentRoom && roomManager.currentRoom.displayName === displayName
|
||||
focus: true
|
||||
action: Kirigami.Action {
|
||||
id: enterRoomAction
|
||||
@@ -134,7 +134,7 @@ Kirigami.ScrollablePage {
|
||||
Layout.minimumWidth: size
|
||||
Layout.maximumWidth: size
|
||||
|
||||
source: avatar ? "image://mxc/" + avatar : ""
|
||||
source: avatar ? ("image://mxc/" + avatar) : ""
|
||||
name: model.name || i18n("No Name")
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ Kirigami.ScrollablePage {
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
text: name ?? ""
|
||||
text: displayName ?? ""
|
||||
elide: Text.ElideRight
|
||||
font.bold: unreadCount >= 0 || highlightCount > 0 || notificationCount > 0
|
||||
wrapMode: Text.NoWrap
|
||||
|
||||
@@ -28,30 +28,55 @@ Kirigami.ScrollablePage {
|
||||
signal switchRoomUp()
|
||||
signal switchRoomDown()
|
||||
|
||||
title: currentRoom.name
|
||||
Connections {
|
||||
target: roomManager.actionsHandler
|
||||
onShowMessage: {
|
||||
page.header.contentItem.text = message;
|
||||
page.header.contentItem.type = messageType === ActionsHandler.Error ? Kirigami.MessageType.Error : Kirigami.MessageType.Information;
|
||||
page.header.contentItem.visible = true;
|
||||
}
|
||||
|
||||
onHideMessage: page.header.contentItem.visible = false
|
||||
}
|
||||
|
||||
header: QQC2.Control {
|
||||
height: visible ? implicitHeight : 0
|
||||
visible: contentItem.visible
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
contentItem: Kirigami.InlineMessage {
|
||||
showCloseButton: true
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
|
||||
title: currentRoom.displayName
|
||||
|
||||
titleDelegate: Component {
|
||||
RowLayout {
|
||||
visible: !Kirigami.Settings.isMobile
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: implicitWidth + 1 // The +1 is to make sure we do not trigger eliding at max width
|
||||
Layout.minimumWidth: 0
|
||||
spacing: Kirigami.Units.gridUnit * 0.8
|
||||
Kirigami.Heading {
|
||||
level: 1
|
||||
id: titleLabel
|
||||
level: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: page.title
|
||||
opacity: page.isCurrentPage ? 1 : 0.4
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
text: currentRoom.topic
|
||||
Layout.fillWidth: true
|
||||
maximumLineCount: 1
|
||||
//wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
anchors.baseline: lineCount < 2 ? titleLabel.baseline : undefined // necessary, since there is no way to do this with Layout.alignment
|
||||
text: currentRoom.topic
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.9
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,10 +95,10 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
switchRoomDown();
|
||||
} else if (event.key === Qt.Key_PageUp) {
|
||||
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.modifiers & Qt.ControlModifier) && event.key < Qt.Key_Escape) {
|
||||
event.accepted = true;
|
||||
chatTextInput.addText(event.text);
|
||||
@@ -121,6 +146,14 @@ Kirigami.ScrollablePage {
|
||||
room: currentRoom
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
visible: messageListView.count === 0 && !currentRoom.allHistoryLoaded
|
||||
QQC2.BusyIndicator {
|
||||
running: true
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Popup {
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -187,12 +220,14 @@ Kirigami.ScrollablePage {
|
||||
filterRowCallback: Config.showLeaveJoinEvent ? dontFilterLeaveJoin : filterLeaveJoin
|
||||
|
||||
function dontFilterLeaveJoin(row, parent) {
|
||||
return messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.MessageRole) !== 0x10
|
||||
return messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.SpecialMarksRole) !== EventStatus.Hidden
|
||||
&& messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.MessageRole) !== 0x10
|
||||
&& messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.EventTypeRole) !== "other";
|
||||
}
|
||||
|
||||
function filterLeaveJoin(row, parent) {
|
||||
return messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.MessageRole) !== 0x10
|
||||
return messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.SpecialMarksRole) !== EventStatus.Hidden
|
||||
&& messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.MessageRole) !== 0x10
|
||||
&& messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.EventTypeRole) !== "other"
|
||||
&& messageEventModel.data(messageEventModel.index(row, 0), MessageEventModel.EventTypeRole) !== "state";
|
||||
}
|
||||
@@ -285,19 +320,19 @@ Kirigami.ScrollablePage {
|
||||
innerObject: MessageDelegate {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: messageListView.width
|
||||
mouseArea: MouseArea {
|
||||
acceptedButtons: (Kirigami.Settings.isMobile ? Qt.LeftButton : 0) | Qt.RightButton
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (mouse.button == Qt.RightButton) {
|
||||
openMessageContext(author, display, eventId, toolTip);
|
||||
}
|
||||
}
|
||||
onPressAndHold: openMessageContext(author, display, eventId, toolTip);
|
||||
}
|
||||
onReplyClicked: goToEvent(eventID)
|
||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
||||
innerObject: [
|
||||
MouseArea {
|
||||
acceptedButtons: (Kirigami.Settings.isMobile ? Qt.LeftButton : 0) | Qt.RightButton
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (mouse.button == Qt.RightButton) {
|
||||
openMessageContext(author, display, eventId, toolTip);
|
||||
}
|
||||
}
|
||||
onPressAndHold: openMessageContext(author, display, eventId, toolTip);
|
||||
},
|
||||
TextDelegate {
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
@@ -22,29 +22,44 @@ Kirigami.ScrollablePage {
|
||||
Kirigami.FormData.label: i18n("Notifications and events:")
|
||||
text: i18n("Show notifications")
|
||||
checked: Config.showNotifications
|
||||
onToggled: Config.showNotifications = checked
|
||||
onToggled: {
|
||||
Config.showNotifications = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Show leave and join events")
|
||||
checked: Config.showLeaveJoinEvent
|
||||
onToggled: Config.showLeaveJoinEvent = checked
|
||||
onToggled: {
|
||||
Config.showLeaveJoinEvent = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.RadioButton {
|
||||
Kirigami.FormData.label: i18n("Rooms and private chats:")
|
||||
text: i18n("Separated")
|
||||
checked: !Config.mergeRoomList
|
||||
onToggled: Config.mergeRoomList = false
|
||||
onToggled: {
|
||||
Config.mergeRoomList = false
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.RadioButton {
|
||||
text: i18n("Intermixed")
|
||||
checked: Config.mergeRoomList
|
||||
onToggled: Config.mergeRoomList = true
|
||||
onToggled: {
|
||||
Config.mergeRoomList = true
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
Kirigami.FormData.label: i18n("Timeline:")
|
||||
text: i18n("Show User Avatar")
|
||||
checked: Config.showAvatarInTimeline
|
||||
onToggled: Config.showAvatarInTimeline = checked
|
||||
onToggled: {
|
||||
Config.showAvatarInTimeline = checked
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import QtQuick.Layouts 1.12
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
import NeoChat.Component 1.0
|
||||
import NeoChat.Effect 1.0
|
||||
import NeoChat.Setting 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
@@ -110,8 +110,8 @@ Kirigami.OverlayDrawer {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3.5
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3.5
|
||||
|
||||
name: room ? room.displayName : i18n("No name")
|
||||
source: room ? "image://mxc/" + room.avatarMediaId : undefined
|
||||
name: room ? room.name : i18n("No name")
|
||||
source: room ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
@@ -190,7 +190,7 @@ Kirigami.OverlayDrawer {
|
||||
Layout.preferredWidth: height
|
||||
Layout.fillHeight: true
|
||||
|
||||
source: "image://mxc/" + avatar
|
||||
source: avatar ? ("image://mxc/" + avatar) : ""
|
||||
name: name
|
||||
}
|
||||
|
||||
|
||||
@@ -1,74 +1,95 @@
|
||||
[Global]
|
||||
IconName=neochat
|
||||
IconName=org.kde.neochat
|
||||
Name=Neochat
|
||||
Name[ca]=Neochat
|
||||
Name[ca@valencia]=Neochat
|
||||
Name[da]=Neochat
|
||||
Name[de]=Neochat
|
||||
Name[en_GB]=Neochat
|
||||
Name[es]=Neochat
|
||||
Name[eu]=Neochat
|
||||
Name[fi]=Neochat
|
||||
Name[fr]=Neochat
|
||||
Name[hu]=Neochat
|
||||
Name[it]=Neochat
|
||||
Name[nl]=Neochat
|
||||
Name[nn]=Neochat
|
||||
Name[pl]=Neochat
|
||||
Name[pt]=Neochat
|
||||
Name[pt_BR]=Neochat
|
||||
Name[sk]=Neochat
|
||||
Name[sl]=Neochat
|
||||
Name[sv]=Neochat
|
||||
Name[uk]=Neochat
|
||||
Name[x-test]=xxNeochatxx
|
||||
Name[zh_CN]=Neochat
|
||||
DesktopEntry=org.kde.neochat
|
||||
Comment=IM client for the Matrix protocol
|
||||
Comment[ca]=Client de MI per al protocol Matrix
|
||||
Comment[ca@valencia]=Client de MI per al protocol Matrix
|
||||
Comment[de]=IM-Programm für das Matrix-Protokoll
|
||||
Comment[en_GB]=IM client for the Matrix protocol
|
||||
Comment[es]=Cliente de MI para el protocolo Matrix
|
||||
Comment[eu]=Matrix protokolorako bat-bateko mezularitza bezeroa
|
||||
Comment[fi]=Pikaviestiasiakas Matrix-yhteyskäytännölle
|
||||
Comment[fr]=Client « IM » pour le protocole « Matrix »
|
||||
Comment[hu]=Azonnali üzenetküldő kliens a Matrix protokollhoz
|
||||
Comment[it]=Client di messaggistica istantanea per il protocollo Matrix
|
||||
Comment[nl]=IM-client voor het Matrix-protocol
|
||||
Comment[nn]=Lynmeldingsklient for Matrix-protokollen
|
||||
Comment[pl]=Komunikator internetowy dla protokołu Matrix
|
||||
Comment[pt]=Cliente de MI para o protocolo Matrix
|
||||
Comment[pt_BR]=Cliente de mensageiro instantâneo para o protocolo Matrix
|
||||
Comment[sk]=IM klient pre protokol Matrix
|
||||
Comment[sl]=Odjemalec neposrednega sporočanja po protokolu Matrix
|
||||
Comment[sv]=Direktmeddelandeklient för protokollet Matrix
|
||||
Comment[uk]=Клієнт служби миттєвого обміну повідомленнями для протоколу Matrix
|
||||
Comment[x-test]=xxIM client for the Matrix protocolxx
|
||||
Comment[zh_CN]=为 Matrix 协议打造的 IM 客户端
|
||||
|
||||
[Event/message]
|
||||
Name=New message
|
||||
Name[ca]=Missatge nou
|
||||
Name[ca@valencia]=Missatge nou
|
||||
Name[de]=Neue Nachricht
|
||||
Name[en_GB]=New message
|
||||
Name[es]=Nuevo mensaje
|
||||
Name[eu]=Mezu berria
|
||||
Name[fi]=Uusi viesti
|
||||
Name[fr]=Nouveau message
|
||||
Name[hu]=Új üzenet
|
||||
Name[it]=Nuovo messaggio
|
||||
Name[nl]=Nieuw bericht
|
||||
Name[nn]=Ny melding
|
||||
Name[pl]=Nowa wiadomość
|
||||
Name[pt]=Nova mensagem
|
||||
Name[pt_BR]=Nova mensagem
|
||||
Name[sk]=Nová správa
|
||||
Name[sl]=Novo sporočilo
|
||||
Name[sv]=Nytt meddelande
|
||||
Name[uk]=Нове повідомлення
|
||||
Name[x-test]=xxNew messagexx
|
||||
Name[zh_CN]=新消息
|
||||
Comment=There is a new message
|
||||
Comment[ca]=Hi ha un missatge nou
|
||||
Comment[ca@valencia]=Hi ha un missatge nou
|
||||
Comment[de]=Es ist eine neue Nachricht vorhanden
|
||||
Comment[en_GB]=There is a new message
|
||||
Comment[es]=Hay un mensaje nuevo
|
||||
Comment[eu]=Mezu berri bat dago
|
||||
Comment[fi]=Saapui uusi viesti
|
||||
Comment[fr]=Il y a un nouveau message
|
||||
Comment[hu]=Új üzenet érkezett
|
||||
Comment[it]=È presente un nuovo messaggio
|
||||
Comment[nl]=Er is een nieuw bericht
|
||||
Comment[nn]=Du har ei ny melding
|
||||
Comment[pl]=Dostępna jest nowa wiadomość
|
||||
Comment[pt]=Tem uma mensagem nova
|
||||
Comment[pt_BR]=Existe uma nova mensagem
|
||||
Comment[sk]=Je nová správa
|
||||
Comment[sl]=Prišlo je novo sporočilo
|
||||
Comment[sv]=Det finns ett nytt meddelande
|
||||
Comment[uk]=Надійшло нове повідомлення
|
||||
Comment[x-test]=xxThere is a new messagexx
|
||||
Comment[zh_CN]=有新消息
|
||||
Action=Popup
|
||||
|
||||
@@ -8,15 +8,20 @@
|
||||
<name xml:lang="ca">Neochat</name>
|
||||
<name xml:lang="ca-valencia">Neochat</name>
|
||||
<name xml:lang="da">Neochat</name>
|
||||
<name xml:lang="de">Neochat</name>
|
||||
<name xml:lang="en-GB">Neochat</name>
|
||||
<name xml:lang="es">Neochat</name>
|
||||
<name xml:lang="eu">Neochat</name>
|
||||
<name xml:lang="fi">Neochat</name>
|
||||
<name xml:lang="fr">Neochat</name>
|
||||
<name xml:lang="hu">Neochat</name>
|
||||
<name xml:lang="id">Neochat</name>
|
||||
<name xml:lang="it">Neochat</name>
|
||||
<name xml:lang="nl">Neochat</name>
|
||||
<name xml:lang="nn">Neochat</name>
|
||||
<name xml:lang="pl">Neochat</name>
|
||||
<name xml:lang="pt">Neochat</name>
|
||||
<name xml:lang="pt-BR">Neochat</name>
|
||||
<name xml:lang="sk">Neochat</name>
|
||||
<name xml:lang="sl">Neochat</name>
|
||||
<name xml:lang="sv">Neochat</name>
|
||||
@@ -25,15 +30,21 @@
|
||||
<summary>A client for matrix, the decentralized communication protocol</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="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll</summary>
|
||||
<summary xml:lang="en-GB">A client for matrix, the decentralised communication protocol</summary>
|
||||
<summary xml:lang="es">Un cliente para Matrix, el protocolo de comunicaciones descentralizado</summary>
|
||||
<summary xml:lang="eu">Matrix, deszentralizatutako komunikazio protokolorako bezero bat</summary>
|
||||
<summary xml:lang="fi">Asiakas Matrixille, hajautetulle viestintäyhteyskäytännölle</summary>
|
||||
<summary xml:lang="fr">Un client pour « Matrix », le protocole décentralisé de communications.</summary>
|
||||
<summary xml:lang="hu">Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz</summary>
|
||||
<summary xml:lang="id">Klien untuk matrix, protokol komunikasi terdesentralisasi</summary>
|
||||
<summary xml:lang="it">Un client per matrix, il protocollo di comunicazione decentralizzato</summary>
|
||||
<summary xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol</summary>
|
||||
<summary xml:lang="nn">Ein klient for Matrix, den desentraliserte lynmeldingsprotokollen</summary>
|
||||
<summary xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się</summary>
|
||||
<summary xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado</summary>
|
||||
<summary xml:lang="pt-BR">Um cliente do Matrix, o protocolo de comunicação descentralizado</summary>
|
||||
<summary xml:lang="sk">Klient pre matrix, decentralizovaný komunikačný protokol</summary>
|
||||
<summary xml:lang="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol</summary>
|
||||
<summary xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet</summary>
|
||||
<summary xml:lang="uk">Клієнт matrix, децентралізованого протоколу обміну даними</summary>
|
||||
@@ -42,15 +53,21 @@
|
||||
<p>A client for matrix, the decentralized communication protocol.</p>
|
||||
<p xml:lang="ca">Un client per al Matrix, el protocol de comunicacions descentralitzat.</p>
|
||||
<p xml:lang="ca-valencia">Un client per al Matrix, el protocol de comunicacions descentralitzat.</p>
|
||||
<p xml:lang="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll.</p>
|
||||
<p xml:lang="en-GB">A client for matrix, the decentralised communication protocol.</p>
|
||||
<p xml:lang="es">Un cliente para Matrix, el protocolo de comunicaciones descentralizado.</p>
|
||||
<p xml:lang="eu">Matrix, deszentralizatutako komunikazio protokolorako bezero bat.</p>
|
||||
<p xml:lang="fi">Asiakas Matrixille, hajautetulle viestintäyhteyskäytännölle.</p>
|
||||
<p xml:lang="fr">Un client « Matrix », le protocole décentralisé de communications.</p>
|
||||
<p xml:lang="hu">Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz.</p>
|
||||
<p xml:lang="id">Klien untuk matrix, protokol komunikasi terdesentralisasi.</p>
|
||||
<p xml:lang="it">Un client per matrix, il protocollo di comunicazione decentralizzato.</p>
|
||||
<p xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol.</p>
|
||||
<p xml:lang="nn">Ein klient for Matrix, den desentraliserte lynmeldingsprotokollen.</p>
|
||||
<p xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się.</p>
|
||||
<p xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado.</p>
|
||||
<p xml:lang="pt-BR">Um cliente do Matrix, o protocolo de comunicação descentralizado.</p>
|
||||
<p xml:lang="sk">Klient pre matrix, decentralizovaný komunikačný protokol.</p>
|
||||
<p xml:lang="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol.</p>
|
||||
<p xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet.</p>
|
||||
<p xml:lang="uk">Клієнт matrix, децентралізованого протоколу обміну даними.</p>
|
||||
@@ -59,21 +76,25 @@
|
||||
<url type="homepage">https://kde.org</url>
|
||||
<url type="bugtracker">https://bugs.kde.org</url>
|
||||
<categories>
|
||||
<category>Matrix</category>
|
||||
<category>Internet</category>
|
||||
<category>Network</category>
|
||||
</categories>
|
||||
<developer_name>The KDE Community</developer_name>
|
||||
<developer_name xml:lang="ca">La comunitat KDE</developer_name>
|
||||
<developer_name xml:lang="ca-valencia">La comunitat KDE</developer_name>
|
||||
<developer_name xml:lang="de">Die KDE-Gemeinschaft</developer_name>
|
||||
<developer_name xml:lang="en-GB">The KDE Community</developer_name>
|
||||
<developer_name xml:lang="es">La comunidad KDE</developer_name>
|
||||
<developer_name xml:lang="eu">KDE komunitatea</developer_name>
|
||||
<developer_name xml:lang="fi">KDE-yhteisö</developer_name>
|
||||
<developer_name xml:lang="fr">La communauté de KDE</developer_name>
|
||||
<developer_name xml:lang="hu">A KDE Közösség</developer_name>
|
||||
<developer_name xml:lang="id">Komunitas KDE</developer_name>
|
||||
<developer_name xml:lang="it">La comunità KDE</developer_name>
|
||||
<developer_name xml:lang="nl">De KDE gemeenschap</developer_name>
|
||||
<developer_name xml:lang="nn">KDE-fellesskapet</developer_name>
|
||||
<developer_name xml:lang="pl">Społeczność KDE</developer_name>
|
||||
<developer_name xml:lang="pt">A Comunidade do KDE</developer_name>
|
||||
<developer_name xml:lang="pt-BR">A comunidade KDE</developer_name>
|
||||
<developer_name xml:lang="sk">KDE Komunita</developer_name>
|
||||
<developer_name xml:lang="sl">Skupnost KDE</developer_name>
|
||||
<developer_name xml:lang="sv">KDE-gemenskapen</developer_name>
|
||||
@@ -83,6 +104,9 @@
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/application.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://www.plasma-mobile.org/img/post-2020-10/post-2020-10-neochat-timeline.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
@@ -90,6 +114,25 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="alpha" date="2020-12-04"/>
|
||||
<release version="1.0.1" date="2021-01-13">
|
||||
<description>
|
||||
<p>This version fixes several bugs.</p>
|
||||
<ul>
|
||||
<li>NeoChat doesn't require a .well-know configuration in the server to work.</li>
|
||||
<li>Edited messages won't show up duplicated anymore.</li>
|
||||
<li>Various graphic glitches have been fixed.</li>
|
||||
<li>NeoChat now ask for consent to terms and conditions if required instead of displaying nothing.</li>
|
||||
<li>Users avatar in the room list are now displayed correctly.</li>
|
||||
<li>Fix image saving</li>
|
||||
</ul>
|
||||
</description>
|
||||
<url>https://carlschwan.eu/2020/01/13/neochat-1.0.1/</url>
|
||||
</release>
|
||||
<release version="1.0" date="2020-12-23">
|
||||
<description>
|
||||
<p>Initial release of NeoChat, the KDE matrix client.</p>
|
||||
</description>
|
||||
<url>https://carlschwan.eu/2020/12/23/announcing-neochat-1.0-the-kde-matrix-client/</url>
|
||||
</release>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
@@ -3,55 +3,71 @@ Name=Neochat
|
||||
Name[ca]=Neochat
|
||||
Name[ca@valencia]=Neochat
|
||||
Name[da]=Neochat
|
||||
Name[de]=Neochat
|
||||
Name[en_GB]=Neochat
|
||||
Name[es]=Neochat
|
||||
Name[eu]=Neochat
|
||||
Name[fi]=Neochat
|
||||
Name[fr]=Neochat
|
||||
Name[hu]=Neochat
|
||||
Name[it]=Neochat
|
||||
Name[nl]=Neochat
|
||||
Name[nn]=Neochat
|
||||
Name[pl]=Neochat
|
||||
Name[pt]=Neochat
|
||||
Name[pt_BR]=Neochat
|
||||
Name[sk]=Neochat
|
||||
Name[sl]=Neochat
|
||||
Name[sv]=Neochat
|
||||
Name[uk]=Neochat
|
||||
Name[x-test]=xxNeochatxx
|
||||
Name[zh_CN]=Neochat
|
||||
GenericName=Matrix Client
|
||||
GenericName[ca]=Client del Matrix
|
||||
GenericName[ca@valencia]=Client del Matrix
|
||||
GenericName[de]=Matrix-Programm
|
||||
GenericName[en_GB]=Matrix Client
|
||||
GenericName[es]=Cliente para Matrix
|
||||
GenericName[eu]=Matrix bezeroa
|
||||
GenericName[fi]=Matrix-asiakas
|
||||
GenericName[fr]=Client « Matrix »
|
||||
GenericName[hu]=Matrix kliens
|
||||
GenericName[it]=Client Matrix
|
||||
GenericName[nl]=Matrix-client
|
||||
GenericName[nn]=Matrix-klient
|
||||
GenericName[pl]=Program Matriksa
|
||||
GenericName[pt]=Cliente de Matrix
|
||||
GenericName[pt_BR]=Cliente Matrix
|
||||
GenericName[sk]=Matrix Client
|
||||
GenericName[sl]=Odjemalec Matrix
|
||||
GenericName[sv]=Matrix-klient
|
||||
GenericName[uk]=Клієнт Matrix
|
||||
GenericName[x-test]=xxMatrix Clientxx
|
||||
GenericName[zh_CN]=Matrix 客户端
|
||||
Comment=Client for the Matrix protocol
|
||||
Comment[ca]=Client per al protocol Matrix
|
||||
Comment[ca@valencia]=Client per al protocol Matrix
|
||||
Comment[de]=Programm für das Matrix-Protokoll
|
||||
Comment[en_GB]=Client for the Matrix protocol
|
||||
Comment[es]=Cliente para el protocolo Matrix
|
||||
Comment[eu]=Matrix protokolorako bezeroa
|
||||
Comment[fi]=Asiakas Matrix-yhteyskäytännölle
|
||||
Comment[fr]=Client pour le protocole « Matrix »
|
||||
Comment[hu]=Kliens a Matrix protokollhoz
|
||||
Comment[it]=Client per il protocollo Matrix
|
||||
Comment[nl]=Client voor het Matrix-protocol
|
||||
Comment[nn]=Lynmeldingsklient for Matrix-protokollen
|
||||
Comment[pl]=Program obsługi protokołu Matriksa
|
||||
Comment[pt]=Cliente para o protocolo Matrix
|
||||
Comment[pt_BR]=Cliente para o protocolo Matrix
|
||||
Comment[sk]=Klient protokolu Matrix
|
||||
Comment[sl]=Odjemalec za protokol Matrix
|
||||
Comment[sv]=Klient för protokollet Matrix
|
||||
Comment[uk]=Клієнт протоколу Matrix
|
||||
Comment[x-test]=xxClient for the Matrix protocolxx
|
||||
Comment[zh_CN]=为 Matrix 协议打造的客户端
|
||||
Exec=neochat
|
||||
Terminal=false
|
||||
Icon=neochat
|
||||
Icon=org.kde.neochat
|
||||
Type=Application
|
||||
Categories=Network;InstantMessaging;
|
||||
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
76
qml/main.qml
76
qml/main.qml
@@ -41,6 +41,7 @@ Kirigami.ApplicationWindow {
|
||||
property alias pageStack: root.pageStack
|
||||
property bool invitationOpen: false
|
||||
property var roomList: null
|
||||
property Item roomItem: null
|
||||
|
||||
readonly property bool hasOpenRoom: currentRoom !== null
|
||||
|
||||
@@ -51,8 +52,8 @@ Kirigami.ApplicationWindow {
|
||||
if (Config.openRoom) {
|
||||
const room = Controller.activeConnection.room(Config.openRoom);
|
||||
currentRoom = room;
|
||||
let item = pageStack.push(roomPage, { 'currentRoom': room, });
|
||||
connectRoomToSignal(item);
|
||||
roomItem = pageStack.push(roomPage, { 'currentRoom': room, });
|
||||
connectRoomToSignal(roomItem);
|
||||
} else {
|
||||
// TODO create welcome page
|
||||
}
|
||||
@@ -61,16 +62,16 @@ Kirigami.ApplicationWindow {
|
||||
function enterRoom(room) {
|
||||
let item = null;
|
||||
if (currentRoom != null || invitationOpen) {
|
||||
currentRoom = null;
|
||||
item = pageStack.replace(roomPage, { 'currentRoom': room, });
|
||||
roomItem.currentRoom = room;
|
||||
pageStack.currentIndex = pageStack.depth - 1;
|
||||
} else {
|
||||
item = pageStack.push(roomPage, { 'currentRoom': room, });
|
||||
roomItem = pageStack.push(roomPage, { 'currentRoom': room, });
|
||||
}
|
||||
currentRoom = room;
|
||||
Config.openRoom = room.id;
|
||||
Config.save();
|
||||
connectRoomToSignal(item);
|
||||
return item;
|
||||
connectRoomToSignal(roomItem);
|
||||
return roomItem;
|
||||
}
|
||||
|
||||
function openInvitation(room) {
|
||||
@@ -135,14 +136,13 @@ Kirigami.ApplicationWindow {
|
||||
text: i18n("Explore rooms")
|
||||
icon.name: "compass"
|
||||
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {"connection": Controller.activeConnection})
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Explore Rooms")
|
||||
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})
|
||||
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat")
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Create a Room")
|
||||
@@ -151,14 +151,21 @@ Kirigami.ApplicationWindow {
|
||||
let dialog = createRoomDialog.createObject(root.overlay);
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat")
|
||||
shortcut: StandardKey.New
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Accounts")
|
||||
icon.name: "im-user"
|
||||
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/AccountsPage.qml")
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Accounts")
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Accounts") && Controller.accountCount > 0
|
||||
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Devices")
|
||||
iconName: "network-connect"
|
||||
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/DevicesPage.qml")
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Devices") && Controller.accountCount > 0
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Settings")
|
||||
@@ -172,6 +179,18 @@ Kirigami.ApplicationWindow {
|
||||
icon.name: "help-about"
|
||||
onTriggered: pushReplaceLayer(aboutPage)
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("About")
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Logout")
|
||||
icon.name: "list-remove-user"
|
||||
enabled: Controller.accountCount > 0
|
||||
onTriggered: Controller.logout(Controller.activeConnection, true)
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Quit")
|
||||
icon.name: "gtk-quit"
|
||||
shortcut: StandardKey.Quit
|
||||
onTriggered: Qt.quit()
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -223,7 +242,36 @@ Kirigami.ApplicationWindow {
|
||||
|
||||
onShowWindow: root.showWindow()
|
||||
|
||||
onOpenRoom: roomManager.enterRoom(room)
|
||||
function onOpenRoom(room) {
|
||||
roomManager.enterRoom(room)
|
||||
}
|
||||
|
||||
function onUserConsentRequired(url) {
|
||||
consentSheet.url = url
|
||||
consentSheet.open()
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: consentSheet
|
||||
|
||||
property string url: ""
|
||||
|
||||
header: Kirigami.Heading {
|
||||
text: i18n("User consent")
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
id: label
|
||||
|
||||
text: i18n("Your homeserver requires you to agree to its terms and conditions before being able to use it. Please click the button below to read them.")
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
}
|
||||
footer: QQC2.Button {
|
||||
text: i18n("Open")
|
||||
onClicked: Qt.openUrlExternally(consentSheet.url)
|
||||
}
|
||||
}
|
||||
|
||||
RoomListModel {
|
||||
|
||||
@@ -27,6 +27,10 @@ target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
||||
target_link_libraries(neochat PRIVATE Qt5::Quick Qt5::Qml Qt5::Gui Qt5::Network Qt5::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons Quotient cmark::cmark)
|
||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||
|
||||
if(NEOCHAT_FLATPAK)
|
||||
target_compile_definitions(neochat PRIVATE NEOCHAT_FLATPAK)
|
||||
endif()
|
||||
|
||||
if (KQuickImageEditor_FOUND)
|
||||
target_compile_definitions(neochat PRIVATE HAS_KQUICKIMAGEEDITOR)
|
||||
endif()
|
||||
|
||||
@@ -167,7 +167,8 @@ QVariantMap ChatDocumentHandler::getAutocompletionInfo()
|
||||
};
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::postMessage(const QString &text, const QString &attachementPath, const QString &replyEventId) const
|
||||
void ChatDocumentHandler::postMessage(const QString &text, const QString &attachementPath,
|
||||
const QString &replyEventId, const QVariantMap usernames) const
|
||||
{
|
||||
if (!m_room || !m_document) {
|
||||
return;
|
||||
@@ -177,6 +178,13 @@ void ChatDocumentHandler::postMessage(const QString &text, const QString &attach
|
||||
|
||||
cleanedText = cleanedText.trimmed();
|
||||
|
||||
for (const auto username : usernames.keys()) {
|
||||
const auto replacement = usernames.value(username);
|
||||
cleanedText = cleanedText.replace(username,
|
||||
"[" + username + "](https://matrix.to/#/" + replacement.toString() + ")");
|
||||
}
|
||||
|
||||
|
||||
if (attachementPath.length() > 0) {
|
||||
m_room->uploadFile(attachementPath, cleanedText);
|
||||
}
|
||||
@@ -200,7 +208,7 @@ void ChatDocumentHandler::postMessage(const QString &text, const QString &attach
|
||||
for (int i = 0; i < cleanedText.length(); i++) {
|
||||
rainbowText = rainbowText % QStringLiteral("<font color='") % rainbowColors.at(i % rainbowColors.length()) % "'>" % cleanedText.at(i) % "</font>";
|
||||
}
|
||||
m_room->postHtmlMessage(cleanedText, rainbowText, messageEventType, replyEventId);
|
||||
m_room->postHtmlMessage(text, rainbowText, messageEventType, replyEventId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
[[nodiscard]] NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
Q_INVOKABLE void postMessage(const QString &text, const QString &attachementPath, const QString &replyEventId) const;
|
||||
Q_INVOKABLE void postMessage(const QString &text, const QString &attachementPath, const QString &replyEventId, const QVariantMap usernames) const;
|
||||
|
||||
/// This function will look at the current QTextCursor and determine if there
|
||||
/// is the posibility to autocomplete it.
|
||||
|
||||
@@ -64,7 +64,7 @@ Controller::Controller(QObject *parent)
|
||||
#ifndef Q_OS_ANDROID
|
||||
TrayIcon *trayIcon = new TrayIcon(this);
|
||||
connect(trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
trayIcon->setIconSource("neochat");
|
||||
trayIcon->setIconSource("org.kde.neochat");
|
||||
trayIcon->setIsOnline(true);
|
||||
#endif
|
||||
|
||||
@@ -76,7 +76,6 @@ Controller::Controller(QObject *parent)
|
||||
Controller::~Controller()
|
||||
{
|
||||
for (auto c : qAsConst(m_connections)) {
|
||||
c->stopSync();
|
||||
c->saveState();
|
||||
}
|
||||
}
|
||||
@@ -106,11 +105,11 @@ void Controller::loginWithCredentials(const QString &serverAddr, const QString &
|
||||
}
|
||||
|
||||
auto conn = new Connection(this);
|
||||
const QUrl serverUrl = QUrl(serverAddr);
|
||||
const QUrl serverUrl = QUrl::fromUserInput(serverAddr);
|
||||
// we are using a fake mixd since resolveServer just set the homeserver url :sigh:
|
||||
conn->resolveServer("@username:" + serverUrl.host() + ":" + QString::number(serverUrl.port(443)));
|
||||
|
||||
connect(conn, &Connection::homeserverChanged, this, [this, user, conn, pass, deviceName]() {
|
||||
connect(conn, &Connection::loginFlowsChanged, this, [this, user, conn, pass, deviceName]() {
|
||||
conn->loginWithPassword(user, pass, deviceName, "");
|
||||
connect(conn, &Connection::connected, this, [this, conn, deviceName] {
|
||||
AccountSettings account(conn->userId());
|
||||
@@ -198,7 +197,7 @@ void Controller::logout(Connection *conn, bool serverSideLogout)
|
||||
conn->stopSync();
|
||||
Q_EMIT conn->stateChanged();
|
||||
Q_EMIT conn->loggedOut();
|
||||
if (!m_connections.isEmpty()) {
|
||||
if (conn == activeConnection() && !m_connections.isEmpty()) {
|
||||
setActiveConnection(m_connections[0]);
|
||||
} else {
|
||||
setActiveConnection(nullptr);
|
||||
@@ -232,11 +231,18 @@ void Controller::addConnection(Connection *c)
|
||||
dropConnection(c);
|
||||
});
|
||||
|
||||
connect(c, &Connection::requestFailed, this, [=] (BaseJob *job) {
|
||||
if(job->error() == BaseJob::UserConsentRequiredError) {
|
||||
Q_EMIT userConsentRequired(job->errorUrl());
|
||||
}
|
||||
});
|
||||
|
||||
setBusy(true);
|
||||
|
||||
c->sync();
|
||||
|
||||
Q_EMIT connectionAdded(c);
|
||||
Q_EMIT accountCountChanged();
|
||||
}
|
||||
|
||||
void Controller::dropConnection(Connection *c)
|
||||
@@ -245,50 +251,50 @@ void Controller::dropConnection(Connection *c)
|
||||
m_connections.removeOne(c);
|
||||
|
||||
Q_EMIT connectionDropped(c);
|
||||
Q_EMIT accountCountChanged();
|
||||
c->deleteLater();
|
||||
}
|
||||
|
||||
void Controller::invokeLogin()
|
||||
{
|
||||
const auto accounts = SettingsGroup("Accounts").childGroups();
|
||||
QString id = NeoChatConfig::self()->activeConnection();
|
||||
for (const auto &accountId : accounts) {
|
||||
AccountSettings account {accountId};
|
||||
if (id.isEmpty()) {
|
||||
// handle case where the account config is empty
|
||||
id = accountId;
|
||||
}
|
||||
if (!account.homeserver().isEmpty()) {
|
||||
auto accessToken = loadAccessTokenFromKeyChain(account);
|
||||
|
||||
auto c = new Connection(account.homeserver(), this);
|
||||
connect(c, &Connection::connected, this, [=] {
|
||||
c->loadState();
|
||||
addConnection(c);
|
||||
});
|
||||
connect(c, &Connection::loginError, this, [=](const QString &error, const QString &) {
|
||||
if (error == "Unrecognised access token") {
|
||||
Q_EMIT errorOccured(i18n("Login Failed"), i18n("Access Token invalid or revoked"));
|
||||
logout(c, false);
|
||||
} else {
|
||||
Q_EMIT errorOccured(i18n("Login Failed"), error);
|
||||
logout(c, true);
|
||||
auto connection = new Connection(account.homeserver(), this);
|
||||
connect(connection, &Connection::connected, this, [=] {
|
||||
connection->loadState();
|
||||
addConnection(connection);
|
||||
if (connection->userId() == id) {
|
||||
setActiveConnection(connection);
|
||||
Q_EMIT initiated();
|
||||
}
|
||||
});
|
||||
connect(c, &Connection::networkError, this, [=](const QString &error, const QString &, int, int) {
|
||||
connect(connection, &Connection::loginError, this, [=](const QString &error, const QString &) {
|
||||
if (error == "Unrecognised access token") {
|
||||
Q_EMIT errorOccured(i18n("Login Failed"), i18n("Access Token invalid or revoked"));
|
||||
logout(connection, false);
|
||||
} else {
|
||||
Q_EMIT errorOccured(i18n("Login Failed"), error);
|
||||
logout(connection, true);
|
||||
}
|
||||
});
|
||||
connect(connection, &Connection::networkError, this, [=](const QString &error, const QString &, int, int) {
|
||||
Q_EMIT errorOccured("Network Error", error);
|
||||
});
|
||||
c->connectWithToken(account.userId(), accessToken, account.deviceId());
|
||||
connection->connectWithToken(account.userId(), accessToken, account.deviceId());
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_connections.isEmpty()) {
|
||||
const QString id = NeoChatConfig::self()->activeConnection();
|
||||
for (auto *connection : qAsConst(m_connections)) {
|
||||
if (connection->userId() == id) {
|
||||
setActiveConnection(connection);
|
||||
Q_EMIT initiated();
|
||||
return;
|
||||
}
|
||||
}
|
||||
setActiveConnection(m_connections[0]);
|
||||
if (m_connections.isEmpty()) {
|
||||
Q_EMIT initiated();
|
||||
}
|
||||
Q_EMIT initiated();
|
||||
}
|
||||
|
||||
QByteArray Controller::loadAccessTokenFromFile(const AccountSettings &account)
|
||||
|
||||
@@ -27,7 +27,7 @@ using namespace Quotient;
|
||||
class Controller : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int accountCount READ accountCount NOTIFY connectionAdded NOTIFY connectionDropped)
|
||||
Q_PROPERTY(int accountCount READ accountCount NOTIFY accountCountChanged)
|
||||
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE setQuitOnLastWindowClosed NOTIFY quitOnLastWindowClosedChanged)
|
||||
Q_PROPERTY(Connection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
|
||||
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
|
||||
@@ -103,6 +103,7 @@ Q_SIGNALS:
|
||||
void syncDone();
|
||||
void connectionAdded(Quotient::Connection *_t1);
|
||||
void connectionDropped(Quotient::Connection *_t1);
|
||||
void accountCountChanged();
|
||||
void initiated();
|
||||
void notificationClicked(const QString &_t1, const QString &_t2);
|
||||
void quitOnLastWindowClosedChanged();
|
||||
@@ -112,6 +113,7 @@ Q_SIGNALS:
|
||||
void passwordStatus(Controller::PasswordStatus _t1);
|
||||
void showWindow();
|
||||
void openRoom(NeoChatRoom *room);
|
||||
void userConsentRequired(QUrl url);
|
||||
|
||||
public Q_SLOTS:
|
||||
void logout(Quotient::Connection *conn, bool serverSideLogout);
|
||||
|
||||
12
src/main.cpp
12
src/main.cpp
@@ -58,6 +58,10 @@ int main(int argc, char *argv[])
|
||||
QQuickStyle::setStyle(QStringLiteral("Material"));
|
||||
#else
|
||||
QApplication app(argc, argv);
|
||||
// Default to org.kde.desktop style unless the user forces another style
|
||||
if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) {
|
||||
QQuickStyle::setStyle(QStringLiteral("org.kde.desktop"));
|
||||
}
|
||||
#endif
|
||||
|
||||
QApplication::setOrganizationName("KDE");
|
||||
@@ -69,11 +73,19 @@ int main(int argc, char *argv[])
|
||||
about.setOrganizationDomain("kde.org");
|
||||
|
||||
KAboutData::setApplicationData(about);
|
||||
QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("org.kde.neochat")));
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
KDBusService service(KDBusService::Unique);
|
||||
#endif
|
||||
|
||||
#ifdef NEOCHAT_FLATPAK
|
||||
// Copy over the included FontConfig configuration to the
|
||||
// app's config dir:
|
||||
QFile::copy("/app/etc/fonts/conf.d/99-noto-mono-color-emoji.conf",
|
||||
"/var/config/fontconfig/conf.d/99-noto-mono-color-emoji.conf");
|
||||
#endif
|
||||
|
||||
Clipboard clipboard;
|
||||
auto config = NeoChatConfig::self();
|
||||
|
||||
|
||||
@@ -54,6 +54,9 @@ ThumbnailResponse::ThumbnailResponse(QString id, QSize size)
|
||||
|
||||
void ThumbnailResponse::startRequest()
|
||||
{
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
return;
|
||||
}
|
||||
// Runs in the main thread, not QML thread
|
||||
Q_ASSERT(QThread::currentThread() == Controller::instance().activeConnection()->thread());
|
||||
job = Controller::instance().activeConnection()->getThumbnail(mediaId, requestedSize);
|
||||
@@ -94,6 +97,9 @@ void ThumbnailResponse::prepareResult()
|
||||
|
||||
void ThumbnailResponse::doCancel()
|
||||
{
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
return;
|
||||
}
|
||||
// Runs in the main thread, not QML thread
|
||||
if (job) {
|
||||
Q_ASSERT(QThread::currentThread() == job->thread());
|
||||
|
||||
@@ -44,6 +44,7 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
roles[ShowAuthorRole] = "showAuthor";
|
||||
roles[ShowSectionRole] = "showSection";
|
||||
roles[ReactionRole] = "reaction";
|
||||
roles[IsEditedRole] = "isEdited";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -89,6 +90,9 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
m_currentRoom = room;
|
||||
if (room) {
|
||||
room->setDisplayed();
|
||||
if (m_currentRoom->timelineSize() < 10) {
|
||||
room->getPreviousContent(50);
|
||||
}
|
||||
lastReadEventId = room->readMarkerEventId();
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -301,6 +305,7 @@ inline QVariantMap userAtEvent(NeoChatUser *user, NeoChatRoom *room, const RoomE
|
||||
{"avatarMediaId", user->avatarMediaId(room)},
|
||||
{"avatarUrl", user->avatarUrl(room)},
|
||||
{"displayName", user->displayname(room)},
|
||||
{"display", user->name()},
|
||||
{"color", user->color()},
|
||||
{"object", QVariant::fromValue(user)},
|
||||
};
|
||||
@@ -415,6 +420,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
}
|
||||
|
||||
// isReplacement?
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt))
|
||||
if (!e->replacedEvent().isEmpty())
|
||||
return EventStatus::Hidden;
|
||||
|
||||
if (is<RedactionEvent>(evt) || is<ReactionEvent>(evt)) {
|
||||
return EventStatus::Hidden;
|
||||
}
|
||||
@@ -439,6 +449,13 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return EventStatus::Normal;
|
||||
}
|
||||
|
||||
if (role == IsEditedRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
return !e->unsignedJson().isEmpty() && e->unsignedJson().contains("m.relations") && e->unsignedJson()["m.relations"].toObject().contains("m.replace");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (role == EventIdRole) {
|
||||
return !evt.id().isEmpty() ? evt.id() : evt.transactionId();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ public:
|
||||
ShowSectionRole,
|
||||
|
||||
ReactionRole,
|
||||
IsEditedRole,
|
||||
|
||||
// For debugging
|
||||
EventResolvedTypeRole,
|
||||
|
||||
@@ -161,7 +161,7 @@ const RoomMessageEvent *NeoChatRoom::lastEvent(bool ignoreStateEvent) const
|
||||
QString NeoChatRoom::lastEventToString() const
|
||||
{
|
||||
if (auto event = lastEvent()) {
|
||||
return user(event->senderId())->displayname() + (event->isStateEvent() ? " " : ": ") + eventToString(*event);
|
||||
return user(event->senderId())->displayname(this) + (event->isStateEvent() ? " " : ": ") + eventToString(*event);
|
||||
}
|
||||
return QLatin1String("");
|
||||
}
|
||||
@@ -283,7 +283,7 @@ QString NeoChatRoom::avatarMediaId() const
|
||||
const auto dcUsers = directChatUsers();
|
||||
for (const auto u : dcUsers) {
|
||||
if (u != localUser()) {
|
||||
return u->avatarMediaId();
|
||||
return u->avatarMediaId(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,6 +467,8 @@ QString NeoChatRoom::markdownToHTML(const QString &markdown)
|
||||
|
||||
auto result = QString::fromStdString(html).trimmed();
|
||||
|
||||
result.replace("<!-- raw HTML omitted -->", "<br />");
|
||||
result.replace(QRegularExpression("(<br />)*$"), "");
|
||||
result.replace("<p>", "");
|
||||
result.replace("</p>", "");
|
||||
|
||||
|
||||
@@ -256,6 +256,9 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
NeoChatRoom *room = m_rooms.at(index.row());
|
||||
if (role == NameRole) {
|
||||
return !room->name().isEmpty() ? room->name() : room->displayName();
|
||||
}
|
||||
if (role == DisplayNameRole) {
|
||||
return room->displayName();
|
||||
}
|
||||
if (role == AvatarRole) {
|
||||
@@ -324,6 +327,7 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[NameRole] = "name";
|
||||
roles[DisplayNameRole] = "displayName";
|
||||
roles[AvatarRole] = "avatar";
|
||||
roles[TopicRole] = "topic";
|
||||
roles[CategoryRole] = "category";
|
||||
@@ -352,7 +356,7 @@ QString RoomListModel::categoryName(int section)
|
||||
case 5:
|
||||
return i18n("Low priority");
|
||||
default:
|
||||
return i18n("Deadbeef");
|
||||
return "Deadbeef";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ class RoomListModel : public QAbstractListModel
|
||||
public:
|
||||
enum EventRoles {
|
||||
NameRole = Qt::UserRole + 1,
|
||||
DisplayNameRole,
|
||||
AvatarRole,
|
||||
TopicRole,
|
||||
CategoryRole,
|
||||
|
||||
Reference in New Issue
Block a user