Compare commits
1 Commits
v23.01.0
...
work/redst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b1b21dd07 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,4 +9,3 @@ compile_commands.json
|
|||||||
kate.project.ctags.*
|
kate.project.ctags.*
|
||||||
*.user
|
*.user
|
||||||
.flatpak-builder/
|
.flatpak-builder/
|
||||||
.idea/
|
|
||||||
|
|||||||
@@ -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
0
LICENSES/MIT.txt
Normal file → Executable 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 -->
|
||||||
|
|||||||
@@ -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]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
1493
po/ar/neochat.po
1493
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1657
po/az/neochat.po
1657
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1477
po/ca/neochat.po
1477
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1535
po/cs/neochat.po
1535
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1502
po/da/neochat.po
1502
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1515
po/de/neochat.po
1515
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
2214
po/el/neochat.po
2214
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1507
po/en_GB/neochat.po
1507
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1489
po/es/neochat.po
1489
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1491
po/eu/neochat.po
1491
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1651
po/fi/neochat.po
1651
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1522
po/fr/neochat.po
1522
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1643
po/hu/neochat.po
1643
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1800
po/ia/neochat.po
1800
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1624
po/id/neochat.po
1624
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1551
po/ie/neochat.po
1551
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1538
po/it/neochat.po
1538
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1449
po/ja/neochat.po
1449
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1492
po/ka/neochat.po
1492
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1635
po/ko/neochat.po
1635
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1495
po/nl/neochat.po
1495
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1445
po/nn/neochat.po
1445
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1625
po/pa/neochat.po
1625
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1511
po/pl/neochat.po
1511
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1514
po/pt/neochat.po
1514
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1658
po/pt_BR/neochat.po
1658
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1513
po/ru/neochat.po
1513
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1659
po/sk/neochat.po
1659
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1489
po/sl/neochat.po
1489
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1655
po/sv/neochat.po
1655
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1501
po/ta/neochat.po
1501
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1510
po/tok/neochat.po
1510
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1502
po/tr/neochat.po
1502
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1497
po/uk/neochat.po
1497
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1482
po/zh_CN/neochat.po
1482
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1447
po/zh_TW/neochat.po
1447
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -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"
|
||||||
|
|||||||
38
src/main.cpp
38
src/main.cpp
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்
|
||||||
|
|||||||
@@ -10,115 +10,188 @@ 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 {
|
Image {
|
||||||
id: imageEditorPage
|
id: image
|
||||||
ImageEditorPage {
|
property real preferredHeight: Math.min(implicitHeight, Kirigami.Units.gridUnit * 8)
|
||||||
imagePath: root.attachmentPath
|
height: preferredHeight
|
||||||
|
anchors {
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
bottom: fileInfoLayout.top
|
||||||
|
bottomMargin: parent.spacing
|
||||||
|
}
|
||||||
|
width: Math.min(implicitWidth, attachmentPane.availableWidth)
|
||||||
|
asynchronous: true
|
||||||
|
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
|
||||||
|
smooth: height === preferredHeight && parent.height === parent.implicitHeight // Don't smooth until height animation stops
|
||||||
|
source: hasImage ? attachmentPaneLoader.attachmentPath : ""
|
||||||
|
visible: hasImage
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
|
onSourceChanged: {
|
||||||
|
// Reset source size height, which affect implicitHeight
|
||||||
|
sourceSize.height = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
onSourceSizeChanged: {
|
||||||
|
if (implicitHeight > Kirigami.Units.gridUnit * 8) {
|
||||||
|
// This can save a lot of RAM when loading large images.
|
||||||
|
// It also improves visual quality for large images.
|
||||||
|
sourceSize.height = Kirigami.Units.gridUnit * 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "height"
|
||||||
|
duration: Kirigami.Units.shortDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.BusyIndicator {
|
||||||
|
id: imageBusyIndicator
|
||||||
|
anchors {
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
top: parent.top
|
||||||
|
bottom: fileInfoLayout.top
|
||||||
|
bottomMargin: parent.spacing
|
||||||
|
}
|
||||||
|
visible: running
|
||||||
|
running: image.visible && image.progress < 1
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: fileInfoLayout
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.verticalCenter: undefined
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
spacing: parent.spacing
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
id: mimetypeIcon
|
||||||
|
implicitHeight: Kirigami.Units.fontMetrics.roundedIconSize(fileLabel.implicitHeight)
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
source: attachmentMimetype.iconName
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
id: fileLabel
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
background: Rectangle {
|
||||||
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
|
color: Kirigami.Theme.backgroundColor
|
||||||
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 {
|
|
||||||
id: image
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
asynchronous: true
|
|
||||||
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
|
|
||||||
source: hasImage ? root.attachmentPath : ""
|
|
||||||
visible: hasImage
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
|
|
||||||
onSourceChanged: {
|
|
||||||
// Reset source size height, which affect implicitHeight
|
|
||||||
sourceSize.height = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
onSourceSizeChanged: {
|
|
||||||
if (implicitHeight > Kirigami.Units.gridUnit * 8) {
|
|
||||||
// This can save a lot of RAM when loading large images.
|
|
||||||
// It also improves visual quality for large images.
|
|
||||||
sourceSize.height = Kirigami.Units.gridUnit * 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Kirigami.Units.shortDuration
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.BusyIndicator {
|
|
||||||
id: imageBusyIndicator
|
|
||||||
|
|
||||||
visible: running
|
|
||||||
running: image.visible && image.progress < 1
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
id: fileInfoLayout
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
spacing: parent.spacing
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
id: mimetypeIcon
|
|
||||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
|
||||||
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
|
||||||
source: attachmentMimetype.iconName
|
|
||||||
}
|
|
||||||
QQC2.Label {
|
|
||||||
id: fileLabel
|
|
||||||
text: baseFileName
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,62 +5,206 @@
|
|||||||
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
|
position: QQC2.ToolBar.Footer
|
||||||
|
|
||||||
// Matrix does not allow sending attachments in replies
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
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: {
|
// Using a custom background because some styles like Material
|
||||||
if (Clipboard.hasImage) {
|
// or Fusion might have ugly colors for a TextArea placed inside
|
||||||
attachDialog.open()
|
// of a toolbar. ToolBar is otherwise the closest QQC2 component
|
||||||
} else {
|
// to what we want because of the padding and spacing values.
|
||||||
var fileDialog = openFileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
background: Rectangle {
|
||||||
fileDialog.chosen.connect((path) => {
|
color: Kirigami.Theme.backgroundColor
|
||||||
if (!path) {
|
}
|
||||||
return;
|
|
||||||
}
|
contentItem: RowLayout {
|
||||||
currentRoom.chatBoxAttachmentPath = path;
|
spacing: chatBar.spacing
|
||||||
})
|
|
||||||
fileDialog.open()
|
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
|
||||||
|
|
||||||
|
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||||
|
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||||
|
|
||||||
|
FontMetrics {
|
||||||
|
id: fontMetrics
|
||||||
|
font: inputField.font
|
||||||
}
|
}
|
||||||
|
|
||||||
tooltip: text
|
QQC2.TextArea {
|
||||||
},
|
id: inputField
|
||||||
Kirigami.Action {
|
focus: true
|
||||||
id: emojiAction
|
/* 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
|
||||||
|
}
|
||||||
|
|
||||||
property bool isBusy: false
|
leftPadding: mirrored ? 0 : Kirigami.Units.largeSpacing
|
||||||
|
rightPadding: !mirrored ? 0 : Kirigami.Units.largeSpacing
|
||||||
|
topPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
|
||||||
|
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
|
||||||
|
horizontalAlignment: TextEdit.AlignLeft
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
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 {
|
||||||
|
id: repeatTimer
|
||||||
|
interval: 5000
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextChanged: {
|
||||||
|
if (!repeatTimer.running && Config.typingNotifications) {
|
||||||
|
var textExists = text.length > 0
|
||||||
|
currentRoom.sendTypingNotification(textExists)
|
||||||
|
textExists ? repeatTimer.start() : repeatTimer.stop()
|
||||||
|
}
|
||||||
|
currentRoom.chatBoxText = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
visible: currentRoom.chatBoxReplyId.length === 0 && (currentRoom.chatBoxAttachmentPath.length === 0 || uploadingBusySpinner.running)
|
||||||
|
implicitWidth: uploadButton.implicitWidth
|
||||||
|
implicitHeight: uploadButton.implicitHeight
|
||||||
|
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 {
|
||||||
|
var fileDialog = openFileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
||||||
|
fileDialog.chosen.connect((path) => {
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentRoom.chatBoxAttachmentPath = path;
|
||||||
|
})
|
||||||
|
fileDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
}
|
||||||
|
QQC2.BusyIndicator {
|
||||||
|
id: uploadingBusySpinner
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: running
|
||||||
|
running: currentRoom && currentRoom.hasFileUploading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolButton {
|
||||||
|
id: emojiButton
|
||||||
icon.name: "smiley"
|
icon.name: "smiley"
|
||||||
text: i18n("Add an Emoji")
|
text: i18n("Add an Emoji")
|
||||||
displayHint: Kirigami.DisplayHint.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
checkable: true
|
checkable: true
|
||||||
|
|
||||||
onTriggered: {
|
onClicked: {
|
||||||
if (emojiDialog.visible) {
|
if (emojiDialog.visible) {
|
||||||
emojiDialog.close()
|
emojiDialog.close()
|
||||||
} else {
|
} else {
|
||||||
@@ -68,266 +212,29 @@ QQC2.Control {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tooltip: text
|
QQC2.ToolTip.text: text
|
||||||
},
|
QQC2.ToolTip.visible: hovered
|
||||||
Kirigami.Action {
|
}
|
||||||
id: sendAction
|
|
||||||
|
|
||||||
property bool isBusy: false
|
|
||||||
|
|
||||||
|
QQC2.ToolButton {
|
||||||
|
id: sendButton
|
||||||
icon.name: "document-send"
|
icon.name: "document-send"
|
||||||
text: i18n("Send message")
|
text: i18n("Send message")
|
||||||
displayHint: Kirigami.DisplayHint.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
checkable: true
|
|
||||||
|
|
||||||
onTriggered: {
|
onClicked: {
|
||||||
root.postMessage()
|
chatBar.postMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
tooltip: text
|
QQC2.ToolTip.text: text
|
||||||
}
|
QQC2.ToolTip.visible: hovered
|
||||||
]
|
|
||||||
|
|
||||||
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{
|
|
||||||
id: textField
|
|
||||||
|
|
||||||
topPadding: Kirigami.Units.largeSpacing + (paneLoader.visible ? paneLoader.height : 0)
|
|
||||||
bottomPadding: Kirigami.Units.largeSpacing
|
|
||||||
leftPadding: LayoutMirroring.enabled ? actionsRow.width : (root.width > chatBoxMaxWidth ? 0 : Kirigami.Units.largeSpacing)
|
|
||||||
rightPadding: LayoutMirroring.enabled ? (root.width > chatBoxMaxWidth ? 0 : Kirigami.Units.largeSpacing) : actionsRow.width
|
|
||||||
|
|
||||||
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…")
|
|
||||||
verticalAlignment: TextEdit.AlignVCenter
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
readOnly: (currentRoom.usesEncryption && !Controller.encryptionSupported)
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: repeatTimer
|
|
||||||
interval: 5000
|
|
||||||
}
|
|
||||||
|
|
||||||
onTextChanged: {
|
|
||||||
if (!repeatTimer.running && Config.typingNotifications) {
|
|
||||||
var textExists = text.length > 0
|
|
||||||
currentRoom.sendTypingNotification(textExists)
|
|
||||||
textExists ? repeatTimer.start() : repeatTimer.stop()
|
|
||||||
}
|
|
||||||
currentRoom.chatBoxText = text
|
|
||||||
}
|
|
||||||
onCursorRectangleChanged: chatBarScrollView.ensureVisible(cursorRectangle)
|
|
||||||
|
|
||||||
Keys.onEnterPressed: {
|
|
||||||
if (completionMenu.visible) {
|
|
||||||
completionMenu.complete()
|
|
||||||
} else if (event.modifiers & Qt.ShiftModifier) {
|
|
||||||
textField.insert(cursorPosition, "\n")
|
|
||||||
} else {
|
|
||||||
chatBar.postMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Keys.onReturnPressed: {
|
|
||||||
if (completionMenu.visible) {
|
|
||||||
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 {
|
|
||||||
id: paneLoader
|
|
||||||
|
|
||||||
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.ToolButton {
|
|
||||||
id: cancelButton
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.right: parent.right
|
|
||||||
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
|
|
||||||
action: Kirigami.Action {
|
|
||||||
text: root.isReplying ? i18nc("@action:button", "Cancel reply") : i18nc("@action:button", "Cancel edit")
|
|
||||||
icon.name: "dialog-close"
|
|
||||||
onTriggered: {
|
|
||||||
currentRoom.chatBoxReplyId = "";
|
|
||||||
currentRoom.chatBoxEditId = "";
|
|
||||||
currentRoom.chatBoxAttachmentPath = "";
|
|
||||||
root.forceActiveFocus()
|
|
||||||
}
|
|
||||||
shortcut: "Escape"
|
|
||||||
}
|
|
||||||
QQC2.ToolTip.text: text
|
|
||||||
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 {
|
|
||||||
model: root.actions
|
|
||||||
Kirigami.Icon {
|
|
||||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
|
||||||
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
|
||||||
|
|
||||||
source: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
|
|
||||||
active: actionArea.containsPress
|
|
||||||
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: modelData.tooltip
|
|
||||||
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()
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,83 +10,108 @@ 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 {
|
||||||
Layout.fillWidth: true
|
id: replyPane
|
||||||
Layout.alignment: Qt.AlignLeft
|
|
||||||
Layout.columnSpan: 3
|
|
||||||
topPadding: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
text: isReply ? i18n("Replying to:") : i18n("Editing message:")
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
id: verticalBorder
|
|
||||||
|
|
||||||
Layout.fillHeight: true
|
spacing: leftPadding
|
||||||
Layout.rowSpan: 2
|
|
||||||
|
|
||||||
implicitWidth: Kirigami.Units.smallSpacing
|
contentItem: RowLayout {
|
||||||
color: userColor
|
Layout.fillWidth: true
|
||||||
}
|
spacing: replyPane.spacing
|
||||||
Kirigami.Avatar {
|
|
||||||
id: replyAvatar
|
|
||||||
|
|
||||||
implicitWidth: Kirigami.Units.iconSizes.small
|
FontMetrics {
|
||||||
implicitHeight: Kirigami.Units.iconSizes.small
|
id: fontMetrics
|
||||||
|
font: textArea.font
|
||||||
|
}
|
||||||
|
|
||||||
source: userAvatar
|
Kirigami.Avatar {
|
||||||
name: userName
|
id: avatar
|
||||||
color: userColor
|
Layout.alignment: textContentLayout.height > avatar.height ? Qt.AlignHCenter | Qt.AlignTop : Qt.AlignCenter
|
||||||
}
|
Layout.preferredWidth: Layout.preferredHeight
|
||||||
QQC2.Label {
|
Layout.preferredHeight: fontMetrics.lineSpacing * 2 - fontMetrics.leading
|
||||||
Layout.fillWidth: true
|
source: user ? "image://mxc/" + currentRoom.getUser(user.id).avatarMediaId : ""
|
||||||
Layout.alignment: Qt.AlignLeft
|
name: user ? user.displayName : ""
|
||||||
|
color: user ? user.color : "transparent"
|
||||||
|
visible: Boolean(user)
|
||||||
|
}
|
||||||
|
|
||||||
color: userColor
|
ColumnLayout {
|
||||||
text: userName
|
id: textContentLayout
|
||||||
elide: Text.ElideRight
|
Layout.alignment: Qt.AlignCenter
|
||||||
}
|
Layout.fillWidth: true
|
||||||
QQC2.TextArea {
|
spacing: fontMetrics.leading
|
||||||
id: textArea
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
textFormat: Text.StyledText
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
Layout.fillWidth: true
|
return heading
|
||||||
Layout.columnSpan: 2
|
}
|
||||||
|
}
|
||||||
|
//TODO edit user mentions
|
||||||
|
QQC2.ScrollView {
|
||||||
|
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
|
||||||
|
|
||||||
leftPadding: 0
|
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||||
rightPadding: 0
|
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
QQC2.TextArea {
|
||||||
text: "<style> a{color:" + Kirigami.Theme.linkColor + ";}.user-pill{}</style>" + replyTextMetrics.elidedText
|
id: textArea
|
||||||
selectByMouse: true
|
leftPadding: 0
|
||||||
selectByKeyboard: true
|
rightPadding: 0
|
||||||
readOnly: true
|
topPadding: 0
|
||||||
wrapMode: QQC2.Label.Wrap
|
bottomPadding: 0
|
||||||
textFormat: TextEdit.RichText
|
text: "<style> a{color:" + Kirigami.Theme.linkColor + ";}.user-pill{}</style>" + (currentRoom.chatBoxEditId.length > 0 ? currentRoom.chatBoxEditMessage : currentRoom.chatBoxReplyMessage)
|
||||||
background: Item {}
|
selectByMouse: true
|
||||||
HoverHandler {
|
selectByKeyboard: true
|
||||||
cursorShape: textArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
readOnly: true
|
||||||
|
wrapMode: QQC2.Label.Wrap
|
||||||
|
textFormat: TextEdit.RichText
|
||||||
|
background: Item {}
|
||||||
|
HoverHandler {
|
||||||
|
cursorShape: textArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolButton {
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextMetrics {
|
background: Rectangle {
|
||||||
id: replyTextMetrics
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
|
||||||
text: root.text
|
|
||||||
font: textArea.font
|
|
||||||
elide: Qt.ElideRight
|
|
||||||
elideWidth: textArea.width * 2 - Kirigami.Units.smallSpacing * 2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/")) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user