chore(cpp): add chat section and list chats

Closes: #6747
This commit is contained in:
Sale Djenic 2022-08-03 14:43:33 +02:00 committed by saledjenic
parent 86a06c9ef9
commit 45f5a53069
41 changed files with 1344 additions and 52 deletions

View File

@ -31,6 +31,7 @@ add_subdirectory(vendor)
add_subdirectory(libs/ApplicationCore) add_subdirectory(libs/ApplicationCore)
add_subdirectory(libs/Assets) add_subdirectory(libs/Assets)
add_subdirectory(libs/ChatSection)
add_subdirectory(libs/Helpers) add_subdirectory(libs/Helpers)
add_subdirectory(libs/Onboarding) add_subdirectory(libs/Onboarding)
add_subdirectory(libs/Wallet) add_subdirectory(libs/Wallet)

View File

@ -67,6 +67,7 @@ target_link_libraries(${PROJECT_NAME}
# TODO: Use Status:: namespace # TODO: Use Status:: namespace
Status::ApplicationCore Status::ApplicationCore
Status::Helpers Status::Helpers
Status::ChatSection
Status::Onboarding Status::Onboarding
Status::Wallet Status::Wallet
Status::Assets Status::Assets

View File

@ -45,16 +45,30 @@ Item {
//type: // TODO: appController.bannerController.type //type: // TODO: appController.bannerController.type
visible: false // TODO: appController.bannerController.visible visible: false // TODO: appController.bannerController.visible
} }
Loader {
StackLayout {
id: container
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: 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 { StatusApplicationSections {
id: appSections id: appSections
appController: root.appController
} }
} }

View File

