diff --git a/db/emojis.db b/db/emojis.db new file mode 100644 index 0000000..397eae8 Binary files /dev/null and b/db/emojis.db differ diff --git a/harbour-fernschreiber.pro b/harbour-fernschreiber.pro index 359923b..75db4e6 100644 --- a/harbour-fernschreiber.pro +++ b/harbour-fernschreiber.pro @@ -16,7 +16,7 @@ CONFIG += sailfishapp sailfishapp_i18n PKGCONFIG += nemonotifications-qt5 ngf-qt5 -QT += core dbus +QT += core dbus sql SOURCES += src/harbour-fernschreiber.cpp \ src/appsettings.cpp \ @@ -24,6 +24,7 @@ SOURCES += src/harbour-fernschreiber.cpp \ src/chatmodel.cpp \ src/dbusadaptor.cpp \ src/dbusinterface.cpp \ + src/emojisearchworker.cpp \ src/fernschreiberutils.cpp \ src/notificationmanager.cpp \ src/processlauncher.cpp \ @@ -102,8 +103,11 @@ ICONPATH = /usr/share/icons/hicolor fernschreiber.desktop.path = /usr/share/applications/ fernschreiber.desktop.files = harbour-fernschreiber.desktop +database.files = db +database.path = /usr/share/$${TARGET} + INSTALLS += telegram 86.png 108.png 128.png 172.png 256.png \ - fernschreiber.desktop gui images + fernschreiber.desktop gui images database HEADERS += \ src/appsettings.h \ @@ -111,6 +115,7 @@ HEADERS += \ src/chatmodel.h \ src/dbusadaptor.h \ src/dbusinterface.h \ + src/emojisearchworker.h \ src/fernschreiberutils.h \ src/notificationmanager.h \ src/processlauncher.h \ diff --git a/qml/pages/ChatPage.qml b/qml/pages/ChatPage.qml index fa8d37a..0e51efc 100644 --- a/qml/pages/ChatPage.qml +++ b/qml/pages/ChatPage.qml @@ -43,6 +43,7 @@ Page { property variant chatPartnerInformation; property variant chatGroupInformation; property int chatOnlineMemberCount: 0; + property variant emojiProposals; function updateChatPartnerStatusText() { if (chatPartnerInformation.status['@type'] === "userStatusEmpty" ) { @@ -176,6 +177,44 @@ Page { controlSendButton(); } + function getWordBoundaries(text, cursorPosition) { + var wordBoundaries = { beginIndex : 0, endIndex : text.length}; + var currentIndex = 0; + for (currentIndex = (cursorPosition - 1); currentIndex > 0; currentIndex--) { + if (text.charAt(currentIndex) === ' ') { + wordBoundaries.beginIndex = currentIndex + 1; + break; + } + } + for (currentIndex = cursorPosition; currentIndex < text.length; currentIndex++) { + if (text.charAt(currentIndex) === ' ') { + wordBoundaries.endIndex = currentIndex; + break; + } + } + return wordBoundaries; + } + + function handleMessageTextReplacement(text, cursorPosition) { + var wordBoundaries = getWordBoundaries(text, cursorPosition); + + var currentWord = text.substring(wordBoundaries.beginIndex, wordBoundaries.endIndex); + if (currentWord.length > 1 && currentWord.charAt(0) === ':') { + tdLibWrapper.searchEmoji(currentWord.substring(1)); + } else { + chatPage.emojiProposals = null; + } + } + + function replaceMessageText(text, cursorPosition, newText) { + var wordBoundaries = getWordBoundaries(text, cursorPosition); + var newCompleteText = text.substring(0, wordBoundaries.beginIndex) + newText + " " + text.substring(wordBoundaries.endIndex); + var newIndex = wordBoundaries.beginIndex + newText.length + 1; + newMessageTextField.text = newCompleteText; + newMessageTextField.cursorPosition = newIndex; + lostFocusTimer.start(); + } + Component.onCompleted: { initializePage(); } @@ -227,6 +266,9 @@ Page { uploadingProgressBar.maximumValue = fileInformation.size; uploadingProgressBar.value = fileInformation.remote.uploaded_size; } + onEmojiSearchSuccessful: { + chatPage.emojiProposals = result; + } } Connections { @@ -269,6 +311,26 @@ Page { } } + Timer { + id: lostFocusTimer + interval: 200 + running: false + repeat: false + onTriggered: { + newMessageTextField.forceActiveFocus(); + } + } + + Timer { + id: textReplacementTimer + interval: 600 + running: false + repeat: false + onTriggered: { + handleMessageTextReplacement(newMessageTextField.text, newMessageTextField.cursorPosition); + } + } + Timer { id: chatContactTimeUpdater interval: 60000 @@ -1151,6 +1213,58 @@ Page { } + Column { + id: emojiColumn + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + visible: emojiProposals ? ( emojiProposals.length > 0 ? true : false ) : false + opacity: emojiProposals ? ( emojiProposals.length > 0 ? 1 : 0 ) : 0 + Behavior on opacity { NumberAnimation {} } + spacing: Theme.paddingMedium + + Flickable { + width: parent.width + height: emojiResultRow.height + Theme.paddingSmall + anchors.horizontalCenter: parent.horizontalCenter + contentWidth: emojiResultRow.width + clip: true + Row { + id: emojiResultRow + spacing: Theme.paddingMedium + Repeater { + model: emojiProposals + + Item { + height: singleEmojiRow.height + width: singleEmojiRow.width + + Row { + id: singleEmojiRow + spacing: Theme.paddingSmall + + Image { + id: emojiPicture + source: "../js/emoji/" + modelData.file_name + width: Theme.fontSizeLarge + height: Theme.fontSizeLarge + } + + } + + MouseArea { + anchors.fill: parent + onClicked: { + replaceMessageText(newMessageTextField.text, newMessageTextField.cursorPosition, modelData.emoji); + emojiProposals = null; + } + } + } + + } + } + } + } + Text { width: parent.width @@ -1199,6 +1313,7 @@ Page { onTextChanged: { controlSendButton(); + textReplacementTimer.restart(); } } diff --git a/rpm/harbour-fernschreiber.spec b/rpm/harbour-fernschreiber.spec index fa78aed..2054851 100644 --- a/rpm/harbour-fernschreiber.spec +++ b/rpm/harbour-fernschreiber.spec @@ -24,6 +24,7 @@ BuildRequires: pkgconfig(Qt5Core) BuildRequires: pkgconfig(Qt5Qml) BuildRequires: pkgconfig(Qt5Quick) BuildRequires: pkgconfig(Qt5DBus) +BuildRequires: pkgconfig(Qt5Sql) BuildRequires: pkgconfig(nemonotifications-qt5) BuildRequires: pkgconfig(ngf-qt5) BuildRequires: desktop-file-utils diff --git a/rpm/harbour-fernschreiber.yaml b/rpm/harbour-fernschreiber.yaml index 0524663..21c163a 100644 --- a/rpm/harbour-fernschreiber.yaml +++ b/rpm/harbour-fernschreiber.yaml @@ -24,6 +24,7 @@ PkgConfigBR: - Qt5Qml - Qt5Quick - Qt5DBus + - Qt5Sql - nemonotifications-qt5 - ngf-qt5 diff --git a/src/emojisearchworker.cpp b/src/emojisearchworker.cpp new file mode 100644 index 0000000..5e97d0b --- /dev/null +++ b/src/emojisearchworker.cpp @@ -0,0 +1,69 @@ +/* + Copyright (C) 2020 Sebastian J. Wolf + + This file is part of Fernschreiber. + + Fernschreiber is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fernschreiber is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Fernschreiber. If not, see . +*/ +#include "emojisearchworker.h" + +#define LOG(x) qDebug() << "[EmojiSearchWorker]" << x + +EmojiSearchWorker::~EmojiSearchWorker() +{ + LOG("Destroying myself..."); + database.close(); +} + +EmojiSearchWorker::EmojiSearchWorker(QObject *parent) : QThread(parent) +{ + LOG("Initializing Emoji database"); + QSqlDatabase::removeDatabase("emojis"); + database = QSqlDatabase::addDatabase("QSQLITE", "emojis"); + database.setDatabaseName("/usr/share/harbour-fernschreiber/db/emojis.db"); +} + +void EmojiSearchWorker::setParameters(const QString &queryString) +{ + this->queryString = queryString; +} + +void EmojiSearchWorker::performSearch() +{ + LOG("Performing emoji search" << this->queryString); + QVariantList resultList; + + if (database.open()) { + QSqlQuery query(database); + query.prepare("select * from emojis where description match (:queryString) limit 25"); + query.bindValue(":queryString", queryString + "*"); + query.exec(); + while (query.next()) { + if (isInterruptionRequested()) { + break; + } + QVariantMap foundEmoji; + foundEmoji.insert("file_name", query.value(0).toString()); + foundEmoji.insert("emoji", query.value(1).toString()); + foundEmoji.insert("emoji_version", query.value(2).toString()); + foundEmoji.insert("description", query.value(3).toString()); + resultList.append(foundEmoji); + } + database.close(); + } else { + LOG("Unable to perform a query on database" << database.lastError().databaseText()); + } + + emit searchCompleted(queryString, resultList); +} diff --git a/src/emojisearchworker.h b/src/emojisearchworker.h new file mode 100644 index 0000000..c728b07 --- /dev/null +++ b/src/emojisearchworker.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2020 Sebastian J. Wolf + + This file is part of Fernschreiber. + + Fernschreiber is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fernschreiber is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Fernschreiber. If not, see . +*/ +#ifndef EMOJISEARCHWORKER_H +#define EMOJISEARCHWORKER_H + +#include +#include +#include +#include +#include +#include + +class EmojiSearchWorker : public QThread +{ + Q_OBJECT + void run() Q_DECL_OVERRIDE { + performSearch(); + } +public: + ~EmojiSearchWorker() override; + explicit EmojiSearchWorker(QObject *parent = nullptr); + void setParameters(const QString &queryString); + +signals: + void searchCompleted(const QString &queryString, const QVariantList &resultList); + +public slots: + +private: + QSqlDatabase database; + QString queryString; + + void performSearch(); +}; + +#endif // EMOJISEARCHWORKER_H diff --git a/src/tdlibwrapper.cpp b/src/tdlibwrapper.cpp index 447e1b6..d5b534b 100644 --- a/src/tdlibwrapper.cpp +++ b/src/tdlibwrapper.cpp @@ -93,6 +93,8 @@ TDLibWrapper::TDLibWrapper(QObject *parent) : QObject(parent) connect(this->tdLibReceiver, SIGNAL(stickerSets(QVariantList)), this, SLOT(handleStickerSets(QVariantList))); connect(this->tdLibReceiver, SIGNAL(stickerSet(QVariantMap)), this, SLOT(handleStickerSet(QVariantMap))); + connect(&emojiSearchWorker, SIGNAL(searchCompleted(QString, QVariantList)), this, SLOT(handleEmojiSearchCompleted(QString, QVariantList))); + this->tdLibReceiver->start(); this->setLogVerbosityLevel(); @@ -467,6 +469,16 @@ void TDLibWrapper::getStickerSet(const QString &setId) this->sendRequest(requestObject); } +void TDLibWrapper::searchEmoji(const QString &queryString) +{ + LOG("Searching emoji" << queryString); + while (this->emojiSearchWorker.isRunning()) { + this->emojiSearchWorker.requestInterruption(); + } + this->emojiSearchWorker.setParameters(queryString); + this->emojiSearchWorker.start(); +} + QVariantMap TDLibWrapper::getUserInformation() { return this->userInformation; @@ -828,6 +840,12 @@ void TDLibWrapper::handleStickerSet(const QVariantMap &stickerSet) emit stickerSetReceived(stickerSet); } +void TDLibWrapper::handleEmojiSearchCompleted(const QString &queryString, const QVariantList &resultList) +{ + LOG("Emoji search completed" << queryString); + emit emojiSearchSuccessful(resultList); +} + void TDLibWrapper::setInitialParameters() { LOG("Sending initial parameters to TD Lib"); diff --git a/src/tdlibwrapper.h b/src/tdlibwrapper.h index a583505..6723470 100644 --- a/src/tdlibwrapper.h +++ b/src/tdlibwrapper.h @@ -24,6 +24,7 @@ #include "tdlibreceiver.h" #include "dbusadaptor.h" #include "dbusinterface.h" +#include "emojisearchworker.h" class TDLibWrapper : public QObject { @@ -129,6 +130,9 @@ public: Q_INVOKABLE void getInstalledStickerSets(); Q_INVOKABLE void getStickerSet(const QString &setId); + // Others (candidates for extraction ;)) + Q_INVOKABLE void searchEmoji(const QString &queryString); + public: const Group* getGroup(qlonglong groupId) const; static ChatMemberStatus chatMemberStatusFromString(const QString &status); @@ -169,6 +173,7 @@ signals: void installedStickerSetsUpdated(const QVariantList &stickerSetIds); void stickerSetsReceived(const QVariantList &stickerSets); void stickerSetReceived(const QVariantMap &stickerSet); + void emojiSearchSuccessful(const QVariantList &result); public slots: void handleVersionDetected(const QString &version); @@ -204,6 +209,7 @@ public slots: void handleInstalledStickerSetsUpdated(const QVariantList &stickerSetIds); void handleStickerSets(const QVariantList &stickerSets); void handleStickerSet(const QVariantMap &stickerSet); + void handleEmojiSearchCompleted(const QString &queryString, const QVariantList &resultList); private: void setInitialParameters(); @@ -228,6 +234,8 @@ private: QVariantMap unreadChatInformation; QHash basicGroups; QHash superGroups; + EmojiSearchWorker emojiSearchWorker; + }; #endif // TDLIBWRAPPER_H