Compare commits

..

1 Commits

Author SHA1 Message Date
Joshua Goins
2b1b21dd07 Add spell checking hint to ChatBar again 2023-01-05 21:52:52 -05:00
119 changed files with 26158 additions and 33377 deletions

1
.gitignore vendored
View File

@@ -9,4 +9,3 @@ compile_commands.json
kate.project.ctags.* kate.project.ctags.*
*.user *.user
.flatpak-builder/ .flatpak-builder/
.idea/

View File

@@ -6,14 +6,11 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
set(PROJECT_VERSION "23.01") project(NeoChat)
project(NeoChat VERSION ${PROJECT_VERSION}) set(PROJECT_VERSION "22.11")
set(KF5_MIN_VERSION "5.91.0") set(KF5_MIN_VERSION "5.91.0")
set(QT_MIN_VERSION "5.15.2") set(QT_MIN_VERSION "5.15.2")
if (ANDROID)
set(QT_MIN_VERSION "5.15.8")
endif()
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
@@ -125,8 +122,6 @@ set_package_properties(KF5DocTools PROPERTIES DESCRIPTION
TYPE OPTIONAL TYPE OPTIONAL
) )
find_package(Sqlite3)
if(NOT Quotient_VERSION_MINOR GREATER 6) if(NOT Quotient_VERSION_MINOR GREATER 6)
cmake_policy(SET CMP0063 OLD) cmake_policy(SET CMP0063 OLD)
endif() endif()

0
LICENSES/MIT.txt Normal file → Executable file
View File

View File

@@ -14,8 +14,7 @@
android:name="org.qtproject.qt5.android.bindings.QtActivity" android:name="org.qtproject.qt5.android.bindings.QtActivity"
android:label="NeoChat" android:label="NeoChat"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
android:launchMode="singleTop" android:launchMode="singleTop">
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
@@ -23,6 +22,7 @@
</intent-filter> </intent-filter>
<meta-data android:name="android.app.lib_name" android:value="neochat-app"/> <meta-data android:name="android.app.lib_name" android:value="neochat-app"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/> <meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/> <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/> <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
@@ -38,6 +38,8 @@
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/> <meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/> <meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Messages maps --> <!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/> <meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<!-- Splash screen --> <!-- Splash screen -->

View File

@@ -12,7 +12,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.0.2' classpath 'com.android.tools.build:gradle:3.6.4'
} }
} }
@@ -73,10 +73,6 @@ android {
defaultConfig { defaultConfig {
minSdkVersion qtMinSdkVersion minSdkVersion qtMinSdkVersion
targetSdkVersion qtTargetSdkVersion targetSdkVersion qtTargetSdkVersion
applicationId "org.kde.neochat"
namespace "org.kde.neochat"
versionCode timestamp
versionName projectVersionFull
manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp] manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp]
} }

View File

@@ -231,20 +231,6 @@
<content_attribute id="social-chat">intense</content_attribute> <content_attribute id="social-chat">intense</content_attribute>
</content_rating> </content_rating>
<releases> <releases>
<release version="23.01" date="2023-01-30">
<url>https://plasma-mobile.org/2023/01/30/january-blog-post/</url>
<description>
<p>New features and bugfixes:</p>
<ul>
<li>Notifications will now be shown for all accounts, not just the active one</li>
<li>There is a new "compact" mode for the room list</li>
<li>You can now search in the room history</li>
<li>Emojis and Reactions have been significantly improved</li>
<li>Fixed several crashes around user invitations</li>
<li>Room permission settings can now be configured</li>
</ul>
</description>
</release>
<release version="22.11" date="2022-11-30"> <release version="22.11" date="2022-11-30">
<url>https://plasma-mobile.org/2022/11/30/plasma-mobile-gear-22-11/</url> <url>https://plasma-mobile.org/2022/11/30/plasma-mobile-gear-22-11/</url>
</release> </release>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,47 +6,47 @@
add_library(neochat STATIC add_library(neochat STATIC
controller.cpp controller.cpp
actionshandler.cpp actionshandler.cpp
models/emojimodel.cpp emojimodel.cpp
emojitones.cpp emojitones.cpp
models/customemojimodel.cpp customemojimodel.cpp
clipboard.cpp clipboard.cpp
matriximageprovider.cpp matriximageprovider.cpp
models/messageeventmodel.cpp messageeventmodel.cpp
models/messagefiltermodel.cpp messagefiltermodel.cpp
models/roomlistmodel.cpp roomlistmodel.cpp
models/sortfilterspacelistmodel.cpp sortfilterspacelistmodel.cpp
spacehierarchycache.cpp spacehierarchycache.cpp
roommanager.cpp roommanager.cpp
neochatroom.cpp neochatroom.cpp
neochatuser.cpp neochatuser.cpp
models/userlistmodel.cpp userlistmodel.cpp
models/userfiltermodel.cpp userfiltermodel.cpp
models/publicroomlistmodel.cpp publicroomlistmodel.cpp
models/userdirectorylistmodel.cpp userdirectorylistmodel.cpp
models/keywordnotificationrulemodel.cpp keywordnotificationrulemodel.cpp
utils.cpp utils.cpp
notificationsmanager.cpp notificationsmanager.cpp
models/sortfilterroomlistmodel.cpp sortfilterroomlistmodel.cpp
chatdocumenthandler.cpp chatdocumenthandler.cpp
models/devicesmodel.cpp devicesmodel.cpp
filetypesingleton.cpp filetypesingleton.cpp
login.cpp login.cpp
stickerevent.cpp stickerevent.cpp
models/webshortcutmodel.cpp webshortcutmodel.cpp
blurhash.cpp blurhash.cpp
blurhashimageprovider.cpp blurhashimageprovider.cpp
joinrulesevent.cpp joinrulesevent.cpp
models/collapsestateproxymodel.cpp collapsestateproxymodel.cpp
urlhelper.cpp urlhelper.cpp
windowcontroller.cpp windowcontroller.cpp
linkpreviewer.cpp linkpreviewer.cpp
models/completionmodel.cpp completionmodel.cpp
models/completionproxymodel.cpp completionproxymodel.cpp
models/actionsmodel.cpp actionsmodel.cpp
models/serverlistmodel.cpp serverlistmodel.cpp
models/statemodel.cpp statemodel.cpp
filetransferpseudojob.cpp filetransferpseudojob.cpp
models/searchmodel.cpp searchmodel.cpp
) )
add_executable(neochat-app add_executable(neochat-app
@@ -103,9 +103,6 @@ endif()
if(ANDROID) if(ANDROID)
target_sources(neochat PRIVATE notifyrc.qrc) target_sources(neochat PRIVATE notifyrc.qrc)
target_link_libraries(neochat PRIVATE Qt::Svg OpenSSL::SSL) target_link_libraries(neochat PRIVATE Qt::Svg OpenSSL::SSL)
if(SQLite3_FOUND)
target_link_libraries(neochat-app PRIVATE SQLite::SQLite3)
endif()
target_sources(neochat-app PRIVATE notifyrc.qrc) target_sources(neochat-app PRIVATE notifyrc.qrc)
target_link_libraries(neochat PUBLIC Qt::Svg OpenSSL::SSL) target_link_libraries(neochat PUBLIC Qt::Svg OpenSSL::SSL)
kirigami_package_breeze_icons(ICONS kirigami_package_breeze_icons(ICONS

View File

@@ -13,9 +13,9 @@
#include <KLocalizedString> #include <KLocalizedString>
#include <QStringBuilder> #include <QStringBuilder>
#include "actionsmodel.h"
#include "controller.h" #include "controller.h"
#include "models/actionsmodel.h" #include "customemojimodel.h"
#include "models/customemojimodel.h"
#include "neochatconfig.h" #include "neochatconfig.h"
#include "neochatuser.h" #include "neochatuser.h"
#include "roommanager.h" #include "roommanager.h"

View File

@@ -158,12 +158,11 @@ QVector<ActionsModel::Action> actions{
return QString(); return QString();
} }
#ifdef QUOTIENT_07 #ifdef QUOTIENT_07
const RoomMemberEvent *roomMemberEvent = room->currentState().get<RoomMemberEvent>(text); if (room->currentState().get<RoomMemberEvent>(text)->membership() == Membership::Invite) {
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Invite) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text)); Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
return QString(); return QString();
} }
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Ban) { if (room->currentState().get<RoomMemberEvent>(text)->membership() == Membership::Ban) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text)); Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
return QString(); return QString();
} }

View File