@ -2,17 +2,33 @@ import QtQml
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Status.Application
import Status.Application.Navigation import Status.Application.Navigation
import Status.Controls.Navigation import Status.Controls.Navigation
import Status.Wallet import Status.Wallet
import Status.ChatSection as ChatSectionModule
Item { Item {
property var sections: [walletSection, settingsSection] id: root
required property ApplicationController appController
property var sections: [chatSection, walletSection, settingsSection]
ButtonGroup { ButtonGroup {
id: oneSectionSelectedGroup id: oneSectionSelectedGroup
} }
ApplicationSection {
id: chatSection
navigationSection: SimpleNavBarSection {
name: "Chat"
mutuallyExclusiveGroup: oneSectionSelectedGroup
}
content: ChatSectionModule.MainView {
sectionId: root.appController.dbSettings.publicKey
}
}
ApplicationSection { ApplicationSection {
id: walletSection id: walletSection
navigationSection: SimpleNavBarSection { navigationSection: SimpleNavBarSection {
@ -21,6 +37,7 @@ Item {
} }
content: WalletView {} content: WalletView {}
} }
ApplicationSection { ApplicationSection {
id: settingsSection id: settingsSection
navigationSection: SimpleNavBarSection { navigationSection: SimpleNavBarSection {

View File

@ -31,6 +31,7 @@ Item {
OnboardingView { OnboardingView {
onUserLoggedIn: function (statusAccount) { onUserLoggedIn: function (statusAccount) {
splashScreenPopup.open() splashScreenPopup.open()
appController.initOnLogin();
//appController.statusAccount = statusAccount //appController.statusAccount = statusAccount
contentLoader.sourceComponent = mainViewComponent contentLoader.sourceComponent = mainViewComponent
} }

View File

@ -1,15 +1,30 @@
#include "ApplicationController.h" #include "ApplicationController.h"
#include <QtQml/QQmlEngine>
namespace Status::Application { namespace Status::Application {
ApplicationController::ApplicationController(QObject *parent) ApplicationController::ApplicationController(QObject *parent)
: QObject{parent} : QObject{parent}
, m_dataProvider(std::make_unique<DataProvider>())
{ {
} }
void ApplicationController::initOnLogin()
{
auto dbSettings = m_dataProvider->getSettings();
m_dbSettings = std::make_shared<DbSettingsObj>(dbSettings);
}
QObject *ApplicationController::dbSettings() const
{
return m_dbSettings.get();
}
QObject *ApplicationController::statusAccount() const QObject *ApplicationController::statusAccount() const
{ {
QQmlEngine::setObjectOwnership(m_statusAccount, QQmlEngine::CppOwnership);
return m_statusAccount; return m_statusAccount;
} }

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "DbSettingsObj.h"
#include "DataProvider.h"
#include <QObject> #include <QObject>
#include <QtQml/qqmlregistration.h> #include <QtQml/qqmlregistration.h>
@ -18,17 +20,25 @@ class ApplicationController : public QObject
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QObject* statusAccount READ statusAccount WRITE setStatusAccount NOTIFY statusAccountChanged) Q_PROPERTY(QObject* statusAccount READ statusAccount WRITE setStatusAccount NOTIFY statusAccountChanged)
Q_PROPERTY(QObject* dbSettings READ dbSettings CONSTANT)
public: public:
explicit ApplicationController(QObject *parent = nullptr); explicit ApplicationController(QObject *parent = nullptr);
Q_INVOKABLE void initOnLogin();
QObject *statusAccount() const; QObject *statusAccount() const;
void setStatusAccount(QObject *newStatusAccount); void setStatusAccount(QObject *newStatusAccount);
QObject* dbSettings() const;
signals: signals:
void statusAccountChanged(); void statusAccountChanged();
private: private:
QObject* m_statusAccount{}; QObject* m_statusAccount{};
std::unique_ptr<DataProvider> m_dataProvider;
std::shared_ptr<DbSettingsObj> m_dbSettings;
}; };
} }

View File

@ -6,8 +6,12 @@ target_include_directories(${PROJECT_NAME}
target_sources(${PROJECT_NAME} target_sources(${PROJECT_NAME}
PRIVATE PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/ApplicationController.h ApplicationController.h
${CMAKE_CURRENT_SOURCE_DIR}/ApplicationController.cpp ApplicationController.cpp
DataProvider.h
DataProvider.cpp
DbSettingsObj.h
DbSettingsObj.cpp
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt CMakeLists.txt
) )

View File

@ -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{};
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <StatusGo/SettingsAPI>
#include <QtCore/QtCore>
namespace Status::Application {
class DataProvider: public QObject
{
Q_OBJECT
public:
DataProvider();
StatusGo::Settings::SettingsDto getSettings() const;
};
}

View File

@ -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();
}

View File

@ -0,0 +1,49 @@
#pragma once
#include <StatusGo/SettingsAPI>
#include <QtCore/QtCore>
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;
};
}

View File

@ -9,7 +9,7 @@ namespace fs = std::filesystem;
namespace Status::ApplicationCore { namespace Status::ApplicationCore {
namespace { namespace {
/// `status-go` data location constexpr auto statusFolder = "Status";
constexpr auto dataSubfolder = "data"; constexpr auto dataSubfolder = "data";
} }
@ -40,7 +40,7 @@ void UserConfiguration::setUserDataFolder(const QString &newUserDataFolder)
void UserConfiguration::generateReleaseConfiguration() void UserConfiguration::generateReleaseConfiguration()
{ {
m_userDataFolder = toPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))/dataSubfolder; m_userDataFolder = toPath(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation))/statusFolder/dataSubfolder;
emit userDataFolderChanged(); emit userDataFolderChanged();
} }

View File

@ -14,7 +14,11 @@ class UserConfiguration: public QObject
Q_OBJECT Q_OBJECT
QML_ELEMENT 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) Q_PROPERTY(QString userDataFolder READ qmlUserDataFolder WRITE setUserDataFolder NOTIFY userDataFolderChanged)
public: public:
explicit UserConfiguration(QObject *parent = nullptr); explicit UserConfiguration(QObject *parent = nullptr);

View File

@ -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
)

View File

@ -0,0 +1,17 @@
#pragma once
#include <StatusGo/ChatAPI>
#include <QtCore/QtCore>
namespace Status::ChatSection {
class ChatDataProvider: public QObject
{
Q_OBJECT
public:
ChatDataProvider();
StatusGo::Chats::ChannelGroupDto getSectionData(const QString& sectionId) const;
};
}

View File

@ -0,0 +1,52 @@
#pragma once
#include <StatusGo/ChatAPI>
#include <QtCore/QtCore>
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<ChatItem>;
}

View File

@ -0,0 +1,37 @@
#pragma once
#include "ChatItem.h"
#include "ChatDataProvider.h"
#include <Helpers/QObjectVectorModel.h>
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<ChatItem>;
std::shared_ptr<ChatsModel> m_chats;
std::unique_ptr<ChatDataProvider> m_dataProvider;
ChatItemPtr m_currentChat;
};
}

