Compare commits

...

56 Commits

Author SHA1 Message Date
Carl Schwan
bc977c3fc6 Update apstream screenshots 2021-05-31 20:26:07 +02:00
Carl Schwan
c4c283c85a Update versioning to 1.2.0 2021-05-31 17:25:35 +02:00
Carl Schwan
56f49fabf7 Don't mark message as read when the current window is not visible
Fix #378

(cherry picked from commit 87d1fefae2)
2021-05-31 17:22:09 +02:00
Carl Schwan
fe407a3421 Mark all message as read when clicking on down button
Fix #379

(cherry picked from commit 6e5bca4928)
2021-05-31 17:22:03 +02:00
l10n daemon script
bbe539885e GIT_SILENT made messages (after extraction)
(cherry picked from commit 4d236a201b)
2021-05-31 16:58:08 +02:00
Carl Schwan
ff978b9586 Update Appstream 2021-05-31 16:57:50 +02:00
Noah Davis
885b75e35f Move TypingIndicator to the right side
So that it's less likely to cover message text.
2021-05-31 16:54:41 +02:00
Srevin Saju
cb81eaf26f feat: show the username and avatar again on date-change
when the clock hits 00:00 in the user's time zone, but in the
case of a continuous discussion, it is likely that "Today"
date-change marker would obstruct the conversation, and the
username and avatar would be missing.


(cherry picked from commit 3e78bff8a1)
2021-05-29 22:16:42 +00:00
Carl Schwan
22732b801b Don't steal focus in panel search field
(cherry picked from commit 807112fb19)
2021-05-30 00:06:58 +02:00
Carl Schwan
ae3e395b47 Reinitialize completion list after switching room
(cherry picked from commit e15e10d319)
2021-05-29 23:55:58 +02:00
Carl Schwan
96c91e2a35 Make sure we only add non empty name or display name in autocompletion
list

Otherwise this will breaks when replacing names later

(cherry picked from commit c7fd5cc511)
2021-05-29 23:15:26 +02:00
Carl Schwan
e36204bbd8 Open room when pressing Enter or Return
Fix #381

(cherry picked from commit b37152ff89)
2021-05-29 20:16:45 +02:00
Arnav Rawat
1804140ac0 Restore I-Beam cursor on hover
Should not cause issues with themes


(cherry picked from commit df0ad391ba)
2021-05-28 13:35:05 +00:00
Carl Schwan
5a28a93ab6 Fix reverse tabbing not working in autocompletion
Now call autocomplete() also for shift+tab

Fix #377

(cherry picked from commit 3329739d55)
2021-05-28 14:57:35 +02:00
Carl Schwan
76bd529c3c Fix size of replies in mesage delegate 2021-05-28 14:53:43 +02:00
Carl Schwan
293288a0b6 Fix completion when tabing users
This was caused by weird bindings. Now just access the userId value
directly.

Fix #324

(cherry picked from commit d9125148fe)
2021-05-28 14:27:44 +02:00
Carl Schwan
51b6593f96 Better read market handling: Mark room as read when scrolling to the
bottom

Fix #372

(cherry picked from commit db0f421811)
2021-05-28 14:07:19 +02:00
Carl Schwan
51e73568c4 Fix date being show too often
(cherry picked from commit 3d251b9b25)
2021-05-28 14:07:19 +02:00
Carl Schwan
e461e2098b Don't use SystemTray integration on GNOME and ElementaryOS
These platforms don't support it so hiding NeoChat in the tray in these
platforms is not a good idea and other a rather poor user experience.

(cherry picked from commit 13888401fa)
2021-05-28 14:07:19 +02:00
Hannah von Reth
79ceb45fae Fix Windows builds
(cherry picked from commit 92fcff1dce)
2021-05-28 14:07:19 +02:00
Nicolas Fella
0c292b34ff Remove minSdk version from AndroidManifest
(cherry picked from commit 41838d01df)
2021-05-28 14:07:19 +02:00
Nicolas Fella
db6640ba49 Fix Android ifdef
(cherry picked from commit af75cebba1)
2021-05-28 14:07:19 +02:00
Nicolas Fella
3827249f0c remove spurious QFileDialog include
(cherry picked from commit 1cec672c0a)
2021-05-28 14:07:19 +02:00
Nicolas Fella
f40a3daef4 remove spurious QMenu include
(cherry picked from commit bd5f6c9c9e)
2021-05-28 14:07:19 +02:00
Nicolas Fella
8d2608a230 Use QGuiApplication instead of QApplication where appropriate
(cherry picked from commit 6e04d343b7)
2021-05-28 14:07:19 +02:00
Nicolas Fella
3e5628def3 Don't find Widgets on Android
(cherry picked from commit 454e35433b)
2021-05-28 12:09:01 +00:00
Tobias Fella
3b3673fdff Fix multiple headers for the same sections
(cherry picked from commit 4dea02197c)
2021-05-28 12:08:32 +00:00
Adriaan de Groot
d81e4c417d CMake: various tidying-up
(cherry picked from commit 294f0c7e1a)
2021-05-28 12:07:48 +00:00
Carl Schwan
44d3f628d9 Fix username autocompletion 2021-05-28 14:02:03 +02:00
Carl Schwan
0db9c0454f pushReplace more
(cherry picked from commit 7cd9f788dd)
2021-05-28 12:05:59 +00:00
Noah Davis
e5c65a662e Use 3 dot typing indicator, clean up code a bit.
Move TypingIndicator.qml out of ChatBox folder.
It wasn't part of the ChatBox.

fixes #367 by eliding instead of wrapping text


(cherry picked from commit bbcf4239a4)
2021-05-27 14:18:05 +00:00
l10n daemon script
8913aa8a66 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"
2021-05-26 02:28:13 +00:00
l10n daemon script
5db3e14ae6 GIT_SILENT made messages (after extraction) 2021-05-26 01:52:33 +00:00
l10n daemon script
c5a3fc0431 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"
2021-05-25 02:21:15 +00:00
l10n daemon script
fc791d41fa GIT_SILENT made messages (after extraction) 2021-05-25 01:47:45 +00:00
Carl Schwan
127ad19109 Fix minor bugs
(cherry picked from commit d14674c2cd)
2021-05-24 14:55:28 +00:00
Carl Schwan
066ea4f8bd Make sure message are loaded when scrolling to the top
(cherry picked from commit 49c1736f7c)
2021-05-24 14:53:11 +00:00
Carl Schwan
cf60337b27 Make effects more visible
(cherry picked from commit db62f06de4)
2021-05-24 14:52:55 +00:00
l10n daemon script
98672cf870 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"
2021-05-24 02:13:42 +00:00
l10n daemon script
abd03299ec GIT_SILENT made messages (after extraction) 2021-05-24 01:39:11 +00:00
Carl Schwan
41b64f977c Fix creating broken direct chat for user with a direct chat already open
Just enter the existing room instead of trying to create a new one but
broken.

Fix !237


(cherry picked from commit 0dbb56ba1e)
2021-05-23 19:51:30 +00:00
Carl Schwan
ac75dd57c0 Minor optimization
(cherry picked from commit 7bdfdc0eec)
2021-05-23 16:33:13 +00:00
Carl Schwan
1d3d61ed77 Fix loading events when scrolling or opening a room for the first time
Fix #362


(cherry picked from commit bae7813f68)
2021-05-23 16:31:41 +00:00
Carl Schwan
1e047a8ff1 Fix mode without avatar
It seems that this mode didn't get much love when I added the bubbles so
it was quite broken. This patches removes the bubbles and fix the
alignment issues when using this mode.

We probably should rename it to compact mode in a follow up commit (but
not this one so we can backport it to the stable branch).


(cherry picked from commit dded804f00)
2021-05-23 16:30:55 +00:00
l10n daemon script
530b4c24a0 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"
2021-05-23 02:12:00 +00:00
l10n daemon script
ada7bcef65 GIT_SILENT made messages (after extraction) 2021-05-23 01:38:51 +00:00
Carl Schwan
ad4ca3ad9e Fix i18n
(cherry picked from commit dbb43addc8)
2021-05-22 16:24:24 +00:00
Tobias Fella
4103c44eb5 Don't hide redacted events
(cherry picked from commit 135b2e49fa)
2021-05-22 12:18:07 +00:00
Tobias Fella
dd4ed7539e Revert "Fix showing multiple deleted messages from the same author"
This reverts commit b48c9bdadc.


(cherry picked from commit 011f649cbf)
2021-05-22 12:17:45 +00:00
Tobias Fella
52ad911b2d Revert "Show deleted messages"
This reverts commit 116f883699.


(cherry picked from commit 36a2f5719f)
2021-05-22 12:17:20 +00:00
Tobias Fella
f09dff979e Fix banning users
(cherry picked from commit af6880b2ca)
2021-05-22 12:09:43 +00:00
Tobias Fella
0476398f91 Don't offer banning users that are already banned
(cherry picked from commit 48d1fa27cf)
2021-05-22 12:09:20 +00:00
Tobias Fella
41993bfe24 Don't offer to kick users that already left
(cherry picked from commit bd893adb34)
2021-05-21 22:42:45 +00:00
Tobias Fella
b3d90ebf82 Fix showing multiple deleted messages from the same author
(cherry picked from commit b48c9bdadc)
2021-05-21 22:42:15 +00:00
Tobias Fella
ef0a6e276c Show deleted messages
We used to show those, then a bug was fixed in the code that was
supposed to hide them.


(cherry picked from commit 116f883699)
2021-05-21 22:41:46 +00:00
Tobias Fella
a104968a29 Prioritize "low priority" over "direct chat" in roomList categories
There's no exact right or wrong here, but if a room was explicitly
marked as "low priority", we should honor this tag.

Fixes a slight inconsistency with the implementation in Element

Fixes #357


(cherry picked from commit 3ea783b370)
2021-05-21 19:12:00 +00:00
32 changed files with 380 additions and 264 deletions

View File

@@ -34,12 +34,12 @@ endif()
# Fix a crash due to problems with quotient's event system. Can probably be removed once the reworked event system is in # 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) cmake_policy(SET CMP0063 OLD)
ecm_setup_version(1.1.80 ecm_setup_version(1.2.0
VARIABLE_PREFIX NEOCHAT VARIABLE_PREFIX NEOCHAT
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
) )
find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Widgets Core Quick Gui QuickControls2 Multimedia Svg) find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
set_package_properties(Qt5 PROPERTIES set_package_properties(Qt5 PROPERTIES
TYPE REQUIRED TYPE REQUIRED
PURPOSE "Basic application components" PURPOSE "Basic application components"
@@ -67,6 +67,7 @@ if(ANDROID)
PURPOSE "Encrypted communications" PURPOSE "Encrypted communications"
) )
else() else()
find_package(Qt5 ${QT_MIN_VERSION} COMPONENTS Widgets)
find_package(KF5QQC2DesktopStyle ${KF5_MIN_VERSION} REQUIRED) find_package(KF5QQC2DesktopStyle ${KF5_MIN_VERSION} REQUIRED)
set_package_properties(KF5QQC2DesktopStyle PROPERTIES set_package_properties(KF5QQC2DesktopStyle PROPERTIES
TYPE RUNTIME TYPE RUNTIME

View File

@@ -53,7 +53,6 @@
</activity> </activity>
</application> </application>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/> <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

View File

@@ -7,14 +7,16 @@
# first try to find cmark-config.cmake # first try to find cmark-config.cmake
# path to a file not in the search path can be set with 'cmake -Dcmark_DIR=some/path/' # path to a file not in the search path can be set with 'cmake -Dcmark_DIR=some/path/'
find_package(cmark CONFIG) find_package(cmark CONFIG QUIET)
if(cmark_FOUND AND TARGET cmark::cmark) if(cmark_FOUND AND TARGET cmark::cmark)
# found it! # found it!
return() return()
endif() endif()
include(FindPkgConfig) find_package(PkgConfig QUIET)
pkg_check_modules(PC_CMARK QUIET cmark) if(PKG_CONFIG_FOUND)
pkg_check_modules(PC_CMARK QUIET cmark)
endif()
if(NOT CMARK_INCLUDE_DIR) if(NOT CMARK_INCLUDE_DIR)
find_path(CMARK_INCLUDE_DIR find_path(CMARK_INCLUDE_DIR

View File

@@ -76,7 +76,11 @@ ToolBar {
* background colors being very different from the QPalette::Base color. * background colors being very different from the QPalette::Base color.
* Luckily, none of the Qt QQC2 styles do that and neither do KDE's QQC2 styles. * Luckily, none of the Qt QQC2 styles do that and neither do KDE's QQC2 styles.
*/ */
background: null background: MouseArea {
acceptedButtons: Qt.NoButton
cursorShape: Qt.IBeamCursor
z: 1
}
leftPadding: mirrored ? 0 : Kirigami.Units.largeSpacing leftPadding: mirrored ? 0 : Kirigami.Units.largeSpacing
rightPadding: !mirrored ? 0 : Kirigami.Units.largeSpacing rightPadding: !mirrored ? 0 : Kirigami.Units.largeSpacing
topPadding: 0 topPadding: 0
@@ -165,12 +169,18 @@ ToolBar {
nextItemInFocusChain(false).forceActiveFocus(Qt.TabFocusReason) nextItemInFocusChain(false).forceActiveFocus(Qt.TabFocusReason)
return return
} }
let decrementedIndex = completionMenu.currentIndex - 1 if (!autoAppeared) {
// Wrap around to the last item let decrementedIndex = completionMenu.currentIndex - 1
if (decrementedIndex < 0) { // Wrap around to the last item
decrementedIndex = Math.max(completionMenu.count - 1, 0) // 0 if count == 0 if (decrementedIndex < 0) {
decrementedIndex = Math.max(completionMenu.count - 1, 0) // 0 if count == 0
}
completionMenu.currentIndex = decrementedIndex
} else {
autoAppeared = false;
} }
completionMenu.currentIndex = decrementedIndex
chatBar.complete();
} }
Keys.onTabPressed: { Keys.onTabPressed: {
@@ -187,7 +197,7 @@ ToolBar {
// ignore first time tab was clicked so that user can select // ignore first time tab was clicked so that user can select
// first emoji/user // first emoji/user
if (autoAppeared === false) { if (!autoAppeared) {
let incrementedIndex = completionMenu.currentIndex + 1; let incrementedIndex = completionMenu.currentIndex + 1;
// Wrap around to the first item // Wrap around to the first item
if (incrementedIndex > completionMenu.count - 1) { if (incrementedIndex > completionMenu.count - 1) {
@@ -370,8 +380,10 @@ ToolBar {
function complete() { function complete() {
documentHandler.replaceAutoComplete(completionMenu.currentDisplayText); documentHandler.replaceAutoComplete(completionMenu.currentDisplayText);
if (completionMenu.completionType === "username") { if (completionMenu.completionType === ChatDocumentHandler.User
userAutocompleted[completionMenu.currentDisplayText] = completionMenu.currentUserId; && completionMenu.currentDisplayText.length > 0
&& completionMenu.currentItem.userId.length > 0) {
userAutocompleted[completionMenu.currentDisplayText] = completionMenu.currentItem.userId;
} }
} }
} }

View File

@@ -227,6 +227,14 @@ Item {
chatBar.inputFieldForceActiveFocusTriggered() chatBar.inputFieldForceActiveFocusTriggered()
} }
Connections {
target: RoomManager
function onCurrentRoomChanged() {
chatBar.userAutocompleted = {};
}
}
Connections { Connections {
target: ChatBoxHelper target: ChatBoxHelper

View File

@@ -22,8 +22,7 @@ Popup {
property alias delegate: completionListView.delegate property alias delegate: completionListView.delegate
// Autocomplee text // Autocomplee text
property string currentDisplayText: currentItem && currentItem.displayName ? currentItem.displayName : "" property string currentDisplayText: currentItem && (currentItem.displayName ?? "")
property string currentUserId: currentItem && currentItem.id ? currentItem.id : ""
property int completionType: ChatDocumentHandler.Emoji property int completionType: ChatDocumentHandler.Emoji
property int beginPosition: 0 property int beginPosition: 0
@@ -78,6 +77,7 @@ Popup {
id: usernameItem id: usernameItem
width: ListView.view.width ?? implicitWidth width: ListView.view.width ?? implicitWidth
property string displayName: modelData.displayName property string displayName: modelData.displayName
property string userId: modelData.id
leading: Kirigami.Avatar { leading: Kirigami.Avatar {
implicitHeight: Kirigami.Units.gridUnit implicitHeight: Kirigami.Units.gridUnit
implicitWidth: implicitHeight implicitWidth: implicitHeight
@@ -86,11 +86,6 @@ Popup {
} }
text: modelData.displayName text: modelData.displayName
onClicked: completeTriggered(); onClicked: completeTriggered();
Component.onCompleted: {
completionMenu.currentUserId = Qt.binding(() => {
return modelData.id ?? "";
});
}
} }
} }

View File

@@ -1,66 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
* SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
* SPDX-FileCopyrightText: 2021 Srevin Saju <srevinsaju@sugarlabs.org>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import org.kde.kirigami 2.14 as Kirigami
import org.kde.neochat 1.0
Loader {
id: root
property var typingNotification: null
active: visible
sourceComponent: Pane {
id: typingPane
padding: fontMetrics.lineSpacing * 0.25
spacing: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
contentItem: RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
spacing: 0
FontMetrics {
id: fontMetrics
font: typingLabel.font
}
Label {
id: typingLabel
textFormat: TextEdit.RichText
wrapMode: Label.Wrap
text: typingNotification
}
BusyIndicator {
running: root.active
Layout.alignment: Qt.AlignRight
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
}
}
background: Item {
Rectangle {
height: 1
property color borderColor: Kirigami.Theme.textColor
color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.1)
anchors {
left: typingIndicatorBackground.left
right: typingIndicatorBackground.right
bottom: typingIndicatorBackground.top
}
}
Rectangle {
anchors.fill: parent
id: typingIndicatorBackground
color: Kirigami.Theme.backgroundColor
}
}
}
}

View File

@@ -5,4 +5,3 @@ ReplyPane 1.0 ReplyPane.qml
AttachmentPane 1.0 AttachmentPane.qml AttachmentPane 1.0 AttachmentPane.qml
CompletionMenu 1.0 CompletionMenu.qml CompletionMenu 1.0 CompletionMenu.qml
EmojiPickerPane 1.0 EmojiPickerPane.qml EmojiPickerPane 1.0 EmojiPickerPane.qml
TypingPane 1.0 TypingPane.qml

View File

@@ -8,7 +8,7 @@ import QtQuick.Particles 2.15
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
Rectangle { Item {
id: item id: item
property bool enabled: false property bool enabled: false
property int effectInterval: Kirigami.Units.veryLongDuration*10; property int effectInterval: Kirigami.Units.veryLongDuration*10;
@@ -27,11 +27,6 @@ Rectangle {
fireworksTimer.start() fireworksTimer.start()
} }
// backgroundColor
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Kirigami.Theme.inherit: false
// Confetti // Confetti
Timer { Timer {

View File

@@ -15,7 +15,7 @@ LoginStep {
readonly property var homeserver: customHomeserver.visible ? customHomeserver.text : serverCombo.currentText readonly property var homeserver: customHomeserver.visible ? customHomeserver.text : serverCombo.currentText
property bool loading: false property bool loading: false
title: i18n("@title", "Select a Homeserver") title: i18nc("@title", "Select a Homeserver")
action: Kirigami.Action { action: Kirigami.Action {
enabled: LoginHelper.homeserverReachable && !customHomeserver.visible || customHomeserver.acceptableInput enabled: LoginHelper.homeserverReachable && !customHomeserver.visible || customHomeserver.acceptableInput

View File

@@ -15,7 +15,7 @@ MouseArea {
id: replyButton id: replyButton
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: replyName.implicitHeight + (loader.item ? loader.item.height : 0) + Kirigami.Units.largeSpacing implicitHeight: replyName.implicitHeight + (loader.item ? loader.item.height : 0) + Kirigami.Units.largeSpacing
implicitWidth: Math.min(bubbleMaxWidth, Math.max((loader.item ? loader.item.width : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3 implicitWidth: Math.min(bubbleMaxWidth, Math.max((loader.item ? loader.item.width + Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
Component.onCompleted: { Component.onCompleted: {
parent.Layout.fillWidth = true; parent.Layout.fillWidth = true;
parent.Layout.preferredWidth = Qt.binding(function() { return implicitWidth; }) parent.Layout.preferredWidth = Qt.binding(function() { return implicitWidth; })
@@ -32,7 +32,7 @@ MouseArea {
id: avatatReply id: avatatReply
anchors.left: replyLeftBorder.right anchors.left: replyLeftBorder.right
anchors.leftMargin: Kirigami.Units.smallSpacing anchors.leftMargin: Kirigami.Units.smallSpacing
width: Kirigami.Units.gridUnit width: visible ? Kirigami.Units.gridUnit : 0
height: Kirigami.Units.gridUnit height: Kirigami.Units.gridUnit
sourceSize.width: width sourceSize.width: width
sourceSize.height: height sourceSize.height: height
@@ -79,7 +79,7 @@ MouseArea {
textMessage: reply.display textMessage: reply.display
textFormat: Text.RichText textFormat: Text.RichText
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: Math.min(implicitWidth, bubbleMaxWidth) - Kirigami.Units.smallSpacing * 5 - avatatReply.width width: Math.min(implicitWidth, bubbleMaxWidth - Kirigami.Units.largeSpacing * 3)
x: Kirigami.Units.smallSpacing * 3 + avatatReply.width x: Kirigami.Units.smallSpacing * 3 + avatatReply.width
} }
} }

View File

@@ -21,7 +21,7 @@ QQC2.ItemDelegate {
property bool isEmote: false property bool isEmote: false
property bool cardBackground: true property bool cardBackground: true
readonly property int bubbleMaxWidth: Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 4, Kirigami.Units.gridUnit * 20) readonly property int bubbleMaxWidth: !Config.showAvatarInTimeline ? width : Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 4, Kirigami.Units.gridUnit * 20)
signal saveFileAs() signal saveFileAs()
signal openExternally() signal openExternally()
@@ -48,20 +48,20 @@ QQC2.ItemDelegate {
} }
} }
height: sectionDelegate.height + Math.max(avatar.height, bubble.implicitHeight) + loader.height + (model.showAuthor ? Kirigami.Units.smallSpacing : 0) height: sectionDelegate.height + Math.max(avatar.height, bubble.implicitHeight) + loader.height + (model.showAuthor ? Kirigami.Units.smallSpacing : 0) - (Config.showAvatarInTimeline ? 0 : Kirigami.Units.largeSpacing)
SectionDelegate { SectionDelegate {
id: sectionDelegate id: sectionDelegate
width: parent.width width: parent.width
anchors.left: parent.left anchors.left: avatar.left
anchors.leftMargin: Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing anchors.leftMargin: Kirigami.Units.smallSpacing
visible: model.showSection visible: model.showSection
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
} }
Kirigami.Avatar { Kirigami.Avatar {
id: avatar id: avatar
width: Kirigami.Units.gridUnit * 2 width: visible || Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0
height: width height: width
sourceSize.width: width sourceSize.width: width
sourceSize.height: width sourceSize.height: width
@@ -74,7 +74,7 @@ QQC2.ItemDelegate {
visible: model.showAuthor && Config.showAvatarInTimeline visible: model.showAuthor && Config.showAvatarInTimeline
name: model.author.name ?? model.author.displayName name: model.author.name ?? model.author.displayName
source: model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : "" source: visible && model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : ""
color: model.author.color color: model.author.color
MouseArea { MouseArea {
@@ -94,7 +94,7 @@ QQC2.ItemDelegate {
QQC2.Control { QQC2.Control {
id: bubble id: bubble
topPadding: Kirigami.Units.largeSpacing topPadding: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
bottomPadding: 0 bottomPadding: 0
leftPadding: 0 leftPadding: 0
rightPadding: 0 rightPadding: 0
@@ -103,6 +103,8 @@ QQC2.ItemDelegate {
top: avatar.top top: avatar.top
left: avatar.right left: avatar.right
leftMargin: Kirigami.Units.smallSpacing leftMargin: Kirigami.Units.smallSpacing
right: Config.showAvatarInTimeline ? undefined : parent.right
rightMargin: Config.showAvatarInTimeline ? undefined : Kirigami.Units.largeSpacing
} }
contentItem: ColumnLayout { contentItem: ColumnLayout {
@@ -112,10 +114,10 @@ QQC2.ItemDelegate {
id: rowLayout id: rowLayout
visible: model.showAuthor && !isEmote visible: model.showAuthor && !isEmote
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
Layout.rightMargin: Kirigami.Units.largeSpacing Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.preferredWidth: nameLabel.implicitWidth + timeLabel.implicitWidth + Kirigami.Units.largeSpacing Layout.preferredWidth: nameLabel.implicitWidth + timeLabel.implicitWidth + Kirigami.Units.largeSpacing
Layout.maximumWidth: bubbleMaxWidth - Kirigami.Units.largeSpacing * 2 Layout.maximumWidth: bubbleMaxWidth
implicitHeight: visible ? nameLabel.implicitHeight : 0 implicitHeight: visible ? nameLabel.implicitHeight : 0
QQC2.Label { QQC2.Label {
@@ -131,6 +133,19 @@ QQC2.ItemDelegate {
font.weight: Font.Bold font.weight: Font.Bold
color: author.color color: author.color
wrapMode: Text.Wrap wrapMode: Text.Wrap
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
room: currentRoom,
user: author.object,
displayName: author.displayName,
avatarMediaId: author.avatarMediaId,
avatarUrl: author.avatarUrl
}).open();
}
}
} }
QQC2.Label { QQC2.Label {
id: timeLabel id: timeLabel
@@ -157,7 +172,7 @@ QQC2.ItemDelegate {
} }
background: Kirigami.ShadowedRectangle { background: Kirigami.ShadowedRectangle {
visible: cardBackground visible: cardBackground && Config.showAvatarInTimeline
color: model.isHighlighted ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor color: model.isHighlighted ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
radius: Kirigami.Units.smallSpacing radius: Kirigami.Units.smallSpacing
shadow.size: Kirigami.Units.smallSpacing shadow.size: Kirigami.Units.smallSpacing
@@ -170,13 +185,12 @@ QQC2.ItemDelegate {
Loader { Loader {
id: loader id: loader
anchors { anchors {
left: parent.left left: bubble.left
leftMargin: Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing * 2
right: parent.right right: parent.right
top: bubble.bottom top: bubble.bottom
topMargin: active ? Kirigami.Units.smallSpacing : 0 topMargin: active && Config.showAvatarInTimeline ? Kirigami.Units.smallSpacing : 0
} }
height: active ? item.implicitHeight + Kirigami.Units.smallSpacing : 0 height: active ? item.implicitHeight : 0
//Layout.bottomMargin: readMarker ? Kirigami.Units.smallSpacing : 0 //Layout.bottomMargin: readMarker ? Kirigami.Units.smallSpacing : 0
active: eventType !== "state" && eventType !== "notice" && reaction != undefined && reaction.length > 0 active: eventType !== "state" && eventType !== "notice" && reaction != undefined && reaction.length > 0
visible: active visible: active

View File

@@ -0,0 +1,106 @@
/* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
* SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
* SPDX-FileCopyrightText: 2021 Srevin Saju <srevinsaju@sugarlabs.org>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import org.kde.kirigami 2.14 as Kirigami
import org.kde.neochat 1.0
Loader {
id: root
property string labelText: ""
active: visible
sourceComponent: Pane {
id: typingPane
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.largeSpacing
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.largeSpacing
FontMetrics {
id: fontMetrics
}
contentItem: RowLayout {
spacing: typingPane.spacing
Row {
id: dotRow
property int duration: 400
spacing: Kirigami.Units.smallSpacing
Repeater {
model: 3
delegate: Rectangle {
id: dot
color: Kirigami.Theme.textColor
radius: height/2
implicitWidth: fontMetrics.xHeight
implicitHeight: fontMetrics.xHeight
// rotating 45 degrees makes the dots look a bit smoother when scaled up
rotation: 45
opacity: 0.5
scale: 1
// FIXME: Sometimes the animation timings for each
// dot drift slightly reletative to each other.
// Not everyone can see this, but I'm pretty sure it's there.
SequentialAnimation {
running: true
PauseAnimation { duration: dotRow.duration * index / 2 }
SequentialAnimation {
loops: Animation.Infinite
ParallelAnimation {
// Animators unfortunately sync up instead of being
// staggered, so I'm using NumberAnimations instead.
NumberAnimation {
target: dot; property: "scale";
from: 1; to: 1.33
duration: dotRow.duration
}
NumberAnimation {
target: dot; property: "opacity"
from: 0.5; to: 1
duration: dotRow.duration
}
}
ParallelAnimation {
NumberAnimation {
target: dot; property: "scale"
from: 1.33; to: 1
duration: dotRow.duration
}
NumberAnimation {
target: dot; property: "opacity"
from: 1; to: 0.5
duration: dotRow.duration
}
}
PauseAnimation { duration: dotRow.duration }
}
}
}
}
}
Label {
id: typingLabel
elide: Text.ElideRight
text: root.labelText
}
}
leftInset: !mirrored ? 0 : -background.radius
rightInset: mirrored ? 0 : -background.radius
bottomInset: -background.radius
background: Rectangle {
radius: 3
color: Kirigami.Theme.backgroundColor
border.color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
border.width: 1
}
}
}

View File

@@ -2,3 +2,4 @@ module NeoChat.Component
FullScreenImage 1.0 FullScreenImage.qml FullScreenImage 1.0 FullScreenImage.qml
ChatTextInput 1.0 ChatTextInput.qml ChatTextInput 1.0 ChatTextInput.qml
FancyEffectsContainer 1.0 FancyEffectsContainer.qml FancyEffectsContainer 1.0 FancyEffectsContainer.qml
TypingPane 1.0 TypingPane.qml

View File

@@ -104,7 +104,7 @@ Kirigami.OverlaySheet {
} }
} }
Kirigami.BasicListItem { Kirigami.BasicListItem {
visible: user !== room.localUser && room.canSendState("kick") visible: user !== room.localUser && room.canSendState("kick") && room.containsUser(user.id)
action: Kirigami.Action { action: Kirigami.Action {
text: i18n("Kick this user") text: i18n("Kick this user")
@@ -116,14 +116,14 @@ Kirigami.OverlaySheet {
} }
} }
Kirigami.BasicListItem { Kirigami.BasicListItem {
visible: user !== room.localUser && room.canSendState("ban") visible: user !== room.localUser && room.canSendState("ban") && !room.isUserBanned(user.id)
action: Kirigami.Action { action: Kirigami.Action {
text: i18n("Ban this user") text: i18n("Ban this user")
icon.name: "im-ban-user" icon.name: "im-ban-user"
icon.color: Kirigami.Theme.negativeTextColor icon.color: Kirigami.Theme.negativeTextColor
onTriggered: { onTriggered: {
room.banMember(user.id) room.ban(user.id)
root.close() root.close()
} }
} }
@@ -133,7 +133,7 @@ Kirigami.OverlaySheet {
text: i18n("Open a private chat") text: i18n("Open a private chat")
icon.name: "document-send" icon.name: "document-send"
onTriggered: { onTriggered: {
Controller.activeConnection.requestDirectChat(user) Controller.openOrCreateDirectChat(user);
root.close() root.close()
} }
} }

View File

@@ -125,6 +125,8 @@ Kirigami.ScrollablePage {
sortFilterRoomListModel.index(index, 0)), ItemSelectionModel.SelectCurrent) sortFilterRoomListModel.index(index, 0)), ItemSelectionModel.SelectCurrent)
} }
} }
Keys.onEnterPressed: enterRoomAction.trigger()
Keys.onReturnPressed: enterRoomAction.trigger()
bold: unreadCount > 0 bold: unreadCount > 0
label: name ?? "" label: name ?? ""
subtitle: { subtitle: {
@@ -202,7 +204,7 @@ Kirigami.ScrollablePage {
} }
delegate: Kirigami.BasicListItem { delegate: Kirigami.BasicListItem {
checkable: true checkable: true
checked: Controller.activeConnection.localUser.id === model.user.id checked: Controller.activeConnection && Controller.activeConnection.localUser.id === model.user.id
onClicked: Controller.activeConnection = model.connection onClicked: Controller.activeConnection = model.connection
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true

View File

@@ -23,13 +23,18 @@ Kirigami.ScrollablePage {
/// It's not readonly because of the seperate window view. /// It's not readonly because of the seperate window view.
property var currentRoom: RoomManager.currentRoom property var currentRoom: RoomManager.currentRoom
/// Used to determine if scrolling to the bottom should mark the message as unread
property bool hasScrolledUpBefore: false;
title: currentRoom.displayName title: currentRoom.displayName
signal switchRoomUp() signal switchRoomUp()
signal switchRoomDown() signal switchRoomDown()
onCurrentRoomChanged: ChatBoxHelper.clearEditReply() onCurrentRoomChanged: {
hasScrolledUpBefore = false;
ChatBoxHelper.clearEditReply()
}
ActionsHandler { ActionsHandler {
id: actionsHandler id: actionsHandler
@@ -221,7 +226,6 @@ Kirigami.ScrollablePage {
visible: !invitation.visible visible: !invitation.visible
readonly property int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1 readonly property int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1
readonly property bool noNeedMoreContent: !currentRoom || currentRoom.eventsHistoryJob || currentRoom.allHistoryLoaded
readonly property bool isLoaded: page.width * page.height > 10 readonly property bool isLoaded: page.width * page.height > 10
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
@@ -232,20 +236,38 @@ Kirigami.ScrollablePage {
model: !isLoaded ? undefined : sortedMessageEventModel model: !isLoaded ? undefined : sortedMessageEventModel
onContentYChanged: fetchMoreContent()
function fetchMoreContent() {
if(!noNeedMoreContent && contentY - 5000 < originY) {
currentRoom.getPreviousContent(20);
}
}
MessageEventModel { MessageEventModel {
id: messageEventModel id: messageEventModel
room: currentRoom room: currentRoom
} }
Timer {
interval: 1000
running: messageListView.atYBeginning
triggeredOnStart: true
onTriggered: {
if (messageListView.atYBeginning && messageEventModel.canFetchMore(messageEventModel.index(0, 0))) {
messageEventModel.fetchMore(messageEventModel.index(0, 0));
}
}
repeat: true
}
// HACK: The view should do this automatically but doesn't.
onAtYBeginningChanged: if (atYBeginning && messageEventModel.canFetchMore(messageEventModel.index(0, 0))) {
messageEventModel.fetchMore(messageEventModel.index(0, 0));
}
onAtYEndChanged: if (atYEnd && hasScrolledUpBefore) {
if (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden) {
currentRoom.markAllMessagesAsRead();
}
hasScrolledUpBefore = false;
} else if (!atYEnd) {
hasScrolledUpBefore = true;
}
QQC2.Popup { QQC2.Popup {
anchors.centerIn: parent anchors.centerIn: parent
@@ -343,9 +365,10 @@ Kirigami.ScrollablePage {
innerObject: TextDelegate { innerObject: TextDelegate {
isEmote: true isEmote: true
Layout.fillWidth: !Config.showAvatarInTimeline
Layout.maximumWidth: emoteContainer.bubbleMaxWidth Layout.maximumWidth: emoteContainer.bubbleMaxWidth
Layout.rightMargin: Kirigami.Units.largeSpacing Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
Layout.bottomMargin: Kirigami.Units.largeSpacing * 2 Layout.bottomMargin: Kirigami.Units.largeSpacing * 2
TapHandler { TapHandler {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
@@ -369,10 +392,11 @@ Kirigami.ScrollablePage {
hoverComponent: hoverActions hoverComponent: hoverActions
innerObject: TextDelegate { innerObject: TextDelegate {
Layout.fillWidth: !Config.showAvatarInTimeline
Layout.maximumWidth: messageContainer.bubbleMaxWidth Layout.maximumWidth: messageContainer.bubbleMaxWidth
Layout.rightMargin: Kirigami.Units.largeSpacing Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing Layout.bottomMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
TapHandler { TapHandler {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onTapped: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody) onTapped: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
@@ -394,9 +418,10 @@ Kirigami.ScrollablePage {
onReplyClicked: goToEvent(eventID) onReplyClicked: goToEvent(eventID)
innerObject: TextDelegate { innerObject: TextDelegate {
Layout.fillWidth: !Config.showAvatarInTimeline
Layout.maximumWidth: noticeContainer.bubbleMaxWidth Layout.maximumWidth: noticeContainer.bubbleMaxWidth
Layout.rightMargin: Kirigami.Units.largeSpacing Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
Layout.bottomMargin: Kirigami.Units.largeSpacing * 2 Layout.bottomMargin: Kirigami.Units.largeSpacing * 2
} }
} }
@@ -550,7 +575,9 @@ Kirigami.ScrollablePage {
Timer { Timer {
id: makeMeDisapearTimer id: makeMeDisapearTimer
interval: Kirigami.Units.humanMoment * 2 interval: Kirigami.Units.humanMoment * 2
onTriggered: currentRoom.markAllMessagesAsRead(); onTriggered: if (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden) {
currentRoom.markAllMessagesAsRead();
}
} }
ListView.onPooled: makeMeDisapearTimer.stop() ListView.onPooled: makeMeDisapearTimer.stop()
@@ -577,7 +604,9 @@ Kirigami.ScrollablePage {
if (view.atYEnd) { if (view.atYEnd) {
// easy case just mark everything as read // easy case just mark everything as read
currentRoom.markAllMessagesAsRead(); if (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden) {
currentRoom.markAllMessagesAsRead();
}
return; return;
} }
@@ -622,7 +651,7 @@ Kirigami.ScrollablePage {
QQC2.RoundButton { QQC2.RoundButton {
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: Kirigami.Units.largeSpacing anchors.bottomMargin: Kirigami.Units.largeSpacing + messageListView.headerItem.height
anchors.rightMargin: Kirigami.Units.largeSpacing anchors.rightMargin: Kirigami.Units.largeSpacing
implicitWidth: Kirigami.Units.gridUnit * 2 implicitWidth: Kirigami.Units.gridUnit * 2
implicitHeight: Kirigami.Units.gridUnit * 2 implicitHeight: Kirigami.Units.gridUnit * 2
@@ -633,6 +662,7 @@ Kirigami.ScrollablePage {
action: Kirigami.Action { action: Kirigami.Action {
onTriggered: { onTriggered: {
goToLastMessage(); goToLastMessage();
currentRoom.markAllMessagesAsRead();
} }
icon.name: "go-down" icon.name: "go-down"
} }
@@ -643,10 +673,10 @@ Kirigami.ScrollablePage {
} }
Component.onCompleted: { Component.onCompleted: {
updateReadMarker()
if (currentRoom) { if (currentRoom) {
if (currentRoom.timelineSize < 20) if (currentRoom.timelineSize < 20) {
currentRoom.getPreviousContent(50) currentRoom.getPreviousContent(50);
}
} }
positionViewAtBeginning(); positionViewAtBeginning();
@@ -705,8 +735,12 @@ Kirigami.ScrollablePage {
header: TypingPane { header: TypingPane {
id: typingPane id: typingPane
visible: !loadingIndicator.visible && currentRoom && currentRoom.usersTyping.length > 0 visible: !loadingIndicator.visible && currentRoom && currentRoom.usersTyping.length > 0
typingNotification: visible ? i18ncp("Message displayed when some users are typing", "%2 is typing", "%2 are typing", currentRoom.usersTyping.length, currentRoom.usersTyping.map(user => user.displayName).join(", ")) : "" labelText: visible ? i18ncp(
width: parent.width "Message displayed when some users are typing", "%2 is typing", "%2 are typing",
currentRoom.usersTyping.length,
currentRoom.usersTyping.map(user => user.displayName).join(", ")
) : ""
anchors.right: parent.right
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
Behavior on height { Behavior on height {
NumberAnimation { NumberAnimation {
@@ -747,6 +781,7 @@ Kirigami.ScrollablePage {
background: FancyEffectsContainer { background: FancyEffectsContainer {
id: fancyEffectsContainer id: fancyEffectsContainer
z: 100
enabled: Config.showFancyEffects enabled: Config.showFancyEffects
@@ -847,7 +882,6 @@ Kirigami.ScrollablePage {
/// Open context menu for normal message /// Open context menu for normal message
function openMessageContext(author, message, eventId, source, eventType, formattedBody) { function openMessageContext(author, message, eventId, source, eventType, formattedBody) {
console.log("message", message)
const contextMenu = messageDelegateContextMenu.createObject(page, { const contextMenu = messageDelegateContextMenu.createObject(page, {
author: author, author: author,
message: message, message: message,

View File

@@ -157,6 +157,16 @@ Kirigami.OverlayDrawer {
} }
} }
Pane {
padding: Kirigami.Units.smallSpacing
implicitWidth: parent.width
z: 2
contentItem: Kirigami.SearchField {
id: userListSearchField
onAccepted: sortedMessageEventModel.filterString = text;
}
}
ScrollView { ScrollView {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
@@ -168,16 +178,6 @@ Kirigami.OverlayDrawer {
boundsBehavior: Flickable.DragOverBounds boundsBehavior: Flickable.DragOverBounds
activeFocusOnTab: true activeFocusOnTab: true
header: Pane {
padding: Kirigami.Units.smallSpacing
implicitWidth: parent.width
z: 2
contentItem: Kirigami.SearchField {
id: userListSearchField
onTextChanged: sortedMessageEventModel.filterString = text;
}
}
model: KSortFilterProxyModel { model: KSortFilterProxyModel {
id: sortedMessageEventModel id: sortedMessageEventModel

View File

@@ -1,51 +1,37 @@
[Global] [Global]
IconName=org.kde.neochat IconName=org.kde.neochat
Name=NeoChat Name=NeoChat
Name[az]=NeoChat
Name[ca]=NeoChat Name[ca]=NeoChat
Name[ca@valencia]=NeoChat Name[ca@valencia]=NeoChat
Name[cs]=NeoChat
Name[de]=NeoChat
Name[en_GB]=NeoChat
Name[es]=NeoChat Name[es]=NeoChat
Name[fi]=NeoChat Name[fi]=NeoChat
Name[fr]=NeoChat
Name[ia]=Neochat Name[ia]=Neochat
Name[it]=NeoChat Name[it]=NeoChat
Name[ko]=NeoChat
Name[nl]=NeoChat Name[nl]=NeoChat
Name[nn]=NeoChat Name[nn]=NeoChat
Name[pa]=ਨਿਓ-ਚੈਟ
Name[pl]=NeoChat Name[pl]=NeoChat
Name[pt]=NeoChat
Name[pt_BR]=NeoChat
Name[ro]=NeoChat
Name[sl]=NeoChat
Name[sv]=NeoChat Name[sv]=NeoChat
Name[ta]=நியோச்சாட்
Name[uk]=NeoChat Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx Name[x-test]=xxNeoChatxx
DesktopEntry=org.kde.neochat DesktopEntry=org.kde.neochat
Comment=A client for matrix, the decentralized communication protocol Comment=A client for matrix, the decentralized communication protocol
Comment[az]=Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu Comment[ca]=Un client per al Matrix, el protocol de comunicacions descentralitzat
Comment[ca]=Un client per a Matrix, el protocol de comunicacions descentralitzat Comment[ca@valencia]=Un client per al Matrix, el protocol de comunicacions descentralitzat
Comment[ca@valencia]=Un client per a Matrix, el protocol de comunicacions descentralitzat
Comment[de]=Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll Comment[de]=Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll
Comment[en_GB]=A client for matrix, the decentralised communication protocol Comment[en_GB]=A client for matrix, the decentralised communication protocol
Comment[es]=Un cliente para Matrix, el protocolo de comunicaciones descentralizado Comment[es]=Un cliente para Matrix, el protocolo de comunicaciones descentralizado
Comment[eu]=Matrix, deszentralizatutako komunikazio protokolorako, bezero bat Comment[eu]=Matrix, deszentralizatutako komunikazio protokolorako, bezero bat
Comment[fi]=Hajautetun Matrix-viestintäyhteyskäytännön asiakasohjelma Comment[fi]=Hajautetun Matrix-viestintäyhteyskäytännön asiakasohjelma
Comment[fr]=Un client pour « Matrix », le protocole décentralisé de communications. Comment[fr]=Un client pour « Matrix », le protocole décentralisé de communications.
Comment[hu]=Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz Comment[ia]=Un cliente per matrix, le protocollo de communication decentralisate
Comment[ia]=Un cliente per Matrix, le protocollo de communication decentralisate
Comment[it]=Un client per matrix, il protocollo di comunicazione decentralizzato Comment[it]=Un client per matrix, il protocollo di comunicazione decentralizzato
Comment[ko]=Matrix, 분산 대화 프로토콜 클라이언트
Comment[nl]=Een client voor matrix, het gedecentraliseerde communicatieprotocol Comment[nl]=Een client voor matrix, het gedecentraliseerde communicatieprotocol
Comment[nn]=Klient for Matrix, den desentraliserte lynmeldings­protokollen. Comment[nn]=Klient for Matrix, den desentraliserte lynmeldings­protokollen.
Comment[pa]=ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ
Comment[pl]=Program do obsługi matriksa, rozproszonego protokołu porozumiewania się Comment[pl]=Program do obsługi matriksa, rozproszonego protokołu porozumiewania się
Comment[pt]=Um cliente para o Matrix, o protocolo descentralizado de comunicações
Comment[pt_BR]=Um cliente para o Matrix, o protocolo de comunicação decentralizado Comment[pt_BR]=Um cliente para o Matrix, o protocolo de comunicação decentralizado
Comment[ro]=Client pentru Matrix, protocolul de comunicare descentralizată Comment[ro]=Client pentru Matrix, protocolul de comunicare descentralizată
Comment[sk]=Klient pre matrix, decentralizovaný komunikačný protokol
Comment[sl]=Odjemalec za decentralizirani komunikacijski protokol matrix Comment[sl]=Odjemalec za decentralizirani komunikacijski protokol matrix
Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokollet Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokollet
Comment[uk]=Клієнт matrix, децентралізованого протоколу обміну даними Comment[uk]=Клієнт matrix, децентралізованого протоколу обміну даними
@@ -55,7 +41,6 @@ Comment[zh_CN]=分布式通讯协议 Matrix 的客户端
[Event/message] [Event/message]
Name=New message Name=New message
Name[az]=Yeni ismarıc
Name[ca]=Missatge nou Name[ca]=Missatge nou
Name[ca@valencia]=Missatge nou Name[ca@valencia]=Missatge nou
Name[cs]=Nová zpráva Name[cs]=Nová zpráva
@@ -68,9 +53,9 @@ Name[fr]=Nouveau message
Name[hu]=Új üzenet Name[hu]=Új üzenet
Name[ia]=Nove message Name[ia]=Nove message
Name[it]=Nuovo messaggio Name[it]=Nuovo messaggio
Name[ko]=새 메시지
Name[nl]=Nieuw bericht Name[nl]=Nieuw bericht
Name[nn]=Ny melding Name[nn]=Ny melding
Name[pa]=ਨਵਾਂ ਸੁਨੇਹਾ
Name[pl]=Nowa wiadomość Name[pl]=Nowa wiadomość
Name[pt]=Nova mensagem Name[pt]=Nova mensagem
Name[pt_BR]=Nova mensagem Name[pt_BR]=Nova mensagem
@@ -78,12 +63,10 @@ Name[ro]=Mesaj nou
Name[sk]=Nová správa Name[sk]=Nová správa
Name[sl]=Novo sporočilo Name[sl]=Novo sporočilo
Name[sv]=Nytt meddelande Name[sv]=Nytt meddelande
Name[ta]=புதிய செய்தி
Name[uk]=Нове повідомлення Name[uk]=Нове повідомлення
Name[x-test]=xxNew messagexx Name[x-test]=xxNew messagexx
Name[zh_CN]=新消息 Name[zh_CN]=新消息
Comment=There is a new message Comment=There is a new message
Comment[az]=Yeni ismarıc var
Comment[ca]=Hi ha un missatge nou Comment[ca]=Hi ha un missatge nou
Comment[ca@valencia]=Hi ha un missatge nou Comment[ca@valencia]=Hi ha un missatge nou
Comment[de]=Es ist eine neue Nachricht vorhanden Comment[de]=Es ist eine neue Nachricht vorhanden
@@ -93,11 +76,11 @@ Comment[eu]=Mezu berri bat dago
Comment[fi]=Saapui uusi viesti Comment[fi]=Saapui uusi viesti
Comment[fr]=Il y a un nouveau message Comment[fr]=Il y a un nouveau message
Comment[hu]=Új üzenet érkezett Comment[hu]=Új üzenet érkezett
Comment[ia]=Il ha un nove message Comment[ia]=Isto es un nove message
Comment[it]=È presente un nuovo messaggio Comment[it]=È presente un nuovo messaggio
Comment[ko]=새 메시지가 있음
Comment[nl]=Er is een nieuw bericht Comment[nl]=Er is een nieuw bericht
Comment[nn]=Du har ei ny melding Comment[nn]=Du har ei ny melding
Comment[pa]=ਨਵਾਂ ਸੁਨੇਹਾ ਹੈ
Comment[pl]=Dostępna jest nowa wiadomość Comment[pl]=Dostępna jest nowa wiadomość
Comment[pt]=Tem uma mensagem nova Comment[pt]=Tem uma mensagem nova
Comment[pt_BR]=Existe uma nova mensagem Comment[pt_BR]=Existe uma nova mensagem
@@ -105,7 +88,6 @@ Comment[ro]=Este un mesaj nou
Comment[sk]=Je nová správa Comment[sk]=Je nová správa
Comment[sl]=Prišlo je novo sporočilo Comment[sl]=Prišlo je novo sporočilo
Comment[sv]=Det finns ett nytt meddelande Comment[sv]=Det finns ett nytt meddelande
Comment[ta]=ஒரு புதிய செய்தி உள்ளது
Comment[uk]=Надійшло нове повідомлення Comment[uk]=Надійшло нове повідомлення
Comment[x-test]=xxThere is a new messagexx Comment[x-test]=xxThere is a new messagexx
Comment[zh_CN]=有新消息 Comment[zh_CN]=有新消息

View File

@@ -5,31 +5,20 @@
<binary>neochat</binary> <binary>neochat</binary>
</provides> </provides>
<name>NeoChat</name> <name>NeoChat</name>
<name xml:lang="az">NeoChat</name>
<name xml:lang="ca">NeoChat</name> <name xml:lang="ca">NeoChat</name>
<name xml:lang="ca-valencia">NeoChat</name> <name xml:lang="ca-valencia">NeoChat</name>
<name xml:lang="cs">NeoChat</name>
<name xml:lang="de">NeoChat</name>
<name xml:lang="en-GB">NeoChat</name>
<name xml:lang="es">NeoChat</name> <name xml:lang="es">NeoChat</name>
<name xml:lang="fi">NeoChat</name> <name xml:lang="fi">NeoChat</name>
<name xml:lang="fr">NeoChat</name>
<name xml:lang="ia">Neochat</name> <name xml:lang="ia">Neochat</name>
<name xml:lang="id">NeoChat</name>
<name xml:lang="it">NeoChat</name> <name xml:lang="it">NeoChat</name>
<name xml:lang="ko">NeoChat</name>
<name xml:lang="nl">NeoChat</name> <name xml:lang="nl">NeoChat</name>
<name xml:lang="nn">NeoChat</name> <name xml:lang="nn">NeoChat</name>
<name xml:lang="pa">ਨਿਓ-ਚੈਟ</name>
<name xml:lang="pl">NeoChat</name> <name xml:lang="pl">NeoChat</name>
<name xml:lang="pt">NeoChat</name>
<name xml:lang="pt-BR">NeoChat</name>
<name xml:lang="sl">NeoChat</name>
<name xml:lang="sv">NeoChat</name> <name xml:lang="sv">NeoChat</name>
<name xml:lang="ta">நியோச்சாட்</name>
<name xml:lang="uk">NeoChat</name> <name xml:lang="uk">NeoChat</name>
<name xml:lang="x-test">xxNeoChatxx</name> <name xml:lang="x-test">xxNeoChatxx</name>
<summary>A client for matrix, the decentralized communication protocol</summary> <summary>A client for matrix, the decentralized communication protocol</summary>
<summary xml:lang="az">Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu</summary>
<summary xml:lang="ca">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary> <summary xml:lang="ca">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary>
<summary xml:lang="ca-valencia">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary> <summary xml:lang="ca-valencia">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary>
<summary xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix</summary> <summary xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix</summary>
@@ -40,12 +29,12 @@
<summary xml:lang="fi">Asiakas Matrixille, hajautetulle viestintäyhteyskäytännölle</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="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="hu">Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz</summary>
<summary xml:lang="ia">Un cliente per Matrix, le protocollo de communication decentralisate</summary> <summary xml:lang="ia">Un cliente per matrix, le protocollo de communication decentralisate</summary>
<summary xml:lang="id">Klien untuk matrix, protokol komunikasi terdesentralisasi</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="it">Un client per matrix, il protocollo di comunicazione decentralizzato</summary>
<summary xml:lang="ko">Matrix, 분산 대화 프로토콜 클라이언트</summary>
<summary xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol</summary> <summary xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol</summary>
<summary xml:lang="nn">Ein klient for Matrix, den desentraliserte lynmeldings­protokollen</summary> <summary xml:lang="nn">Ein klient for Matrix, den desentraliserte lynmeldings­protokollen</summary>
<summary xml:lang="pa">ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ</summary>
<summary xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się</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">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="pt-BR">Um cliente do Matrix, o protocolo de comunicação descentralizado</summary>
@@ -55,32 +44,33 @@
<summary xml:lang="uk">Клієнт matrix, децентралізованого протоколу обміну даними</summary> <summary xml:lang="uk">Клієнт matrix, децентралізованого протоколу обміну даними</summary>
<summary xml:lang="x-test">xxA client for matrix, the decentralized communication protocolxx</summary> <summary xml:lang="x-test">xxA client for matrix, the decentralized communication protocolxx</summary>
<description> <description>
<p>A client for matrix, the decentralized communication protocol.</p> <p>NeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.</p>
<p xml:lang="az">Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu.</p> <p xml:lang="az">NeoChat Mtrix müştərisidir. O, Matrix protokolundan istifadə edərək, ailənizə, dostlarınıza, iş yoldaşlarınıza mətn, səsli və görüntülü ismarıclar göndərməyə imkan verir.</p>
<p xml:lang="ca">Un client per al Matrix, el protocol de comunicacions descentralitzat.</p> <p xml:lang="ca">El NeoChat és un client Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p>
<p xml:lang="ca-valencia">Un client per al Matrix, el protocol de comunicacions descentralitzat.</p> <p xml:lang="ca-valencia">El NeoChat és un client Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p>
<p xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix.</p> <p xml:lang="de">NeoChat ist ein Matrix-Client. Er ermöglicht Ihnen das Senden von Textnachrichten, Videos und Audiodateien an Ihre Familie, Kollegen und Freunde unter Verwendung des Matrix-Protokolls.</p>
<p xml:lang="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll.</p> <p xml:lang="es">NeoChat es un cliente para Matrix. Le permite enviar mensajes de texto, vídeos y archivos de sonido a su familia, compañeros de trabajo y amigos usando el protocolo Matrix.</p>
<p xml:lang="en-GB">A client for matrix, the decentralised communication protocol.</p> <p xml:lang="nl">NeoChat is een Matrix-client. Het biedt u het verzenden van tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden met het Matrix-protocol.</p>
<p xml:lang="es">Un cliente para Matrix, el protocolo de comunicaciones descentralizado.</p> <p xml:lang="uk">NeoChat — клієнт мережі обміну повідомленнями Matrix. За допомогою цієї програми ви зможете надсилати текстові повідомлення, відео та звукові файли вашій родині, колегам та друзям за допомогою протоколу Matrix.</p>
<p xml:lang="eu">Matrix, deszentralizatutako komunikazio protokolorako bezero bat.</p> <p xml:lang="x-test">xxNeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.xx</p>
<p xml:lang="fi">Asiakas Matrixille, hajautetulle viestintäyhteyskäytännölle.</p> <p>Matrix is a decentralized communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p>
<p xml:lang="fr">Un client « Matrix », le protocole décentralisé de communications.</p> <p xml:lang="az">Matrix, istifadəçini nəzarətdə saxlayan, mərkəzləşməmişi rabitə protokoludur. NeoChat, söhbətin və video əlaqəsinin şifrələnməsindən başqa bir çox protokolları həyata keçirə bilir.</p>
<p xml:lang="hu">Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz.</p> <p xml:lang="ca">El Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
<p xml:lang="ia">Un cliente per Matrix, le protocollo de communication decentralisate.</p> <p xml:lang="ca-valencia">El Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
<p xml:lang="id">Klien untuk matrix, protokol komunikasi terdesentralisasi.</p> <p xml:lang="de">Matrix ist ein dezentralisiertes Kommunikationsprotokoll, das dem Benutzer wieder die Kontrolle zurückgibt. Derzeit implementiert NeoChat einen großen Teil des Protokolls mit der Ausnahme von verschlüsselten Chats und Video-Chat.</p>
<p xml:lang="it">Un client per matrix, il protocollo di comunicazione decentralizzato.</p> <p xml:lang="es">Matrix es un protocolo de comunicaciones descentralizado, que devuelve el control al usuario. En la actualidad, NeoChat implementa gran parte del protocolo con la excepción de chats cifrados y chats de vídeo.</p>
<p xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol.</p> <p xml:lang="nl">Matrix is een gedecentraliseerd communicatieprotocol, dat de gebruiker de controle teruggeeft. Op dit moment implementeert NeoChat grote delen van het protocol met de uitzondering van versleutelde chats en video-chat.</p>
<p xml:lang="nn">Ein klient for Matrix, den desentraliserte lynmeldings­protokollen.</p> <p xml:lang="uk">Matrix — протокол децентралізованого спілкування, який передає контроль над даними користувачеві. У поточній версії NeoChat реалізовано більшу частину протоколу, окрім зашифрованого спілкування та відеоспілкування.</p>
<p xml:lang="pa">ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ।</p> <p xml:lang="x-test">xxMatrix is a decentralized communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.xx</p>
<p xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się.</p> <p>NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
<p xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado.</p> <p xml:lang="az">Vahid istifadəçi interfeysi ilə təmin olunan NeoChat, həm mobil telefonda həm də kompyuterlərdə işləyir.</p>
<p xml:lang="pt-BR">Um cliente do Matrix, o protocolo de comunicação descentralizado.</p> <p xml:lang="ca">El NeoChat funciona en el mòbils i a l'escriptori, proporcionant un experiència d'usuari coherent.</p>
<p xml:lang="sk">Klient pre matrix, decentralizovaný komunikačný protokol.</p> <p xml:lang="ca-valencia">El NeoChat funciona en el mòbils i a l'escriptori, proporcionant un experiència d'usuari coherent.</p>
<p xml:lang="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol.</p> <p xml:lang="de">NeoChat funktioniert sowohl auf dem Mobiltelefon als auch auf dem Arbeitsfläche und bietet ein einheitliches Benutzererlebnis. </p>
<p xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet.</p> <p xml:lang="es">NeoChat funciona en móviles y en el escritorio a la vez que proporciona una experiencia de usuario consistente.</p>
<p xml:lang="uk">Клієнт matrix, децентралізованого протоколу обміну даними.</p> <p xml:lang="nl">NeoChat werkt zowel op de mobiel en het bureaublad met het leveren van een consistente gebruikerservaring.</p>
<p xml:lang="x-test">xxA client for matrix, the decentralized communication protocol.xx</p> <p xml:lang="uk">NeoChat працює на мобільних пристроях та звичайних комп'ютерах, маючи однорідний інтерфейс на усіх підтримуваних пристроях.</p>
<p xml:lang="x-test">xxNeoChat works both on mobile and desktop while providing a consistent user experience.xx</p>
</description> </description>
<url type="homepage">https://apps.kde.org/neochat/</url> <url type="homepage">https://apps.kde.org/neochat/</url>
<url type="bugtracker">https://invent.kde.org/network/neochat/-/issues</url> <url type="bugtracker">https://invent.kde.org/network/neochat/-/issues</url>
@@ -88,7 +78,6 @@
<category>Network</category> <category>Network</category>
</categories> </categories>
<developer_name>The KDE Community</developer_name> <developer_name>The KDE Community</developer_name>
<developer_name xml:lang="az">KDE Cəmiyyəti</developer_name>
<developer_name xml:lang="ca">La comunitat KDE</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="ca-valencia">La comunitat KDE</developer_name>
<developer_name xml:lang="cs">Komunita KDE</developer_name> <developer_name xml:lang="cs">Komunita KDE</developer_name>
@@ -102,9 +91,9 @@
<developer_name xml:lang="ia">Le communitate de KDE</developer_name> <developer_name xml:lang="ia">Le communitate de KDE</developer_name>
<developer_name xml:lang="id">Komunitas KDE</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="it">La comunità KDE</developer_name>
<developer_name xml:lang="ko">KDE 커뮤니티</developer_name>
<developer_name xml:lang="nl">De KDE gemeenschap</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="nn">KDE-fellesskapet</developer_name>
<developer_name xml:lang="pa">ਕੇਡੀਈ ਕਮਿਊਨਟੀ</developer_name>
<developer_name xml:lang="pl">Społeczność KDE</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">A Comunidade do KDE</developer_name>
<developer_name xml:lang="pt-BR">A comunidade KDE</developer_name> <developer_name xml:lang="pt-BR">A comunidade KDE</developer_name>
@@ -118,13 +107,24 @@
<value key="KDE::matrix">#neochat:kde.org</value> <value key="KDE::matrix">#neochat:kde.org</value>
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">
<image>https://www.plasma-mobile.org/img/post-2020-10/post-2020-10-neochat-timeline.png</image> <image>https://cdn.kde.org/screenshots/neochat/application-mobile.png</image>
</screenshot>
<screenshot type="default">
<image>https://cdn.kde.org/screenshots/neochat/application.png</image>
</screenshot> </screenshot>
</screenshots> </screenshots>
<content_rating type="oars-1.1"> <content_rating type="oars-1.1">
<content_attribute id="social-chat">intense</content_attribute> <content_attribute id="social-chat">intense</content_attribute>
</content_rating> </content_rating>
<releases> <releases>
<release version="1.2.0" date="2021-06-01">
<description>
<p>NeoChat 1.2 brings a major redesign of the user interface. The chat page is now using bubbles for the messages and the input component was completely rewritten with a nicer look as well.</p>
<p>It's now possible to send custom reactions by replying to a comment with /react &lt;message&gt;.</p>
<p>NeoChat now supports opening Matrix URIs from your browser.</p>
</description>
<url>https://carlschwan.eu/2021/06/01/neochat-1.2/</url>
</release>
<release urgency="critical" version="1.1.1" date="2021-02-23"/> <release urgency="critical" version="1.1.1" date="2021-02-23"/>
<release version="1.1.0" date="2021-02-22"> <release version="1.1.0" date="2021-02-22">
<description> <description>
@@ -134,7 +134,7 @@
<p>We added a few commands to NeoChat (/shrug, /lenny, /join, /ignore, ...).</p> <p>We added a few commands to NeoChat (/shrug, /lenny, /join, /ignore, ...).</p>
<p>We improved the Plasma integration a bit. Now the number of unread messages is displayed in the Plasma Taskbar.</p> <p>We improved the Plasma integration a bit. Now the number of unread messages is displayed in the Plasma Taskbar.</p>
</description> </description>
<url>https://carlschwan.eu/2020/02/22/neochat-1.1/</url> <url>https://carlschwan.eu/2021/02/22/neochat-1.1/</url>
</release> </release>
<release version="1.0.1" date="2021-01-13"> <release version="1.0.1" date="2021-01-13">
<description> <description>

View File

@@ -1,32 +1,21 @@
[Desktop Entry] [Desktop Entry]
Name=NeoChat Name=NeoChat
Name[az]=NeoChat
Name[ca]=NeoChat Name[ca]=NeoChat
Name[ca@valencia]=NeoChat Name[ca@valencia]=NeoChat
Name[cs]=NeoChat
Name[de]=NeoChat
Name[en_GB]=NeoChat
Name[es]=NeoChat Name[es]=NeoChat
Name[fi]=NeoChat Name[fi]=NeoChat
Name[fr]=NeoChat
Name[ia]=Neochat Name[ia]=Neochat
Name[it]=NeoChat Name[it]=NeoChat
Name[ko]=NeoChat
Name[nl]=NeoChat Name[nl]=NeoChat
Name[nn]=NeoChat Name[nn]=NeoChat
Name[pa]=ਨਿਓ-ਚੈਟ
Name[pl]=NeoChat Name[pl]=NeoChat
Name[pt]=NeoChat
Name[pt_BR]=NeoChat
Name[ro]=NeoChat
Name[sl]=NeoChat
Name[sv]=NeoChat Name[sv]=NeoChat
Name[ta]=நியோச்சாட்
Name[uk]=NeoChat Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx Name[x-test]=xxNeoChatxx
GenericName=Matrix Client GenericName=Matrix Client
GenericName[az]=Matrix Müştərisi GenericName[ca]=Client del Matrix
GenericName[ca]=Client de Matrix GenericName[ca@valencia]=Client del Matrix
GenericName[ca@valencia]=Client de Matrix
GenericName[cs]=Klient protokolu Matrix GenericName[cs]=Klient protokolu Matrix
GenericName[de]=Matrix-Programm GenericName[de]=Matrix-Programm
GenericName[en_GB]=Matrix Client GenericName[en_GB]=Matrix Client
@@ -35,11 +24,11 @@ GenericName[eu]=Matrix bezeroa
GenericName[fi]=Matrix-asiakas GenericName[fi]=Matrix-asiakas
GenericName[fr]=Client « Matrix » GenericName[fr]=Client « Matrix »
GenericName[hu]=Matrix kliens GenericName[hu]=Matrix kliens
GenericName[ia]=Cliente de Matrice GenericName[ia]=Cliente de Matrix
GenericName[it]=Client Matrix GenericName[it]=Client Matrix
GenericName[ko]=Matrix 클라이언트
GenericName[nl]=Matrix-client GenericName[nl]=Matrix-client
GenericName[nn]=Matrix-klient GenericName[nn]=Matrix-klient
GenericName[pa]=ਮੈਟਰਿਕਸ ਕਲਾਈਂਟ
GenericName[pl]=Program Matriksa GenericName[pl]=Program Matriksa
GenericName[pt]=Cliente de Matrix GenericName[pt]=Cliente de Matrix
GenericName[pt_BR]=Cliente Matrix GenericName[pt_BR]=Cliente Matrix
@@ -47,12 +36,10 @@ GenericName[ro]=Client Matrix
GenericName[sk]=Matrix Client GenericName[sk]=Matrix Client
GenericName[sl]=Odjemalec Matrix GenericName[sl]=Odjemalec Matrix
GenericName[sv]=Matrix-klient GenericName[sv]=Matrix-klient
GenericName[ta]=Matrix வாங்கி
GenericName[uk]=Клієнт Matrix GenericName[uk]=Клієнт Matrix
GenericName[x-test]=xxMatrix Clientxx GenericName[x-test]=xxMatrix Clientxx
GenericName[zh_CN]=Matrix 客户端 GenericName[zh_CN]=Matrix 客户端
Comment=Client for the Matrix protocol Comment=Client for the Matrix protocol
Comment[az]=Matrix protokolu üçün müştəri
Comment[ca]=Client per al protocol Matrix Comment[ca]=Client per al protocol Matrix
Comment[ca@valencia]=Client per al protocol Matrix Comment[ca@valencia]=Client per al protocol Matrix
Comment[de]=Programm für das Matrix-Protokoll Comment[de]=Programm für das Matrix-Protokoll
@@ -64,9 +51,9 @@ Comment[fr]=Client pour le protocole « Matrix »
Comment[hu]=Kliens a Matrix protokollhoz Comment[hu]=Kliens a Matrix protokollhoz
Comment[ia]=Cliente per le protocollo de Matrix Comment[ia]=Cliente per le protocollo de Matrix
Comment[it]=Client per il protocollo Matrix Comment[it]=Client per il protocollo Matrix
Comment[ko]=Matrix 프로토콜용 클라이언트
Comment[nl]=Client voor het Matrix-protocol Comment[nl]=Client voor het Matrix-protocol
Comment[nn]=Lynmeldings­klient for Matrix-protokollen Comment[nn]=Lynmeldings­klient for Matrix-protokollen
Comment[pa]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
Comment[pl]=Program obsługi protokołu Matriksa Comment[pl]=Program obsługi protokołu Matriksa
Comment[pt]=Cliente para o protocolo Matrix Comment[pt]=Cliente para o protocolo Matrix
Comment[pt_BR]=Cliente para o protocolo Matrix Comment[pt_BR]=Cliente para o protocolo Matrix
@@ -74,7 +61,6 @@ Comment[ro]=Client pentru protocolul Matrix
Comment[sk]=Klient protokolu Matrix Comment[sk]=Klient protokolu Matrix
Comment[sl]=Odjemalec za protokol Matrix Comment[sl]=Odjemalec za protokol Matrix
Comment[sv]=Klient för protokollet Matrix Comment[sv]=Klient för protokollet Matrix
Comment[ta]=Matrix நெறிமுறைக்கான வாங்கி
Comment[uk]=Клієнт протоколу Matrix Comment[uk]=Клієнт протоколу Matrix
Comment[x-test]=xxClient for the Matrix protocolxx Comment[x-test]=xxClient for the Matrix protocolxx
Comment[zh_CN]=为 Matrix 协议打造的客户端 Comment[zh_CN]=为 Matrix 协议打造的客户端

View File

@@ -192,7 +192,7 @@ Kirigami.ApplicationWindow {
Kirigami.Action { Kirigami.Action {
text: i18n("Devices") text: i18n("Devices")
iconName: "network-connect" iconName: "network-connect"
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/DevicesPage.qml") onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/DevicesPage.qml")
enabled: pageStack.layers.currentItem.title !== i18n("Devices") && Controller.accountCount > 0 enabled: pageStack.layers.currentItem.title !== i18n("Devices") && Controller.accountCount > 0
}, },
Kirigami.Action { Kirigami.Action {

View File

@@ -17,6 +17,7 @@
<file>imports/NeoChat/Component/qmldir</file> <file>imports/NeoChat/Component/qmldir</file>
<file>imports/NeoChat/Component/FullScreenImage.qml</file> <file>imports/NeoChat/Component/FullScreenImage.qml</file>
<file>imports/NeoChat/Component/FancyEffectsContainer.qml</file> <file>imports/NeoChat/Component/FancyEffectsContainer.qml</file>
<file>imports/NeoChat/Component/TypingPane.qml</file>
<file>imports/NeoChat/Component/ChatBox</file> <file>imports/NeoChat/Component/ChatBox</file>
<file>imports/NeoChat/Component/ChatBox/ChatBox.qml</file> <file>imports/NeoChat/Component/ChatBox/ChatBox.qml</file>
<file>imports/NeoChat/Component/ChatBox/ChatBar.qml</file> <file>imports/NeoChat/Component/ChatBox/ChatBar.qml</file>

View File

@@ -33,7 +33,7 @@ add_executable(neochat
../res.qrc ../res.qrc
) )
if(${Quotient_VERSION_MINOR} GREATER 6) if(Quotient_VERSION_MINOR GREATER 6)
target_compile_definitions(neochat PRIVATE QUOTIENT_07) target_compile_definitions(neochat PRIVATE QUOTIENT_07)
endif() endif()

View File

@@ -20,7 +20,9 @@
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QGuiApplication>
#include <QMovie> #include <QMovie>
#include <QNetworkConfigurationManager>
#include <QNetworkReply> #include <QNetworkReply>
#include <QPixmap> #include <QPixmap>
#include <QQuickWindow> #include <QQuickWindow>
@@ -29,7 +31,6 @@
#include <QSysInfo> #include <QSysInfo>
#include <QTimer> #include <QTimer>
#include <utility> #include <utility>
#include <QNetworkConfigurationManager>
#include <signal.h> #include <signal.h>
@@ -47,6 +48,7 @@
#include "neochatuser.h" #include "neochatuser.h"
#include "settings.h" #include "settings.h"
#include "utils.h" #include "utils.h"
#include "roommanager.h"
#include <KStandardShortcut> #include <KStandardShortcut>
#ifndef Q_OS_ANDROID #ifndef Q_OS_ANDROID
@@ -67,7 +69,7 @@ Controller::Controller(QObject *parent)
if (NeoChatConfig::self()->systemTray()) { if (NeoChatConfig::self()->systemTray()) {
trayIcon->show(); trayIcon->show();
connect(trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow); connect(trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
QApplication::setQuitOnLastWindowClosed(false); QGuiApplication::setQuitOnLastWindowClosed(false);
} }
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, [=]() { connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, [=]() {
if (NeoChatConfig::self()->systemTray()) { if (NeoChatConfig::self()->systemTray()) {
@@ -77,7 +79,7 @@ Controller::Controller(QObject *parent)
trayIcon->hide(); trayIcon->hide();
disconnect(trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow); disconnect(trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
} }
QApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray()); QGuiApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray());
}); });
#endif #endif
@@ -85,7 +87,7 @@ Controller::Controller(QObject *parent)
invokeLogin(); invokeLogin();
}); });
QObject::connect(QApplication::instance(), &QCoreApplication::aboutToQuit, QApplication::instance(), [] { QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [] {
NeoChatConfig::self()->save(); NeoChatConfig::self()->save();
}); });
@@ -427,7 +429,8 @@ bool Controller::supportSystemTray() const
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
return false; return false;
#else #else
return true; QString de = getenv("XDG_CURRENT_DESKTOP");
return de != QStringLiteral("GNOME") && de != QStringLiteral("Pantheon");
#endif #endif
} }
@@ -496,13 +499,13 @@ int Controller::accountCount() const
bool Controller::quitOnLastWindowClosed() bool Controller::quitOnLastWindowClosed()
{ {
return QApplication::quitOnLastWindowClosed(); return QGuiApplication::quitOnLastWindowClosed();
} }
void Controller::setQuitOnLastWindowClosed(bool value) void Controller::setQuitOnLastWindowClosed(bool value)
{ {
if (quitOnLastWindowClosed() != value) { if (quitOnLastWindowClosed() != value) {
QApplication::setQuitOnLastWindowClosed(value); QGuiApplication::setQuitOnLastWindowClosed(value);
Q_EMIT quitOnLastWindowClosedChanged(); Q_EMIT quitOnLastWindowClosedChanged();
} }
} }
@@ -592,6 +595,17 @@ void Controller::joinRoom(const QString &alias)
}); });
} }
void Controller::openOrCreateDirectChat(NeoChatUser *user)
{
const auto existing = activeConnection()->directChats();
if (existing.contains(user)) {
RoomManager::instance().enterRoom(static_cast<NeoChatRoom *>(activeConnection()->room(existing.value(user))));
return;
}
activeConnection()->requestDirectChat(user);
}
QString Controller::formatByteSize(double size, int precision) const QString Controller::formatByteSize(double size, int precision) const
{ {
return KFormat().formatByteSize(size, precision); return KFormat().formatByteSize(size, precision);

View File

@@ -3,9 +3,7 @@
#pragma once #pragma once
#include <QApplication>
#include <QMediaPlayer> #include <QMediaPlayer>
#include <QMenu>
#include <QObject> #include <QObject>
#include <KAboutData> #include <KAboutData>
@@ -21,6 +19,7 @@ class QNetworkConfigurationManager;
#include "user.h" #include "user.h"
class NeoChatRoom; class NeoChatRoom;
class NeoChatUser;
class QQuickWindow; class QQuickWindow;
using namespace Quotient; using namespace Quotient;
@@ -86,6 +85,8 @@ public:
Q_INVOKABLE QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options = KFormat::DefaultDuration) const; Q_INVOKABLE QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options = KFormat::DefaultDuration) const;
Q_INVOKABLE QString formatByteSize(double size, int precision = 1) const; Q_INVOKABLE QString formatByteSize(double size, int precision = 1) const;
Q_INVOKABLE void openOrCreateDirectChat(NeoChatUser *user);
private: private:
explicit Controller(QObject *parent = nullptr); explicit Controller(QObject *parent = nullptr);
~Controller() override; ~Controller() override;

View File

@@ -12,6 +12,12 @@
#include <QQuickWindow> #include <QQuickWindow>
#include <QDebug> #include <QDebug>
#ifdef Q_OS_ANDROID
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include <KAboutData> #include <KAboutData>
#ifdef HAVE_KDBUSADDONS #ifdef HAVE_KDBUSADDONS
#include <KDBusService> #include <KDBusService>
@@ -57,7 +63,7 @@ Q_DECL_EXPORT
#endif #endif
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QNetworkProxyFactory::setUseSystemConfiguration(true); QNetworkProxyFactory::setUseSystemConfiguration(true);
@@ -84,7 +90,7 @@ int main(int argc, char *argv[])
app.setFont(font); app.setFont(font);
#endif #endif
QApplication::setOrganizationName("KDE"); QGuiApplication::setOrganizationName("KDE");
KAboutData about(QStringLiteral("neochat"), KAboutData about(QStringLiteral("neochat"),
i18n("NeoChat"), i18n("NeoChat"),
@@ -98,7 +104,7 @@ int main(int argc, char *argv[])
about.setOrganizationDomain("kde.org"); about.setOrganizationDomain("kde.org");
KAboutData::setApplicationData(about); KAboutData::setApplicationData(about);
QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("org.kde.neochat"))); QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("org.kde.neochat")));
#ifdef HAVE_KDBUSADDONS #ifdef HAVE_KDBUSADDONS
KDBusService service(KDBusService::Unique); KDBusService service(KDBusService::Unique);
@@ -224,5 +230,5 @@ int main(int argc, char *argv[])
} }
} }
#endif #endif
return QApplication::exec(); return app.exec();
} }

View File

@@ -370,6 +370,22 @@ int MessageEventModel::rowCount(const QModelIndex &parent) const
} }
bool MessageEventModel::canFetchMore(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_currentRoom && !m_currentRoom->eventsHistoryJob() && !m_currentRoom->allHistoryLoaded();
}
void MessageEventModel::fetchMore(const QModelIndex &parent)
{
Q_UNUSED(parent);
if (m_currentRoom) {
m_currentRoom->getPreviousContent(20);
}
}
inline QVariantMap userAtEvent(NeoChatUser *user, NeoChatRoom *room, const RoomEvent &evt) inline QVariantMap userAtEvent(NeoChatUser *user, NeoChatRoom *room, const RoomEvent &evt)
{ {
Q_UNUSED(evt) Q_UNUSED(evt)
@@ -405,6 +421,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
const KFormat format; const KFormat format;
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat); return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
} }
case SpecialMarksRole:
return EventStatus::Hidden;
} }
return {}; return {};
} }
@@ -532,9 +550,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
if (is<RedactionEvent>(evt) || is<ReactionEvent>(evt)) { if (is<RedactionEvent>(evt) || is<ReactionEvent>(evt)) {
return EventStatus::Hidden; return EventStatus::Hidden;
} }
if (evt.isRedacted()) {
return EventStatus::Hidden;
}
if (evt.isStateEvent() && static_cast<const StateEventBase &>(evt).repeatsState()) { if (evt.isStateEvent() && static_cast<const StateEventBase &>(evt).repeatsState()) {
return EventStatus::Hidden; return EventStatus::Hidden;
@@ -667,7 +682,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
auto i = index(r); auto i = index(r);
if (data(i, SpecialMarksRole) != EventStatus::Hidden) { if (data(i, SpecialMarksRole) != EventStatus::Hidden) {
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, EventTypeRole) != data(idx, EventTypeRole) return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, EventTypeRole) != data(idx, EventTypeRole)
|| data(idx, TimeRole).toDateTime().msecsTo(data(i, TimeRole).toDateTime()) > 600000; || data(idx, TimeRole).toDateTime().msecsTo(data(i, TimeRole).toDateTime()) > 600000
|| data(idx, TimeRole).toDateTime().toLocalTime().date().day() != data(i, TimeRole).toDateTime().toLocalTime().date().day();
} }
} }
@@ -684,7 +700,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
} }
} }
return true; return false;
} }
if (role == ReactionRole) { if (role == ReactionRole) {

View File

@@ -76,6 +76,9 @@ private:
[[nodiscard]] QDateTime makeMessageTimestamp(const Quotient::Room::rev_iter_t &baseIt) const; [[nodiscard]] QDateTime makeMessageTimestamp(const Quotient::Room::rev_iter_t &baseIt) const;
[[nodiscard]] static QString renderDate(const QDateTime &timestamp); [[nodiscard]] static QString renderDate(const QDateTime &timestamp);
bool canFetchMore(const QModelIndex &parent) const override;
void fetchMore(const QModelIndex &parent) override;
void refreshLastUserEvents(int baseTimelineRow); void refreshLastUserEvents(int baseTimelineRow);
void refreshEventRoles(int row, const QVector<int> &roles = {}); void refreshEventRoles(int row, const QVector<int> &roles = {});
int refreshEventRoles(const QString &eventId, const QVector<int> &roles = {}); int refreshEventRoles(const QString &eventId, const QVector<int> &roles = {});

View File

@@ -5,7 +5,6 @@
#include <cmark.h> #include <cmark.h>
#include <QFileDialog>
#include <QFileInfo> #include <QFileInfo>
#include <QImageReader> #include <QImageReader>
#include <QMetaObject> #include <QMetaObject>
@@ -685,3 +684,8 @@ bool NeoChatRoom::isInvite() const
{ {
return joinState() == JoinState::Invite; return joinState() == JoinState::Invite;
} }
bool NeoChatRoom::isUserBanned(const QString &user) const
{
return getCurrentState<RoomMemberEvent>(user)->membership() == MembershipType::Ban;
}

View File

@@ -98,6 +98,7 @@ public:
[[nodiscard]] QString eventToString(const RoomEvent &evt, Qt::TextFormat format = Qt::PlainText, bool removeReply = true) const; [[nodiscard]] QString eventToString(const RoomEvent &evt, Qt::TextFormat format = Qt::PlainText, bool removeReply = true) const;
Q_INVOKABLE [[nodiscard]] bool containsUser(const QString &userID) const; Q_INVOKABLE [[nodiscard]] bool containsUser(const QString &userID) const;
Q_INVOKABLE [[nodiscard]] bool isUserBanned(const QString &user) const;
Q_INVOKABLE [[nodiscard]] bool canSendEvent(const QString &eventType) const; Q_INVOKABLE [[nodiscard]] bool canSendEvent(const QString &eventType) const;
Q_INVOKABLE [[nodiscard]] bool canSendState(const QString &eventType) const; Q_INVOKABLE [[nodiscard]] bool canSendState(const QString &eventType) const;

View File

@@ -346,12 +346,12 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
if (room->isFavourite()) { if (room->isFavourite()) {
return RoomType::Favorite; return RoomType::Favorite;
} }
if (room->isDirectChat()) {
return RoomType::Direct;
}
if (room->isLowPriority()) { if (room->isLowPriority()) {
return RoomType::Deprioritized; return RoomType::Deprioritized;
} }
if (room->isDirectChat()) {
return RoomType::Direct;
}
return RoomType::Normal; return RoomType::Normal;
} }
if (role == UnreadCountRole) { if (role == UnreadCountRole) {