@@ -14,9 +14,9 @@
#include <Sonnet/BackgroundChecker> #include <Sonnet/BackgroundChecker>
#include <Sonnet/Settings> #include <Sonnet/Settings>
#include "models/actionsmodel.h" #include "actionsmodel.h"
#include "models/roomlistmodel.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "roomlistmodel.h"
class SyntaxHighlighter : public QSyntaxHighlighter class SyntaxHighlighter : public QSyntaxHighlighter
{ {
@@ -105,7 +105,7 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
{ {
connect(this, &ChatDocumentHandler::roomChanged, this, [this]() { connect(this, &ChatDocumentHandler::roomChanged, this, [this]() {
m_completionModel->setRoom(m_room); m_completionModel->setRoom(m_room);
static QPointer<NeoChatRoom> previousRoom = nullptr; static NeoChatRoom *previousRoom = nullptr;
if (previousRoom) { if (previousRoom) {
disconnect(previousRoom, &NeoChatRoom::chatBoxTextChanged, this, nullptr); disconnect(previousRoom, &NeoChatRoom::chatBoxTextChanged, this, nullptr);
} }

View File

@@ -7,8 +7,8 @@
#include <QQuickTextDocument> #include <QQuickTextDocument>
#include <QTextCursor> #include <QTextCursor>
#include "models/completionmodel.h" #include "completionmodel.h"
#include "models/userlistmodel.h" #include "userlistmodel.h"
class QTextDocument; class QTextDocument;
class NeoChatRoom; class NeoChatRoom;

View File

@@ -33,14 +33,13 @@ QImage Clipboard::image() const
QString Clipboard::saveImage(QString localPath) const QString Clipboard::saveImage(QString localPath) const
{ {
QString imageDir(QStringLiteral("%1/screenshots").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))); if (!QDir().exists(QStringLiteral("%1/screenshots").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)))) {
QDir().mkdir(QStringLiteral("%1/screenshots").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
if (!QDir().exists(imageDir)) {
QDir().mkdir(imageDir);
} }
if (localPath.isEmpty()) { if (localPath.isEmpty()) {
localPath = QStringLiteral("file://%1/%2.png").arg(imageDir, QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd-hh-mm-ss"))); localPath = QStringLiteral("file://%1/screenshots/%2.png")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation),
QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd-hh-mm-ss")));
} }
QUrl url(localPath); QUrl url(localPath);
if (!url.isLocalFile()) { if (!url.isLocalFile()) {
@@ -52,11 +51,14 @@ QString Clipboard::saveImage(QString localPath) const
return {}; return {};
} }
if (image.save(url.toLocalFile())) { QDir dir;
return localPath; if (!dir.exists(QFileInfo(url.fileName()).absoluteFilePath())) {
} else { dir.mkpath(QFileInfo(url.fileName()).absoluteFilePath());
return {};
} }
image.save(url.toLocalFile());
return localPath;
} }
void Clipboard::saveText(QString message) void Clipboard::saveText(QString message)

View File

@@ -128,7 +128,16 @@ Controller::Controller(QObject *parent)
if (AccountRegistry::instance().size() > oldAccountCount) { if (AccountRegistry::instance().size() > oldAccountCount) {
auto connection = AccountRegistry::instance().accounts()[AccountRegistry::instance().size() - 1]; auto connection = AccountRegistry::instance().accounts()[AccountRegistry::instance().size() - 1];
connect(connection, &Connection::syncDone, this, [=]() { connect(connection, &Connection::syncDone, this, [=]() {
handleNotifications(connection); bool changes = false;
for (const auto &room : connection->allRooms()) {
if (m_notificationCounts[room] != room->unreadStats().notableCount) {
m_notificationCounts[room] = room->unreadStats().notableCount;
changes = true;
}
}
if (changes) {
handleNotifications();
}
}); });
} }
oldAccountCount = AccountRegistry::instance().size(); oldAccountCount = AccountRegistry::instance().size();
@@ -137,16 +146,19 @@ Controller::Controller(QObject *parent)
} }
#ifdef QUOTIENT_07 #ifdef QUOTIENT_07
void Controller::handleNotifications(QPointer<Quotient::Connection> connection) void Controller::handleNotifications()
{ {
static QStringList initial; static bool initial = true;
static QStringList oldNotifications; static QStringList oldNotifications;
auto job = connection->callApi<GetNotificationsJob>(); if (!m_connection) {
return;
}
auto job = m_connection->callApi<GetNotificationsJob>();
connect(job, &BaseJob::success, this, [job, connection]() { connect(job, &BaseJob::success, this, [this, job]() {
const auto notifications = job->jsonData()["notifications"].toArray(); const auto notifications = job->jsonData()["notifications"].toArray();
if (!initial.contains(connection->user()->id())) { if (initial) {
initial.append(connection->user()->id()); initial = false;
for (const auto &n : notifications) { for (const auto &n : notifications) {
oldNotifications += n.toObject()["event"].toObject()["event_id"].toString(); oldNotifications += n.toObject()["event"].toObject()["event_id"].toString();
} }
@@ -162,7 +174,7 @@ void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
continue; continue;
} }
oldNotifications += notification["event"].toObject()["event_id"].toString(); oldNotifications += notification["event"].toObject()["event_id"].toString();
auto room = connection->room(notification["room_id"].toString()); auto room = m_connection->room(notification["room_id"].toString());
// If room exists, room is NOT active OR the application is NOT active, show notification // If room exists, room is NOT active OR the application is NOT active, show notification
if (room && !(room->id() == RoomManager::instance().currentRoom()->id() && QGuiApplication::applicationState() == Qt::ApplicationActive)) { if (room && !(room->id() == RoomManager::instance().currentRoom()->id() && QGuiApplication::applicationState() == Qt::ApplicationActive)) {
@@ -184,7 +196,7 @@ void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
if (notification["event"]["type"] == "m.room.encrypted") { if (notification["event"]["type"] == "m.room.encrypted") {
#ifdef Quotient_E2EE_ENABLED #ifdef Quotient_E2EE_ENABLED
auto decrypted = connection->decryptNotification(notification); auto decrypted = m_connection->decryptNotification(notification);
body = decrypted["content"].toObject()["body"].toString(); body = decrypted["content"].toObject()["body"].toString();
#endif #endif
if (body.isEmpty()) { if (body.isEmpty()) {
@@ -369,11 +381,6 @@ void Controller::invokeLogin()
if (error == "Unrecognised access token") { if (error == "Unrecognised access token") {
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked")); Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
logout(connection, false); logout(connection, false);
} else if (error == "Connection closed") {
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
// Failed due to network connection issue. This might happen when the homeserver is
// temporary down, or the user trying to re-launch NeoChat in a network that cannot
// connect to the homeserver. In this case, we don't want to do logout().
} else { } else {
Q_EMIT errorOccured(i18n("Login Failed: %1", error)); Q_EMIT errorOccured(i18n("Login Failed: %1", error));
logout(connection, true); logout(connection, true);

View File

@@ -119,7 +119,7 @@ private:
bool hasWindowSystem() const; bool hasWindowSystem() const;
#ifdef QUOTIENT_07 #ifdef QUOTIENT_07
void handleNotifications(QPointer<Quotient::Connection> connection); void handleNotifications();
#endif #endif
private Q_SLOTS: private Q_SLOTS:

View File

@@ -4,8 +4,8 @@
#pragma once #pragma once
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QRegularExpression>
#include <memory> #include <memory>
#include <QRegularExpression>
struct CustomEmoji { struct CustomEmoji {
QString name; // with :semicolons: QString name; // with :semicolons:

View File

@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LGPL-2.0-or-later // SPDX-License-Identifier: LGPL-2.0-or-later
#include "emojitones.h" #include "emojitones.h"
#include "models/emojimodel.h" #include "emojimodel.h"
QMultiHash<QString, QVariant> EmojiTones::_tones = { QMultiHash<QString, QVariant> EmojiTones::_tones = {
#include "emojitones_data.h" #include "emojitones_data.h"

View File

@@ -42,39 +42,39 @@
#include "blurhashimageprovider.h" #include "blurhashimageprovider.h"
#include "chatdocumenthandler.h" #include "chatdocumenthandler.h"
#include "clipboard.h" #include "clipboard.h"
#include "collapsestateproxymodel.h"
#include "controller.h" #include "controller.h"
#include "customemojimodel.h"
#include "devicesmodel.h"
#include "emojimodel.h"
#include "filetypesingleton.h" #include "filetypesingleton.h"
#include "joinrulesevent.h" #include "joinrulesevent.h"
#include "linkpreviewer.h" #include "linkpreviewer.h"
#include "keywordnotificationrulemodel.h"
#include "login.h" #include "login.h"
#include "matriximageprovider.h" #include "matriximageprovider.h"
#include "models/collapsestateproxymodel.h" #include "messageeventmodel.h"
#include "models/customemojimodel.h" #include "messagefiltermodel.h"
#include "models/devicesmodel.h"
#include "models/emojimodel.h"
#include "models/keywordnotificationrulemodel.h"
#include "models/messageeventmodel.h"
#include "models/messagefiltermodel.h"
#include "models/publicroomlistmodel.h"
#include "models/roomlistmodel.h"
#include "models/searchmodel.h"
#include "models/serverlistmodel.h"
#include "models/sortfilterroomlistmodel.h"
#include "models/sortfilterspacelistmodel.h"
#include "models/userdirectorylistmodel.h"
#include "models/userfiltermodel.h"
#include "models/userlistmodel.h"
#include "models/webshortcutmodel.h"
#include "neochatconfig.h" #include "neochatconfig.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "neochatuser.h" #include "neochatuser.h"
#include "notificationsmanager.h" #include "notificationsmanager.h"
#include "searchmodel.h"
#ifdef QUOTIENT_07 #ifdef QUOTIENT_07
#include "pollhandler.h" #include "pollhandler.h"
#endif #endif
#include "publicroomlistmodel.h"
#include "roomlistmodel.h"
#include "roommanager.h" #include "roommanager.h"
#include "serverlistmodel.h"
#include "sortfilterroomlistmodel.h"
#include "sortfilterspacelistmodel.h"
#include "spacehierarchycache.h" #include "spacehierarchycache.h"
#include "urlhelper.h" #include "urlhelper.h"
#include "userdirectorylistmodel.h"
#include "userfiltermodel.h"
#include "userlistmodel.h"
#include "webshortcutmodel.h"
#include "windowcontroller.h" #include "windowcontroller.h"
#ifdef QUOTIENT_07 #ifdef QUOTIENT_07
#include <keyverificationsession.h> #include <keyverificationsession.h>
@@ -82,9 +82,9 @@
#ifdef HAVE_COLORSCHEME #ifdef HAVE_COLORSCHEME
#include "colorschemer.h" #include "colorschemer.h"
#endif #endif
#include "models/completionmodel.h" #include "completionmodel.h"
#include "models/statemodel.h"
#include "neochatuser.h" #include "neochatuser.h"
#include "statemodel.h"
#ifdef HAVE_RUNNER #ifdef HAVE_RUNNER
#include "runner.h" #include "runner.h"

View File

@@ -439,7 +439,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
case EventTypeRole: case EventTypeRole:
return DelegateType::ReadMarker; return DelegateType::ReadMarker;
case TimeRole: { case TimeRole: {
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime(); const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime();
const KFormat format; const KFormat format;
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat); return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
} }

View File

@@ -25,6 +25,10 @@
<label>Background transparency value</label> <label>Background transparency value</label>
<default>0.3</default> <default>0.3</default>
</entry> </entry>
<entry name="ShowNotifications" type="bool">
<label>Show notifications</label>
<default>true</default>
</entry>
<entry name="MergeRoomList" type="bool"> <entry name="MergeRoomList" type="bool">
<label>Merge Room Lists</label> <label>Merge Room Lists</label>
<default>false</default> <default>false</default>

View File

@@ -11,12 +11,6 @@
#include <KNotification> #include <KNotification>
#include <KNotificationReplyAction> #include <KNotificationReplyAction>
#ifdef QUOTIENT_07
#include <accountregistry.h>
#else
#include "neochataccountregistry.h"
#endif
#include <connection.h> #include <connection.h>
#include <csapi/pushrules.h> #include <csapi/pushrules.h>
#include <jobs/basejob.h> #include <jobs/basejob.h>
@@ -55,6 +49,10 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
const QString &replyEventId, const QString &replyEventId,
bool canReply) bool canReply)
{ {
if (!NeoChatConfig::self()->showNotifications()) {
return;
}
QPixmap img; QPixmap img;
img.convertFromImage(icon); img.convertFromImage(icon);
KNotification *notification = new KNotification("message"); KNotification *notification = new KNotification("message");
@@ -70,15 +68,8 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
notification->setDefaultAction(i18n("Open NeoChat in this room")); notification->setDefaultAction(i18n("Open NeoChat in this room"));
connect(notification, &KNotification::defaultActivated, this, [=]() { connect(notification, &KNotification::defaultActivated, this, [=]() {
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
if (room->localUser()->id() != Controller::instance().activeConnection()->userId()) {
#ifdef QUOTIENT_07
Controller::instance().setActiveConnection(Accounts.get(room->localUser()->id()));
#else
Controller::instance().setActiveConnection(AccountRegistry::instance().get(room->localUser()->id()));
#endif
}
RoomManager::instance().enterRoom(room); RoomManager::instance().enterRoom(room);
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
}); });
if (canReply) { if (canReply) {
@@ -99,6 +90,9 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
void NotificationsManager::postInviteNotification(NeoChatRoom *room, const QString &title, const QString &sender, const QImage &icon) void NotificationsManager::postInviteNotification(NeoChatRoom *room, const QString &title, const QString &sender, const QImage &icon)
{ {
if (!NeoChatConfig::self()->showNotifications()) {
return;
}
QPixmap img; QPixmap img;
img.convertFromImage(icon); img.convertFromImage(icon);
KNotification *notification = new KNotification("invite"); KNotification *notification = new KNotification("invite");
@@ -108,9 +102,9 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *room, const QStri
notification->setFlags(KNotification::Persistent); notification->setFlags(KNotification::Persistent);
notification->setDefaultAction(i18n("Open this invitation in NeoChat")); notification->setDefaultAction(i18n("Open this invitation in NeoChat"));
connect(notification, &KNotification::defaultActivated, this, [=]() { connect(notification, &KNotification::defaultActivated, this, [=]() {
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
notification->close(); notification->close();
RoomManager::instance().enterRoom(room); RoomManager::instance().enterRoom(room);
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
}); });
notification->setActions({i18n("Accept Invitation"), i18n("Reject Invitation")}); notification->setActions({i18n("Accept Invitation"), i18n("Reject Invitation")});
connect(notification, &KNotification::action1Activated, this, [room, notification]() { connect(notification, &KNotification::action1Activated, this, [room, notification]() {
@@ -277,9 +271,7 @@ void NotificationsManager::updateNotificationRules(const QString &type)
if (overrideRule["rule_id"] == ".m.rule.master") { if (overrideRule["rule_id"] == ".m.rule.master") {
bool ruleEnabled = overrideRule["enabled"].toBool(); bool ruleEnabled = overrideRule["enabled"].toBool();
m_globalNotificationsEnabled = !ruleEnabled; m_globalNotificationsEnabled = !ruleEnabled;
if (!m_globalNotificationsSet) { NeoChatConfig::self()->setShowNotifications(m_globalNotificationsEnabled);
m_globalNotificationsSet = true;
}
Q_EMIT globalNotificationsEnabledChanged(m_globalNotificationsEnabled); Q_EMIT globalNotificationsEnabledChanged(m_globalNotificationsEnabled);
} }

View File

@@ -33,7 +33,6 @@ class NotificationsManager : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool globalNotificationsEnabled MEMBER m_globalNotificationsEnabled WRITE setGlobalNotificationsEnabled NOTIFY globalNotificationsEnabledChanged) Q_PROPERTY(bool globalNotificationsEnabled MEMBER m_globalNotificationsEnabled WRITE setGlobalNotificationsEnabled NOTIFY globalNotificationsEnabledChanged)
Q_PROPERTY(bool globalNotificationsSet MEMBER m_globalNotificationsSet NOTIFY globalNotificationsSetChanged)
Q_PROPERTY(PushNotificationAction::Action oneToOneNotificationAction MEMBER m_oneToOneNotificationAction WRITE setOneToOneNotificationAction NOTIFY Q_PROPERTY(PushNotificationAction::Action oneToOneNotificationAction MEMBER m_oneToOneNotificationAction WRITE setOneToOneNotificationAction NOTIFY
oneToOneNotificationActionChanged) oneToOneNotificationActionChanged)
Q_PROPERTY(PushNotificationAction::Action encryptedOneToOneNotificationAction MEMBER m_encryptedOneToOneNotificationAction WRITE Q_PROPERTY(PushNotificationAction::Action encryptedOneToOneNotificationAction MEMBER m_encryptedOneToOneNotificationAction WRITE
@@ -74,8 +73,7 @@ private:
QMultiMap<QString, KNotification *> m_notifications; QMultiMap<QString, KNotification *> m_notifications;
QHash<QString, QPointer<KNotification>> m_invitations; QHash<QString, QPointer<KNotification>> m_invitations;
bool m_globalNotificationsEnabled = false; bool m_globalNotificationsEnabled;
bool m_globalNotificationsSet = false;
PushNotificationAction::Action m_oneToOneNotificationAction = PushNotificationAction::Unknown; PushNotificationAction::Action m_oneToOneNotificationAction = PushNotificationAction::Unknown;
PushNotificationAction::Action m_encryptedOneToOneNotificationAction = PushNotificationAction::Unknown; PushNotificationAction::Action m_encryptedOneToOneNotificationAction = PushNotificationAction::Unknown;
PushNotificationAction::Action m_groupChatNotificationAction = PushNotificationAction::Unknown; PushNotificationAction::Action m_groupChatNotificationAction = PushNotificationAction::Unknown;
@@ -109,7 +107,6 @@ private Q_SLOTS:
Q_SIGNALS: Q_SIGNALS:
void globalNotificationsEnabledChanged(bool newState); void globalNotificationsEnabledChanged(bool newState);
void globalNotificationsSetChanged(bool newState);
void oneToOneNotificationActionChanged(PushNotificationAction::Action action); void oneToOneNotificationActionChanged(PushNotificationAction::Action action);
void encryptedOneToOneNotificationActionChanged(PushNotificationAction::Action action); void encryptedOneToOneNotificationActionChanged(PushNotificationAction::Action action);
void groupChatNotificationActionChanged(PushNotificationAction::Action action); void groupChatNotificationActionChanged(PushNotificationAction::Action action);

View File

@@ -60,7 +60,7 @@ Comment[nl]=Rooms zoeken in NeoChat
Comment[pl]=Znajdź pokoje w NeoChat Comment[pl]=Znajdź pokoje w NeoChat
Comment[pt]=Procurar salas no NeoChat Comment[pt]=Procurar salas no NeoChat
Comment[pt_BR]=Encontrar salas no NeoChat Comment[pt_BR]=Encontrar salas no NeoChat
Comment[ru]=Поиск комнат NeoChat Comment[ru]=Поиск комнаты NeoChat
Comment[sl]=Najdi sobe v NeoChatu Comment[sl]=Najdi sobe v NeoChatu
Comment[sv]=Sök efter rum i NeoChat Comment[sv]=Sök efter rum i NeoChat
Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும் Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்

View File

@@ -10,72 +10,43 @@ import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
ColumnLayout { Loader {
id: root id: attachmentPaneLoader
signal attachmentCancelled() readonly property var attachmentMimetype: FileType.mimeTypeForUrl(attachmentPaneLoader.attachmentPath)
property string attachmentPath
readonly property var attachmentMimetype: FileType.mimeTypeForUrl(attachmentPath)
readonly property bool hasImage: attachmentMimetype.valid && FileType.supportedImageFormats.includes(attachmentMimetype.preferredSuffix) readonly property bool hasImage: attachmentMimetype.valid && FileType.supportedImageFormats.includes(attachmentMimetype.preferredSuffix)
readonly property string attachmentPath: currentRoom.chatBoxAttachmentPath
readonly property string baseFileName: attachmentPath.substring(attachmentPath.lastIndexOf('/') + 1, attachmentPath.length) readonly property string baseFileName: attachmentPath.substring(attachmentPath.lastIndexOf('/') + 1, attachmentPath.length)
RowLayout { active: visible
spacing: Kirigami.Units.smallSpacing sourceComponent: Component {
QQC2.Pane {
id: attachmentPane
Kirigami.Theme.colorSet: Kirigami.Theme.View
QQC2.Label { contentItem: Item {
Layout.fillWidth: true property real spacing: attachmentPane.spacing > 0 ? attachmentPane.spacing : toolBar.spacing
Layout.alignment: Qt.AlignLeft implicitWidth: Math.max(image.implicitWidth, imageBusyIndicator.implicitWidth, fileInfoLayout.implicitWidth, toolBar.implicitWidth)
text: i18n("Attachment:") implicitHeight: Math.max(
horizontalAlignment: Text.AlignLeft (hasImage ? Math.max(image.preferredHeight, imageBusyIndicator.implicitHeight) + spacing : 0)
verticalAlignment: Text.AlignVCenter + fileInfoLayout.implicitHeight,
} toolBar.implicitHeight
QQC2.ToolButton { )
id: editImageButton
visible: hasImage
icon.name: "document-edit"
text: i18n("Edit")
display: QQC2.AbstractButton.IconOnly
Component {
id: imageEditorPage
ImageEditorPage {
imagePath: root.attachmentPath
}
}
onClicked: {
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
imageEditor.newPathChanged.connect(function(newPath) {
applicationWindow().pageStack.layers.pop();
root.attachmentPath = newPath;
});
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
}
QQC2.ToolButton {
id: cancelAttachmentButton
display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action {
text: i18n("Cancel sending attachment")
icon.name: "dialog-close"
onTriggered: attachmentCancelled();
shortcut: "Escape"
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
}
}
Image { Image {
id: image id: image
Layout.alignment: Qt.AlignHCenter property real preferredHeight: Math.min(implicitHeight, Kirigami.Units.gridUnit * 8)
height: preferredHeight
anchors {
horizontalCenter: parent.horizontalCenter
bottom: fileInfoLayout.top
bottomMargin: parent.spacing
}
width: Math.min(implicitWidth, attachmentPane.availableWidth)
asynchronous: true asynchronous: true
cache: false // Cache is not needed. Images will rarely be shown repeatedly. cache: false // Cache is not needed. Images will rarely be shown repeatedly.
source: hasImage ? root.attachmentPath : "" smooth: height === preferredHeight && parent.height === parent.implicitHeight // Don't smooth until height animation stops
source: hasImage ? attachmentPaneLoader.attachmentPath : ""
visible: hasImage visible: hasImage
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
@@ -94,31 +65,133 @@ ColumnLayout {
Behavior on height { Behavior on height {
NumberAnimation { NumberAnimation {
property: "height"
duration: Kirigami.Units.shortDuration duration: Kirigami.Units.shortDuration
easing.type: Easing.OutCubic easing.type: Easing.OutCubic
} }
} }
} }
QQC2.BusyIndicator { QQC2.BusyIndicator {
id: imageBusyIndicator id: imageBusyIndicator
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
bottom: fileInfoLayout.top
bottomMargin: parent.spacing
}
visible: running visible: running
running: image.visible && image.progress < 1 running: image.visible && image.progress < 1
} }
RowLayout { RowLayout {
id: fileInfoLayout id: fileInfoLayout
Layout.alignment: Qt.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: undefined
anchors.bottom: parent.bottom
spacing: parent.spacing spacing: parent.spacing
Kirigami.Icon { Kirigami.Icon {
id: mimetypeIcon id: mimetypeIcon
implicitWidth: Kirigami.Units.iconSizes.smallMedium implicitHeight: Kirigami.Units.fontMetrics.roundedIconSize(fileLabel.implicitHeight)
implicitHeight: Kirigami.Units.iconSizes.smallMedium implicitWidth: implicitHeight
source: attachmentMimetype.iconName source: attachmentMimetype.iconName
} }
QQC2.Label { QQC2.Label {
id: fileLabel id: fileLabel
text: baseFileName text: baseFileName
} }
states: State {
when: !hasImage
AnchorChanges {
target: fileInfoLayout
anchors.bottom: undefined
anchors.verticalCenter: parent.verticalCenter
}
}
}
// Using a toolbar to get a button spacing consistent with what the QQC2 style normally has
// Also has some accessibility info
QQC2.ToolBar {
id: toolBar
width: parent.width
anchors.top: parent.top
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
Kirigami.Theme.inherit: true
Kirigami.Theme.colorSet: Kirigami.Theme.View
contentItem: RowLayout {
spacing: parent.spacing
QQC2.Label {
Layout.leftMargin: -attachmentPane.leftPadding
Layout.topMargin: -attachmentPane.topPadding
leftPadding: cancelAttachmentButton.leftPadding + 1 + attachmentPane.leftPadding
rightPadding: cancelAttachmentButton.rightPadding + 1
topPadding: cancelAttachmentButton.topPadding + attachmentPane.topPadding
bottomPadding: cancelAttachmentButton.bottomPadding
text: i18n("Attachment:")
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
background: Kirigami.ShadowedRectangle {
property real cornerRadius: cancelAttachmentButton.background.hasOwnProperty("radius") ?
Math.min(cancelAttachmentButton.background.radius, height/2) : 0
corners.bottomLeftRadius: toolBar.mirrored ? cornerRadius : 0
corners.bottomRightRadius: toolBar.mirrored ? 0 : cornerRadius
color: Kirigami.Theme.backgroundColor
opacity: 0.75
}
}
Item {
Layout.fillWidth: true
}
QQC2.ToolButton {
id: editImageButton
visible: hasImage
icon.name: "document-edit"
text: i18n("Edit")
display: QQC2.AbstractButton.IconOnly
Component {
id: imageEditorPage
ImageEditorPage {
imagePath: attachmentPaneLoader.attachmentPath
}
}
onClicked: {
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
imageEditor.newPathChanged.connect(function(newPath) {
applicationWindow().pageStack.layers.pop();
attachmentPaneLoader.attachmentPath = newPath;
});
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
}
QQC2.ToolButton {
id: cancelAttachmentButton
icon.name: "dialog-close"
text: i18n("Cancel sending Image")
display: QQC2.AbstractButton.IconOnly
onClicked: currentRoom.chatBoxAttachmentPath = "";
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
}
}
background: null
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
}
} }
} }

View File

@@ -5,127 +5,142 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15 as QQC2 import QtQuick.Controls 2.15 as QQC2
import QtQuick.Window 2.15
import org.kde.kirigami 2.18 as Kirigami import org.kde.kirigami 2.18 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
QQC2.Control { QQC2.ToolBar {
id: root id: chatBar
property alias inputFieldText: inputField.text
property alias textField: textField property alias textField: inputField
property bool isReplying: currentRoom.chatBoxReplyId.length > 0 property alias cursorPosition: inputField.cursorPosition
property bool isEditing: currentRoom.chatBoxEditId.length > 0
property bool replyPaneVisible: isReplying || isEditing
property NeoChatUser replyUser: currentRoom.chatBoxReplyUser ?? currentRoom.chatBoxEditUser
property bool attachmentPaneVisible: currentRoom.chatBoxAttachmentPath.length > 0
signal inputFieldForceActiveFocusTriggered()
signal messageSent() signal messageSent()
property list<Kirigami.Action> actions : [ onInputFieldForceActiveFocusTriggered: {
Kirigami.Action { inputField.forceActiveFocus();
id: attachmentAction // set the cursor to the end of the text
inputField.cursorPosition = inputField.length;
property bool isBusy: currentRoom && currentRoom.hasFileUploading
// Matrix does not allow sending attachments in replies
visible: currentRoom.chatBoxReplyId.length === 0 && currentRoom.chatBoxAttachmentPath.length === 0
icon.name: "mail-attachment"
text: i18n("Attach an image or file")
displayHint: Kirigami.DisplayHint.IconOnly
onTriggered: {
if (Clipboard.hasImage) {
attachDialog.open()
} else {
var fileDialog = openFileDialog.createObject(QQC2.ApplicationWindow.overlay)
fileDialog.chosen.connect((path) => {
if (!path) {
return;
}
currentRoom.chatBoxAttachmentPath = path;
})
fileDialog.open()
}
} }
tooltip: text position: QQC2.ToolBar.Footer
},
Kirigami.Action {
id: emojiAction
property bool isBusy: false Kirigami.Theme.colorSet: Kirigami.Theme.View
icon.name: "smiley" // Using a custom background because some styles like Material
text: i18n("Add an Emoji") // or Fusion might have ugly colors for a TextArea placed inside
displayHint: Kirigami.DisplayHint.IconOnly // of a toolbar. ToolBar is otherwise the closest QQC2 component
checkable: true // to what we want because of the padding and spacing values.
background: Rectangle {
onTriggered: { color: Kirigami.Theme.backgroundColor
if (emojiDialog.visible) {
emojiDialog.close()
} else {
emojiDialog.open()
}
} }
tooltip: text contentItem: RowLayout {
}, spacing: chatBar.spacing
Kirigami.Action {
id: sendAction
property bool isBusy: false QQC2.ScrollView {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.minimumHeight: inputField.implicitHeight
// lineSpacing is height+leading, so subtract leading once since leading only exists between lines.
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
+ inputField.topPadding + inputField.bottomPadding
icon.name: "document-send" // HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
text: i18n("Send message") QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
displayHint: Kirigami.DisplayHint.IconOnly
checkable: true
onTriggered: { FontMetrics {
root.postMessage() id: fontMetrics
} font: inputField.font
tooltip: text
}
]
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
contentItem: QQC2.ScrollView {
id: chatBarScrollView
property var textFieldHeight: textField.height
property var visualLeftPadding: (root.width - chatBoxMaxWidth) / 2 - (root.width > chatBoxMaxWidth ? Kirigami.Units.largeSpacing : 0)
property var visualRightPadding: (root.width - chatBoxMaxWidth) / 2 + (root.width > chatBoxMaxWidth ? Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing : 0)
leftPadding: LayoutMirroring.enabled ? visualRightPadding : visualLeftPadding
rightPadding: LayoutMirroring.enabled ? visualLeftPadding : visualRightPadding
// HACK: This is to stop the ScrollBar flickering on and off as the height is increased
QQC2.ScrollBar.vertical.policy: chatBarHeightAnimation.running && implicitHeight <= height ? QQC2.ScrollBar.AlwaysOff : QQC2.ScrollBar.AsNeeded
Behavior on implicitHeight {
NumberAnimation {
id: chatBarHeightAnimation
duration: Kirigami.Units.shortDuration
easing.type: Easing.InOutCubic
}
} }
QQC2.TextArea { QQC2.TextArea {
id: textField id: inputField
focus: true
/* Some QQC2 styles will have their own predefined backgrounds for TextAreas.
* Make sure there is no background since we are using the ToolBar background.
*
* This could cause a problem if the QQC2 style was designed around TextArea
* 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.
*/
background: MouseArea {
acceptedButtons: Qt.NoButton
cursorShape: Qt.IBeamCursor
z: 1
}
topPadding: Kirigami.Units.largeSpacing + (paneLoader.visible ? paneLoader.height : 0) leftPadding: mirrored ? 0 : Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing rightPadding: !mirrored ? 0 : Kirigami.Units.largeSpacing
leftPadding: LayoutMirroring.enabled ? actionsRow.width : (root.width > chatBoxMaxWidth ? 0 : Kirigami.Units.largeSpacing) topPadding: 0
rightPadding: LayoutMirroring.enabled ? (root.width > chatBoxMaxWidth ? 0 : Kirigami.Units.largeSpacing) : actionsRow.width bottomPadding: 0
placeholderText: readOnly ? i18n("This room is encrypted. Build libQuotient with encryption enabled to send encrypted messages.") : currentRoom.chatBoxEditId.length > 0 ? i18n("Edit Message") : currentRoom.usesEncryption ? i18n("Send an encrypted message…") : currentRoom.chatBoxAttachmentPath.length > 0 ? i18n("Set an attachment caption...") : i18n("Send a message…") placeholderText: readOnly ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : currentRoom.chatBoxEditId.length > 0 ? i18n("Edit Message") : currentRoom.usesEncryption ? i18n("Send an encrypted message…") : i18n("Send a message…")
verticalAlignment: TextEdit.AlignVCenter verticalAlignment: TextEdit.AlignVCenter
horizontalAlignment: TextEdit.AlignLeft
wrapMode: Text.Wrap wrapMode: Text.Wrap
readOnly: (currentRoom.usesEncryption && !Controller.encryptionSupported) readOnly: currentRoom.usesEncryption && !Controller.encryptionSupported
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
Kirigami.SpellChecking.enabled: true
color: Kirigami.Theme.textColor
selectionColor: Kirigami.Theme.highlightColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
hoverEnabled: !Kirigami.Settings.tabletMode
selectByMouse: !Kirigami.Settings.tabletMode
Keys.onEnterPressed: {
if (completionMenu.visible) {
completionMenu.complete()
} else if (event.modifiers & Qt.ShiftModifier) {
inputField.insert(cursorPosition, "\n")
} else {
chatBar.postMessage();
}
}
Keys.onReturnPressed: {
if (completionMenu.visible) {
completionMenu.complete()
} else if (event.modifiers & Qt.ShiftModifier) {
inputField.insert(cursorPosition, "\n")
} else {
chatBar.postMessage();
}
}
Keys.onTabPressed: {
if (completionMenu.visible) {
completionMenu.complete()
}
}
Keys.onPressed: {
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
chatBar.pasteImage();
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
let replyEvent = messageEventModel.getLatestMessageFromIndex(0)
if (replyEvent && replyEvent["event_id"]) {
currentRoom.chatBoxReplyId = replyEvent["event_id"]
}
} else if (event.key === Qt.Key_Up && inputField.text.length === 0) {
let editEvent = messageEventModel.getLastLocalUserMessageEventId()
if (editEvent) {
currentRoom.chatBoxEditId = editEvent["event_id"]
}
} else if (event.key === Qt.Key_Up && completionMenu.visible) {
completionMenu.decrementIndex()
} else if (event.key === Qt.Key_Down && completionMenu.visible) {
completionMenu.incrementIndex()
} else if (event.key === Qt.Key_Backspace && inputField.text.length <= 1) {
currentRoom.sendTypingNotification(false)
repeatTimer.stop()
}
}
Timer { Timer {
id: repeatTimer id: repeatTimer
@@ -140,194 +155,86 @@ QQC2.Control {
} }
currentRoom.chatBoxText = text currentRoom.chatBoxText = text
} }
onCursorRectangleChanged: chatBarScrollView.ensureVisible(cursorRectangle) }
}
Keys.onEnterPressed: { Item {
if (completionMenu.visible) { visible: currentRoom.chatBoxReplyId.length === 0 && (currentRoom.chatBoxAttachmentPath.length === 0 || uploadingBusySpinner.running)
completionMenu.complete() implicitWidth: uploadButton.implicitWidth
} else if (event.modifiers & Qt.ShiftModifier) { implicitHeight: uploadButton.implicitHeight
textField.insert(cursorPosition, "\n") QQC2.ToolButton {
id: uploadButton
anchors.fill: parent
// Matrix does not allow sending attachments in replies
visible: currentRoom.chatBoxReplyId.length === 0 && currentRoom.chatBoxAttachmentPath.length === 0 && !uploadingBusySpinner.running
icon.name: "mail-attachment"
text: i18n("Attach an image or file")
display: QQC2.AbstractButton.IconOnly
onClicked: {
if (Clipboard.hasImage) {
attachDialog.open()
} else { } else {
chatBar.postMessage(); var fileDialog = openFileDialog.createObject(QQC2.ApplicationWindow.overlay)
fileDialog.chosen.connect((path) => {
if (!path) {
return;
} }
} currentRoom.chatBoxAttachmentPath = path;
Keys.onReturnPressed: { })
if (completionMenu.visible) { fileDialog.open()
completionMenu.complete()
} else if (event.modifiers & Qt.ShiftModifier) {
textField.insert(cursorPosition, "\n")
} else {
chatBar.postMessage();
}
}
Keys.onTabPressed: {
if (completionMenu.visible) {
completionMenu.complete()
}
}
Keys.onPressed: {
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
chatBar.pasteImage();
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
let replyEvent = messageEventModel.getLatestMessageFromIndex(0)
if (replyEvent && replyEvent["event_id"]) {
currentRoom.chatBoxReplyId = replyEvent["event_id"]
}
} else if (event.key === Qt.Key_Up && textField.text.length === 0) {
let editEvent = messageEventModel.getLastLocalUserMessageEventId()
if (editEvent) {
currentRoom.chatBoxEditId = editEvent["event_id"]
}
} else if (event.key === Qt.Key_Up && completionMenu.visible) {
completionMenu.decrementIndex()
} else if (event.key === Qt.Key_Down && completionMenu.visible) {
completionMenu.incrementIndex()
} else if (event.key === Qt.Key_Backspace && textField.text.length <= 1) {
currentRoom.sendTypingNotification(false)
repeatTimer.stop()
} }
} }
Loader { QQC2.ToolTip.text: text
id: paneLoader QQC2.ToolTip.visible: hovered
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: root.width > chatBoxMaxWidth ? 0 : Kirigami.Units.largeSpacing
anchors.right: parent.right
anchors.rightMargin: root.width > chatBoxMaxWidth ? 0 : (chatBarScrollView.QQC2.ScrollBar.vertical.visible ? Kirigami.Units.largeSpacing * 3.5 : Kirigami.Units.largeSpacing)
active: visible
visible: root.replyPaneVisible || root.attachmentPaneVisible
sourceComponent: root.replyPaneVisible ? replyPane : attachmentPane
}
Component {
id: replyPane
ReplyPane {
userName: root.replyUser ? root.replyUser.displayName : ""
userColor: root.replyUser ? root.replyUser.color : ""
userAvatar: root.replyUser ? "image://mxc/" + currentRoom.getUser(root.replyUser.id).avatarMediaId : ""
isReply: root.isReplying
text: isEditing ? currentRoom.chatBoxEditMessage : currentRoom.chatBoxReplyMessage
}
}
Component {
id: attachmentPane
AttachmentPane {
attachmentPath: currentRoom.chatBoxAttachmentPath
onAttachmentCancelled: {
currentRoom.chatBoxAttachmentPath = "";
root.forceActiveFocus()
}
}
}
background: MouseArea {
acceptedButtons: Qt.NoButton
cursorShape: Qt.IBeamCursor
z: 1
}
}
/**
* Because of the paneLoader we have to manage the scroll
* position manually or it doesn't keep the cursor visible properly all the time.
*/
function ensureVisible(r) {
// Find the child that is the Flickable created by ScrollView.
let flickable = undefined;
for (var index in children) {
if (children[index] instanceof Flickable) {
flickable = children[index];
}
}
if (flickable) {
if (flickable.contentX >= r.x) {
flickable.contentX = r.x;
} else if (flickable.contentX + width <= r.x + r.width) {
flickable.contentX = r.x + r.width - width;
} if (flickable.contentY >= r.y) {
flickable.contentY = r.y;
} else if (flickable.contentY + height <= r.y + r.height) {
flickable.contentY = r.y + r.height - height + textField.bottomPadding;
}
} }
QQC2.BusyIndicator {
id: uploadingBusySpinner
anchors.fill: parent
visible: running
running: currentRoom && currentRoom.hasFileUploading
} }
} }
QQC2.ToolButton { QQC2.ToolButton {
id: cancelButton id: emojiButton
anchors.top: parent.top icon.name: "smiley"
anchors.right: parent.right text: i18n("Add an Emoji")
anchors.rightMargin: (root.width - chatBoxMaxWidth) / 2 + Kirigami.Units.largeSpacing + (chatBarScrollView.QQC2.ScrollBar.vertical.visible && !(root.width > chatBoxMaxWidth) ? Kirigami.Units.largeSpacing * 2.5 : 0)
visible: root.replyPaneVisible
display: QQC2.AbstractButton.IconOnly display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action { checkable: true
text: root.isReplying ? i18nc("@action:button", "Cancel reply") : i18nc("@action:button", "Cancel edit")
icon.name: "dialog-close" onClicked: {
onTriggered: { if (emojiDialog.visible) {
currentRoom.chatBoxReplyId = ""; emojiDialog.close()
currentRoom.chatBoxEditId = ""; } else {
currentRoom.chatBoxAttachmentPath = ""; emojiDialog.open()
root.forceActiveFocus()
} }
shortcut: "Escape"
} }
QQC2.ToolTip.text: text QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered QQC2.ToolTip.visible: hovered
} }
Row {
id: actionsRow
padding: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
anchors.right: parent.right
property var requiredMargin: (root.width - chatBoxMaxWidth) / 2 + Kirigami.Units.largeSpacing + (chatBarScrollView.QQC2.ScrollBar.vertical.visible && !(root.width > chatBoxMaxWidth) ? Kirigami.Units.largeSpacing * 2.5 : 0)
anchors.leftMargin: layoutDirection === Qt.RightToLeft ? requiredMargin : 0
anchors.rightMargin: layoutDirection === Qt.RightToLeft ? 0 : requiredMargin
anchors.bottom: parent.bottom
anchors.bottomMargin: Kirigami.Units.largeSpacing - 2
Repeater { QQC2.ToolButton {
model: root.actions id: sendButton
Kirigami.Icon { icon.name: "document-send"
implicitWidth: Kirigami.Units.iconSizes.smallMedium text: i18n("Send message")
implicitHeight: Kirigami.Units.iconSizes.smallMedium display: QQC2.AbstractButton.IconOnly
source: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source) onClicked: {
active: actionArea.containsPress chatBar.postMessage()
visible: modelData.visible
enabled: modelData.enabled
MouseArea {
id: actionArea
anchors.fill: parent
onClicked: modelData.trigger()
cursorShape: Qt.PointingHandCursor
} }
QQC2.ToolTip.visible: modelData.tooltip !== "" && hoverHandler.hovered QQC2.ToolTip.text: text
QQC2.ToolTip.text: modelData.tooltip QQC2.ToolTip.visible: hovered
HoverHandler { id: hoverHandler }
QQC2.BusyIndicator {
anchors.fill: parent
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
visible: running
running: modelData.isBusy
}
}
} }
} }
EmojiDialog { EmojiDialog {
id: emojiDialog id: emojiDialog
x: parent.width - implicitWidth x: parent.width - implicitWidth
y: -implicitHeight // - Kirigami.Units.smallSpacing y: -implicitHeight - Kirigami.Units.smallSpacing
modal: false modal: false
includeCustom: true includeCustom: true
@@ -337,10 +244,6 @@ QQC2.Control {
onClosed: if (emojiButton.checked) emojiButton.checked = false onClosed: if (emojiButton.checked) emojiButton.checked = false
} }
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
CompletionMenu { CompletionMenu {
id: completionMenu id: completionMenu
height: implicitHeight height: implicitHeight
@@ -360,34 +263,22 @@ QQC2.Control {
target: currentRoom target: currentRoom
function onChatBoxEditIdChanged() { function onChatBoxEditIdChanged() {
if (currentRoom.chatBoxEditMessage.length > 0) { if (currentRoom.chatBoxEditMessage.length > 0) {
textField.text = currentRoom.chatBoxEditMessage chatBar.inputFieldText = currentRoom.chatBoxEditMessage
} }
} }
} }
ChatDocumentHandler { ChatDocumentHandler {
id: documentHandler id: documentHandler
document: textField.textDocument document: inputField.textDocument
cursorPosition: textField.cursorPosition cursorPosition: inputField.cursorPosition
selectionStart: textField.selectionStart selectionStart: inputField.selectionStart
selectionEnd: textField.selectionEnd selectionEnd: inputField.selectionEnd
Component.onCompleted: { Component.onCompleted: {
RoomManager.chatDocumentHandler = documentHandler; RoomManager.chatDocumentHandler = documentHandler;
} }
} }
function forceActiveFocus() {
textField.forceActiveFocus();
// set the cursor to the end of the text
textField.cursorPosition = textField.length;
}
function insertText(text) {
let initialCursorPosition = textField.cursorPosition;
textField.text = textField.text.substr(0, initialCursorPosition) + text + textField.text.substr(initialCursorPosition)
textField.cursorPosition = initialCursorPosition + text.length
}
function pasteImage() { function pasteImage() {
let localPath = Clipboard.saveImage(); let localPath = Clipboard.saveImage();
@@ -401,7 +292,7 @@ QQC2.Control {
actionsHandler.handleMessage(); actionsHandler.handleMessage();
repeatTimer.stop() repeatTimer.stop()
currentRoom.markAllMessagesAsRead(); currentRoom.markAllMessagesAsRead();
textField.clear(); inputField.clear();
currentRoom.chatBoxReplyId = ""; currentRoom.chatBoxReplyId = "";
currentRoom.chatBoxEditId = ""; currentRoom.chatBoxEditId = "";
messageSent() messageSent()

View File

@@ -5,52 +5,108 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2 import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
ColumnLayout { ColumnLayout {
id: chatBox id: chatBox
property alias inputFieldText: chatBar.inputFieldText
signal messageSent() signal messageSent()
property alias chatBar: chatBar
readonly property int extraWidth: width >= Kirigami.Units.gridUnit * 47 ? Math.min((width - Kirigami.Units.gridUnit * 47), Kirigami.Units.gridUnit * 20) : 0
readonly property int chatBoxMaxWidth: Config.compactLayout ? width : Math.min(width, Kirigami.Units.gridUnit * 39 + extraWidth)
spacing: 0 spacing: 0
Kirigami.InlineMessage { Kirigami.Separator {
id: connectionPaneSeparator
visible: connectionPane.visible
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 1 // So we can see the border }
Layout.rightMargin: 1 // So we can see the border
text: i18n("NeoChat is offline. Please check your network connection.") QQC2.Pane {
id: connectionPane
padding: fontMetrics.lineSpacing * 0.25
FontMetrics {
id: fontMetrics
font: networkLabel.font
}
spacing: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
visible: !Controller.isOnline visible: !Controller.isOnline
Layout.fillWidth: true
QQC2.Label {
id: networkLabel
width: parent.width
wrapMode: Text.Wrap
text: i18n("NeoChat is offline. Please check your network connection.")
}
} }
Kirigami.Separator { Kirigami.Separator {
id: replySeparator
visible: replyPane.visible
Layout.fillWidth: true
}
ReplyPane {
id: replyPane
visible: currentRoom.chatBoxReplyId.length > 0 || currentRoom.chatBoxEditId.length > 0
Layout.fillWidth: true
onReplyCancelled: {
chatBox.focusInputField()
}
}
Kirigami.Separator {
id: attachmentSeparator
visible: attachmentPane.visible
Layout.fillWidth: true
}
AttachmentPane {
id: attachmentPane
visible: currentRoom.chatBoxAttachmentPath.length > 0
Layout.fillWidth: true
}
Kirigami.Separator {
id: chatBarSeparator
visible: chatBar.visible
Layout.fillWidth: true Layout.fillWidth: true
} }
ChatBar { ChatBar {
id: chatBar id: chatBar
visible: currentRoom.canSendEvent("m.room.message") visible: currentRoom.canSendEvent("m.room.message")
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: implicitHeight + Kirigami.Units.largeSpacing
// lineSpacing is height+leading, so subtract leading once since leading only exists between lines.
Layout.maximumHeight: chatBarFontMetrics.lineSpacing * 8 - chatBarFontMetrics.leading + textField.topPadding + textField.bottomPadding
FontMetrics {
id: chatBarFontMetrics
font: chatBar.textField.font
}
onMessageSent: { onMessageSent: {
chatBox.messageSent(); chatBox.messageSent();
} }
Behavior on implicitHeight {
NumberAnimation {
property: "implicitHeight"
duration: Kirigami.Units.shortDuration
easing.type: Easing.OutCubic
}
}
}
function insertText(str) {
let index = chatBar.cursorPosition;
chatBox.inputFieldText = inputFieldText.substr(0, chatBar.cursorPosition) + str + inputFieldText.substr(chatBar.cursorPosition);
chatBar.cursorPosition = index + str.length;
}
function focusInputField() {
chatBar.inputFieldForceActiveFocusTriggered()
} }
} }

View File

@@ -10,66 +10,77 @@ import org.kde.kirigami 2.14 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
GridLayout { Loader {
id: root id: replyPane
property string userName property NeoChatUser user: currentRoom.chatBoxReplyUser ?? currentRoom.chatBoxEditUser
property color userColor: Kirigami.Theme.highlightColor
property var userAvatar: ""
property bool isReply
property var text
rows: 3 signal replyCancelled()
columns: 3
rowSpacing: Kirigami.Units.smallSpacing
columnSpacing: Kirigami.Units.largeSpacing
QQC2.Label { active: visible
id: replyLabel sourceComponent: QQC2.Pane {
id: replyPane
Kirigami.Theme.colorSet: Kirigami.Theme.View
spacing: leftPadding
contentItem: RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft spacing: replyPane.spacing
Layout.columnSpan: 3
topPadding: Kirigami.Units.smallSpacing
text: isReply ? i18n("Replying to:") : i18n("Editing message:") FontMetrics {
id: fontMetrics
font: textArea.font
} }
Rectangle {
id: verticalBorder
Layout.fillHeight: true
Layout.rowSpan: 2
implicitWidth: Kirigami.Units.smallSpacing
color: userColor
}
Kirigami.Avatar { Kirigami.Avatar {
id: replyAvatar id: avatar
Layout.alignment: textContentLayout.height > avatar.height ? Qt.AlignHCenter | Qt.AlignTop : Qt.AlignCenter
implicitWidth: Kirigami.Units.iconSizes.small Layout.preferredWidth: Layout.preferredHeight
implicitHeight: Kirigami.Units.iconSizes.small Layout.preferredHeight: fontMetrics.lineSpacing * 2 - fontMetrics.leading
source: user ? "image://mxc/" + currentRoom.getUser(user.id).avatarMediaId : ""
source: userAvatar name: user ? user.displayName : ""
name: userName color: user ? user.color : "transparent"
color: userColor visible: Boolean(user)
} }
ColumnLayout {
id: textContentLayout
Layout.alignment: Qt.AlignCenter
Layout.fillWidth: true
spacing: fontMetrics.leading
QQC2.Label { QQC2.Label {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft textFormat: Text.StyledText
color: userColor
text: userName
elide: Text.ElideRight elide: Text.ElideRight
text: {
let heading = "<b>%1</b>"
let userName = user ? "<font color=\""+ user.color +"\">" + currentRoom.htmlSafeMemberName(user.id) + "</font>" : ""
if (currentRoom.chatBoxEditId.length > 0) {
heading = heading.arg(i18n("Editing message:")) + "<br/>"
} else {
heading = heading.arg(i18n("Replying to %1:", userName))
} }
return heading
}
}
//TODO edit user mentions
QQC2.ScrollView {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
QQC2.TextArea { QQC2.TextArea {
id: textArea id: textArea
Layout.fillWidth: true
Layout.columnSpan: 2
leftPadding: 0 leftPadding: 0
rightPadding: 0 rightPadding: 0
topPadding: 0 topPadding: 0
bottomPadding: 0 bottomPadding: 0
text: "<style> a{color:" + Kirigami.Theme.linkColor + ";}.user-pill{}</style>" + replyTextMetrics.elidedText text: "<style> a{color:" + Kirigami.Theme.linkColor + ";}.user-pill{}</style>" + (currentRoom.chatBoxEditId.length > 0 ? currentRoom.chatBoxEditMessage : currentRoom.chatBoxReplyMessage)
selectByMouse: true selectByMouse: true
selectByKeyboard: true selectByKeyboard: true
readOnly: true readOnly: true
@@ -79,14 +90,28 @@ GridLayout {
HoverHandler { HoverHandler {
cursorShape: textArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor cursorShape: textArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
} }
}
}
}
TextMetrics { QQC2.ToolButton {
id: replyTextMetrics display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action {
text: i18nc("@action:button", "Cancel reply")
icon.name: "dialog-close"
onTriggered: {
currentRoom.chatBoxReplyId = "";
currentRoom.chatBoxEditId = "";
}
shortcut: "Escape"
}
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
}
}
text: root.text background: Rectangle {
font: textArea.font color: Kirigami.Theme.backgroundColor
elide: Qt.ElideRight
elideWidth: textArea.width * 2 - Kirigami.Units.smallSpacing * 2
} }
} }
} }

View File

@@ -16,11 +16,4 @@ Kirigami.LoadingPlaceholder {
QQC2.Label { QQC2.Label {
text: i18n("Please wait. This might take a little while.") text: i18n("Please wait. This might take a little while.")
} }
Connections {
target: Controller
function onInitiated() {
closeDialog()
}
}
} }

View File

@@ -11,13 +11,13 @@ import org.kde.kirigami 2.15 as Kirigami
TextEdit { TextEdit {
id: contentLabel id: contentLabel
readonly property var isEmoji: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u20D0-\u2fff]|[\u3190-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/ readonly property var isEmoji: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
readonly property var hasSpoiler: /data-mx-spoiler/g readonly property var hasSpoiler: /data-mx-spoiler/g
property bool isEmote: false property bool isEmote: false
property bool isReplyLabel: false property bool isReplyLabel: false
readonly property var linkRegex: /(href=["'])?(\b(https?):\/\/[^\s\<\>\"\'\\\?\:\)\(]+(\(.*?\))*(\?(?=[a-z])[^\s\\\)]+|$)?)/g readonly property var linkRegex: /(href=["'])?(\b(https?):\/\/[^\s\<\>\"\'\\]+)/g
property string textMessage: model.display.includes("http") property string textMessage: model.display.includes("http")
? model.display.replace(linkRegex, function() { ? model.display.replace(linkRegex, function() {
if (arguments[0].includes("/_matrix/media/r0/download/")) { if (arguments[0].includes("/_matrix/media/r0/download/")) {

View File

@@ -87,7 +87,6 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing) Layout.topMargin: showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing)
Layout.leftMargin: Kirigami.Units.smallSpacing Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.smallSpacing
implicitHeight: Math.max(model.showAuthor ? avatar.implicitHeight : 0, bubble.height) implicitHeight: Math.max(model.showAuthor ? avatar.implicitHeight : 0, bubble.height)

View File

@@ -18,10 +18,7 @@ TimelineContainer {
readonly property bool downloaded: progressInfo && progressInfo.completed readonly property bool downloaded: progressInfo && progressInfo.completed
property bool supportStreaming: true property bool supportStreaming: true
readonly property var maxWidth: Kirigami.Units.gridUnit * 30 readonly property int maxWidth: 1000 // TODO messageListView.width
readonly property var maxHeight: Kirigami.Units.gridUnit * 30
readonly property var info: model.content.info
onOpenContextMenu: openFileContext(model, vid) onOpenContextMenu: openFileContext(model, vid)
@@ -39,104 +36,17 @@ TimelineContainer {
innerObject: Video { innerObject: Video {
id: vid id: vid
property var videoWidth: { Layout.maximumWidth: videoDelegate.contentMaxWidth
if (videoDelegate.info && videoDelegate.info.w && videoDelegate.info.w > 0) { Layout.fillWidth: true
return videoDelegate.info.w; Layout.maximumHeight: Kirigami.Units.gridUnit * 15
} else if (metaData.resolution && metaData.resolution.width) { Layout.minimumHeight: Kirigami.Units.gridUnit * 5
return metaData.resolution.width;
} else {
return videoDelegate.contentMaxWidth;
}
}
property var videoHeight: {
if (videoDelegate.info && videoDelegate.info.w && videoDelegate.info.h > 0) {
return videoDelegate.info.h;
} else if (metaData.resolution && metaData.resolution.height) {
return metaData.resolution.height;
} else {
// Default to a 16:9 placeholder
return videoDelegate.contentMaxWidth / 16 * 9;
}
}
readonly property var aspectRatio: videoWidth / videoHeight Layout.preferredWidth: (model.content.info.w === undefined || model.content.info.w > videoDelegate.maxWidth) ? videoDelegate.maxWidth : content.info.w
/** Layout.preferredHeight: model.content.info.w === undefined ? (videoDelegate.maxWidth * 3 / 4) : (model.content.info.w > videoDelegate.maxWidth ? (model.content.info.h / model.content.info.w * videoDelegate.maxWidth) : model.content.info.h)
* Whether the video should be limited by height or width.
* We need to prevent excessively tall as well as excessively wide media.
*
* @note In the case of a tie the media is width limited.
*/
readonly property bool limitWidth: videoWidth >= videoHeight
readonly property size maxSize: { loops: MediaPlayer.Infinite
if (limitWidth) {
let width = Math.min(videoDelegate.contentMaxWidth, videoDelegate.maxWidth);
let height = width / aspectRatio;
return Qt.size(width, height);
} else {
let height = Math.min(videoDelegate.maxHeight, videoDelegate.contentMaxWidth / aspectRatio);
let width = height * aspectRatio;
return Qt.size(width, height);
}
}
Layout.maximumWidth: maxSize.width
Layout.maximumHeight: maxSize.height
Layout.preferredWidth: videoWidth
Layout.preferredHeight: videoHeight
fillMode: VideoOutput.PreserveAspectFit fillMode: VideoOutput.PreserveAspectFit
flushMode: VideoOutput.FirstFrame
states: [
State {
name: "notDownloaded"
when: !model.progressInfo.completed && !model.progressInfo.active
PropertyChanges {
target: noDownloadLabel
visible: true
}
PropertyChanges {
target: mediaThumbnail
visible: true
}
},
State {
name: "downloading"
when: model.progressInfo.active && !model.progressInfo.completed
PropertyChanges {
target: downloadBar
visible: true
}
},
State {
name: "paused"
when: model.progressInfo.completed && (vid.playbackState === MediaPlayer.StoppedState || vid.playbackState === MediaPlayer.PausedState)
PropertyChanges {
target: videoControls
stateVisible: true
}
PropertyChanges {
target: playButton
icon.name: "media-playback-start"
onClicked: vid.play()
}
},
State {
name: "playing"
when: model.progressInfo.completed && vid.playbackState === MediaPlayer.PlayingState
PropertyChanges {
target: videoControls
stateVisible: true
}
PropertyChanges {
target: playButton
icon.name: "media-playback-pause"
onClicked: vid.pause()
}
}
]
onDurationChanged: { onDurationChanged: {
if (!duration) { if (!duration) {
@@ -151,10 +61,9 @@ TimelineContainer {
} }
Image { Image {
id: mediaThumbnail
anchors.fill: parent anchors.fill: parent
visible: false visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
source: model.content.thumbnailMediaId ? "image://mxc/" + model.content.thumbnailMediaId : "" source: model.content.thumbnailMediaId ? "image://mxc/" + model.content.thumbnailMediaId : ""
@@ -162,10 +71,9 @@ TimelineContainer {
} }
QQC2.Label { QQC2.Label {
id: noDownloadLabel
anchors.centerIn: parent anchors.centerIn: parent
visible: false visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
color: "white" color: "white"
text: i18n("Video") text: i18n("Video")
font.pixelSize: 16 font.pixelSize: 16
@@ -180,12 +88,11 @@ TimelineContainer {
} }
Rectangle { Rectangle {
id: downloadBar
anchors.fill: parent anchors.fill: parent
visible: false
color: Kirigami.Theme.backgroundColor visible: progressInfo.active && !videoDelegate.downloaded
radius: Kirigami.Units.smallSpacing
color: "#BB000000"
QQC2.ProgressBar { QQC2.ProgressBar {
anchors.centerIn: parent anchors.centerIn: parent
@@ -198,148 +105,6 @@ TimelineContainer {
} }
} }
QQC2.Control {
id: videoControls
property bool stateVisible: false
anchors.bottom: vid.bottom
anchors.left: vid.left
anchors.right: vid.right
visible: stateVisible && (videoHoverHandler.hovered || volumePopupHoverHandler.hovered || volumeSlider.hovered || videoControlTimer.running)
contentItem: RowLayout {
id: controlRow
QQC2.ToolButton {
id: playButton
}
QQC2.Slider {
Layout.fillWidth: true
from: 0
to: vid.duration
value: vid.position
onMoved: vid.seek(value)
}
QQC2.Label {
text: Controller.formatDuration(vid.position) + "/" + Controller.formatDuration(vid.duration)
}
QQC2.ToolButton {
id: volumeButton
property var unmuteVolume: vid.volume
icon.name: vid.volume <= 0 ? "player-volume-muted" : "player-volume"
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.timeout: Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: i18nc("@action:button", "Volume")
onClicked: {
if (vid.volume > 0) {
vid.volume = 0
} else {
if (unmuteVolume === 0) {
vid.volume = 1
} else {
vid.volume = unmuteVolume
}
}
}
onHoveredChanged: {
if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
videoControlTimer.restart()
volumePopupTimer.restart()
}
}
QQC2.Popup {
id: volumePopup
y: -height
width: volumeButton.width
visible: videoControls.stateVisible && (volumeButton.hovered || volumePopupHoverHandler.hovered || volumeSlider.hovered || volumePopupTimer.running)
focus: true
padding: Kirigami.Units.smallSpacing
closePolicy: QQC2.Popup.NoAutoClose
QQC2.Slider {
id: volumeSlider
anchors.centerIn: parent
implicitHeight: Kirigami.Units.gridUnit * 7
orientation: Qt.Vertical
padding: 0
from: 0
to: 1
value: vid.volume
onMoved: {
vid.volume = value
volumeButton.unmuteVolume = value
}
onHoveredChanged: {
if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
videoControlTimer.restart()
volumePopupTimer.restart()
}
}
}
Timer {
id: volumePopupTimer
interval: 500
}
HoverHandler {
id: volumePopupHoverHandler
onHoveredChanged: {
if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
videoControlTimer.restart()
volumePopupTimer.restart()
}
}
}
background: Kirigami.ShadowedRectangle {
radius: 4
color: Kirigami.Theme.backgroundColor
opacity: 0.8
property color borderColor: Kirigami.Theme.textColor
border.color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.3)
border.width: 1
shadow.xOffset: 0
shadow.yOffset: 4
shadow.color: Qt.rgba(0, 0, 0, 0.3)
shadow.size: 8
}
}
}
}
background: Kirigami.ShadowedRectangle {
radius: 4
color: Kirigami.Theme.backgroundColor
opacity: 0.8
property color borderColor: Kirigami.Theme.textColor
border.color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.3)
border.width: 1
shadow.xOffset: 0
shadow.yOffset: 4
shadow.color: Qt.rgba(0, 0, 0, 0.3)
shadow.size: 8
}
}
Timer {
id: videoControlTimer
interval: 1000
}
HoverHandler {
id: videoHoverHandler
onHoveredChanged: {
if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
videoControlTimer.restart()
}
}
}
TapHandler { TapHandler {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onTapped: if (vid.supportStreaming || progressInfo.completed) { onTapped: if (vid.supportStreaming || progressInfo.completed) {

View File

@@ -138,7 +138,7 @@ QQC2.ToolBar {
name: Controller.activeConnection.localUser.displayName ?? Controller.activeConnection.localUser.id name: Controller.activeConnection.localUser.displayName ?? Controller.activeConnection.localUser.id
actions.main: Kirigami.Action { actions.main: Kirigami.Action {
text: i18n("Edit this account") text: i18n("Edit this account")
icon.name: "document-edit" iconName: "document-edit"
onTriggered: pageStack.pushDialogLayer(Qt.resolvedUrl('./AccountEditorPage.qml'), { onTriggered: pageStack.pushDialogLayer(Qt.resolvedUrl('./AccountEditorPage.qml'), {
connection: Controller.activeConnection connection: Controller.activeConnection
}, { }, {

View File

@@ -15,7 +15,7 @@ import org.kde.kirigami 2.14 as Kirigami
*/ */
Kirigami.Action { Kirigami.Action {
id: shareAction id: shareAction
icon.name: "emblem-shared-symbolic" iconName: "emblem-shared-symbolic"
text: i18n("Share") text: i18n("Share")
tooltip: i18n("Share the selected media") tooltip: i18n("Share the selected media")

View File

@@ -34,14 +34,14 @@ Kirigami.Page {
left: Kirigami.Action { left: Kirigami.Action {
id: undoAction id: undoAction
text: i18nc("@action:button Undo modification", "Undo") text: i18nc("@action:button Undo modification", "Undo")
icon.name: "edit-undo" iconName: "edit-undo"
onTriggered: imageDoc.undo(); onTriggered: imageDoc.undo();
visible: imageDoc.edited visible: imageDoc.edited
} }
main: Kirigami.Action { main: Kirigami.Action {
id: okAction id: okAction
text: i18nc("@action:button Accept image modification", "Accept") text: i18nc("@action:button Accept image modification", "Accept")
icon.name: "dialog-ok" iconName: "dialog-ok"
onTriggered: { onTriggered: {
let newPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + (new Date()).getTime() + "." + imagePath.split('.').pop(); let newPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + (new Date()).getTime() + "." + imagePath.split('.').pop();
if (imageDoc.saveAs(newPath)) {; if (imageDoc.saveAs(newPath)) {;
@@ -121,7 +121,7 @@ Kirigami.Page {
display: QQC2.Button.TextBesideIcon display: QQC2.Button.TextBesideIcon
actions: [ actions: [
Kirigami.Action { Kirigami.Action {
icon.name: rootEditorView.resizing ? "dialog-cancel" : "transform-crop" iconName: rootEditorView.resizing ? "dialog-cancel" : "transform-crop"
text: rootEditorView.resizing ? i18n("Cancel") : i18nc("@action:button Crop an image", "Crop"); text: rootEditorView.resizing ? i18n("Cancel") : i18nc("@action:button Crop an image", "Crop");
onTriggered: { onTriggered: {
resizeRectangle.width = editImage.paintedWidth resizeRectangle.width = editImage.paintedWidth
@@ -137,31 +137,31 @@ Kirigami.Page {
} }
}, },
Kirigami.Action { Kirigami.Action {
icon.name: "dialog-ok" iconName: "dialog-ok"
visible: rootEditorView.resizing visible: rootEditorView.resizing
text: i18nc("@action:button Crop an image", "Crop"); text: i18nc("@action:button Crop an image", "Crop");
onTriggered: rootEditorView.crop(); onTriggered: rootEditorView.crop();
}, },
Kirigami.Action { Kirigami.Action {
icon.name: "object-rotate-left" iconName: "object-rotate-left"
text: i18nc("@action:button Rotate an image to the left", "Rotate left"); text: i18nc("@action:button Rotate an image to the left", "Rotate left");
onTriggered: imageDoc.rotate(-90); onTriggered: imageDoc.rotate(-90);
visible: !rootEditorView.resizing visible: !rootEditorView.resizing
}, },
Kirigami.Action { Kirigami.Action {
icon.name: "object-rotate-right" iconName: "object-rotate-right"
text: i18nc("@action:button Rotate an image to the right", "Rotate right"); text: i18nc("@action:button Rotate an image to the right", "Rotate right");
onTriggered: imageDoc.rotate(90); onTriggered: imageDoc.rotate(90);
visible: !rootEditorView.resizing visible: !rootEditorView.resizing
}, },
Kirigami.Action { Kirigami.Action {
icon.name: "object-flip-vertical" iconName: "object-flip-vertical"
text: i18nc("@action:button Mirror an image vertically", "Flip"); text: i18nc("@action:button Mirror an image vertically", "Flip");
onTriggered: imageDoc.mirror(false, true); onTriggered: imageDoc.mirror(false, true);
visible: !rootEditorView.resizing visible: !rootEditorView.resizing
}, },
Kirigami.Action { Kirigami.Action {
icon.name: "object-flip-horizontal" iconName: "object-flip-horizontal"
text: i18nc("@action:button Mirror an image horizontally", "Mirror"); text: i18nc("@action:button Mirror an image horizontally", "Mirror");
onTriggered: imageDoc.mirror(true, false); onTriggered: imageDoc.mirror(true, false);
visible: !rootEditorView.resizing visible: !rootEditorView.resizing

View File

@@ -50,7 +50,6 @@ Kirigami.ScrollablePage {
applicationWindow().hoverLinkIndicator.text = ""; applicationWindow().hoverLinkIndicator.text = "";
messageListView.positionViewAtBeginning(); messageListView.positionViewAtBeginning();
hasScrolledUpBefore = false; hasScrolledUpBefore = false;
chatBox.chatBar.forceActiveFocus();
} }
Connections { Connections {
@@ -138,8 +137,8 @@ Kirigami.ScrollablePage {
switchRoomUp(); switchRoomUp();
} else if (!(event.modifiers & Qt.ControlModifier) && event.key < Qt.Key_Escape) { } else if (!(event.modifiers & Qt.ControlModifier) && event.key < Qt.Key_Escape) {
event.accepted = true; event.accepted = true;
chatBox.chatBar.insertText(event.text); chatBox.addText(event.text);
chatBox.chatBar.forceActiveFocus(); chatBox.focusInputField();
return; return;
} }
} }
@@ -262,7 +261,7 @@ Kirigami.ScrollablePage {
maxWidth: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.width - Kirigami.Units.largeSpacing * 2 : 0 maxWidth: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.width - Kirigami.Units.largeSpacing * 2 : 0
z: 3 z: 3
visible: messageListView.sectionBannerItem != undefined && messageListView.sectionBannerItem.ListView.section != "" && !Config.blur visible: messageListView.sectionBannerItem != undefined && messageListView.sectionBannerItem.ListView.section != ""
labelText: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.ListView.section : "" labelText: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.ListView.section : ""
} }
footerPositioning: ListView.OverlayHeader footerPositioning: ListView.OverlayHeader
@@ -350,7 +349,7 @@ Kirigami.ScrollablePage {
visible: currentRoom && currentRoom.hasUnreadMessages && currentRoom.readMarkerLoaded visible: currentRoom && currentRoom.hasUnreadMessages && currentRoom.readMarkerLoaded
action: Kirigami.Action { action: Kirigami.Action {
onTriggered: { onTriggered: {
chatBox.chatBar.forceActiveFocus(); chatBox.focusInputField();
messageListView.goToEvent(currentRoom.readMarkerEventId) messageListView.goToEvent(currentRoom.readMarkerEventId)
} }
icon.name: "go-up" icon.name: "go-up"
@@ -374,7 +373,7 @@ Kirigami.ScrollablePage {
visible: !messageListView.atYEnd visible: !messageListView.atYEnd
action: Kirigami.Action { action: Kirigami.Action {
onTriggered: { onTriggered: {
chatBox.chatBar.forceActiveFocus(); chatBox.focusInputField();
goToLastMessage(); goToLastMessage();
currentRoom.markAllMessagesAsRead(); currentRoom.markAllMessagesAsRead();
} }
@@ -520,14 +519,13 @@ Kirigami.ScrollablePage {
QQC2.ToolTip.visible: hovered QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
icon.name: "preferences-desktop-emoticons" icon.name: "preferences-desktop-emoticons"
onClicked: emojiDialog.open(); onClicked: emojiDialog.open();
EmojiDialog { EmojiDialog {
id: emojiDialog id: emojiDialog
showQuickReaction: true showQuickReaction: true
onChosen: { onChosen: {
page.currentRoom.toggleReaction(hoverActions.event.eventId, emoji); page.currentRoom.toggleReaction(hoverActions.event.eventId, emoji);
chatBox.chatBar.forceActiveFocus(); chatBox.focusInputField();
} }
} }
} }
@@ -540,7 +538,7 @@ Kirigami.ScrollablePage {
onClicked: { onClicked: {
currentRoom.chatBoxEditId = hoverActions.event.eventId; currentRoom.chatBoxEditId = hoverActions.event.eventId;
currentRoom.chatBoxReplyId = ""; currentRoom.chatBoxReplyId = "";
chatBox.chatBar.forceActiveFocus(); chatBox.focusInputField();
} }
} }
QQC2.Button { QQC2.Button {
@@ -551,7 +549,7 @@ Kirigami.ScrollablePage {
onClicked: { onClicked: {
currentRoom.chatBoxReplyId = hoverActions.event.eventId; currentRoom.chatBoxReplyId = hoverActions.event.eventId;
currentRoom.chatBoxEditId = ""; currentRoom.chatBoxEditId = "";
chatBox.chatBar.forceActiveFocus(); chatBox.focusInputField();
} }
} }
} }

View File

@@ -291,8 +291,7 @@ Kirigami.OverlayDrawer {
room: roomDrawer.room room: roomDrawer.room
} }
sortRole: "powerLevel" sortRole: "perm"
sortOrder: Qt.DescendingOrder
filterRole: "name" filterRole: "name"
filterCaseSensitivity: Qt.CaseInsensitive filterCaseSensitivity: Qt.CaseInsensitive
} }
@@ -326,9 +325,22 @@ Kirigami.OverlayDrawer {
} }
trailing: QQC2.Label { trailing: QQC2.Label {
visible: powerLevel > 0 visible: perm != UserType.Member
text: powerLevelString text: {
switch (perm) {
case UserType.Owner:
return i18n("Owner");
case UserType.Admin:
return i18n("Admin");
case UserType.Moderator:
return i18n("Mod");
case UserType.Muted:
return i18n("Muted");
default:
return "";
}
}
color: Kirigami.Theme.disabledTextColor color: Kirigami.Theme.disabledTextColor
textFormat: Text.PlainText textFormat: Text.PlainText
wrapMode: Text.NoWrap wrapMode: Text.NoWrap

View File

@@ -44,11 +44,10 @@ Kirigami.ScrollablePage {
Repeater { Repeater {
model: KSortFilterProxyModel { model: KSortFilterProxyModel {
sourceModel: userListModel sourceModel: userListModel
sortRole: "powerLevel" sortRole: "perm"
sortOrder: Qt.DescendingOrder
filterRowCallback: function(source_row, source_parent) { filterRowCallback: function(source_row, source_parent) {
let powerLevelRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), Qt.UserRole + 5) let permRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), Qt.UserRole + 5)
return powerLevelRole > 0; return permRole != UserType.Muted && permRole != UserType.Member;
} }
} }
delegate: MobileForm.FormTextDelegate { delegate: MobileForm.FormTextDelegate {
@@ -58,7 +57,22 @@ Kirigami.ScrollablePage {
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
QQC2.Label { QQC2.Label {
visible: !room.canSendState("m.room.power_levels") visible: !room.canSendState("m.room.power_levels")
text: powerLevelString text: {
switch (perm) {
case UserType.Owner:
return i18n("Owner");
case UserType.Admin:
return i18n("Admin");
case UserType.Moderator:
return i18n("Mod");
case UserType.Muted:
return i18n("Muted");
case UserType.Member:
return i18n("Member");
default:
return ""
}
}
color: Kirigami.Theme.disabledTextColor color: Kirigami.Theme.disabledTextColor
} }
QQC2.ComboBox { QQC2.ComboBox {
@@ -177,9 +191,22 @@ Kirigami.ScrollablePage {
} }
trailing: QQC2.Label { trailing: QQC2.Label {
visible: powerLevel > 0 visible: perm != UserType.Member
text: powerLevelString text: {
switch (perm) {
case UserType.Owner:
return i18n("Owner");
case UserType.Admin:
return i18n("Admin");
case UserType.Moderator:
return i18n("Mod");
case UserType.Muted:
return i18n("Muted");
default:
return "";
}
}
color: Kirigami.Theme.disabledTextColor color: Kirigami.Theme.disabledTextColor
textFormat: Text.PlainText textFormat: Text.PlainText
wrapMode: Text.NoWrap wrapMode: Text.NoWrap

View File

@@ -115,7 +115,7 @@ Kirigami.ScrollablePage {
action: Kirigami.Action { action: Kirigami.Action {
id: editDeviceAction id: editDeviceAction
text: i18n("Edit device name") text: i18n("Edit device name")
icon.name: "document-edit" iconName: "document-edit"
onTriggered: deviceDelegate.editDeviceName = true onTriggered: deviceDelegate.editDeviceName = true
} }
QQC2.ToolTip { QQC2.ToolTip {
@@ -129,7 +129,7 @@ Kirigami.ScrollablePage {
action: Kirigami.Action { action: Kirigami.Action {
id: verifyDeviceAction id: verifyDeviceAction
text: i18n("Verify device") text: i18n("Verify device")
icon.name: "security-low-symbolic" iconName: "security-low-symbolic"
onTriggered: { onTriggered: {
devices.connection.startKeyVerificationSession(devices.connection.localUserId, model.id) devices.connection.startKeyVerificationSession(devices.connection.localUserId, model.id)
} }
@@ -144,7 +144,7 @@ Kirigami.ScrollablePage {
action: Kirigami.Action { action: Kirigami.Action {
id: logoutDeviceAction id: logoutDeviceAction
text: i18n("Logout device") text: i18n("Logout device")
icon.name: "edit-delete-remove" iconName: "edit-delete-remove"
onTriggered: { onTriggered: {
passwordSheet.index = index passwordSheet.index = index
passwordSheet.open() passwordSheet.open()

View File

@@ -19,12 +19,14 @@ Kirigami.ScrollablePage {
MobileForm.FormCard { MobileForm.FormCard {
Layout.fillWidth: true Layout.fillWidth: true
contentItem: MobileForm.FormCheckDelegate { contentItem: MobileForm.FormCheckDelegate {
text: i18n("Enable notifications for this account") text: i18n("Enable notifications for this account")
description: i18n("Whether push notifications are generated by your Matrix server") checked: Config.showNotifications
checked: NotificationsManager.globalNotificationsEnabled enabled: !Config.isShowNotificationsImmutable && Controller.activeConnection
enabled: NotificationsManager.globalNotificationsSet
onToggled: { onToggled: {
Config.showNotifications = checked
Config.save()
NotificationsManager.globalNotificationsEnabled = checked NotificationsManager.globalNotificationsEnabled = checked
} }
} }

View File

@@ -35,18 +35,18 @@ Kirigami.CategorizedSettings {
}, },
Kirigami.SettingAction { Kirigami.SettingAction {
text: i18n("Spell Checking") text: i18n("Spell Checking")
icon.name: "tools-check-spelling" iconName: "tools-check-spelling"
page: Qt.resolvedUrl("SonnetConfigPage.qml") page: Qt.resolvedUrl("SonnetConfigPage.qml")
visible: Qt.platform.os !== "android" visible: Qt.platform.os !== "android"
}, },
Kirigami.SettingAction { Kirigami.SettingAction {
text: i18n("Network Proxy") text: i18n("Network Proxy")
icon.name: "network-connect" iconName: "network-connect"
page: Qt.resolvedUrl("NetworkProxyPage.qml") page: Qt.resolvedUrl("NetworkProxyPage.qml")
}, },
Kirigami.SettingAction { Kirigami.SettingAction {
text: i18n("Devices") text: i18n("Devices")
icon.name: "computer" iconName: "computer"
page: Qt.resolvedUrl("DevicesPage.qml") page: Qt.resolvedUrl("DevicesPage.qml")
}, },
Kirigami.SettingAction { Kirigami.SettingAction {

View File

@@ -11,8 +11,8 @@
#include <QString> #include <QString>
#include <QVariantMap> #include <QVariantMap>
#include "models/roomlistmodel.h" #include "roomlistmodel.h"
#include "models/sortfilterroomlistmodel.h" #include "sortfilterroomlistmodel.h"
// Copied from KRunner/QueryMatch // Copied from KRunner/QueryMatch
enum MatchType { enum MatchType {

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