View File

@ -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)
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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{};
}

View File

@ -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();
}

View File

@ -0,0 +1,43 @@
#include "Status/ChatSection/ChatSectionController.h"
using namespace Status::ChatSection;
ChatSectionController::ChatSectionController()
: QObject(nullptr)
, m_dataProvider(std::make_unique<ChatDataProvider>())
{
}
void ChatSectionController::init(const QString& sectionId)
{
auto chatSectionData = m_dataProvider->getSectionData(sectionId);
std::vector<ChatItemPtr> model;
for (auto c : chatSectionData.chats) {
model.push_back(std::make_shared<ChatItem>(std::move(c)));
}
m_chats = std::make_shared<ChatsModel>(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();
}

View File

@ -35,17 +35,17 @@ endif()
set(BUILD_GENERATED_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/generated) 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" "${BUILD_GENERATED_DIRECTORY}/Helpers/BuildConfiguration.h"
@ONLY) @ONLY)
target_include_directories(Helpers target_include_directories(Helpers
PUBLIC PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src src
${BUILD_GENERATED_DIRECTORY} ${BUILD_GENERATED_DIRECTORY}
PRIVATE PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers src/Helpers
${BUILD_GENERATED_DIRECTORY}/Helpers ${BUILD_GENERATED_DIRECTORY}/Helpers
) )
@ -66,12 +66,14 @@ install(
target_sources(Helpers target_sources(Helpers
PRIVATE PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/conversions.h src/Helpers/conversions.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/conversions.cpp src/Helpers/conversions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/helpers.h src/Helpers/helpers.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.h src/Helpers/logs.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.cpp src/Helpers/logs.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/NamedType.h src/Helpers/NamedType.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/QObjectVectorModel.h src/Helpers/QObjectVectorModel.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/Singleton.h src/Helpers/Singleton.h
src/Helpers/Macros.h
src/Helpers/JsonMacros.h
) )

View File

@ -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__) \
)

View File

@ -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

View File

