From 45f5a53069408e36e68d9430ded844ab69fb36e8 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Wed, 3 Aug 2022 14:43:33 +0200 Subject: [PATCH] chore(cpp): add chat section and list chats Closes: #6747 --- CMakeLists.txt | 1 + app/CMakeLists.txt | 1 + .../Status/Application/MainView/MainView.qml | 18 +- .../MainView/StatusApplicationSections.qml | 19 +- .../Status/Application/StatusContentView.qml | 1 + app/src/Application/ApplicationController.cpp | 15 ++ app/src/Application/ApplicationController.h | 10 + app/src/Application/CMakeLists.txt | 10 +- app/src/Application/DataProvider.cpp | 24 ++ app/src/Application/DataProvider.h | 17 ++ app/src/Application/DbSettingsObj.cpp | 74 ++++++ app/src/Application/DbSettingsObj.h | 49 ++++ .../src/ApplicationCore/UserConfiguration.cpp | 4 +- .../src/ApplicationCore/UserConfiguration.h | 4 + libs/ChatSection/CMakeLists.txt | 67 +++++ .../Status/ChatSection/ChatDataProvider.h | 17 ++ .../include/Status/ChatSection/ChatItem.h | 52 ++++ .../ChatSection/ChatSectionController.h | 37 +++ .../qml/Status/ChatSection/ContentView.qml | 41 +++ .../qml/Status/ChatSection/MainView.qml | 51 ++++ .../qml/Status/ChatSection/NavigationView.qml | 83 ++++++ libs/ChatSection/src/ChatDataProvider.cpp | 28 ++ libs/ChatSection/src/ChatItem.cpp | 79 ++++++ .../ChatSection/src/ChatSectionController.cpp | 43 ++++ libs/Helpers/CMakeLists.txt | 24 +- libs/Helpers/src/Helpers/JsonMacros.h | 38 +++ libs/Helpers/src/Helpers/Macros.h | 12 + libs/StatusGoQt/CMakeLists.txt | 68 ++--- libs/StatusGoQt/src/StatusGo/Chat/ChatAPI.cpp | 27 ++ libs/StatusGoQt/src/StatusGo/Chat/ChatAPI.h | 9 + libs/StatusGoQt/src/StatusGo/Chat/ChatDto.cpp | 241 ++++++++++++++++++ libs/StatusGoQt/src/StatusGo/Chat/ChatDto.h | 132 ++++++++++ libs/StatusGoQt/src/StatusGo/ChatAPI | 4 + .../src/StatusGo/Settings/SettingsAPI.cpp | 27 ++ .../src/StatusGo/Settings/SettingsAPI.h | 9 + .../src/StatusGo/Settings/SettingsDto.cpp | 23 ++ .../src/StatusGo/Settings/SettingsDto.h | 23 ++ libs/StatusGoQt/src/StatusGo/SettingsAPI | 4 + .../qml/Status/Containers/LayoutSpacer.qml | 6 +- .../Controls/Navigation/NavigationBar.qml | 2 +- libs/Wallet/CMakeLists.txt | 2 +- 41 files changed, 1344 insertions(+), 52 deletions(-) create mode 100644 app/src/Application/DataProvider.cpp create mode 100644 app/src/Application/DataProvider.h create mode 100644 app/src/Application/DbSettingsObj.cpp create mode 100644 app/src/Application/DbSettingsObj.h create mode 100644 libs/ChatSection/CMakeLists.txt create mode 100644 libs/ChatSection/include/Status/ChatSection/ChatDataProvider.h create mode 100644 libs/ChatSection/include/Status/ChatSection/ChatItem.h create mode 100644 libs/ChatSection/include/Status/ChatSection/ChatSectionController.h create mode 100644 libs/ChatSection/qml/Status/ChatSection/ContentView.qml create mode 100644 libs/ChatSection/qml/Status/ChatSection/MainView.qml create mode 100644 libs/ChatSection/qml/Status/ChatSection/NavigationView.qml create mode 100644 libs/ChatSection/src/ChatDataProvider.cpp create mode 100644 libs/ChatSection/src/ChatItem.cpp create mode 100644 libs/ChatSection/src/ChatSectionController.cpp create mode 100644 libs/Helpers/src/Helpers/JsonMacros.h create mode 100644 libs/Helpers/src/Helpers/Macros.h create mode 100644 libs/StatusGoQt/src/StatusGo/Chat/ChatAPI.cpp create mode 100644 libs/StatusGoQt/src/StatusGo/Chat/ChatAPI.h create mode 100644 libs/StatusGoQt/src/StatusGo/Chat/ChatDto.cpp create mode 100644 libs/StatusGoQt/src/StatusGo/Chat/ChatDto.h create mode 100644 libs/StatusGoQt/src/StatusGo/ChatAPI create mode 100644 libs/StatusGoQt/src/StatusGo/Settings/SettingsAPI.cpp create mode 100644 libs/StatusGoQt/src/StatusGo/Settings/SettingsAPI.h create mode 100644 libs/StatusGoQt/src/StatusGo/Settings/SettingsDto.cpp create mode 100644 libs/StatusGoQt/src/StatusGo/Settings/SettingsDto.h create mode 100644 libs/StatusGoQt/src/StatusGo/SettingsAPI diff --git a/CMakeLists.txt b/CMakeLists.txt index 19b3402ce..ab81c7808 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ add_subdirectory(vendor) add_subdirectory(libs/ApplicationCore) add_subdirectory(libs/Assets) +add_subdirectory(libs/ChatSection) add_subdirectory(libs/Helpers) add_subdirectory(libs/Onboarding) add_subdirectory(libs/Wallet) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 05fbd1d7d..78b5672fc 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -67,6 +67,7 @@ target_link_libraries(${PROJECT_NAME} # TODO: Use Status:: namespace Status::ApplicationCore Status::Helpers + Status::ChatSection Status::Onboarding Status::Wallet Status::Assets diff --git a/app/qml/Status/Application/MainView/MainView.qml b/app/qml/Status/Application/MainView/MainView.qml index d663e1e6c..ebbd77c00 100644 --- a/app/qml/Status/Application/MainView/MainView.qml +++ b/app/qml/Status/Application/MainView/MainView.qml @@ -45,16 +45,30 @@ Item { //type: // TODO: appController.bannerController.type visible: false // TODO: appController.bannerController.visible } - Loader { + + StackLayout { + id: container Layout.fillWidth: true Layout.fillHeight: true - sourceComponent: navBar.currentSection + currentIndex: navBar.currentIndex + + Repeater{ + model: appSections.sections + + delegate: Loader { + Layout.fillWidth: true + Layout.fillHeight: true + + sourceComponent: modelData.content + } + } } } } StatusApplicationSections { id: appSections + appController: root.appController } } diff --git a/app/qml/Status/Application/MainView/StatusApplicationSections.qml b/app/qml/Status/Application/MainView/StatusApplicationSections.qml index 8fc06d92a..cc5c2429e 100644 --- a/app/qml/Status/Application/MainView/StatusApplicationSections.qml +++ b/app/qml/Status/Application/MainView/StatusApplicationSections.qml @@ -2,17 +2,33 @@ import QtQml import QtQuick import QtQuick.Controls +import Status.Application import Status.Application.Navigation import Status.Controls.Navigation import Status.Wallet +import Status.ChatSection as ChatSectionModule Item { - property var sections: [walletSection, settingsSection] + id: root + + required property ApplicationController appController + property var sections: [chatSection, walletSection, settingsSection] ButtonGroup { id: oneSectionSelectedGroup } + ApplicationSection { + id: chatSection + navigationSection: SimpleNavBarSection { + name: "Chat" + mutuallyExclusiveGroup: oneSectionSelectedGroup + } + content: ChatSectionModule.MainView { + sectionId: root.appController.dbSettings.publicKey + } + } + ApplicationSection { id: walletSection navigationSection: SimpleNavBarSection { @@ -21,6 +37,7 @@ Item { } content: WalletView {} } + ApplicationSection { id: settingsSection navigationSection: SimpleNavBarSection { diff --git a/app/qml/Status/Application/StatusContentView.qml b/app/qml/Status/Application/StatusContentView.qml index 4c943d1b5..4d857ce10 100644 --- a/app/qml/Status/Application/StatusContentView.qml +++ b/app/qml/Status/Application/StatusContentView.qml @@ -31,6 +31,7 @@ Item { OnboardingView { onUserLoggedIn: function (statusAccount) { splashScreenPopup.open() + appController.initOnLogin(); //appController.statusAccount = statusAccount contentLoader.sourceComponent = mainViewComponent } diff --git a/app/src/Application/ApplicationController.cpp b/app/src/Application/ApplicationController.cpp index 28cae09ac..139dfdfd3 100644 --- a/app/src/Application/ApplicationController.cpp +++ b/app/src/Application/ApplicationController.cpp @@ -1,15 +1,30 @@ #include "ApplicationController.h" +#include + namespace Status::Application { ApplicationController::ApplicationController(QObject *parent) : QObject{parent} + , m_dataProvider(std::make_unique()) { } +void ApplicationController::initOnLogin() +{ + auto dbSettings = m_dataProvider->getSettings(); + m_dbSettings = std::make_shared(dbSettings); +} + +QObject *ApplicationController::dbSettings() const +{ + return m_dbSettings.get(); +} + QObject *ApplicationController::statusAccount() const { + QQmlEngine::setObjectOwnership(m_statusAccount, QQmlEngine::CppOwnership); return m_statusAccount; } diff --git a/app/src/Application/ApplicationController.h b/app/src/Application/ApplicationController.h index 0fb178065..57fd36dd6 100644 --- a/app/src/Application/ApplicationController.h +++ b/app/src/Application/ApplicationController.h @@ -1,5 +1,7 @@ #pragma once +#include "DbSettingsObj.h" +#include "DataProvider.h" #include #include @@ -18,17 +20,25 @@ class ApplicationController : public QObject QML_ELEMENT Q_PROPERTY(QObject* statusAccount READ statusAccount WRITE setStatusAccount NOTIFY statusAccountChanged) + Q_PROPERTY(QObject* dbSettings READ dbSettings CONSTANT) + public: explicit ApplicationController(QObject *parent = nullptr); + Q_INVOKABLE void initOnLogin(); + QObject *statusAccount() const; void setStatusAccount(QObject *newStatusAccount); + QObject* dbSettings() const; + signals: void statusAccountChanged(); private: QObject* m_statusAccount{}; + std::unique_ptr m_dataProvider; + std::shared_ptr m_dbSettings; }; } diff --git a/app/src/Application/CMakeLists.txt b/app/src/Application/CMakeLists.txt index ac832e0c1..7ac04fae2 100644 --- a/app/src/Application/CMakeLists.txt +++ b/app/src/Application/CMakeLists.txt @@ -6,8 +6,12 @@ target_include_directories(${PROJECT_NAME} target_sources(${PROJECT_NAME} PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/ApplicationController.h - ${CMAKE_CURRENT_SOURCE_DIR}/ApplicationController.cpp + ApplicationController.h + ApplicationController.cpp + DataProvider.h + DataProvider.cpp + DbSettingsObj.h + DbSettingsObj.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt + CMakeLists.txt ) diff --git a/app/src/Application/DataProvider.cpp b/app/src/Application/DataProvider.cpp new file mode 100644 index 000000000..b2c1d410e --- /dev/null +++ b/app/src/Application/DataProvider.cpp @@ -0,0 +1,24 @@ +#include "DataProvider.h" + +using namespace Status::Application; + +namespace StatusGo = Status::StatusGo; + +DataProvider::DataProvider() + : QObject(nullptr) +{ +} + +StatusGo::Settings::SettingsDto DataProvider::getSettings() const +{ + try { + return StatusGo::Settings::getSettings(); + } + catch (std::exception& e) { + qWarning() << "DataProvider::getSettings, error: " << e.what(); + } + catch (...) { + qWarning() << "DataProvider::getSettings, unknown error"; + } + return StatusGo::Settings::SettingsDto{}; +} diff --git a/app/src/Application/DataProvider.h b/app/src/Application/DataProvider.h new file mode 100644 index 000000000..f5c4c5edd --- /dev/null +++ b/app/src/Application/DataProvider.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace Status::Application { + + class DataProvider: public QObject + { + Q_OBJECT + + public: + DataProvider(); + + StatusGo::Settings::SettingsDto getSettings() const; + }; +} diff --git a/app/src/Application/DbSettingsObj.cpp b/app/src/Application/DbSettingsObj.cpp new file mode 100644 index 000000000..da86d2cbe --- /dev/null +++ b/app/src/Application/DbSettingsObj.cpp @@ -0,0 +1,74 @@ +#include "DbSettingsObj.h" + +using namespace Status::Application; + +DbSettingsObj::DbSettingsObj(StatusGo::Settings::SettingsDto rawData) + : QObject(nullptr) + , m_data(std::move(rawData)) +{ +} + +QString DbSettingsObj::address() const +{ + return m_data.address; +} + +void DbSettingsObj::setAddress(const QString& value) +{ + if (m_data.address == value) + return; + m_data.address = value; + emit addressChanged(); +} + +QString DbSettingsObj::displayName() const +{ + return m_data.displayName; +} + +void DbSettingsObj::setDisplayName(const QString& value) +{ + if (m_data.displayName == value) + return; + m_data.displayName = value; + emit displayNameChanged(); +} + +QString DbSettingsObj::preferredName() const +{ + return m_data.preferredName; +} + +void DbSettingsObj::setPreferredName(const QString& value) +{ + if (m_data.preferredName == value) + return; + m_data.preferredName = value; + emit preferredNameChanged(); +} + +QString DbSettingsObj::keyUid() const +{ + return m_data.keyUid; +} + +void DbSettingsObj::setKeyUid(const QString& value) +{ + if (m_data.keyUid == value) + return; + m_data.keyUid = value; + emit keyUidChanged(); +} + +QString DbSettingsObj::publicKey() const +{ + return m_data.publicKey; +} + +void DbSettingsObj::setPublicKey(const QString& value) +{ + if (m_data.publicKey == value) + return; + m_data.publicKey = value; + emit publicKeyChanged(); +} diff --git a/app/src/Application/DbSettingsObj.h b/app/src/Application/DbSettingsObj.h new file mode 100644 index 000000000..0c296bab8 --- /dev/null +++ b/app/src/Application/DbSettingsObj.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include + +namespace Status::Application { + + class DbSettingsObj: public QObject + { + Q_OBJECT + + Q_PROPERTY(QString address READ address NOTIFY addressChanged) + Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged) + Q_PROPERTY(QString preferredName READ preferredName NOTIFY preferredNameChanged) + Q_PROPERTY(QString keyUid READ keyUid NOTIFY keyUidChanged) + Q_PROPERTY(QString publicKey READ publicKey NOTIFY publicKeyChanged) + + + public: + explicit DbSettingsObj(StatusGo::Settings::SettingsDto rawData); + + [[nodiscard]] QString address() const; + void setAddress(const QString& address); + + [[nodiscard]] QString displayName() const; + void setDisplayName(const QString& value); + + [[nodiscard]] QString preferredName() const; + void setPreferredName(const QString& value); + + [[nodiscard]] QString keyUid() const; + void setKeyUid(const QString& value); + + [[nodiscard]] QString publicKey() const; + void setPublicKey(const QString& value); + + + signals: + void addressChanged(); + void displayNameChanged(); + void preferredNameChanged(); + void keyUidChanged(); + void publicKeyChanged(); + + private: + StatusGo::Settings::SettingsDto m_data; + }; +} diff --git a/libs/ApplicationCore/src/ApplicationCore/UserConfiguration.cpp b/libs/ApplicationCore/src/ApplicationCore/UserConfiguration.cpp index 268e04ea3..d988cea80 100644 --- a/libs/ApplicationCore/src/ApplicationCore/UserConfiguration.cpp +++ b/libs/ApplicationCore/src/ApplicationCore/UserConfiguration.cpp @@ -9,7 +9,7 @@ namespace fs = std::filesystem; namespace Status::ApplicationCore { namespace { - /// `status-go` data location + constexpr auto statusFolder = "Status"; constexpr auto dataSubfolder = "data"; } @@ -40,7 +40,7 @@ void UserConfiguration::setUserDataFolder(const QString &newUserDataFolder) void UserConfiguration::generateReleaseConfiguration() { - m_userDataFolder = toPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))/dataSubfolder; + m_userDataFolder = toPath(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation))/statusFolder/dataSubfolder; emit userDataFolderChanged(); } diff --git a/libs/ApplicationCore/src/ApplicationCore/UserConfiguration.h b/libs/ApplicationCore/src/ApplicationCore/UserConfiguration.h index e7e65c1a4..79ebef37f 100644 --- a/libs/ApplicationCore/src/ApplicationCore/UserConfiguration.h +++ b/libs/ApplicationCore/src/ApplicationCore/UserConfiguration.h @@ -14,7 +14,11 @@ class UserConfiguration: public QObject Q_OBJECT QML_ELEMENT + // Not sure why `qmlUserDataFolder` is writable??? We should not change it from the qml side. + // Even from the backend side this will be set only on the app start, and it will contain + // necessary data for each created account, so even we're switching accounts, this will be the same path. Q_PROPERTY(QString userDataFolder READ qmlUserDataFolder WRITE setUserDataFolder NOTIFY userDataFolderChanged) + public: explicit UserConfiguration(QObject *parent = nullptr); diff --git a/libs/ChatSection/CMakeLists.txt b/libs/ChatSection/CMakeLists.txt new file mode 100644 index 000000000..614c42c73 --- /dev/null +++ b/libs/ChatSection/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.21) + +project(ChatSection + VERSION 0.1.0 + LANGUAGES CXX) + +set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true) + +find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml Concurrent REQUIRED) +qt6_standard_project_setup() + +qt6_add_qml_module(${PROJECT_NAME} + URI Status.ChatSection + VERSION 1.0 + + QML_FILES + qml/Status/ChatSection/NavigationView.qml + qml/Status/ChatSection/ContentView.qml + qml/Status/ChatSection/MainView.qml + + # Required to suppress "qmllint may not work" warning + OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/${PROJECT_NAME}/ +) +add_library(Status::ChatSection ALIAS ChatSection) + +target_include_directories(${PROJECT_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + + # Workaround to Qt6's *_qmltyperegistrations.cpp + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include/Status/ChatSection/ + + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + Qt6::Quick + Qt6::Qml + Qt6::Concurrent + + Status::ApplicationCore + + Status::StatusGoQt +) + +# QtCreator needs this +set(QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/qml;${QML_IMPORT_PATH} CACHE STRING "For QtCreator" FORCE) +list(REMOVE_DUPLICATES QML_IMPORT_PATH) + +install( + TARGETS + ${PROJECT_NAME} + RUNTIME +) + +target_sources(${PROJECT_NAME} + PRIVATE + include/Status/ChatSection/ChatSectionController.h + src/ChatSectionController.cpp + include/Status/ChatSection/ChatItem.h + src/ChatItem.cpp + include/Status/ChatSection/ChatDataProvider.h + src/ChatDataProvider.cpp +) diff --git a/libs/ChatSection/include/Status/ChatSection/ChatDataProvider.h b/libs/ChatSection/include/Status/ChatSection/ChatDataProvider.h new file mode 100644 index 000000000..4bd6d3896 --- /dev/null +++ b/libs/ChatSection/include/Status/ChatSection/ChatDataProvider.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace Status::ChatSection { + + class ChatDataProvider: public QObject + { + Q_OBJECT + + public: + ChatDataProvider(); + + StatusGo::Chats::ChannelGroupDto getSectionData(const QString& sectionId) const; + }; +} diff --git a/libs/ChatSection/include/Status/ChatSection/ChatItem.h b/libs/ChatSection/include/Status/ChatSection/ChatItem.h new file mode 100644 index 000000000..ad59e44a9 --- /dev/null +++ b/libs/ChatSection/include/Status/ChatSection/ChatItem.h @@ -0,0 +1,52 @@ +#pragma once + +#include + +#include + +namespace Status::ChatSection { + + class ChatItem: public QObject + { + Q_OBJECT + + Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString description READ description NOTIFY descriptionChanged) + Q_PROPERTY(QColor color READ color NOTIFY colorChanged) + Q_PROPERTY(bool muted READ muted NOTIFY mutedChanged) + Q_PROPERTY(bool active READ active NOTIFY activeChanged) + + public: + explicit ChatItem(StatusGo::Chats::ChatDto rawData); + + [[nodiscard]] QString id() const; + + [[nodiscard]] QString name() const; + void setName(const QString& value); + + [[nodiscard]] QString description() const; + void setDescription(const QString& value); + + [[nodiscard]] QColor color() const; + void setColor(const QColor& value); + + [[nodiscard]] bool muted() const; + void setMuted(bool value); + + [[nodiscard]] bool active() const; + void setActive(bool value); + + signals: + void nameChanged(); + void descriptionChanged(); + void colorChanged(); + void mutedChanged(); + void activeChanged(); + + private: + StatusGo::Chats::ChatDto m_data; + }; + + using ChatItemPtr = std::shared_ptr; +} diff --git a/libs/ChatSection/include/Status/ChatSection/ChatSectionController.h b/libs/ChatSection/include/Status/ChatSection/ChatSectionController.h new file mode 100644 index 000000000..fa4a1dc8a --- /dev/null +++ b/libs/ChatSection/include/Status/ChatSection/ChatSectionController.h @@ -0,0 +1,37 @@ +#pragma once + +#include "ChatItem.h" +#include "ChatDataProvider.h" + +#include + +namespace Status::ChatSection { + + class ChatSectionController: public QObject + { + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(QAbstractListModel* chatsModel READ chatsModel NOTIFY chatsModelChanged) + Q_PROPERTY(ChatItem* currentChat READ currentChat NOTIFY currentChatChanged) + + public: + ChatSectionController(); + + QAbstractListModel* chatsModel() const; + ChatItem* currentChat() const; + + Q_INVOKABLE void init(const QString& sectionId); + Q_INVOKABLE void setCurrentChatIndex(int index); + + signals: + void chatsModelChanged(); + void currentChatChanged(); + + private: + using ChatsModel = Helpers::QObjectVectorModel; + std::shared_ptr m_chats; + std::unique_ptr m_dataProvider; + ChatItemPtr m_currentChat; + }; +} diff --git a/libs/ChatSection/qml/Status/ChatSection/ContentView.qml b/libs/ChatSection/qml/Status/ChatSection/ContentView.qml new file mode 100644 index 000000000..6871702c9 --- /dev/null +++ b/libs/ChatSection/qml/Status/ChatSection/ContentView.qml @@ -0,0 +1,41 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Status.ChatSection + +Item { + id: root + + required property var selectedChat + + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + + Label { + text: "selected chat: %1".arg(root.selectedChat.name) + } + + Label { + text: "chat id: %1".arg(root.selectedChat.id) + } + + Label { + text: "description: %1".arg(root.selectedChat.description) + } + + Label { + text: "chat color" + color: root.selectedChat.color + } + + Label { + text: "is active: %1".arg(root.selectedChat.active) + } + + Label { + text: "is muted: %1".arg(root.selectedChat.muted) + } + } +} diff --git a/libs/ChatSection/qml/Status/ChatSection/MainView.qml b/libs/ChatSection/qml/Status/ChatSection/MainView.qml new file mode 100644 index 000000000..3fd5a0601 --- /dev/null +++ b/libs/ChatSection/qml/Status/ChatSection/MainView.qml @@ -0,0 +1,51 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +import QtQml + +import Qt.labs.platform + +import Status.ChatSection + +import Status.Containers +import Status.Controls.Navigation + +PanelAndContentBase { + id: root + + required property string sectionId + + implicitWidth: 1232 + implicitHeight: 770 + + ChatSectionController { + id: chatSectionController + } + + Component.onCompleted: { + chatSectionController.init(root.sectionId) + } + + RowLayout { + id: mainLayout + + anchors.fill: parent + + NavigationView { + id: panel + + Layout.preferredWidth: root.panelWidth + Layout.fillHeight: true + + chatSectionController: chatSectionController + } + + ContentView { + Layout.fillWidth: true + Layout.fillHeight: true + + selectedChat: chatSectionController.currentChat + } + } +} diff --git a/libs/ChatSection/qml/Status/ChatSection/NavigationView.qml b/libs/ChatSection/qml/Status/ChatSection/NavigationView.qml new file mode 100644 index 000000000..4539e27b4 --- /dev/null +++ b/libs/ChatSection/qml/Status/ChatSection/NavigationView.qml @@ -0,0 +1,83 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Status.ChatSection + +import Status.Containers + +Item { + id: root + + required property var chatSectionController + + ColumnLayout { + anchors.left: leftLine.right + anchors.top: parent.top + anchors.right: rightLine.left + anchors.bottom: parent.bottom + + Label { + text: qsTr("Chats") + } + + LayoutSpacer { + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + + model: root.chatSectionController.chatsModel + + onCurrentIndexChanged: root.chatSectionController.setCurrentChatIndex(currentIndex) + + clip: true + + delegate: ItemDelegate { + width: ListView.view.width + highlighted: ListView.isCurrentItem + + onClicked: ListView.view.currentIndex = index + + contentItem: ColumnLayout { + spacing: 4 + + RowLayout { + Rectangle { + Layout.preferredWidth: 15 + Layout.preferredHeight: Layout.preferredWidth + Layout.leftMargin: 5 + Layout.alignment: Qt.AlignVCenter + + radius: width/2 + color: chat.color + } + Label { + Layout.leftMargin: 10 + Layout.topMargin: 5 + Layout.rightMargin: 10 + Layout.alignment: Qt.AlignVCenter + + text: chat.name + + verticalAlignment: Qt.AlignVCenter + + elide: Label.ElideRight + } + } + } + } + } + } + + SideLine { id: leftLine; anchors.left: parent.left } + SideLine { id: rightLine; anchors.right: parent.right } + + component SideLine: Rectangle { + color: "black" + width: 1 + anchors.top: parent.top + anchors.bottom: parent.bottom + } +} diff --git a/libs/ChatSection/src/ChatDataProvider.cpp b/libs/ChatSection/src/ChatDataProvider.cpp new file mode 100644 index 000000000..c86987e6d --- /dev/null +++ b/libs/ChatSection/src/ChatDataProvider.cpp @@ -0,0 +1,28 @@ +#include "ChatDataProvider.h" + +using namespace Status::ChatSection; + +namespace StatusGo = Status::StatusGo; + +ChatDataProvider::ChatDataProvider() + : QObject(nullptr) +{ +} + +StatusGo::Chats::ChannelGroupDto ChatDataProvider::getSectionData(const QString& sectionId) const +{ + try { + auto result = StatusGo::Chats::getChats(); + for(auto chGroup : result.allChannelGroups) { + if (chGroup.id == sectionId) + return chGroup; + } + } + catch (std::exception& e) { + qWarning() << "ChatDataProvider::getSectionData, error: " << e.what(); + } + catch (...) { + qWarning() << "ChatDataProvider::getSectionData, unknown error"; + } + return StatusGo::Chats::ChannelGroupDto{}; +} diff --git a/libs/ChatSection/src/ChatItem.cpp b/libs/ChatSection/src/ChatItem.cpp new file mode 100644 index 000000000..f541bed2e --- /dev/null +++ b/libs/ChatSection/src/ChatItem.cpp @@ -0,0 +1,79 @@ +#include "Status/ChatSection/ChatItem.h" + +using namespace Status::ChatSection; + +ChatItem::ChatItem(StatusGo::Chats::ChatDto rawData) + : QObject(nullptr) + , m_data(std::move(rawData)) +{ +} + +QString ChatItem::id() const +{ + return m_data.id; +} + +QString ChatItem::name() const +{ + return m_data.name; +} + +void ChatItem::setName(const QString& value) +{ + if (m_data.name == value) + return; + m_data.name = value; + emit nameChanged(); +} + +QString ChatItem::description() const +{ + return m_data.description; +} + +void ChatItem::setDescription(const QString& value) +{ + if (m_data.description == value) + return; + m_data.description = value; + emit descriptionChanged(); +} + +QColor ChatItem::color() const +{ + return m_data.color; +} + +void ChatItem::setColor(const QColor& value) +{ + if (m_data.color == value) + return; + m_data.color = value; + emit colorChanged(); +} + +bool ChatItem::muted() const +{ + return m_data.muted; +} + +void ChatItem::setMuted(bool value) +{ + if (m_data.muted == value) + return; + m_data.muted = value; + emit mutedChanged(); +} + +bool ChatItem::active() const +{ + return m_data.active; +} + +void ChatItem::setActive(bool value) +{ + if (m_data.active == value) + return; + m_data.active = value; + emit activeChanged(); +} diff --git a/libs/ChatSection/src/ChatSectionController.cpp b/libs/ChatSection/src/ChatSectionController.cpp new file mode 100644 index 000000000..31d57f10f --- /dev/null +++ b/libs/ChatSection/src/ChatSectionController.cpp @@ -0,0 +1,43 @@ +#include "Status/ChatSection/ChatSectionController.h" + +using namespace Status::ChatSection; + +ChatSectionController::ChatSectionController() + : QObject(nullptr) + , m_dataProvider(std::make_unique()) +{ +} + +void ChatSectionController::init(const QString& sectionId) +{ + auto chatSectionData = m_dataProvider->getSectionData(sectionId); + std::vector model; + for (auto c : chatSectionData.chats) { + model.push_back(std::make_shared(std::move(c))); + } + m_chats = std::make_shared(std::move(model), "chat"); + setCurrentChatIndex(0); + emit chatsModelChanged(); +} + +QAbstractListModel* ChatSectionController::chatsModel() const +{ + return m_chats.get(); +} + +ChatItem* ChatSectionController::currentChat() const +{ + return m_currentChat.get(); +} + +void ChatSectionController::setCurrentChatIndex(int index) +{ + assert(index >= 0 && index < m_chats->size()); + + auto chat = m_chats->get(index); + if (m_currentChat == chat) + return; + + m_currentChat = chat; + emit currentChatChanged(); +} diff --git a/libs/Helpers/CMakeLists.txt b/libs/Helpers/CMakeLists.txt index 74243fd4f..a5dcb6e61 100644 --- a/libs/Helpers/CMakeLists.txt +++ b/libs/Helpers/CMakeLists.txt @@ -35,17 +35,17 @@ endif() set(BUILD_GENERATED_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/generated) -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/template/BuildConfiguration.h.in" +configure_file("template/BuildConfiguration.h.in" "${BUILD_GENERATED_DIRECTORY}/Helpers/BuildConfiguration.h" @ONLY) target_include_directories(Helpers PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src + src ${BUILD_GENERATED_DIRECTORY} PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers + src/Helpers ${BUILD_GENERATED_DIRECTORY}/Helpers ) @@ -66,12 +66,14 @@ install( target_sources(Helpers PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/conversions.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/conversions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/helpers.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/NamedType.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/QObjectVectorModel.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/Singleton.h + src/Helpers/conversions.h + src/Helpers/conversions.cpp + src/Helpers/helpers.h + src/Helpers/logs.h + src/Helpers/logs.cpp + src/Helpers/NamedType.h + src/Helpers/QObjectVectorModel.h + src/Helpers/Singleton.h + src/Helpers/Macros.h + src/Helpers/JsonMacros.h ) diff --git a/libs/Helpers/src/Helpers/JsonMacros.h b/libs/Helpers/src/Helpers/JsonMacros.h new file mode 100644 index 000000000..48526e38f --- /dev/null +++ b/libs/Helpers/src/Helpers/JsonMacros.h @@ -0,0 +1,38 @@ +#pragma once + +#include "Macros.h" + +#define STATUS_READ_NLOHMAN_JSON_PROPERTY_3_ARGS(FIELD, NAME, REQUIRED) \ +if(REQUIRED) \ + j.at(NAME).get_to(d.FIELD); \ +else if(j.contains(NAME)) \ + j.at(NAME).get_to(d.FIELD); \ + +#define STATUS_READ_NLOHMAN_JSON_PROPERTY_2_ARGS(FIELD, NAME) \ +STATUS_READ_NLOHMAN_JSON_PROPERTY_3_ARGS(FIELD, NAME, true) + +#define STATUS_READ_NLOHMAN_JSON_PROPERTY_1_ARGS(FIELD) \ +STATUS_READ_NLOHMAN_JSON_PROPERTY_2_ARGS(FIELD, #FIELD) + +// This macro reads prop from the nlohman json object. It implies that nlohman json object is named `j` and the struct +// instance that json object should be mapped to is named `d`. +// +// If the field is required this macro reads a property from nlohmann json object and sets it to the passed field, +// in case the property doesn't exist an error is thrown. +// +// If the field is not required this macro reads a property from nlohmann json object and sets it to the passed field +// only if the property exists it cannot throws an error ever. +// +// Usage: STATUS_READ_NLOHMAN_JSON_PROPERTY(field) +// STATUS_READ_NLOHMAN_JSON_PROPERTY(field, "realFieldName") +// STATUS_READ_NLOHMAN_JSON_PROPERTY(field, "realFieldName", false) +// +#define STATUS_READ_NLOHMAN_JSON_PROPERTY(...) \ +STATUS_EXPAND( \ + STATUS_MACRO_SELECTOR_3_ARGS( \ + __VA_ARGS__, \ + STATUS_READ_NLOHMAN_JSON_PROPERTY_3_ARGS, \ + STATUS_READ_NLOHMAN_JSON_PROPERTY_2_ARGS, \ + STATUS_READ_NLOHMAN_JSON_PROPERTY_1_ARGS \ + )(__VA_ARGS__) \ +) diff --git a/libs/Helpers/src/Helpers/Macros.h b/libs/Helpers/src/Helpers/Macros.h new file mode 100644 index 000000000..9aaac135e --- /dev/null +++ b/libs/Helpers/src/Helpers/Macros.h @@ -0,0 +1,12 @@ +#pragma once + +// Macro arguments are completely macro-expanded before they are substituted into a macro body. +#define STATUS_EXPAND(x) x + +// 2 arguments macro selector. +#define STATUS_MACRO_SELECTOR_2_ARGS(_1, _2, selected, ...) \ +selected + +// 3 arguments macro selector. +#define STATUS_MACRO_SELECTOR_3_ARGS(_1, _2, _3, selected, ...) \ +selected diff --git a/libs/StatusGoQt/CMakeLists.txt b/libs/StatusGoQt/CMakeLists.txt index 9f924dc35..0f28ca592 100644 --- a/libs/StatusGoQt/CMakeLists.txt +++ b/libs/StatusGoQt/CMakeLists.txt @@ -37,13 +37,13 @@ add_library(Status::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) target_include_directories(${PROJECT_NAME} PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo + src/StatusGo # TODO: Workaround to QML_ELEMENT Qt6 INTERFACE - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo + src/StatusGo PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src + src ) add_subdirectory(tests) @@ -69,35 +69,45 @@ install( target_sources(${PROJECT_NAME} PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/General.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/General.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Types.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Utils.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Utils.cpp + src/StatusGo/General.h + src/StatusGo/General.cpp + src/StatusGo/Types.h + src/StatusGo/Utils.h + src/StatusGo/Utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/Accounts.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/Accounts.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/accounts_types.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/AccountsAPI.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/AccountsAPI.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/ChatOrWalletAccount.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/ChatOrWalletAccount.cpp + src/StatusGo/Accounts/Accounts.h + src/StatusGo/Accounts/Accounts.cpp + src/StatusGo/Accounts/accounts_types.h + src/StatusGo/Accounts/AccountsAPI.h + src/StatusGo/Accounts/AccountsAPI.cpp + src/StatusGo/Accounts/ChatOrWalletAccount.h + src/StatusGo/Accounts/ChatOrWalletAccount.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Messenger/Service.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Messenger/Service.cpp + src/StatusGo/Chat/ChatAPI.h + src/StatusGo/Chat/ChatAPI.cpp + src/StatusGo/Chat/ChatDto.h + src/StatusGo/Chat/ChatDto.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Metadata/api_response.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Metadata/api_response.cpp + src/StatusGo/Messenger/Service.h + src/StatusGo/Messenger/Service.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/SignalsManager.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/SignalsManager.cpp + src/StatusGo/Metadata/api_response.h + src/StatusGo/Metadata/api_response.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/BigInt.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/BigInt.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/DerivedAddress.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/NetworkConfiguration.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/Token.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/wallet_types.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/WalletApi.h - ${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/WalletApi.cpp + src/StatusGo/SignalsManager.h + src/StatusGo/SignalsManager.cpp + + src/StatusGo/Settings/SettingsAPI.h + src/StatusGo/Settings/SettingsAPI.cpp + src/StatusGo/Settings/SettingsDto.h + src/StatusGo/Settings/SettingsDto.cpp + + src/StatusGo/Wallet/BigInt.h + src/StatusGo/Wallet/BigInt.cpp + src/StatusGo/Wallet/DerivedAddress.h + src/StatusGo/Wallet/NetworkConfiguration.h + src/StatusGo/Wallet/Token.h + src/StatusGo/Wallet/wallet_types.h + src/StatusGo/Wallet/WalletApi.h + src/StatusGo/Wallet/WalletApi.cpp ) diff --git a/libs/StatusGoQt/src/StatusGo/Chat/ChatAPI.cpp b/libs/StatusGoQt/src/StatusGo/Chat/ChatAPI.cpp new file mode 100644 index 000000000..e07d0ed34 --- /dev/null +++ b/libs/StatusGoQt/src/StatusGo/Chat/ChatAPI.cpp @@ -0,0 +1,27 @@ +#include "ChatAPI.h" + +#include "Utils.h" +#include "Metadata/api_response.h" + +#include + +#include + +using json = nlohmann::json; + +using namespace Status::StatusGo; + +Chats::AllChannelGroupsDto Chats::getChats() +{ + json inputJson = { + {"jsonrpc", "2.0"}, + {"method", "chat_getChats"}, + {"params", json::array()} + }; + + auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str()); + const auto resultJson = json::parse(result); + checkPrivateRpcCallResultAndReportError(resultJson); + + return resultJson.get().result; +} diff --git a/libs/StatusGoQt/src/StatusGo/Chat/ChatAPI.h b/libs/StatusGoQt/src/StatusGo/Chat/ChatAPI.h new file mode 100644 index 000000000..645bf4083 --- /dev/null +++ b/libs/StatusGoQt/src/StatusGo/Chat/ChatAPI.h @@ -0,0 +1,9 @@ +#pragma once + +#include "ChatDto.h" + +namespace Status::StatusGo::Chats +{ + /// \brief Retrieve all available channel groups + AllChannelGroupsDto getChats(); +} diff --git a/libs/StatusGoQt/src/StatusGo/Chat/ChatDto.cpp b/libs/StatusGoQt/src/StatusGo/Chat/ChatDto.cpp new file mode 100644 index 000000000..dd93bcf8f --- /dev/null +++ b/libs/StatusGoQt/src/StatusGo/Chat/ChatDto.cpp @@ -0,0 +1,241 @@ +#include "ChatDto.h" + +#include +#include + +using namespace Status::StatusGo; + +void Chats::to_json(json& j, const Category& d) { + j = {{"id", d.id}, + {"name", d.name}, + {"position", d.position}, + }; +} + +void Chats::from_json(const json& j, Category& d) { + STATUS_READ_NLOHMAN_JSON_PROPERTY(name) + STATUS_READ_NLOHMAN_JSON_PROPERTY(position) + STATUS_READ_NLOHMAN_JSON_PROPERTY(id, "category_id", false) + if (!j.contains("category_id")){ + STATUS_READ_NLOHMAN_JSON_PROPERTY(id, "id", false) + } +} + +void Chats::to_json(json& j, const Permission& d) { + j = {{"access", d.access}, + {"ens_only", d.ensOnly}, + }; +} + +void Chats::from_json(const json& j, Permission& d) { + STATUS_READ_NLOHMAN_JSON_PROPERTY(access, "access", false) + STATUS_READ_NLOHMAN_JSON_PROPERTY(ensOnly, "ens_only", false) +} + +void Chats::to_json(json& j, const Images& d) { + j = {{"large", d.large}, + {"thumbnail", d.thumbnail}, + {"banner", d.banner}, + }; +} + +void Chats::from_json(const json& j, Images& d) { + constexpr auto large = "large"; + if(j.contains(large)) + j[large].at("uri").get_to(d.large); + + constexpr auto thumbnail = "thumbnail"; + if(j.contains(thumbnail)) + j[thumbnail].at("uri").get_to(d.thumbnail); + + constexpr auto banner = "banner"; + if(j.contains(banner)) + j[banner].at("uri").get_to(d.banner); +} + +void Chats::to_json(json& j, const ChatMember& d) { + j = {{"id", d.id}, + {"admin", d.admin}, + {"joined", d.joined}, + {"roles", d.roles}, + }; +} + +void Chats::from_json(const json& j, ChatMember& d) { + STATUS_READ_NLOHMAN_JSON_PROPERTY(id) + STATUS_READ_NLOHMAN_JSON_PROPERTY(joined) + STATUS_READ_NLOHMAN_JSON_PROPERTY(roles) + STATUS_READ_NLOHMAN_JSON_PROPERTY(admin, "admin", false) +} + +void Chats::to_json(json& j, const ChatDto& d) { + j = {{"id", d.id}, + {"name", d.name}, + {"description", d.description}, + {"color", d.color}, + {"emoji", d.emoji}, + {"active", d.active}, + {"timestamp", d.timestamp}, + {"lastClockValue", d.lastClockValue}, + {"deletedAtClockValue", d.deletedAtClockValue}, + {"readMessagesAtClockValue", d.readMessagesAtClockValue}, + {"unviewedMessagesCount", d.unviewedMessagesCount}, + {"unviewedMentionsCount", d.unviewedMentionsCount}, + {"canPost", d.canPost}, + {"alias", d.alias}, + {"identicon", d.icon}, + {"muted", d.muted}, + {"position", d.position}, + {"communityId", d.communityId}, + {"profile", d.profile}, + {"joined", d.joined}, + {"syncedTo", d.syncedTo}, + {"syncedFrom", d.syncedFrom}, + {"highlight", d.highlight}, + {"categoryId", d.categoryId}, + {"permissions", d.permissions}, + {"chatType", d.chatType}, + {"members", d.members}, + }; +} + +void Chats::from_json(const json& j, ChatDto& d) { + STATUS_READ_NLOHMAN_JSON_PROPERTY(id) + STATUS_READ_NLOHMAN_JSON_PROPERTY(name) + STATUS_READ_NLOHMAN_JSON_PROPERTY(description) + STATUS_READ_NLOHMAN_JSON_PROPERTY(color) + STATUS_READ_NLOHMAN_JSON_PROPERTY(emoji) + STATUS_READ_NLOHMAN_JSON_PROPERTY(active) + STATUS_READ_NLOHMAN_JSON_PROPERTY(timestamp) + STATUS_READ_NLOHMAN_JSON_PROPERTY(lastClockValue) + STATUS_READ_NLOHMAN_JSON_PROPERTY(deletedAtClockValue) + STATUS_READ_NLOHMAN_JSON_PROPERTY(readMessagesAtClockValue) + STATUS_READ_NLOHMAN_JSON_PROPERTY(unviewedMessagesCount) + STATUS_READ_NLOHMAN_JSON_PROPERTY(unviewedMentionsCount) + STATUS_READ_NLOHMAN_JSON_PROPERTY(canPost) + STATUS_READ_NLOHMAN_JSON_PROPERTY(alias, "alias", false) + STATUS_READ_NLOHMAN_JSON_PROPERTY(icon, "icon", false) + STATUS_READ_NLOHMAN_JSON_PROPERTY(muted) + STATUS_READ_NLOHMAN_JSON_PROPERTY(position, "position", false) + STATUS_READ_NLOHMAN_JSON_PROPERTY(communityId) + STATUS_READ_NLOHMAN_JSON_PROPERTY(profile, "profile", false) + STATUS_READ_NLOHMAN_JSON_PROPERTY(joined) + STATUS_READ_NLOHMAN_JSON_PROPERTY(syncedTo) + STATUS_READ_NLOHMAN_JSON_PROPERTY(syncedFrom) + STATUS_READ_NLOHMAN_JSON_PROPERTY(highlight, "highlight", false) + STATUS_READ_NLOHMAN_JSON_PROPERTY(permissions, "permissions", false) + STATUS_READ_NLOHMAN_JSON_PROPERTY(chatType) + + STATUS_READ_NLOHMAN_JSON_PROPERTY(categoryId, "categoryId", false) + if (!j.contains("categoryId")) { + // Communities have `categoryID` and chats have `categoryId` + // This should be fixed in status-go, but would be a breaking change + STATUS_READ_NLOHMAN_JSON_PROPERTY(categoryId, "categoryID", false) + } + + // Add community ID if needed + if (!d.communityId.isEmpty() && !d.id.contains(d.communityId)) { + d.id = d.communityId + d.id; + } + + constexpr auto membersKey = "members"; + if (j.contains(membersKey)) { + if (j[membersKey].is_array()) { + j.at(membersKey).get_to(d.members); + } + else if (j[membersKey].is_object()) { + auto obj = j[membersKey]; + for (json::const_iterator it = obj.cbegin(); it != obj.cend(); ++it) { + ChatMember chatMember; + it.value().get_to(chatMember); + chatMember.id = it.key().c_str(); + d.members.emplace_back(std::move(chatMember)); + } + } + } +} + +void Chats::to_json(json& j, const ChannelGroupDto& d) { + j = {{"id", d.id}, + {"admin", d.admin}, + {"verified", d.verified}, + {"name", d.name}, + {"description", d.description}, + {"introMessage", d.introMessage}, + {"outroMessage", d.outroMessage}, + {"canManageUsers", d.canManageUsers}, + {"color", d.color}, + {"muted", d.muted}, + {"images", d.images}, + {"permissions", d.permissions}, + {"channelGroupType", d.channelGroupType}, + {"chats", d.chats}, + {"categories", d.categories}, + {"members", d.members}, + }; +} + +void Chats::from_json(const json& j, ChannelGroupDto& d) { + STATUS_READ_NLOHMAN_JSON_PROPERTY(admin) + STATUS_READ_NLOHMAN_JSON_PROPERTY(verified) + STATUS_READ_NLOHMAN_JSON_PROPERTY(name) + STATUS_READ_NLOHMAN_JSON_PROPERTY(description) + STATUS_READ_NLOHMAN_JSON_PROPERTY(introMessage) + STATUS_READ_NLOHMAN_JSON_PROPERTY(outroMessage) + STATUS_READ_NLOHMAN_JSON_PROPERTY(canManageUsers) + STATUS_READ_NLOHMAN_JSON_PROPERTY(color) + STATUS_READ_NLOHMAN_JSON_PROPERTY(muted) + STATUS_READ_NLOHMAN_JSON_PROPERTY(images) + STATUS_READ_NLOHMAN_JSON_PROPERTY(permissions, "permissions", false) + + STATUS_READ_NLOHMAN_JSON_PROPERTY(channelGroupType) + if (d.channelGroupType.isEmpty()) + d.channelGroupType = ChannelGroupTypeUnknown; + + constexpr auto chats = "chats"; + if (j.contains(chats)) { + auto obj = j[chats]; + for (json::const_iterator it = obj.cbegin(); it != obj.cend(); ++it) { + ChatDto chatDto; + it.value().get_to(chatDto); + d.chats.emplace_back(std::move(chatDto)); + } + } + + constexpr auto categories = "categories"; + if (j.contains(categories)) { + auto obj = j[categories]; + for (json::const_iterator it = obj.cbegin(); it != obj.cend(); ++it) { + Category category; + it.value().get_to(category); + d.categories.emplace_back(std::move(category)); + } + } + + constexpr auto membersKey = "members"; + if (j.contains(membersKey)) { + if (j[membersKey].is_object()) { + auto obj = j[membersKey]; + for (json::const_iterator it = obj.cbegin(); it != obj.cend(); ++it) { + ChatMember chatMember; + it.value().get_to(chatMember); + chatMember.id = it.key().c_str(); + d.members.emplace_back(std::move(chatMember)); + } + } + } +} + +void Chats::to_json(json& j, const AllChannelGroupsDto& d) { + j = {{"id", d.allChannelGroups}}; +} + +void Chats::from_json(const json& j, AllChannelGroupsDto& d) { + for (json::const_iterator it = j.cbegin(); it != j.cend(); ++it) { + ChannelGroupDto channelGroupDto; + it.value().get_to(channelGroupDto); + channelGroupDto.id = it.key().c_str(); + d.allChannelGroups.emplace_back(std::move(channelGroupDto)); + } +} + diff --git a/libs/StatusGoQt/src/StatusGo/Chat/ChatDto.h b/libs/StatusGoQt/src/StatusGo/Chat/ChatDto.h new file mode 100644 index 000000000..a5512c73d --- /dev/null +++ b/libs/StatusGoQt/src/StatusGo/Chat/ChatDto.h @@ -0,0 +1,132 @@ +#pragma once + +#include + +#include + +#include + +using json = nlohmann::json; + +namespace Status::StatusGo::Chats { + + constexpr auto ChannelGroupTypeUnknown = "unknown"; + constexpr auto ChannelGroupTypePersonal = "personal"; + constexpr auto ChannelGroupTypeCommunity = "community"; + + enum ChatType + { + Unknown = 0, + OneToOne = 1, + Public = 2, + PrivateGroupChat = 3, + Profile = 4, + CommunityChat = 6 + }; + + struct Category { + QString id; + QString name; + int position; + }; + + struct Permission { + int access; + bool ensOnly; + }; + + struct Images { + QString thumbnail; + QString large; + QString banner; + }; + + struct ChatMember { + QString id; + bool admin; + bool joined; + std::vector roles; + }; + + struct ChatDto { + QString id; // ID is the id of the chat, for public chats it is the name e.g. status, + // for one-to-one is the hex encoded public key and for group chats is a random + // uuid appended with the hex encoded pk of the creator of the chat + QString name; + QString description; + QColor color; + QString emoji; + bool active; // indicates whether the chat has been soft deleted + ChatType chatType; + quint64 timestamp; // indicates the last time this chat has received/sent a message + quint64 lastClockValue; // indicates the last clock value to be used when sending messages + quint64 deletedAtClockValue; // indicates the clock value at time of deletion, messages with lower clock value of this should be discarded + quint64 readMessagesAtClockValue; + int unviewedMessagesCount; + int unviewedMentionsCount; + std::vector members; + QString alias; + QString icon; + bool muted; + QString communityId; // set if chat belongs to a community + QString profile; + quint64 joined; // indicates when the user joined the chat last time + quint64 syncedTo; + quint64 syncedFrom; + bool canPost; + int position; + QString categoryId; + bool highlight; + Permission permissions; + }; + + struct ChannelGroupDto { + QString id; + QString channelGroupType; + bool admin; + bool verified; + QString name; + QString ensName; + QString description; + QString introMessage; + QString outroMessage; + std::vector chats; + std::vector categories; + Images images; + Permission permissions; + std::vector members; + bool canManageUsers; + QColor color; + bool muted; + bool historyArchiveSupportEnabled; + bool pinMessageAllMembersEnabled; + }; + + struct AllChannelGroupsDto { + std::vector allChannelGroups; + }; + + NLOHMANN_JSON_SERIALIZE_ENUM(ChatType, { + {Unknown, "Unknown"}, + {OneToOne, "OneToOne"}, + {Public, "Public"}, + {PrivateGroupChat, "PrivateGroupChat"}, + {Profile, "Profile"}, + {CommunityChat, "CommunityChat"}, + }) + + void to_json(json& j, const Category& d); + void from_json(const json& j, Category& d); + void to_json(json& j, const Permission& d); + void from_json(const json& j, Permission& d); + void to_json(json& j, const Images& d); + void from_json(const json& j, Images& d); + void to_json(json& j, const ChatMember& d); + void from_json(const json& j, ChatMember& d); + void to_json(json& j, const ChatDto& d); + void from_json(const json& j, ChatDto& d); + void to_json(json& j, const ChannelGroupDto& d); + void from_json(const json& j, ChannelGroupDto& d); + void to_json(json& j, const AllChannelGroupsDto& d); + void from_json(const json& j, AllChannelGroupsDto& d); +} diff --git a/libs/StatusGoQt/src/StatusGo/ChatAPI b/libs/StatusGoQt/src/StatusGo/ChatAPI new file mode 100644 index 000000000..2bb51eb88 --- /dev/null +++ b/libs/StatusGoQt/src/StatusGo/ChatAPI @@ -0,0 +1,4 @@ +#pragma once + +#include "Chat/ChatAPI.h" +#include "Chat/ChatDto.h" diff --git a/libs/StatusGoQt/src/StatusGo/Settings/SettingsAPI.cpp b/libs/StatusGoQt/src/StatusGo/Settings/SettingsAPI.cpp new file mode 100644 index 000000000..28d7c139d --- /dev/null +++ b/libs/StatusGoQt/src/StatusGo/Settings/SettingsAPI.cpp @@ -0,0 +1,27 @@ +#include "SettingsAPI.h" + +#include "Utils.h" +#include "Metadata/api_response.h" + +#include + +#include + +using json = nlohmann::json; + +using namespace Status::StatusGo; + +Settings::SettingsDto Settings::getSettings() +{ + json inputJson = { + {"jsonrpc", "2.0"}, + {"method", "settings_getSettings"}, + {"params", json::array()} + }; + + auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str()); + const auto resultJson = json::parse(result); + checkPrivateRpcCallResultAndReportError(resultJson); + + return resultJson.get().result; +} diff --git a/libs/StatusGoQt/src/StatusGo/Settings/SettingsAPI.h b/libs/StatusGoQt/src/StatusGo/Settings/SettingsAPI.h new file mode 100644 index 000000000..6878f4879 --- /dev/null +++ b/libs/StatusGoQt/src/StatusGo/Settings/SettingsAPI.h @@ -0,0 +1,9 @@ +#pragma once + +#include "SettingsDto.h" + +namespace Status::StatusGo::Settings +{ + /// \brief Retrieve settings + SettingsDto getSettings(); +} diff --git a/libs/StatusGoQt/src/StatusGo/Settings/SettingsDto.cpp b/libs/StatusGoQt/src/StatusGo/Settings/SettingsDto.cpp new file mode 100644 index 000000000..8e2ec4564 --- /dev/null +++ b/libs/StatusGoQt/src/StatusGo/Settings/SettingsDto.cpp @@ -0,0 +1,23 @@ +#include "SettingsDto.h" + +#include +#include + +using namespace Status::StatusGo; + +void Settings::to_json(json& j, const SettingsDto& d) { + j = {{"address", d.address}, + {"display-name", d.displayName}, + {"preferred-name", d.preferredName}, + {"key-uid", d.keyUid}, + {"public-key", d.publicKey}, + }; +} + +void Settings::from_json(const json& j, SettingsDto& d) { + STATUS_READ_NLOHMAN_JSON_PROPERTY(address) + STATUS_READ_NLOHMAN_JSON_PROPERTY(displayName, "display-name") + STATUS_READ_NLOHMAN_JSON_PROPERTY(preferredName, "preferred-name", false) + STATUS_READ_NLOHMAN_JSON_PROPERTY(keyUid, "key-uid") + STATUS_READ_NLOHMAN_JSON_PROPERTY(publicKey, "public-key") +} diff --git a/libs/StatusGoQt/src/StatusGo/Settings/SettingsDto.h b/libs/StatusGoQt/src/StatusGo/Settings/SettingsDto.h new file mode 100644 index 000000000..3cd94753e --- /dev/null +++ b/libs/StatusGoQt/src/StatusGo/Settings/SettingsDto.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include + +#include + +using json = nlohmann::json; + +namespace Status::StatusGo::Settings { + + struct SettingsDto { + QString address; + QString displayName; + QString preferredName; + QString keyUid; + QString publicKey; + }; + + void to_json(json& j, const SettingsDto& d); + void from_json(const json& j, SettingsDto& d); +} diff --git a/libs/StatusGoQt/src/StatusGo/SettingsAPI b/libs/StatusGoQt/src/StatusGo/SettingsAPI new file mode 100644 index 000000000..46f2366da --- /dev/null +++ b/libs/StatusGoQt/src/StatusGo/SettingsAPI @@ -0,0 +1,4 @@ +#pragma once + +#include "Settings/SettingsAPI.h" +#include "Settings/SettingsDto.h" diff --git a/libs/StatusQ/qml/Status/Containers/LayoutSpacer.qml b/libs/StatusQ/qml/Status/Containers/LayoutSpacer.qml index 136725f86..39d0fd01e 100644 --- a/libs/StatusQ/qml/Status/Containers/LayoutSpacer.qml +++ b/libs/StatusQ/qml/Status/Containers/LayoutSpacer.qml @@ -1,4 +1,6 @@ -import QtQuick.Layouts +import QtQuick -GridLayout { +Item { + height: 16 + width: 1 } diff --git a/libs/StatusQ/qml/Status/Controls/Navigation/NavigationBar.qml b/libs/StatusQ/qml/Status/Controls/Navigation/NavigationBar.qml index 099c6cb9b..b0251ceaf 100644 --- a/libs/StatusQ/qml/Status/Controls/Navigation/NavigationBar.qml +++ b/libs/StatusQ/qml/Status/Controls/Navigation/NavigationBar.qml @@ -15,7 +15,7 @@ Item { implicitWidth: 78 implicitHeight: mainLayout.implicitHeight - readonly property Component currentSection: listView.currentItem.content + property alias currentIndex: listView.currentIndex required property var sections diff --git a/libs/Wallet/CMakeLists.txt b/libs/Wallet/CMakeLists.txt index 164aac17d..b7bb68cd5 100644 --- a/libs/Wallet/CMakeLists.txt +++ b/libs/Wallet/CMakeLists.txt @@ -2,7 +2,7 @@ # cmake_minimum_required(VERSION 3.21) -project(Wallet +project(Wallet # To rename this to WalletSection???? VERSION 0.1.0 LANGUAGES CXX)