diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c5b76c..6655cd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,9 +16,11 @@ set(TS_FILES beerlog_ru_RU.ts) set(PROJECT_SOURCES main.cpp - qml.qrc + qml/qml.qrc + models/abstractmodel.h models/abstractmodel.cpp models/summarymodel.h models/summarymodel.cpp models/usersmodel.h models/usersmodel.cpp + viewmodels/usersviewmodel.h viewmodels/usersviewmodel.cpp services/beerservice.h services/beerservice.cpp services/settingsservice.h services/settingsservice.cpp ${TS_FILES} @@ -32,8 +34,8 @@ if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) ${PROJECT_SOURCES} ) # Define target properties for Android with Qt 6 as: -# set_property(TARGET beerlog APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR -# ${CMAKE_CURRENT_SOURCE_DIR}/android) + set_property(TARGET beerlog APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR + ${CMAKE_CURRENT_SOURCE_DIR}/android) # For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index c861dd7..3542acf 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,9 +1,9 @@ - + - + diff --git a/android/res/drawable-hdpi/icon.png b/android/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..12a5012 Binary files /dev/null and b/android/res/drawable-hdpi/icon.png differ diff --git a/android/res/drawable-ldpi/icon.png b/android/res/drawable-ldpi/icon.png new file mode 100644 index 0000000..fa76d77 Binary files /dev/null and b/android/res/drawable-ldpi/icon.png differ diff --git a/android/res/drawable-mdpi/icon.png b/android/res/drawable-mdpi/icon.png new file mode 100644 index 0000000..d18dcc0 Binary files /dev/null and b/android/res/drawable-mdpi/icon.png differ diff --git a/android/res/drawable-xhdpi/icon.png b/android/res/drawable-xhdpi/icon.png new file mode 100644 index 0000000..3661453 Binary files /dev/null and b/android/res/drawable-xhdpi/icon.png differ diff --git a/android/res/drawable-xxhdpi/icon.png b/android/res/drawable-xxhdpi/icon.png new file mode 100644 index 0000000..68e01ce Binary files /dev/null and b/android/res/drawable-xxhdpi/icon.png differ diff --git a/android/res/drawable-xxxhdpi/icon.png b/android/res/drawable-xxxhdpi/icon.png new file mode 100644 index 0000000..b2a88bc Binary files /dev/null and b/android/res/drawable-xxxhdpi/icon.png differ diff --git a/beerlog_ru_RU.ts b/beerlog_ru_RU.ts index 9bc46e8..fa29e97 100644 --- a/beerlog_ru_RU.ts +++ b/beerlog_ru_RU.ts @@ -4,31 +4,25 @@ main - + Beer Log - - ‹ - + + BeerLog v0.1 + BeerLog v1.0.0 + - - ⋮ - + + Settings + Настройки - Summary: %1 р. - Итого: %1 р. - - - Order - Заказать - - - Ordered %1 items. Amount: %2 р. - Заказано %1 позиций на сумму %2 р. + + Quit + Выход diff --git a/main.cpp b/main.cpp index 29410ed..0cc090e 100644 --- a/main.cpp +++ b/main.cpp @@ -5,8 +5,7 @@ #include #include -#include "models/summarymodel.h" -#include "models/usersmodel.h" +#include "viewmodels/usersviewmodel.h" #include "services/beerservice.h" int main(int argc, char *argv[]) @@ -34,9 +33,8 @@ int main(int argc, char *argv[]) QCoreApplication::exit(-1); }, Qt::QueuedConnection); - engine.rootContext()->setContextProperty("beerService", new BeerService(&engine)); - qmlRegisterType("ru.ded.beerlog", 1, 0, "SummaryModel"); - qmlRegisterType("ru.ded.beerlog", 1, 0, "UsersModel"); + engine.rootContext()->setContextProperty("beerService", BeerService::instance()); + qmlRegisterType("ru.ded.beerlog", 1, 0, "UsersViewModel"); engine.load(url); diff --git a/main.qml b/main.qml deleted file mode 100644 index 315cc9c..0000000 --- a/main.qml +++ /dev/null @@ -1,63 +0,0 @@ -import QtQuick 2.15 -import QtQuick.Window 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 -import QtWebSockets - -import ru.ded.beerlog 1.0 - -ApplicationWindow { - width: 640 - height: 480 - visible: true - title: qsTr("Beer Log") - - header: ToolBar { - RowLayout { - anchors.fill: parent - ToolButton { - text: qsTr("‹") - onClicked: stack.pop() - } - ToolButton { - text: usersModel.selectedUserName - Layout.fillWidth: true - onClicked: usersMenu.open() - } - ToolButton { - text: qsTr("⋮") - onClicked: menu.open() - } - } - - Menu { - id: usersMenu - - Repeater { - model: usersModel.users - - MenuItem { - text: modelData.name - - onClicked: { - usersModel.selectedUser = modelData.id - } - } - } - } - } - - UsersModel { - id: usersModel - - Component.onCompleted: { - beerService.connectSrv(selectedUser) - beerService.connectListener(usersModel) - beerService.sendCommand("users", "get") - } - - onSelectedUserChanged: { - beerService.connectSrv(selectedUser) - } - } -} diff --git a/models/abstractmodel.cpp b/models/abstractmodel.cpp new file mode 100644 index 0000000..f73ccce --- /dev/null +++ b/models/abstractmodel.cpp @@ -0,0 +1,41 @@ +#include "abstractmodel.h" + +#include "services/beerservice.h" + +AbstractModel::AbstractModel(QObject *parent) + : QObject{parent} +{ +} + +void AbstractModel::created(const QVariant &data) +{ + modified(data); +} + +void AbstractModel::modified(const QVariant &data) +{ + QVariantMap d = data.toMap(); + m_data[d.value("id").toString()] = d; + + emit dataChanged(); +} + +void AbstractModel::deleted(const QVariant &data) +{ + QString id = data.toString(); + m_data.remove(id); + + emit dataChanged(); +} + +void AbstractModel::received(const QVariant &data) +{ + m_data = data.toMap(); + + emit dataChanged(); +} + +BeerService *AbstractModel::service() const +{ + return BeerService::instance(); +} diff --git a/models/abstractmodel.h b/models/abstractmodel.h new file mode 100644 index 0000000..b41ae67 --- /dev/null +++ b/models/abstractmodel.h @@ -0,0 +1,34 @@ +#ifndef ABSTRACTMODEL_H +#define ABSTRACTMODEL_H + +#include +#include + +class BeerService; +class AbstractModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString entity READ entity CONSTANT) + +public: + explicit AbstractModel(QObject *parent = nullptr); + + virtual QString entity() const = 0; + +public slots: + void created(const QVariant &data); + void modified(const QVariant &data); + void deleted(const QVariant &data); + void received(const QVariant &data); + +signals: + void dataChanged(); + +protected: + BeerService *service() const; + + QVariantMap m_data; +}; + +#endif // ABSTRACTMODEL_H diff --git a/models/usersmodel.cpp b/models/usersmodel.cpp index f0311e7..197bb9d 100644 --- a/models/usersmodel.cpp +++ b/models/usersmodel.cpp @@ -1,80 +1,42 @@ #include "usersmodel.h" +#include "services/beerservice.h" + +namespace Keys { + +constexpr auto Users = "users"; +constexpr auto Name = "name"; + +} + UsersModel::UsersModel(QObject *parent) - : QObject{parent} + : AbstractModel{parent} { - setSelectedUser(m_settings.value("selected_user").toString()); -} - -void UsersModel::created(const QVariant &data) -{ - modified(data); -} - -void UsersModel::modified(const QVariant &data) -{ - QVariantMap user = data.toMap(); - m_users[user.value("id").toString()] = user; - - emit usersChanged(); - emit selectedUserNameChanged(); -} - -void UsersModel::deleted(const QVariant &data) -{ - QString userId = data.toString(); - m_users.remove(userId); - - emit usersChanged(); - emit selectedUserNameChanged(); -} - -void UsersModel::received(const QVariant &data) -{ - m_users = data.toMap(); - - emit usersChanged(); - emit selectedUserNameChanged(); -} - -void UsersModel::connected(const QVariant &data) -{ - qInfo() << data.toMap().value("name").toString() << "connected"; -} - -void UsersModel::disconnected(const QVariant &data) -{ - qInfo() << data.toMap().value("name").toString() << "disconnected"; + service()->connectListener(this); + service()->sendCommand(Keys::Users, "get"); } QString UsersModel::entity() const { - return QStringLiteral("users"); + return Keys::Users; +} + +void UsersModel::connected(const QVariant &data) +{ + qInfo() << data.toMap().value(Keys::Name).toString() << "connected"; +} + +void UsersModel::disconnected(const QVariant &data) +{ + qInfo() << data.toMap().value(Keys::Name).toString() << "disconnected"; } QVariantList UsersModel::users() const { - return m_users.values(); + return m_data.values(); } -QString UsersModel::selectedUser() const +QString UsersModel::userName(const QString &userId) const { - return m_selectedUser; -} - -void UsersModel::setSelectedUser(const QString &newSelectedUser) -{ - if (m_selectedUser == newSelectedUser) { - return; - } - - m_selectedUser = newSelectedUser; - m_settings.setValue("selected_user", m_selectedUser); - emit selectedUserChanged(); - emit selectedUserNameChanged(); -} - -QString UsersModel::selectedUserName() const -{ - return m_users.value(m_selectedUser).toMap().value("name").toString(); + return m_data.value(userId).toMap().value(Keys::Name).toString(); } diff --git a/models/usersmodel.h b/models/usersmodel.h index 348498a..728c333 100644 --- a/models/usersmodel.h +++ b/models/usersmodel.h @@ -5,43 +5,23 @@ #include #include "services/settingsservice.h" +#include "models/abstractmodel.h" -class UsersModel : public QObject +class UsersModel : public AbstractModel { Q_OBJECT - Q_PROPERTY(QString entity READ entity CONSTANT) - Q_PROPERTY(QVariantList users READ users NOTIFY usersChanged) - Q_PROPERTY(QString selectedUser READ selectedUser WRITE setSelectedUser NOTIFY selectedUserChanged) - Q_PROPERTY(QString selectedUserName READ selectedUserName NOTIFY selectedUserNameChanged) - public: explicit UsersModel(QObject *parent = nullptr); - QString entity() const; + QString entity() const override; + QVariantList users() const; - QString selectedUser() const; - void setSelectedUser(const QString &newSelectedUser); - QString selectedUserName() const; + QString userName(const QString &userId) const; public slots: - void created(const QVariant &data); - void modified(const QVariant &data); - void deleted(const QVariant &data); - void received(const QVariant &data); void connected(const QVariant &data); void disconnected(const QVariant &data); - -signals: - void usersChanged(); - void selectedUserChanged(); - void selectedUserNameChanged(); - -private: - QVariantMap m_users; - QString m_selectedUser; - - SettingsService m_settings; }; #endif // USERSMODEL_H diff --git a/qml/Components/MenuBackButton.qml b/qml/Components/MenuBackButton.qml new file mode 100644 index 0000000..97313db --- /dev/null +++ b/qml/Components/MenuBackButton.qml @@ -0,0 +1,84 @@ +import QtQuick 2.15 + +Item { + id: root + + width: 40 + height: 40 + + property double iconMarigns: 8 + property double iconHeight: width - iconMarigns * 2 + signal clicked() + signal back() + + SystemPalette { + id: palette + } + + MouseArea { + id: ma + + anchors.fill: parent + } + + Rectangle { + id: bar1 + x: root.iconMarigns + y: root.iconMarigns + root.iconHeight / 6 + width: root.iconHeight + height: root.iconHeight / 9 + antialiasing: true + + color: palette.button + } + + Rectangle { + id: bar2 + x: root.iconMarigns + y: root.iconMarigns + root.iconHeight / 2 - height / 2 + width: root.iconHeight + height: root.iconHeight / 9 + antialiasing: true + + color: palette.button + } + + Rectangle { + id: bar3 + x: root.iconMarigns + y: root.iconMarigns + root.iconHeight / 2 + height * 2 + width: root.iconHeight + height: root.iconHeight / 9 + antialiasing: true + + color: palette.button + } + + property int animationDuration: 450 + + state: "menu" + states: [ + State { + name: "menu" + PropertyChanges { target: ma; onClicked: root.clicked() } + }, + + State { + name: "back" + PropertyChanges { target: root; rotation: 180 } + PropertyChanges { target: bar1; rotation: 45; width: root.iconHeight / 3 * 2; x: root.iconMarigns + root.iconHeight / 2; y: root.iconMarigns + root.iconHeight / 4 } + PropertyChanges { target: bar2; width: root.iconHeight / 6 * 5 + 1; x: root.iconMarigns + root.iconHeight / 9 } + PropertyChanges { target: bar3; rotation: -45; width: root.iconHeight / 3 * 2; x: root.iconMarigns + root.iconHeight / 2; y: root.iconMarigns + root.iconHeight / 3 * 2 } + PropertyChanges { target: ma; onClicked: root.back() } + } + ] + + transitions: [ + Transition { + RotationAnimation { target: root; direction: RotationAnimation.Clockwise; duration: animationDuration; easing.type: Easing.InOutQuad } + PropertyAnimation { target: bar1; properties: "rotation, width, x, y"; duration: animationDuration; easing.type: Easing.InOutQuad } + PropertyAnimation { target: bar2; properties: "rotation, width, x, y"; duration: animationDuration; easing.type: Easing.InOutQuad } + PropertyAnimation { target: bar3; properties: "rotation, width, x, y"; duration: animationDuration; easing.type: Easing.InOutQuad } + } + ] +} diff --git a/qml/logo.png b/qml/logo.png new file mode 100644 index 0000000..a6e5cc4 Binary files /dev/null and b/qml/logo.png differ diff --git a/qml/main.qml b/qml/main.qml new file mode 100644 index 0000000..4add4b5 --- /dev/null +++ b/qml/main.qml @@ -0,0 +1,94 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtWebSockets + +import ru.ded.beerlog 1.0 +import "Components" + +ApplicationWindow { + width: 640 + height: 480 + visible: true + title: qsTr("Beer Log") + + header: ToolBar { + RowLayout { + anchors.fill: parent + MenuBackButton { + state: "menu"//stackView.depth > 1 ? "back" : "menu" + onClicked: drawer.open() + onBack: { + state = "menu" + } + } + ToolButton { + text: usersModel.selectedUserName + Layout.fillWidth: true + onClicked: usersMenu.open() + } + } + + Menu { + id: usersMenu + + UsersViewModel { + id: usersModel + } + + Repeater { + model: usersModel.users + + MenuItem { + text: modelData.name + + onClicked: { + usersModel.selectedUser = modelData.id + } + } + } + } + } + + Drawer { + id: drawer + + width: parent.width * 0.66 + height: parent.height + + Column { + anchors.fill: parent + + Row { + width: parent.width + height: 100 + + Image { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.margins: 10 + source: "logo.png" + } + + Label { + anchors.verticalCenter: parent.verticalCenter + font.pointSize: 20 + text: qsTr("BeerLog v0.1") + } + } + + ItemDelegate { + text: qsTr("Settings") + width: parent.width + onClicked: stackView.openPage("SettingsForm.qml") + } + + ItemDelegate { + text: qsTr("Quit") + width: parent.width + onClicked: Qt.quit() + } + } + } +} diff --git a/qml.qrc b/qml/qml.qrc similarity index 50% rename from qml.qrc rename to qml/qml.qrc index abe9dc3..40b775b 100644 --- a/qml.qrc +++ b/qml/qml.qrc @@ -1,6 +1,9 @@ main.qml + Components/MenuBackButton.qml + qtquickcontrols2.conf + logo.png beerlog_ru_RU.qm diff --git a/qml/qtquickcontrols2.conf b/qml/qtquickcontrols2.conf new file mode 100644 index 0000000..5a7d1ec --- /dev/null +++ b/qml/qtquickcontrols2.conf @@ -0,0 +1,13 @@ +; This file can be edited to change the style of the application +; Read "Qt Quick Controls 2 Configuration File" for details: +; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html + +[Controls] +Style=Material + +[Material] +Theme=Dark +Primary=BlueGrey +Accent=Grey +;Foreground=Brown +;Background=Steel diff --git a/services/beerservice.cpp b/services/beerservice.cpp index 51dd4fc..741e5bc 100644 --- a/services/beerservice.cpp +++ b/services/beerservice.cpp @@ -7,14 +7,8 @@ #include #include -namespace { - -constexpr auto GuestUserId = "2641ffe8cd4311eda27f0242ac120002"; - -} - -BeerService::BeerService(QObject *parent) - : QObject{parent} +BeerService::BeerService() + : QObject{nullptr} { restoreStash(); @@ -59,8 +53,7 @@ void BeerService::connectSrv(const QString &userId) } QNetworkRequest request(QUrl("ws://195.133.196.161:8000")); - QString authString = QString("%1:pass").arg(userId.isEmpty() ? GuestUserId : userId); - request.setRawHeader("Authorization", "Basic " + authString.toLatin1().toBase64()); + request.setRawHeader("Authorization", "Basic " + QString("%1:pass").arg(userId).toLatin1().toBase64()); m_socket.open(request); } diff --git a/services/beerservice.h b/services/beerservice.h index b13253e..d18a712 100644 --- a/services/beerservice.h +++ b/services/beerservice.h @@ -9,14 +9,21 @@ class BeerService : public QObject Q_OBJECT public: - explicit BeerService(QObject *parent = nullptr); - ~BeerService(); + static BeerService *instance() + { + static BeerService i; + return &i; + } Q_INVOKABLE void connectSrv(const QString &userId = QString()); - Q_INVOKABLE void sendCommand(const QString &entity, const QString &action, const QVariantMap &data = QVariantMap()); - Q_INVOKABLE void connectListener(QObject *listener); + + void sendCommand(const QString &entity, const QString &action, const QVariantMap &data = QVariantMap()); + void connectListener(QObject *listener); private: + BeerService(); + ~BeerService(); + QString stashFileName() const; void saveStash() const; void restoreStash(); diff --git a/services/settingsservice.cpp b/services/settingsservice.cpp index d3bd0b5..194953f 100644 --- a/services/settingsservice.cpp +++ b/services/settingsservice.cpp @@ -1,5 +1,11 @@ #include "settingsservice.h" +namespace Defaults { + +constexpr auto GuestUserId = "2641ffe8cd4311eda27f0242ac120002"; + +} + QVariant SettingsService::value(const QString &key, const QVariant &defaultValue) const { return m_settings.value(key, defaultValue); @@ -9,3 +15,13 @@ void SettingsService::setValue(const QString &key, const QVariant &value) { m_settings.setValue(key, value); } + +QString SettingsService::selectedUserId() const +{ + return m_settings.value("selected_user", Defaults::GuestUserId).toString(); +} + +void SettingsService::setSelectedUserId(const QString &userId) +{ + m_settings.setValue("selected_user", userId); +} diff --git a/services/settingsservice.h b/services/settingsservice.h index bfb385a..bbc3015 100644 --- a/services/settingsservice.h +++ b/services/settingsservice.h @@ -9,6 +9,9 @@ public: QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; void setValue(const QString &key, const QVariant &value); + QString selectedUserId() const; + void setSelectedUserId(const QString &userId); + private: QSettings m_settings = QSettings("DedSoft", "BeerLog"); }; diff --git a/viewmodels/usersviewmodel.cpp b/viewmodels/usersviewmodel.cpp new file mode 100644 index 0000000..0bb98ff --- /dev/null +++ b/viewmodels/usersviewmodel.cpp @@ -0,0 +1,43 @@ +#include "usersviewmodel.h" + +#include "services/beerservice.h" + +UsersViewModel::UsersViewModel(QObject *parent) + : QObject{parent} +{ + connect(this, &UsersViewModel::selectedUserChanged, this, [this]() { + BeerService::instance()->connectSrv(m_selectedUser); + }); + + setSelectedUser(m_settings.selectedUserId()); + + connect(&m_usersModel, &AbstractModel::dataChanged, this, &UsersViewModel::usersChanged); + connect(&m_usersModel, &AbstractModel::dataChanged, this, &UsersViewModel::selectedUserNameChanged); +} + +QVariantList UsersViewModel::users() const +{ + return m_usersModel.users(); +} + +QString UsersViewModel::selectedUser() const +{ + return m_selectedUser; +} + +void UsersViewModel::setSelectedUser(const QString &newSelectedUser) +{ + if (m_selectedUser == newSelectedUser) { + return; + } + + m_selectedUser = newSelectedUser; + m_settings.setSelectedUserId(m_selectedUser); + emit selectedUserChanged(); + emit selectedUserNameChanged(); +} + +QString UsersViewModel::selectedUserName() const +{ + return m_usersModel.userName(m_selectedUser); +} diff --git a/viewmodels/usersviewmodel.h b/viewmodels/usersviewmodel.h new file mode 100644 index 0000000..892807f --- /dev/null +++ b/viewmodels/usersviewmodel.h @@ -0,0 +1,36 @@ +#ifndef USERSVIEWMODEL_H +#define USERSVIEWMODEL_H + +#include + +#include "models/usersmodel.h" + +class UsersViewModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QVariantList users READ users NOTIFY usersChanged) + Q_PROPERTY(QString selectedUser READ selectedUser WRITE setSelectedUser NOTIFY selectedUserChanged) + Q_PROPERTY(QString selectedUserName READ selectedUserName NOTIFY selectedUserNameChanged) + +public: + explicit UsersViewModel(QObject *parent = nullptr); + + QVariantList users() const; + QString selectedUser() const; + void setSelectedUser(const QString &newSelectedUser); + QString selectedUserName() const; + +signals: + void usersChanged(); + void selectedUserChanged(); + void selectedUserNameChanged(); + +private: + QString m_selectedUser; + + UsersModel m_usersModel; + SettingsService m_settings; +}; + +#endif // USERSVIEWMODEL_H