@ -37,13 +37,13 @@ add_library(Status::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
target_include_directories(${PROJECT_NAME} target_include_directories(${PROJECT_NAME}
PRIVATE PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo src/StatusGo
# TODO: Workaround to QML_ELEMENT Qt6 # TODO: Workaround to QML_ELEMENT Qt6
INTERFACE INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo src/StatusGo
PUBLIC PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src src
) )
add_subdirectory(tests) add_subdirectory(tests)
@ -69,35 +69,45 @@ install(
target_sources(${PROJECT_NAME} target_sources(${PROJECT_NAME}
PRIVATE PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/General.h src/StatusGo/General.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/General.cpp src/StatusGo/General.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Types.h src/StatusGo/Types.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Utils.h src/StatusGo/Utils.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Utils.cpp src/StatusGo/Utils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/Accounts.h src/StatusGo/Accounts/Accounts.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/Accounts.cpp src/StatusGo/Accounts/Accounts.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/accounts_types.h src/StatusGo/Accounts/accounts_types.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/AccountsAPI.h src/StatusGo/Accounts/AccountsAPI.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/AccountsAPI.cpp src/StatusGo/Accounts/AccountsAPI.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/ChatOrWalletAccount.h src/StatusGo/Accounts/ChatOrWalletAccount.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Accounts/ChatOrWalletAccount.cpp src/StatusGo/Accounts/ChatOrWalletAccount.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Messenger/Service.h src/StatusGo/Chat/ChatAPI.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Messenger/Service.cpp 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 src/StatusGo/Messenger/Service.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Metadata/api_response.cpp src/StatusGo/Messenger/Service.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/SignalsManager.h src/StatusGo/Metadata/api_response.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/SignalsManager.cpp src/StatusGo/Metadata/api_response.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/BigInt.h src/StatusGo/SignalsManager.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/BigInt.cpp src/StatusGo/SignalsManager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/DerivedAddress.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/NetworkConfiguration.h src/StatusGo/Settings/SettingsAPI.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/Token.h src/StatusGo/Settings/SettingsAPI.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/wallet_types.h src/StatusGo/Settings/SettingsDto.h
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/WalletApi.h src/StatusGo/Settings/SettingsDto.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/WalletApi.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
) )

View File

@ -0,0 +1,27 @@
#include "ChatAPI.h"
#include "Utils.h"
#include "Metadata/api_response.h"
#include <libstatus.h>
#include <nlohmann/json.hpp>
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<CallPrivateRpcResponse>().result;
}

View File

@ -0,0 +1,9 @@
#pragma once
#include "ChatDto.h"
namespace Status::StatusGo::Chats
{
/// \brief Retrieve all available channel groups
AllChannelGroupsDto getChats();
}

View File

@ -0,0 +1,241 @@
#include "ChatDto.h"
#include <Helpers/conversions.h>
#include <Helpers/JsonMacros.h>
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));
}
}

View File

@ -0,0 +1,132 @@
#pragma once
#include <nlohmann/json.hpp>
#include <vector>
#include <QColor>
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<int> 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<ChatMember> 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<ChatDto> chats;
std::vector<Category> categories;
Images images;
Permission permissions;
std::vector<ChatMember> members;
bool canManageUsers;
QColor color;
bool muted;
bool historyArchiveSupportEnabled;
bool pinMessageAllMembersEnabled;
};
struct AllChannelGroupsDto {
std::vector<ChannelGroupDto> 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);
}

View File

@ -0,0 +1,4 @@
#pragma once
#include "Chat/ChatAPI.h"
#include "Chat/ChatDto.h"

View File

@ -0,0 +1,27 @@
#include "SettingsAPI.h"
#include "Utils.h"
#include "Metadata/api_response.h"
#include <libstatus.h>
#include <nlohmann/json.hpp>
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<CallPrivateRpcResponse>().result;
}

View File

@ -0,0 +1,9 @@
#pragma once
#include "SettingsDto.h"
namespace Status::StatusGo::Settings
{
/// \brief Retrieve settings
SettingsDto getSettings();
}

View File

@ -0,0 +1,23 @@
#include "SettingsDto.h"
#include <Helpers/conversions.h>
#include <Helpers/JsonMacros.h>
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")
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <nlohmann/json.hpp>
#include <vector>
#include <QColor>
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);
}

View File

@ -0,0 +1,4 @@
#pragma once
#include "Settings/SettingsAPI.h"
#include "Settings/SettingsDto.h"

View File

@ -1,4 +1,6 @@
import QtQuick.Layouts import QtQuick
GridLayout { Item {
height: 16
width: 1
} }

View File

@ -15,7 +15,7 @@ Item {
implicitWidth: 78 implicitWidth: 78
implicitHeight: mainLayout.implicitHeight implicitHeight: mainLayout.implicitHeight
readonly property Component currentSection: listView.currentItem.content property alias currentIndex: listView.currentIndex
required property var sections required property var sections

View File

@ -2,7 +2,7 @@
# #
cmake_minimum_required(VERSION 3.21) cmake_minimum_required(VERSION 3.21)
project(Wallet project(Wallet # To rename this to WalletSection????
VERSION 0.1.0 VERSION 0.1.0
LANGUAGES CXX) LANGUAGES CXX)