From dda2b21a9a3d4d77bb3c5538048e53af78641a70 Mon Sep 17 00:00:00 2001 From: dsc Date: Mon, 6 Dec 2021 12:42:13 +0100 Subject: [PATCH] endless scroll, text scaling --- README.md | 5 +- src/CMakeLists.txt | 8 +- src/assets_localdev.qrc | 1 + src/assets_maemo.qrc | 1 + src/chatwindow.cpp | 14 +- src/chatwindow.h | 9 +- src/conversations.cpp | 139 ++---------------- src/conversations.h | 34 ++--- src/lib/config.cpp | 1 + src/lib/config.h | 2 +- src/lib/rtcom.h | 88 +++++++++++ src/main.cpp | 3 + src/mainwindow.cpp | 12 +- src/mainwindow.h | 2 +- src/models/ChatMessage.cpp | 3 + src/models/ChatModel.cpp | 110 +++++++++++++- src/models/ChatModel.h | 45 +++++- src/models/ChatOverviewModel.cpp | 61 -------- src/models/ChatOverviewModel.h | 39 ----- src/qml/.dev/whatsthat.qml | 6 +- src/qml/chat/chatty/chatty.qml | 33 +++-- src/qml/chat/whatsthat/whatsthat.qml | 48 +++--- src/qml/components/ChatListView.qml | 28 ++++ src/qml/components/ChatRoot.qml | 102 +++++++++++++ .../components/ChatScrollToBottomButton.qml | 34 +++++ src/qml/components/qml_components.qrc | 7 + src/qml/overview.qml | 23 ++- src/settings.cpp | 22 +++ src/settings.h | 5 +- src/settings.ui | 91 ++++++++++-- 30 files changed, 644 insertions(+), 332 deletions(-) create mode 100644 src/lib/rtcom.h delete mode 100644 src/models/ChatOverviewModel.cpp delete mode 100644 src/models/ChatOverviewModel.h create mode 100644 src/qml/components/ChatListView.qml create mode 100644 src/qml/components/ChatRoot.qml create mode 100644 src/qml/components/ChatScrollToBottomButton.qml create mode 100644 src/qml/components/qml_components.qrc diff --git a/README.md b/README.md index e216fca..12b9994 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,15 @@ op Maemo: sudo apt install -y ccache cmake build-essential libx11-dev zlib1g-dev libpng-dev qtbase5-dev libqt5svg5-dev libqt5svg5-dev libqt5maemo5-dev libqt5x11extras5-dev gdb libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 qml-module-qtquick2 qml-module-qtquick-layouts qml-module-qtquick-controls qml-module-qtquick-extras qml-module-qtquick-dialogs libqt5websockets5-dev libqt5x11extras5-dev qml-module-qtquick-controls qml-module-qtquick-layouts qtquickcontrols2-5-dev ``` -Als `user`: +As `user`: ```bash cmake -Bbuild . make -Cbuild -j2 ./build/bin/conversations + +# mem usage +/usr/bin/time -v ./build/bin/conversations ``` ### remote debug (dev) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 31085a2..b479026 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,7 +20,13 @@ if(MAEMO) find_package(Qt5 REQUIRED COMPONENTS Maemo5) endif() -qt5_add_resources(RESOURCES assets.qrc assets/images/flags/flags.qrc qml/chat/chatty/chatty.qrc qml/chat/whatsthat/whatsthat.qrc) +qt5_add_resources(RESOURCES + assets.qrc + assets/images/flags/flags.qrc + qml/components/qml_components.qrc + qml/chat/chatty/chatty.qrc + qml/chat/whatsthat/whatsthat.qrc + ) if(MAEMO) qt5_add_resources(RESOURCES assets_maemo.qrc) diff --git a/src/assets_localdev.qrc b/src/assets_localdev.qrc index e3aa36e..8bad2a6 100644 --- a/src/assets_localdev.qrc +++ b/src/assets_localdev.qrc @@ -14,5 +14,6 @@ assets/images/icons/general_sending_failed.png assets/images/icons/general_contacts.png assets/images/icons/general_missed.png + assets/images/icons/rss_reader_move_down.png diff --git a/src/assets_maemo.qrc b/src/assets_maemo.qrc index cffdc56..a37c442 100644 --- a/src/assets_maemo.qrc +++ b/src/assets_maemo.qrc @@ -14,5 +14,6 @@ /usr/share/icons/hicolor/48x48/hildon/general_sending_failed.png /usr/share/icons/hicolor/48x48/hildon/general_contacts.png /usr/share/icons/hicolor/48x48/hildon/general_missed.png + /usr/share/icons/hicolor/48x48/hildon/rss_reader_move_down.png diff --git a/src/chatwindow.cpp b/src/chatwindow.cpp index 8f3846e..b154f4d 100644 --- a/src/chatwindow.cpp +++ b/src/chatwindow.cpp @@ -18,19 +18,27 @@ ChatWindow * ChatWindow::pChatWindow = nullptr; -ChatWindow::ChatWindow(Conversations *ctx, QWidget *parent) : +ChatWindow::ChatWindow(Conversations *ctx, const QString &group_uid, const QString &local_uid, const QString &remote_uid, QWidget *parent) : QMainWindow(parent), ui(new Ui::ChatWindow), + m_group_uid(group_uid), + m_local_uid(local_uid), + m_remote_uid(remote_uid), m_ctx(ctx) { pChatWindow = this; ui->setupUi(this); + this->chatModel = new ChatModel(this); + this->chatModel->getMessages(remote_uid); + #ifdef MAEMO setProperty("X-Maemo-StackedWindow", 1); + setProperty("X-Maemo-Orientation", 2); #endif auto *qctx = ui->quick->rootContext(); - qctx->setContextProperty("chatModel", m_ctx->chatModel); + qctx->setContextProperty("chatModel", this->chatModel); + qctx->setContextProperty("ctx", m_ctx); auto theme = config()->get(ConfigKeys::ChatTheme).toString(); if(theme == "chatty") @@ -56,7 +64,7 @@ Conversations *ChatWindow::getContext(){ } void ChatWindow::closeEvent(QCloseEvent *event) { - m_ctx->chatModel->clear(); + this->chatModel->clear(); QWidget::closeEvent(event); } diff --git a/src/chatwindow.h b/src/chatwindow.h index 527b380..a2a5e96 100644 --- a/src/chatwindow.h +++ b/src/chatwindow.h @@ -26,10 +26,12 @@ class ChatWindow : public QMainWindow { Q_OBJECT public: - explicit ChatWindow(Conversations *ctx, QWidget *parent = nullptr); + Ui::ChatWindow *ui; + explicit ChatWindow(Conversations *ctx, const QString &group_uid, const QString &local_uid, const QString &remote_uid, QWidget *parent = nullptr); static Conversations *getContext(); ~ChatWindow() override; - Ui::ChatWindow *ui; + + ChatModel *chatModel; private slots: void onGatherMessage(); @@ -40,6 +42,9 @@ signals: private: Conversations *m_ctx; static ChatWindow *pChatWindow; + QString m_remote_uid; + QString m_local_uid; + QString m_group_uid; void closeEvent(QCloseEvent *event) override; }; diff --git a/src/conversations.cpp b/src/conversations.cpp index dbea9a9..5f09c03 100644 --- a/src/conversations.cpp +++ b/src/conversations.cpp @@ -10,15 +10,6 @@ #include "lib/utils.h" #include "lib/globals.h" - -#define LOOKUP_INT(x) \ - g_value_get_int((const GValue*)g_hash_table_lookup(values, x)) -#define LOOKUP_BOOL(x) \ - g_value_get_boolean((const GValue*)g_hash_table_lookup(values, x)) -#define LOOKUP_STR(x) \ - g_value_get_string((const GValue*)g_hash_table_lookup(values, x)) - - Conversations::Conversations(QCommandLineParser *cmdargs) { this->cmdargs = cmdargs; @@ -35,133 +26,16 @@ Conversations::Conversations(QCommandLineParser *cmdargs) { // flags, iso codes, etc countries = new Countries(); + m_textScaling = config()->get(ConfigKeys::TextScaling).toFloat(); + if(this->isDebug) { qDebug() << "configRoot: " << configRoot; qDebug() << "homeDir: " << homeDir; qDebug() << "configDirectory: " << configDirectory; } - chatModel = new ChatModel(); - chatOverviewModel = new ChatOverviewModel(); - - this->getOverviewMessages(); -} - -void Conversations::requestChat(const QString &group_uid, const QString &local_uid, const QString &remote_uid) { - this->getMessages(remote_uid, 10, 0); - emit showChat(); -} - -rtcom_query* Conversations::rtcomStartQuery(const int limit, const int offset, const RTComElQueryGroupBy group_by) { - RTComElQuery *query = NULL; - RTComElIter *it = NULL; - RTComEl *el = NULL; - - el = rtcom_el_new(); - query = rtcom_el_query_new(el); - - if(group_by != RTCOM_EL_QUERY_GROUP_BY_NONE) - rtcom_el_query_set_group_by(query, group_by); - - rtcom_el_query_set_limit(query, limit); - rtcom_el_query_set_offset(query, offset); - - return new rtcom_query{query, it , el}; -} - -QList Conversations::rtcomIterateResults(rtcom_query *query_struct) { - QList results; - query_struct->it = rtcom_el_get_events(query_struct->el, query_struct->query); - - if(query_struct->it && rtcom_el_iter_first(query_struct->it)) { - do { - GHashTable *values = NULL; - values = rtcom_el_iter_get_value_map( - query_struct->it, - "id", - "service", - "group-uid", - "local-uid", - "remote-uid", - "remote-name", - "remote-ebook-uid", - "content", - "icon-name", - "start-time", - "event-count", - "group-title", - "event-type", - "outgoing", - "flags", - NULL); - - auto *item = new ChatMessage( - LOOKUP_INT("id"), - LOOKUP_STR("service"), - LOOKUP_STR("group-uid"), - LOOKUP_STR("local-uid"), - LOOKUP_STR("remote-uid"), - LOOKUP_STR("remote-name"), - LOOKUP_STR("remote-ebook-uid"), - LOOKUP_STR("content"), - LOOKUP_STR("icon-name"), - LOOKUP_INT("start-time"), - LOOKUP_INT("event-count"), - LOOKUP_STR("group-title"), - LOOKUP_STR("event-type"), - LOOKUP_BOOL("outgoing"), - LOOKUP_INT("flags")); - results << item; - } while (rtcom_el_iter_next(query_struct->it)); - g_object_unref(query_struct->it); - } else { - qCritical() << "Failed to init iterator to start"; - } - - g_object_unref(query_struct->query); - return results; -} - -void Conversations::getOverviewMessages(const int limit, const int offset) const { - rtcom_query* query_struct = Conversations::rtcomStartQuery(limit, offset, RTCOM_EL_QUERY_GROUP_BY_CONTACT); - bool query_prepared = FALSE; - - query_prepared = rtcom_el_query_prepare(query_struct->query, - "service-id", m_rtcom_sms_service_id, RTCOM_EL_OP_EQUAL, NULL); - if(!query_prepared) { - qCritical() << "Couldn't prepare query"; - g_object_unref(query_struct->query); - delete query_struct; - return; - } - - auto results = Conversations::rtcomIterateResults(query_struct); - for (const auto &message: results) - this->chatOverviewModel->appendOverviewItem(message); -} - -void Conversations::getMessages(const QString &remote_uid, const int limit, const int offset) const { - rtcom_query* query_struct = Conversations::rtcomStartQuery(limit, offset, RTCOM_EL_QUERY_GROUP_BY_NONE); - bool query_prepared = FALSE; - query_prepared = rtcom_el_query_prepare(query_struct->query, - "remote-uid", remote_uid.toStdString().c_str(), RTCOM_EL_OP_EQUAL, - "service-id", m_rtcom_sms_service_id, RTCOM_EL_OP_EQUAL, - NULL); - if(!query_prepared) { - qCritical() << "Couldn't prepare query"; - g_object_unref(query_struct->query); - delete query_struct; - return; - } - - auto results = Conversations::rtcomIterateResults(query_struct); - QList::const_iterator rIt; - rIt = results.constEnd(); - - while(rIt != results.constBegin()) { - --rIt; - this->chatModel->appendMessage(*rIt); - } + chatOverviewModel = new ChatModel(); + this->chatOverviewModel->getOverviewMessages(); } void Conversations::onSendMessage(const QString &message) { @@ -176,6 +50,11 @@ void Conversations::setWindowTitle(const QString &title) { emit setTitle(title); } +void Conversations::onTextScalingChanged() { + m_textScaling = config()->get(ConfigKeys::TextScaling).toFloat(); + emit textScalingChanged(); +} + void Conversations::createConfigDirectory(const QString &dir) { QStringList createDirs({dir}); for(const auto &d: createDirs) { diff --git a/src/conversations.h b/src/conversations.h index 18ab8ce..d803b0d 100644 --- a/src/conversations.h +++ b/src/conversations.h @@ -6,22 +6,12 @@ #include #include -#include -#include -#include - #include "lib/http.h" #include "lib/countries.h" +#include "lib/config.h" #include "models/ChatModel.h" -#include "models/ChatOverviewModel.h" #include "wsclient.h" -struct rtcom_query { - RTComElQuery *query; - RTComElIter *it; - RTComEl *el = NULL; -}; - class Conversations : public QObject { Q_OBJECT Q_PROPERTY(QString configRootconfigDirectory MEMBER configDirectory); @@ -29,11 +19,15 @@ class Conversations : public QObject { Q_PROPERTY(QString pathGenericData MEMBER pathGenericData); Q_PROPERTY(QString homeDir MEMBER homeDir); Q_PROPERTY(QString accountName MEMBER accountName); + Q_PROPERTY(bool isDebug MEMBER isDebug NOTIFY debugChanged); + Q_PROPERTY(float scaleFactor MEMBER m_textScaling NOTIFY textScalingChanged); + Q_PROPERTY(float isMaemo MEMBER isMaemo NOTIFY isMaemoChanged); public: explicit Conversations(QCommandLineParser *cmdargs); ~Conversations() override; bool isDebug = false; + bool isMaemo = false; QCommandLineParser *cmdargs; @@ -47,34 +41,28 @@ public: static void createConfigDirectory(const QString &dir) ; - WSClient *ws; Countries *countries; - ChatModel *chatModel; - ChatOverviewModel *chatOverviewModel; + ChatModel *chatOverviewModel; void setWindowTitle(const QString &title); - static rtcom_query* rtcomStartQuery(int limit, int offset, RTComElQueryGroupBy group_by = RTCOM_EL_QUERY_GROUP_BY_NONE); - static QList rtcomIterateResults(rtcom_query *query_struct); - - void getMessages(const QString &remote_uid, int limit, int offset) const; - void getOverviewMessages(int limit = 20, int offset = 0) const; - signals: void clockSkewDetected(); void setTitle(const QString &title); // set window title void showChat(); - + void debugChanged(); + void textScalingChanged(); + void isMaemoChanged(); public slots: void onSendMessage(const QString &message); - void requestChat(const QString &group_uid, const QString &local_uid, const QString &remote_uid); + void onTextScalingChanged(); private: + float m_textScaling = 1.0; QTimer m_hibernateTimer; std::chrono::seconds m_hibernateDetectInterval{300}; std::chrono::time_point m_hibernatePreviousTime; - gint m_rtcom_sms_service_id = 3; // rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_SMS"); }; #endif // CONV_CONTEXT_H diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 6c35fab..f366933 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -18,6 +18,7 @@ static const QHash configStrings = { // General {ConfigKeys::MaemoTest,{QS("MaemoTest"), ""}}, {ConfigKeys::ChatTheme,{QS("ChatTheme"), "whatsthat"}}, + {ConfigKeys::TextScaling,{QS("TextScaling"), 1.0}}, }; QPointer Config::m_instance(nullptr); diff --git a/src/lib/config.h b/src/lib/config.h index 5b135d0..68ce318 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -13,7 +13,7 @@ namespace ConfigKeys enum ConfigKey { MaemoTest, ChatTheme, - FooBar + TextScaling }; Q_ENUM_NS(ConfigKey) } diff --git a/src/lib/rtcom.h b/src/lib/rtcom.h new file mode 100644 index 0000000..0f9a1a1 --- /dev/null +++ b/src/lib/rtcom.h @@ -0,0 +1,88 @@ +#include +#include +#include + +#include "models/ChatMessage.h" + +struct rtcom_query { + RTComElQuery *query = NULL; + RTComElIter *it = NULL; + RTComEl *el = NULL; +}; + +#define LOOKUP_INT(x) \ + g_value_get_int((const GValue*)g_hash_table_lookup(values, x)) +#define LOOKUP_BOOL(x) \ + g_value_get_boolean((const GValue*)g_hash_table_lookup(values, x)) +#define LOOKUP_STR(x) \ + g_value_get_string((const GValue*)g_hash_table_lookup(values, x)) + +rtcom_query* rtcomStartQuery(const int limit, const int offset, const RTComElQueryGroupBy group_by) { + RTComElQuery *query = NULL; + RTComElIter *it = NULL; + RTComEl *el = NULL; + + el = rtcom_el_new(); + query = rtcom_el_query_new(el); + + if(group_by != RTCOM_EL_QUERY_GROUP_BY_NONE) + rtcom_el_query_set_group_by(query, group_by); + + rtcom_el_query_set_limit(query, limit); + rtcom_el_query_set_offset(query, offset); + + return new rtcom_query{query, it , el}; +} + +QList rtcomIterateResults(rtcom_query *query_struct) { + QList results; + query_struct->it = rtcom_el_get_events(query_struct->el, query_struct->query); + + if(query_struct->it && rtcom_el_iter_first(query_struct->it)) { + do { + GHashTable *values = NULL; + values = rtcom_el_iter_get_value_map( + query_struct->it, + "id", + "service", + "group-uid", + "local-uid", + "remote-uid", + "remote-name", + "remote-ebook-uid", + "content", + "icon-name", + "start-time", + "event-count", + "group-title", + "event-type", + "outgoing", + "flags", + NULL); + + auto *item = new ChatMessage( + LOOKUP_INT("id"), + LOOKUP_STR("service"), + LOOKUP_STR("group-uid"), + LOOKUP_STR("local-uid"), + LOOKUP_STR("remote-uid"), + LOOKUP_STR("remote-name"), + LOOKUP_STR("remote-ebook-uid"), + LOOKUP_STR("content"), + LOOKUP_STR("icon-name"), + LOOKUP_INT("start-time"), + LOOKUP_INT("event-count"), + LOOKUP_STR("group-title"), + LOOKUP_STR("event-type"), + LOOKUP_BOOL("outgoing"), + LOOKUP_INT("flags")); + results << item; + } while (rtcom_el_iter_next(query_struct->it)); + g_object_unref(query_struct->it); + } else { + qCritical() << "Failed to init iterator to start"; + } + + g_object_unref(query_struct->query); + return results; +} diff --git a/src/main.cpp b/src/main.cpp index ca2b994..0623b22 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,6 +67,9 @@ int main(int argc, char *argv[]) { auto *ctx = new Conversations(&parser); ctx->applicationPath = argv_.at(0); ctx->isDebug = debugMode; +#ifdef MAEMO + ctx->isMaemo = true; +#endif auto *mainWindow = new MainWindow(ctx); return QApplication::exec(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 899df23..1b5da24 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -25,6 +25,7 @@ MainWindow::MainWindow(Conversations *ctx, QWidget *parent) : #ifdef MAEMO setProperty("X-Maemo-StackedWindow", 1); + setProperty("X-Maemo-Orientation", 2); #endif this->screenDpiRef = 128; @@ -47,17 +48,16 @@ MainWindow::MainWindow(Conversations *ctx, QWidget *parent) : auto *qctx = ui->quick->rootContext(); qctx->setContextProperty("cfg", config()); + qctx->setContextProperty("ctx", m_ctx); qctx->setContextProperty("chatOverviewModel", m_ctx->chatOverviewModel); ui->quick->setSource(QUrl("qrc:/overview.qml")); ui->menuBar->hide(); connect(ui->actionSettings, &QAction::triggered, this, &MainWindow::openSettingsWindow); - connect((QObject*)ui->quick->rootObject(), SIGNAL(rowClicked(QString, QString, QString)), ctx, SLOT(requestChat(QString, QString, QString))); - //connect(this, &MainWindow::requestChatWindow, this, &MainWindow::openChatWindow); + connect((QObject*)ui->quick->rootObject(), SIGNAL(rowClicked(QString, QString, QString)), this, SLOT(openChatWindow(QString, QString, QString))); connect(m_ctx, &Conversations::setTitle, this, &QMainWindow::setWindowTitle); - connect(m_ctx, &Conversations::showChat, this, &MainWindow::openChatWindow); this->show(); } @@ -65,10 +65,12 @@ MainWindow::MainWindow(Conversations *ctx, QWidget *parent) : void MainWindow::openSettingsWindow() { m_settings = new Settings(m_ctx, this); m_settings->show(); + + connect(m_settings, &Settings::textScalingChanged, this->m_ctx, &Conversations::onTextScalingChanged); } -void MainWindow::openChatWindow() { - m_chatWindow = new ChatWindow(m_ctx, this); +void MainWindow::openChatWindow(const QString &group_uid, const QString &local_uid, const QString &remote_uid) { + m_chatWindow = new ChatWindow(m_ctx, group_uid, local_uid, remote_uid, this); m_chatWindow->show(); connect(m_chatWindow, &ChatWindow::sendMessage, this->m_ctx, &Conversations::onSendMessage); diff --git a/src/mainwindow.h b/src/mainwindow.h index 343a5e7..976a8ab 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -42,7 +42,7 @@ public: qreal screenRatio; private slots: - void openChatWindow(); + void openChatWindow(const QString &group_uid, const QString &local_uid, const QString &remote_uid); void openSettingsWindow(); private: diff --git a/src/models/ChatMessage.cpp b/src/models/ChatMessage.cpp index 7b0d4ad..9e08c4b 100644 --- a/src/models/ChatMessage.cpp +++ b/src/models/ChatMessage.cpp @@ -48,6 +48,9 @@ QString ChatMessage::hourstr() const { return m_date.toString("hh:mm"); } QString ChatMessage::datestr() const { return m_date.toString("dd/MM/yyyy"); } bool ChatMessage::isHead() const { if(previous == nullptr) return true; + if(m_text == "got in") { + int wegewg = 1; + } if(previous->cid() == m_cid) return false; return true; } diff --git a/src/models/ChatModel.cpp b/src/models/ChatModel.cpp index 55f7717..b234466 100644 --- a/src/models/ChatModel.cpp +++ b/src/models/ChatModel.cpp @@ -1,12 +1,26 @@ #include #include +#include "lib/rtcom.h" #include "models/ChatModel.h" + ChatModel::ChatModel(QObject *parent) : QAbstractListModel(parent) { } +void ChatModel::prependMessage(ChatMessage *message) { + if(!chats.isEmpty()) { + auto *n = chats.at(0); + message->next = n; + n->previous = message; + } + + beginInsertRows(QModelIndex(), 0, 0); + chats.prepend(message); + endInsertRows(); +} + void ChatModel::appendMessage(ChatMessage *message) { const int idx = rowCount(); if(idx != 0 && !chats.isEmpty()) { @@ -16,7 +30,7 @@ void ChatModel::appendMessage(ChatMessage *message) { } beginInsertRows(QModelIndex(), idx, rowCount()); - chats << message; + chats.append(message); endInsertRows(); } @@ -30,7 +44,13 @@ QVariant ChatModel::data(const QModelIndex &index, int role) const { return QVariant(); const ChatMessage *message = chats[index.row()]; - if (role == NameRole) + if (role == GroupUIDRole) + return message->group_uid(); + else if (role == LocalUIDRole) + return message->local_uid(); + else if (role == RemoteNameRole) + return message->remote_name(); + else if (role == NameRole || role == RemoteUIDRole) return message->remote_uid(); else if (role == DateRole) return message->datestr(); @@ -44,11 +64,19 @@ QVariant ChatModel::data(const QModelIndex &index, int role) const { return message->isLast(); else if (role == OutgoingRole) return message->outgoing(); + else if (role == IconNameRole) + return message->icon_name(); + else if (role == EventIDRole) + return message->event_id(); return QVariant(); } QHash ChatModel::roleNames() const { QHash roles; + roles[GroupUIDRole] = "group_uid"; + roles[LocalUIDRole] = "local_uid"; + roles[RemoteUIDRole] = "remote_uid"; + roles[RemoteNameRole] = "remote_name"; roles[NameRole] = "name"; roles[DateRole] = "datestr"; roles[HourRole] = "hourstr"; @@ -56,6 +84,8 @@ QHash ChatModel::roleNames() const { roles[isHeadRole] = "isHead"; roles[isLastRole] = "isLast"; roles[OutgoingRole] = "outgoing"; + roles[IconNameRole] = "icon_name"; + roles[EventIDRole] = "event_id"; return roles; } @@ -67,3 +97,79 @@ void ChatModel::clear() { endResetModel(); } + +void ChatModel::getOverviewMessages(const int limit, const int offset) { + rtcom_query* query_struct = rtcomStartQuery(limit, offset, RTCOM_EL_QUERY_GROUP_BY_CONTACT); + bool query_prepared = FALSE; + + query_prepared = rtcom_el_query_prepare(query_struct->query, + "service-id", m_rtcom_sms_service_id, RTCOM_EL_OP_EQUAL, NULL); + if(!query_prepared) { + qCritical() << "Couldn't prepare query"; + g_object_unref(query_struct->query); + delete query_struct; + return; + } + + auto results = rtcomIterateResults(query_struct); + for (const auto &message: results) + this->appendMessage(message); +} + +unsigned int ChatModel::getMessages(const QString &remote_uid) { + return this->getMessages(remote_uid, m_limit, m_offset); +} + +unsigned int ChatModel::getMessages(const QString &remote_uid, const int limit, const int offset) { + m_remote_uid = remote_uid; + + rtcom_query* query_struct = rtcomStartQuery(limit, offset, RTCOM_EL_QUERY_GROUP_BY_NONE); + bool query_prepared = FALSE; + query_prepared = rtcom_el_query_prepare(query_struct->query, + "remote-uid", remote_uid.toStdString().c_str(), RTCOM_EL_OP_EQUAL, + "service-id", m_rtcom_sms_service_id, RTCOM_EL_OP_EQUAL, + NULL); + if(!query_prepared) { + qCritical() << "Couldn't prepare query"; + g_object_unref(query_struct->query); + delete query_struct; + return 0; + } + + auto results = rtcomIterateResults(query_struct); + + bool prepend = offset != 0; + if(prepend) { + for(auto const &message: results) { + this->prependMessage(message); + } + } else { + QList::const_iterator rIt; + rIt = results.constEnd(); + + while (rIt != results.constBegin()) { + --rIt; + if (offset == 0) + this->appendMessage(*rIt); + } + } + + return results.length(); +} + +unsigned int ChatModel::getPage() { + // called from QML for endless scroll, advances m_offset + m_page += 1; + m_offset = m_page * m_limit; + emit offsetChanged(); + + qDebug() << __FUNCTION__ << "limit:" << m_limit << " offset:" << m_offset; + + auto count = this->getMessages(m_remote_uid, m_limit, m_offset); + if(count < m_limit) { + m_exhausted = true; + emit exhaustedChanged(); + } + + return count; +} diff --git a/src/models/ChatModel.h b/src/models/ChatModel.h index 6a3b7fa..6f863a4 100644 --- a/src/models/ChatModel.h +++ b/src/models/ChatModel.h @@ -5,6 +5,10 @@ #include #include +#include +#include +#include + #include "models/ChatMessage.h" @@ -19,19 +23,56 @@ public: MessageRole, isHeadRole, isLastRole, - OutgoingRole + OutgoingRole, + GroupUIDRole, + LocalUIDRole, + RemoteUIDRole, + RemoteNameRole, + IconNameRole, + EventIDRole }; - ChatModel(QObject *parent = nullptr); + explicit ChatModel(QObject *parent = nullptr); + void prependMessage(ChatMessage *message); void appendMessage(ChatMessage *message); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; QList chats; + + // endless scroll exhausted + Q_PROPERTY(bool exhausted READ is_exhausted NOTIFY exhaustedChanged); + Q_PROPERTY(int limit READ limit NOTIFY limitChanged); + Q_PROPERTY(int offset READ offset NOTIFY offsetChanged); + int limit() const { return m_limit; } + int offset() const { return m_offset; } + bool is_exhausted() { + return m_exhausted; + } + + Q_INVOKABLE unsigned int getPage(); + unsigned int getMessages(const QString &remote_uid); + unsigned int getMessages(const QString &remote_uid, int limit, int offset); + void getOverviewMessages(int limit = 20, int offset = 0); + void clear(); + +signals: + void exhaustedChanged(); + void limitChanged(); + void offsetChanged(); + protected: QHash roleNames() const; + +private: + QString m_remote_uid; + int m_page = 0; + int m_limit = 4; + int m_offset = 0; + bool m_exhausted = false; + gint m_rtcom_sms_service_id = 3; // rtcom_el_get_service_id(el, "RTCOM_EL_SERVICE_SMS"); }; #endif diff --git a/src/models/ChatOverviewModel.cpp b/src/models/ChatOverviewModel.cpp deleted file mode 100644 index 29360e8..0000000 --- a/src/models/ChatOverviewModel.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include -#include - -#include "models/ChatOverviewModel.h" -#include "models/ChatMessage.h" - -ChatOverviewModel::ChatOverviewModel(QObject *parent) - : QAbstractListModel(parent) { -} - -void ChatOverviewModel::appendOverviewItem(ChatMessage *message) { - const int idx = rowCount(); - beginInsertRows(QModelIndex(), idx, rowCount()); - items << message; - endInsertRows(); -} - -int ChatOverviewModel::rowCount(const QModelIndex & parent) const { - Q_UNUSED(parent); - return items.count(); -} - -QVariant ChatOverviewModel::data(const QModelIndex &index, int role) const { - if (index.row() < 0 || index.row() >= items.count()) - return QVariant(); - - const ChatMessage *message = items[index.row()]; - if (role == RemoteUIDRole) - return message->remote_uid(); - else if (role == GroupUIDRole) - return message->group_uid(); - else if (role == LocalUIDRole) - return message->local_uid(); - else if (role == RemoteNameRole) - return message->remote_name(); - else if (role == DateRole) - return message->datestr(); - else if (role == HourRole) - return message->hourstr(); - else if (role == MessageRole) - return message->text(); - else if (role == OutgoingRole) - return message->outgoing(); - else if (role == IconNameRole) - return message->icon_name(); - return QVariant(); -} - -QHash ChatOverviewModel::roleNames() const { - QHash roles; - roles[GroupUIDRole] = "group_uid"; - roles[LocalUIDRole] = "local_uid"; - roles[RemoteUIDRole] = "remote_uid"; - roles[RemoteNameRole] = "remote_name"; - roles[DateRole] = "datestr"; - roles[HourRole] = "hourstr"; - roles[MessageRole] = "message"; - roles[OutgoingRole] = "outgoing"; - roles[IconNameRole] = "icon_name"; - return roles; -} diff --git a/src/models/ChatOverviewModel.h b/src/models/ChatOverviewModel.h deleted file mode 100644 index 49a611e..0000000 --- a/src/models/ChatOverviewModel.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef CHATOVERVIEWMODEL_H -#define CHATOVERVIEWMODEL_H - -#include -#include -#include - -#include "models/ChatMessage.h" - -class ChatOverviewModel : public QAbstractListModel -{ - Q_OBJECT -public: - // Name, date, time, content, type? - enum ChatOverviewModelRoles { - RemoteUIDRole = Qt::UserRole + 1, - GroupUIDRole, - LocalUIDRole, - RemoteNameRole, - DateRole, - HourRole, - MessageRole, - OutgoingRole, - IconNameRole - }; - - ChatOverviewModel(QObject *parent = nullptr); - void appendOverviewItem(ChatMessage *item); - - int rowCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - - QList items; - -protected: - QHash roleNames() const; -}; - -#endif diff --git a/src/qml/.dev/whatsthat.qml b/src/qml/.dev/whatsthat.qml index d4dc2de..5f958f5 100644 --- a/src/qml/.dev/whatsthat.qml +++ b/src/qml/.dev/whatsthat.qml @@ -88,16 +88,12 @@ Rectangle { model: xxx delegate: RowLayout { id: item - property int itemHeight: 32 + property int itemHeight: textColumn.implicitHeight + 12 property bool isSelf: name == "dsc" height: itemHeight + 12 width: parent.width spacing: 0 - Component.onCompleted: { - itemHeight = textColumn.implicitHeight + 12; - } - Item { visible: isSelf Layout.fillWidth: true diff --git a/src/qml/chat/chatty/chatty.qml b/src/qml/chat/chatty/chatty.qml index c69fe0f..51c3086 100644 --- a/src/qml/chat/chatty/chatty.qml +++ b/src/qml/chat/chatty/chatty.qml @@ -2,10 +2,13 @@ import QtQuick 2.0 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.0 -Rectangle { +import "../components" as Components + +Components.ChatRoot { id: root - visible: true color: "black" + chatList: chatListView + property string avatarBorderColor: "#999999" property string chatBorderColor: "#30302f" property string nickColor: "#86d5fc" @@ -13,7 +16,7 @@ Rectangle { property int itemHeightDefault: 68 property int itemHeightSmall: 32 - ListView { + Components.ChatListView { id: chatListView property int nickWidth: 0 anchors.fill: parent @@ -21,11 +24,7 @@ Rectangle { anchors.leftMargin: 20 anchors.rightMargin: 20 - model: chatModel - - onCountChanged: { // scroll to bottom - chatListView.currentIndex = count - 1 - } + onScrollToBottom: root.scrollToBottom(); delegate: Rectangle { property int itemHeight: isHead ? root.itemHeightDefault : root.itemHeightSmall; @@ -85,7 +84,7 @@ Rectangle { return name } color: nickColor - font.pointSize: 18 + font.pointSize: 18 * ctx.scaleFactor Component.onCompleted: { if(implicitWidth > chatListView.nickWidth) @@ -103,7 +102,7 @@ Rectangle { text: message color: "white" wrapMode: Text.WordWrap - font.pointSize: 14 + font.pointSize: 14 * ctx.scaleFactor font.bold: name == "_self" ? true : false; } @@ -120,7 +119,7 @@ Rectangle { text: message color: "white" wrapMode: Text.WordWrap - font.pointSize: 14 + font.pointSize: 14 * ctx.scaleFactor font.bold: name == "_self" ? true : false; } @@ -148,10 +147,20 @@ Rectangle { textFormat: Text.PlainText text: hourstr color: "grey" - font.pointSize: 12 + font.pointSize: 12 * ctx.scaleFactor } } } } } + + onFetchHistory: { + // Prepend new items to the model by calling `getPage()`. + // temp. disable visibility to 'break' the touch gesture, + // if we dont the list scrolling bugs out by "jumping" + chatListView.visible = false; + var count_results = chatModel.getPage(); + chatListView.positionViewAtIndex(count_results, ListView.Visible) + chatListView.visible = true; + } } diff --git a/src/qml/chat/whatsthat/whatsthat.qml b/src/qml/chat/whatsthat/whatsthat.qml index 653e1b9..200fd98 100644 --- a/src/qml/chat/whatsthat/whatsthat.qml +++ b/src/qml/chat/whatsthat/whatsthat.qml @@ -2,10 +2,11 @@ import QtQuick 2.0 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.0 -Rectangle { +import "../components" as Components + +Components.ChatRoot { id: root - visible: true - color: "grey" + chatList: chatListView property string chatBackgroundSelf: "#056162" property string chatBackgroundThem: "#262d31" @@ -13,27 +14,25 @@ Rectangle { property int itemHeightSmall: 32 Image { + // background source: "qrc:/whatsthat/bg.png" anchors.fill: parent fillMode: Image.Tile } - ListView { + Components.ChatListView { id: chatListView + anchors.fill: parent anchors.topMargin: 10 anchors.leftMargin: 32 anchors.rightMargin: 32 - model: chatModel - - onCountChanged: { // scroll to bottom - chatListView.currentIndex = count - 1 - } + onScrollToBottom: root.scrollToBottom(); delegate: RowLayout { id: item - property int itemHeight: 32 + property int itemHeight: textColumn.implicitHeight + 12 height: itemHeight + 12 width: parent.width spacing: 0 @@ -49,6 +48,7 @@ Rectangle { radius: 4 clip: true color: outgoing ? root.chatBackgroundSelf : root.chatBackgroundThem + Layout.preferredHeight: itemHeight Layout.preferredWidth: { var max_width = item.width / 6 * 4; @@ -82,7 +82,7 @@ Rectangle { Text { visible: !outgoing && isHead - font.pointSize: 12 + font.pointSize: 12 * ctx.scaleFactor color: "lightblue" text: name } @@ -93,7 +93,7 @@ Rectangle { } Text { - font.pointSize: 12 + font.pointSize: 12 * ctx.scaleFactor color: "#98ac90" text: datestr + " " + hourstr Layout.rightMargin: 0 @@ -106,20 +106,8 @@ Rectangle { text: message wrapMode: Text.WordWrap width: parent.width - font.pointSize: 14 + font.pointSize: 14 * ctx.scaleFactor Layout.preferredWidth: parent.width - - // Dynamically set the height of `textRectangle`. - // Unfortunately this uses a timer, so that the text component has - // time to correctly wrap itself before starting to calculate the height. - Timer { - id: timer - running: false - repeat: false - interval: 50 - onTriggered: itemHeight = textColumn.implicitHeight + 12; - } - Component.onCompleted: timer.start(); } } } @@ -131,4 +119,14 @@ Rectangle { } } } + + onFetchHistory: { + // Prepend new items to the model by calling `getPage()`. + // temp. disable visibility to 'break' the touch gesture, + // if we dont the list scrolling bugs out by "jumping" + chatListView.visible = false; + var count_results = chatModel.getPage(); + chatListView.positionViewAtIndex(count_results, ListView.Visible) + chatListView.visible = true; + } } diff --git a/src/qml/components/ChatListView.qml b/src/qml/components/ChatListView.qml new file mode 100644 index 0000000..43288b5 --- /dev/null +++ b/src/qml/components/ChatListView.qml @@ -0,0 +1,28 @@ +import QtQuick 2.0 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.0 + + +ListView { + id: root + model: chatModel + + signal scrollToBottom() + + boundsBehavior: Flickable.StopAtBounds + property var chatScroll: chatScroll + property bool scrollable: root.childrenRect.height > parent.height + property bool atBottom: (chatScroll.position + chatScroll.size) == 1 + property bool atTop: chatScroll.position <= 0.01 + property bool mayAutoScroll: atBottom && scrollable + + onCountChanged: { // scroll to bottom + if(chatListView.mayAutoScroll) + scrollToBottom(); + } + + ScrollBar.vertical: ScrollBar { + id: chatScroll + visible: false + } +} \ No newline at end of file diff --git a/src/qml/components/ChatRoot.qml b/src/qml/components/ChatRoot.qml new file mode 100644 index 0000000..7af397f --- /dev/null +++ b/src/qml/components/ChatRoot.qml @@ -0,0 +1,102 @@ +import QtQuick 2.0 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.0 + +import "." as Components + +Rectangle { + id: chatRoot + visible: true + color: "grey" + + property var chatList + signal scrollToBottom() + signal fetchHistory() + + onScrollToBottom: scrollBottomTimer.start() + + Components.ChatScrollToBottomButton { + z: parent.z + 1 + visible: !chatList.atBottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.rightMargin: 60 + anchors.bottomMargin: 60 + onClicked: scrollBottomTimer.start(); + } + + Item { + visible: chatList.atTop && !chatModel.exhausted + onVisibleChanged: { + if(!chatModel.exhausted && chatList.atTop && visible && chatListView.count >= chatModel.limit) + fetchHistory(); + } + } + + ColumnLayout { + // debugBar + z: parent.z + 1 + visible: ctx.isDebug + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 60 + anchors.topMargin: 60 + property int pointSize: 16 + + Text { + color: "lime" + text: "Messages: " + chatList.count + " (limit: " + chatModel.limit + " offset: " + chatModel.offset + " exhausted: " + chatModel.exhausted + ")" + font.pointSize: parent.pointSize + } + + Text { + color: "lime" + text: "mayAutoScroll: " + chatList.mayAutoScroll + font.pointSize: parent.pointSize + } + + Text { + color: "lime" + text: "atBottom: " + chatList.atBottom + font.pointSize: parent.pointSize + } + + Text { + color: "lime" + text: "atTop: " + chatList.atTop + font.pointSize: parent.pointSize + } + + Text { + color: "lime" + text: "rootHeight: " + chatRoot.height + font.pointSize: parent.pointSize + } + + Text { + color: "lime" + text: "chatList.cRect.height: " + chatList.childrenRect.height + font.pointSize: parent.pointSize + } + + Text { + color: "lime" + text: "scrollPosition: " + chatList.chatScroll.position.toFixed(4) + font.pointSize: parent.pointSize + } + + Text { + color: "lime" + text: "scaling: " + ctx.scaleFactor + font.pointSize: parent.pointSize + } + } + + Timer { + id: scrollBottomTimer + interval: 10 + repeat: false + running: false + onTriggered: chatList.positionViewAtEnd(); + } +} \ No newline at end of file diff --git a/src/qml/components/ChatScrollToBottomButton.qml b/src/qml/components/ChatScrollToBottomButton.qml new file mode 100644 index 0000000..8b1656b --- /dev/null +++ b/src/qml/components/ChatScrollToBottomButton.qml @@ -0,0 +1,34 @@ +import QtQuick 2.0 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.0 + +Item { + id: root + width: 76 + height: 76 + + signal clicked(); + + Rectangle { // lies: it is a circle + anchors.fill: parent + color: "white" + opacity: 0.3 + radius: width * 0.5 + } + + Image { + width: 48 + height: 48 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + source: "qrc:///rss_reader_move_down.png" + smooth: true + } + + MouseArea { + anchors.fill: parent + onClicked: { + root.clicked(); + } + } +} \ No newline at end of file diff --git a/src/qml/components/qml_components.qrc b/src/qml/components/qml_components.qrc new file mode 100644 index 0000000..8d8b4e3 --- /dev/null +++ b/src/qml/components/qml_components.qrc @@ -0,0 +1,7 @@ + + + ChatScrollToBottomButton.qml + ChatRoot.qml + ChatListView.qml + + diff --git a/src/qml/overview.qml b/src/qml/overview.qml index 9a315de..2b82323 100644 --- a/src/qml/overview.qml +++ b/src/qml/overview.qml @@ -3,12 +3,14 @@ import QtQuick.Controls 2.3 import QtQuick.Layouts 1.0 import MaemoConfig 1.0 +import "components" as Components + Rectangle { id: appWindow visible: true color: "black" property string highlight: "#00a2ff" - property int itemHeight: 76 + property int itemHeight: 76 * ctx.scaleFactor signal rowClicked(string group_uid, string local_uid, string remote_uid); @@ -18,8 +20,9 @@ Rectangle { anchors.leftMargin: 4 anchors.rightMargin: 4 anchors.fill: parent - model: chatOverviewModel + boundsBehavior: Flickable.StopAtBounds + delegate: Rectangle { height: itemHeight width: parent.width @@ -31,13 +34,16 @@ Rectangle { MouseArea { anchors.fill: parent - hoverEnabled: true + hoverEnabled: !ctx.isMaemo + cursorShape: Qt.PointingHandCursor onEntered: { - parent.parent.color = highlight; + if(!ctx.isMaemo) + parent.parent.color = highlight; } onExited: { - parent.parent.color = "black"; + if(!ctx.isMaemo) + parent.parent.color = "black"; } onClicked: { appWindow.rowClicked(group_uid, local_uid, remote_uid); @@ -76,6 +82,7 @@ Rectangle { Layout.fillWidth: true Layout.fillHeight: true spacing: 0 + clip: true RowLayout { Layout.fillWidth: true @@ -85,7 +92,7 @@ Rectangle { text: remote_name textFormat: Text.PlainText color: "white" - font.pointSize: 18 + font.pointSize: 18 * ctx.scaleFactor Layout.alignment: Qt.AlignTop } @@ -97,7 +104,7 @@ Rectangle { } color: "grey" textFormat: Text.PlainText - font.pointSize: 12 + font.pointSize: 12 * ctx.scaleFactor Layout.alignment: Qt.AlignTop } @@ -112,7 +119,7 @@ Rectangle { Layout.preferredHeight: itemHeight / 2 textFormat: Text.PlainText color: "grey" - font.pointSize: 14 + font.pointSize: 14 * ctx.scaleFactor text: { if(message !== "") message; else datestr; diff --git a/src/settings.cpp b/src/settings.cpp index 24256e6..fbf6982 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -25,6 +25,7 @@ Settings::Settings(Conversations *ctx, QWidget *parent) : #ifdef MAEMO setProperty("X-Maemo-StackedWindow", 1); + setProperty("X-Maemo-Orientation", 2); #endif QPixmap p_theme_whatsthat(":whatsthat/whatsthat.png"); QPixmap p_theme_chatty(":chatty/chatty.png"); @@ -47,9 +48,30 @@ Settings::Settings(Conversations *ctx, QWidget *parent) : } }); + // text scaling + float textScaling = config()->get(ConfigKeys::TextScaling).toFloat(); + if(textScaling < 1) textScaling = 1; + auto scaling = 100*(textScaling-1); + ui->label_textScalingValue->setText("x" + QString::number(textScaling)); + ui->sliderTextScaling->setValue(scaling); + connect(ui->sliderTextScaling, &QSlider::valueChanged, this, &Settings::onTextScalingValueChanged); + emit textScalingChanged(); + //connect(this->ui->btnSend, &QPushButton::clicked, this, &Settings::onGatherMessage); } +void Settings::onTextScalingValueChanged(int val) { + float scaling; + if(val == 0) scaling = 1.0; + else if(val <= 25) scaling = 1.25; + else if(val <= 50) scaling = 1.50; + else if(val <= 75) scaling = 1.75; + else if(val <= 100) scaling = 2.0; + ui->label_textScalingValue->setText("x" + QString::number(scaling)); + config()->set(ConfigKeys::TextScaling, scaling); + emit textScalingChanged(); +} + Conversations *Settings::getContext(){ return pSettings->m_ctx; } diff --git a/src/settings.h b/src/settings.h index db4f43b..c990049 100644 --- a/src/settings.h +++ b/src/settings.h @@ -31,7 +31,10 @@ public: Ui::Settings *ui; signals: - void lol(const QString &message); + void textScalingChanged(); + +private slots: + void onTextScalingValueChanged(int val); private: Conversations *m_ctx; diff --git a/src/settings.ui b/src/settings.ui index a8d596d..5795bc6 100644 --- a/src/settings.ui +++ b/src/settings.ui @@ -24,16 +24,7 @@ :/assets/images/appicons/64x64.png:/assets/images/appicons/64x64.png - - - 32 - - - 22 - - - 32 - + @@ -100,6 +91,86 @@ + + + + + + + + + + Text scaling + + + + + + + 1x + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 0 + + + 100 + + + 25 + + + 25 + + + 0 + + + Qt::Horizontal + + + 25 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + +