Choice model settings was added

This commit is contained in:
2023-04-10 19:24:25 +02:00
parent 25e8a3ea79
commit 4a52926be9
31 changed files with 938 additions and 227 deletions

View File

@@ -17,10 +17,14 @@ set(TS_FILES beerlog_ru_RU.ts)
set(PROJECT_SOURCES set(PROJECT_SOURCES
main.cpp main.cpp
qml/qml.qrc qml/qml.qrc
models/abstractmodel.h models/abstractmodel.cpp models/basemodel.h models/basemodel.cpp
models/summarymodel.h models/summarymodel.cpp models/ordersmodel.h models/ordersmodel.cpp
models/usersmodel.h models/usersmodel.cpp models/usersmodel.h models/usersmodel.cpp
viewmodels/usersviewmodel.h viewmodels/usersviewmodel.cpp viewmodels/usersviewmodel.h viewmodels/usersviewmodel.cpp
viewmodels/storesviewmodel.h viewmodels/storesviewmodel.cpp
viewmodels/productsviewmodel.h viewmodels/productsviewmodel.cpp
viewmodels/ordersviewmodel.h viewmodels/ordersviewmodel.cpp
viewmodels/settingsviewmodel.h viewmodels/settingsviewmodel.cpp
services/beerservice.h services/beerservice.cpp services/beerservice.h services/beerservice.cpp
services/settingsservice.h services/settingsservice.cpp services/settingsservice.h services/settingsservice.cpp
${TS_FILES} ${TS_FILES}
@@ -55,8 +59,6 @@ else()
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
endif() endif()
qt_add_translations(beerlog TS_FILES beerlog_ru_RU.ts)
target_link_libraries(beerlog target_link_libraries(beerlog
PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::WebSockets) PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::WebSockets)

View File

@@ -14,6 +14,27 @@
<translation>Не в сети</translation> <translation>Не в сети</translation>
</message> </message>
</context> </context>
<context>
<name>OrdersView</name>
<message>
<location filename="qml/Views/OrdersView.qml" line="56"/>
<source>Summary: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ProductsView</name>
<message>
<location filename="qml/Views/ProductsView.qml" line="88"/>
<source>Summary: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/Views/ProductsView.qml" line="95"/>
<source>Order</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>SettingsView</name> <name>SettingsView</name>
<message> <message>
@@ -40,24 +61,29 @@
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="qml/main.qml" line="72"/> <location filename="qml/main.qml" line="73"/>
<source>BeerLog v0.1</source> <source>BeerLog v0.1</source>
<oldsource>BeerLog v1.0.0</oldsource> <oldsource>BeerLog v1.0.0</oldsource>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="qml/main.qml" line="77"/> <location filename="qml/main.qml" line="78"/>
<source>Orders</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="qml/main.qml" line="82"/>
<source>Settings</source> <source>Settings</source>
<translation>Настройки</translation> <translation>Настройки</translation>
</message> </message>
<message> <message>
<location filename="qml/main.qml" line="81"/> <location filename="qml/main.qml" line="86"/>
<location filename="qml/main.qml" line="117"/> <location filename="qml/main.qml" line="122"/>
<source>Quit</source> <source>Quit</source>
<translation>Выход</translation> <translation>Выход</translation>
</message> </message>
<message> <message>
<location filename="qml/main.qml" line="121"/> <location filename="qml/main.qml" line="126"/>
<source>Realy quit the application?</source> <source>Realy quit the application?</source>
<translation>Действительно выйти из приложения?</translation> <translation>Действительно выйти из приложения?</translation>
</message> </message>

View File

@@ -6,6 +6,11 @@
#include <QQmlContext> #include <QQmlContext>
#include "viewmodels/usersviewmodel.h" #include "viewmodels/usersviewmodel.h"
#include "viewmodels/productsviewmodel.h"
#include "viewmodels/ordersviewmodel.h"
#include "viewmodels/storesviewmodel.h"
#include "viewmodels/settingsviewmodel.h"
#include "services/beerservice.h" #include "services/beerservice.h"
#include "services/settingsservice.h" #include "services/settingsservice.h"
@@ -17,11 +22,6 @@ int main(int argc, char *argv[])
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
QTranslator translator; QTranslator translator;
if (!translator.load("qt_ru.qm", "qrc:/")) {
qWarning() << "Cant load";
}
const QStringList uiLanguages = QLocale::system().uiLanguages(); const QStringList uiLanguages = QLocale::system().uiLanguages();
for (const QString &locale : uiLanguages) { for (const QString &locale : uiLanguages) {
const QString baseName = "beerlog_" + QLocale(locale).name(); const QString baseName = "beerlog_" + QLocale(locale).name();
@@ -45,6 +45,10 @@ int main(int argc, char *argv[])
engine.rootContext()->setContextProperty("settingsService", SettingsService::instance()); engine.rootContext()->setContextProperty("settingsService", SettingsService::instance());
qmlRegisterType<UsersViewModel>("ru.ded.beerlog", 1, 0, "UsersViewModel"); qmlRegisterType<UsersViewModel>("ru.ded.beerlog", 1, 0, "UsersViewModel");
qmlRegisterType<ProductsViewModel>("ru.ded.beerlog", 1, 0, "ProductsViewModel");
qmlRegisterType<OrdersViewModel>("ru.ded.beerlog", 1, 0, "OrdersViewModel");
qmlRegisterType<StoresViewModel>("ru.ded.beerlog", 1, 0, "StoresViewModel");
qmlRegisterType<SettingsViewModel>("ru.ded.beerlog", 1, 0, "SettingsViewModel");
engine.load(url); engine.load(url);

View File

@@ -1,41 +0,0 @@
#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();
}

View File

@@ -1,34 +0,0 @@
#ifndef ABSTRACTMODEL_H
#define ABSTRACTMODEL_H
#include <QObject>
#include <QVariantMap>
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

90
models/basemodel.cpp Normal file
View File

@@ -0,0 +1,90 @@
#include "basemodel.h"
#include "services/beerservice.h"
BaseModel::BaseModel(const QString &entity, QObject *parent) : QObject{parent},
m_entity(entity)
{
Q_ASSERT(!m_entity.isEmpty());
service()->connectListener(this);
service()->sendCommand(m_entity, BeerService::ActionGet);
}
BaseModel::~BaseModel()
{
service()->removeListener(this);
}
QString BaseModel::entity() const
{
return m_entity;
}
QVariantList BaseModel::items() const
{
return m_data.values();
}
QVariantMap BaseModel::item(const QString &itemId) const
{
return m_data.value(itemId).toMap();
}
QVariant BaseModel::itemProperty(const QString &itemId, const QString &propertyName, const QVariant &def) const
{
return item(itemId).value(propertyName, def);
}
void BaseModel::addItem(const QVariantMap &item) const
{
service()->sendCommand(entity(), BeerService::ActionAdd, item);
}
void BaseModel::deleteItem(const QString &itemId) const
{
service()->sendCommand(entity(), BeerService::ActionDelete, QVariantMap { { "id", itemId } });
}
void BaseModel::modifyItem(const QString &itemId, const QVariantMap &properties) const
{
QVariantMap item = this->item(itemId);
for (auto it = properties.constBegin(); it != properties.constEnd(); ++it) {
item[it.key()] = it.value();
}
service()->sendCommand(entity(), BeerService::ActionModify, item);
}
void BaseModel::created(const QVariant &data)
{
modified(data);
}
void BaseModel::modified(const QVariant &data)
{
QVariantMap d = data.toMap();
m_data[d.value("id").toString()] = d;
emit dataChanged();
}
void BaseModel::deleted(const QVariant &data)
{
QString id = data.toString();
m_data.remove(id);
emit dataChanged();
}
void BaseModel::received(const QVariant &data)
{
m_data = data.toMap();
emit dataChanged();
}
BeerService *BaseModel::service() const
{
return BeerService::instance();
}

44
models/basemodel.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef BASEMODEL_H
#define BASEMODEL_H
#include <QObject>
#include <QVariantMap>
class BeerService;
class BaseModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QString entity READ entity CONSTANT)
public:
explicit BaseModel(const QString &entity, QObject *parent = nullptr);
virtual ~BaseModel();
QString entity() const;
QVariantList items() const;
QVariantMap item(const QString &itemId) const;
QVariant itemProperty(const QString &itemId, const QString &propertyName, const QVariant &def = QVariant{}) const;
void addItem(const QVariantMap &item) const;
void deleteItem(const QString &itemId) const;
void modifyItem(const QString &itemId, const QVariantMap &properties) const;
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();
private:
BeerService *service() const;
QVariantMap m_data;
QString m_entity;
};
#endif // BASEMODEL_H

15
models/ordersmodel.cpp Normal file
View File

@@ -0,0 +1,15 @@
#include "ordersmodel.h"
OrdersModel::OrdersModel(QObject *parent) : BaseModel { "orders", parent }
{
}
QVariantList OrdersModel::orders() const
{
return items();
}
void OrdersModel::submitOrder(const QVariantMap &order) const
{
addItem(order);
}

17
models/ordersmodel.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef ORDERSMODEL_H
#define ORDERSMODEL_H
#include "basemodel.h"
class OrdersModel : public BaseModel
{
Q_OBJECT
public:
explicit OrdersModel(QObject *parent = nullptr);
QVariantList orders() const;
void submitOrder(const QVariantMap &order) const;
};
#endif // ORDERSMODEL_H

View File

@@ -1,39 +0,0 @@
#include "summarymodel.h"
QVariantList SummaryModel::items() const
{
return m_items.values();
}
float SummaryModel::sum() const
{
float res = 0.0;
for (auto it = m_items.constBegin(); it != m_items.constEnd(); ++it) {
QVariantMap item = it.value().toMap();
int count = item.value("count", 0).toInt();
float price = item.value("cost", 0.0).toFloat();
res += count * price;
}
return res;
}
void SummaryModel::setItemCount(QVariantMap item, int count)
{
QString id = item.value("id", QString()).toString();
if (count) {
item["count"] = count;
m_items[id] = item;
} else {
m_items.remove(id);
}
emit itemsChanged();
}
void SummaryModel::clear()
{
m_items.clear();
emit itemsChanged();
}

View File

@@ -1,28 +0,0 @@
#ifndef SUMMARYMODEL_H
#define SUMMARYMODEL_H
#include <QObject>
#include <QVariantMap>
class SummaryModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList items READ items NOTIFY itemsChanged)
Q_PROPERTY(float sum READ sum NOTIFY itemsChanged)
public:
QVariantList items() const;
float sum() const;
Q_INVOKABLE void setItemCount(QVariantMap item, int count);
Q_INVOKABLE void clear();
signals:
void itemsChanged();
private:
QVariantMap m_items;
};
#endif // SUMMARYMODEL_H

View File

@@ -1,24 +1,13 @@
#include "usersmodel.h" #include "usersmodel.h"
#include "services/beerservice.h"
namespace Keys { namespace Keys {
constexpr auto Users = "users";
constexpr auto Name = "name"; constexpr auto Name = "name";
} }
UsersModel::UsersModel(QObject *parent) UsersModel::UsersModel(QObject *parent) : BaseModel{ "users", parent }
: AbstractModel{parent}
{ {
service()->connectListener(this);
service()->sendCommand(Keys::Users, "get");
}
QString UsersModel::entity() const
{
return Keys::Users;
} }
void UsersModel::connected(const QVariant &data) void UsersModel::connected(const QVariant &data)
@@ -33,10 +22,5 @@ void UsersModel::disconnected(const QVariant &data)
QVariantList UsersModel::users() const QVariantList UsersModel::users() const
{ {
return m_data.values(); return items();
}
QString UsersModel::userName(const QString &userId) const
{
return m_data.value(userId).toMap().value(Keys::Name).toString();
} }

View File

@@ -4,19 +4,16 @@
#include <QObject> #include <QObject>
#include <QVariantMap> #include <QVariantMap>
#include "models/abstractmodel.h" #include "models/basemodel.h"
class UsersModel : public AbstractModel class UsersModel : public BaseModel
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit UsersModel(QObject *parent = nullptr); explicit UsersModel(QObject *parent = nullptr);
QString entity() const override;
QVariantList users() const; QVariantList users() const;
QString userName(const QString &userId) const;
public slots: public slots:
void connected(const QVariant &data); void connected(const QVariant &data);

View File

@@ -1,12 +1,63 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import ru.ded.beerlog 1.0
Page { Page {
Label { title: qsTr("Orders")
anchors.centerIn: parent
text: "Orders" OrdersViewModel {
id: ordersModel
} }
ListView {
id: ordersList
anchors.fill: parent
anchors.margins: 10
model: ordersModel
section.criteria: ViewSection.FullString
section.property: "date"
section.delegate: Label {
text: section
font.bold: true
}
delegate: Column {
width: ordersList.width
height: productsList.heigt
ListView {
id: productsList
width: ordersList.width
height: contentHeight
model: products
header: Label {
padding: 10
font.bold: true
text: "%1 (%2), %3".arg(userName).arg(storeName).arg(time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat))
}
delegate: Label {
width: ordersList.width
leftPadding: 20
text: "%1 x%2, %3".arg(modelData.product).arg(modelData.quantity.toLocaleString(Qt.locale())).arg(modelData.price.toLocaleCurrencyString(Qt.locale()))
}
footer: Label {
padding: 10
font.bold: true
text: qsTr("Summary: %1").arg(amount.toLocaleCurrencyString(Qt.locale()))
}
}
}
}
} }

106
qml/Views/ProductsView.qml Normal file
View File

@@ -0,0 +1,106 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import ru.ded.beerlog 1.0
Page {
ProductsViewModel {
id: productsModel
}
RowLayout {
anchors.fill: parent
ListView {
id: productsList
Layout.fillWidth: true
Layout.preferredHeight: parent.height
model: productsModel.products
delegate: ItemDelegate {
width: productsList.width
text: modelData.name
height: spinbox.height
SpinBox {
id: spinbox
width: 150
from: 0
to: 100 * 100
stepSize: 50
anchors.right: parent.right
editable: true
property int decimals: 1
property real realValue: value / 100
validator: DoubleValidator {
bottom: Math.min(spinbox.from, spinbox.to)
top: Math.max(spinbox.from, spinbox.to)
}
textFromValue: function(value, locale) {
return value === 0 ? "" : Number(value / 100).toLocaleString(locale, 'f', spinbox.decimals)
}
valueFromText: function(text, locale) {
return Number.fromLocaleString(locale, text) * 100
}
onRealValueChanged: productsModel.setOrderValue(modelData.id, realValue)
}
}
function reload() {
model = undefined
model = productsModel.products
}
}
ListView {
id: orderList
Layout.minimumWidth: parent.width * 0.3
Layout.preferredHeight: parent.height
model: productsModel.order
delegate: ItemDelegate {
width: orderList.width
text: "%1 x%2".arg(modelData.name).arg(modelData.count.toLocaleString(Qt.locale()))
}
footer: Column {
width: orderList.width
Label {
anchors.right: parent.right
anchors.margins: 10
text: qsTr("Summary: %1").arg(productsModel.orderSum.toLocaleCurrencyString(Qt.locale()))
}
Button {
anchors.right: parent.right
anchors.margins: 10
text: qsTr("Order")
enabled: productsModel.orderSum > 0
onClicked: {
productsModel.submitOrder()
productsList.reload()
}
}
}
}
}
}

View File

@@ -1,24 +1,20 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import Components 1.0 import Components 1.0
import ru.ded.beerlog 1.0
Page { Page {
id: root id: root
title: qsTr("Settings") title: qsTr("Settings")
ListModel { property var controls: {
"text": textComponent,
"choice": choiceComponent
}
SettingsViewModel {
id: settingsModel id: settingsModel
ListElement {
title: qsTr("BeerLog service address")
name: "serverAddress"
}
ListElement {
title: qsTr("Selected user id")
name: "selectedUserId"
}
} }
ListView { ListView {
@@ -35,6 +31,8 @@ Page {
onClicked: { onClicked: {
inputDialog.title = model.title inputDialog.title = model.title
inputDialog.name = model.name inputDialog.name = model.name
inputDialog.control = controls[model.control]
inputDialog.choiceModel = model.choiceModel
inputDialog.open() inputDialog.open()
} }
} }
@@ -44,6 +42,8 @@ Page {
id: inputDialog id: inputDialog
property string name: "" property string name: ""
property var choiceModel: undefined
property alias control: editor.sourceComponent
width: root.width * 0.8 width: root.width * 0.8
@@ -53,22 +53,54 @@ Page {
modal: true modal: true
standardButtons: Dialog.Ok | Dialog.Cancel standardButtons: Dialog.Ok | Dialog.Cancel
Column { Loader {
spacing: 20 id: editor
property var value: item && item.value
anchors.fill: parent anchors.fill: parent
}
onAccepted: {
settingsService[inputDialog.name] = editor.value
}
}
Component {
id: textComponent
TextField { TextField {
id: textField id: textField
width: parent.width property alias value: textField.text
inputMethodHints: Qt.ImhNoAutoUppercase inputMethodHints: Qt.ImhNoAutoUppercase
placeholderText: inputDialog.title
text: settingsService[inputDialog.name] || "" text: settingsService[inputDialog.name] || ""
} }
} }
onAccepted: { ButtonGroup {
settingsService[inputDialog.name] = textField.text id: group
}
Component {
id: choiceComponent
Column {
property var value: group.checkedButton && group.checkedButton.valueId
Repeater {
model: inputDialog.choiceModel
delegate: RadioDelegate {
property var valueId: modelData.id
text: modelData.name
width: parent.width
checked: settingsService[inputDialog.name] === valueId
ButtonGroup.group: group
}
}
} }
} }
} }

View File

@@ -26,6 +26,7 @@ ApplicationWindow {
Label { Label {
text: stackView.currentItem.title || usersModel.selectedUserName text: stackView.currentItem.title || usersModel.selectedUserName
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: 100
horizontalAlignment: Qt.AlignCenter horizontalAlignment: Qt.AlignCenter
MouseArea { MouseArea {
@@ -55,24 +56,46 @@ ApplicationWindow {
} }
} }
} }
Menu {
id: storesMenu
StoresViewModel {
id: storesModel
}
Repeater {
model: storesModel.stores
MenuItem {
text: modelData.name
onClicked: {
storesModel.selectedStore = modelData.id
}
}
}
}
} }
MainMenu { MainMenu {
id: mainMenu id: mainMenu
readonly property var actions: { readonly property var actions: {
"orders": () => { stackView.openPage("Views/OrdersView.qml") },
"settings": () => { stackView.openPage("Views/SettingsView.qml") }, "settings": () => { stackView.openPage("Views/SettingsView.qml") },
"quit": () => { Qt.quit() } "quit": () => { Qt.quit() }
} }
width: parent.width * 0.66
height: parent.height
logo: "qrc:/logo.png" logo: "qrc:/logo.png"
appName: qsTr("BeerLog v0.1") appName: qsTr("BeerLog v0.1")
connected: beerService.connected connected: beerService.connected
model: ListModel { model: ListModel {
ListElement {
title: qsTr("Orders")
action: "orders"
}
ListElement { ListElement {
title: qsTr("Settings") title: qsTr("Settings")
action: "settings" action: "settings"
@@ -88,7 +111,7 @@ ApplicationWindow {
StackView { StackView {
id: stackView id: stackView
initialItem: "Views/OrdersView.qml" initialItem: "Views/ProductsView.qml"
anchors.fill: parent anchors.fill: parent
function openPage(page) { function openPage(page) {

View File

@@ -11,5 +11,6 @@
<file>Components/SubtitledItemDelegate.qml</file> <file>Components/SubtitledItemDelegate.qml</file>
<file>Components/qmldir</file> <file>Components/qmldir</file>
<file>qt_ru.qm</file> <file>qt_ru.qm</file>
<file>Views/ProductsView.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -12,6 +12,13 @@
BeerService::BeerService() BeerService::BeerService()
: QObject{nullptr} : QObject{nullptr}
{ {
m_actions = {
{ ActionGet, "get" },
{ ActionAdd, "add" },
{ ActionDelete, "del" },
{ ActionModify, "mod" }
};
restoreStash(); restoreStash();
connect(&m_socket, &QWebSocket::textMessageReceived, this, [this](QString message) { connect(&m_socket, &QWebSocket::textMessageReceived, this, [this](QString message) {
@@ -31,7 +38,7 @@ BeerService::BeerService()
} }
}); });
connect(&m_socket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, [this](QAbstractSocket::SocketError error) { connect(&m_socket, &QWebSocket::errorOccurred, this, [this](QAbstractSocket::SocketError error) {
qInfo() << error << m_socket.errorString(); qInfo() << error << m_socket.errorString();
}); });
@@ -60,11 +67,13 @@ SettingsService *BeerService::settings() const
return SettingsService::instance(); return SettingsService::instance();
} }
void BeerService::sendCommand(const QString &entity, const QString &action, const QVariantMap &data) void BeerService::sendCommand(const QString &entity, Action action, const QVariantMap &data)
{ {
Q_ASSERT(action != ActionUndefined);
sendCommand(QVariantMap { sendCommand(QVariantMap {
{ "entity", entity }, { "entity", entity },
{ "action", action }, { "action", m_actions[action] },
{ "data", data } { "data", data }
}); });
} }
@@ -75,6 +84,12 @@ void BeerService::connectListener(QObject *listener)
m_listeners.insert(entity, listener); m_listeners.insert(entity, listener);
} }
void BeerService::removeListener(QObject *listener)
{
QString entity = listener->property("entity").toString();
m_listeners.remove(entity, listener);
}
QString BeerService::stashFileName() const QString BeerService::stashFileName() const
{ {
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/command.stash"; return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/command.stash";
@@ -102,6 +117,7 @@ void BeerService::restoreStash()
QJsonDocument doc = QJsonDocument::fromJson(stash.readAll()); QJsonDocument doc = QJsonDocument::fromJson(stash.readAll());
m_commandStash = doc.array().toVariantList(); m_commandStash = doc.array().toVariantList();
stash.close(); stash.close();
stash.remove();
} else { } else {
qWarning() << stash.errorString(); qWarning() << stash.errorString();
} }

View File

@@ -18,8 +18,18 @@ public:
return &i; return &i;
} }
void sendCommand(const QString &entity, const QString &action, const QVariantMap &data = QVariantMap()); enum Action
{
ActionUndefined = 0,
ActionGet,
ActionAdd,
ActionDelete,
ActionModify
};
void sendCommand(const QString &entity, Action action, const QVariantMap &data = QVariantMap());
void connectListener(QObject *listener); void connectListener(QObject *listener);
void removeListener(QObject *listener);
signals: signals:
void connectedChanged(); void connectedChanged();
@@ -42,6 +52,7 @@ private:
QWebSocket m_socket; QWebSocket m_socket;
QVariantList m_commandStash; QVariantList m_commandStash;
QMap<Action, QString> m_actions;
}; };
#endif // BEERSERVICE_H #endif // BEERSERVICE_H

View File

@@ -11,6 +11,7 @@ namespace Keys {
constexpr auto ServerAddress = "server_address"; constexpr auto ServerAddress = "server_address";
constexpr auto SelectedUser = "selected_user"; constexpr auto SelectedUser = "selected_user";
constexpr auto SelectedStore = "selected_store";
} }
@@ -26,22 +27,45 @@ void SettingsService::setValue(const QString &key, const QVariant &value)
QString SettingsService::serverAddress() const QString SettingsService::serverAddress() const
{ {
return m_settings.value(Keys::ServerAddress, Defaults::ServerAddress).toString(); return value(Keys::ServerAddress, Defaults::ServerAddress).toString();
} }
void SettingsService::setServerAddress(const QString &address) void SettingsService::setServerAddress(const QString &address)
{ {
m_settings.setValue(Keys::ServerAddress, address); if (serverAddress() == address) {
return;
}
setValue(Keys::ServerAddress, address);
emit serverAddressChanged(); emit serverAddressChanged();
} }
QString SettingsService::selectedUserId() const QString SettingsService::selectedUserId() const
{ {
return m_settings.value(Keys::SelectedUser, Defaults::GuestUserId).toString(); return value(Keys::SelectedUser, Defaults::GuestUserId).toString();
} }
void SettingsService::setSelectedUserId(const QString &userId) void SettingsService::setSelectedUserId(const QString &userId)
{ {
m_settings.setValue(Keys::SelectedUser, userId); if (selectedUserId() == userId) {
return;
}
setValue(Keys::SelectedUser, userId);
emit selectedUserIdChanged(); emit selectedUserIdChanged();
} }
QString SettingsService::selectedStoreId() const
{
return value(Keys::SelectedStore).toString();
}
void SettingsService::setSelectedStoreId(const QString &storeId)
{
if (selectedStoreId() == storeId) {
return;
}
setValue(Keys::SelectedStore, storeId);
emit selectedStoreIdChanged();
}

View File

@@ -10,6 +10,7 @@ class SettingsService : public QObject
Q_PROPERTY(QString serverAddress READ serverAddress WRITE setServerAddress NOTIFY serverAddressChanged) Q_PROPERTY(QString serverAddress READ serverAddress WRITE setServerAddress NOTIFY serverAddressChanged)
Q_PROPERTY(QString selectedUserId READ selectedUserId WRITE setSelectedUserId NOTIFY selectedUserIdChanged) Q_PROPERTY(QString selectedUserId READ selectedUserId WRITE setSelectedUserId NOTIFY selectedUserIdChanged)
Q_PROPERTY(QString selectedStoreId READ selectedStoreId WRITE setSelectedStoreId NOTIFY selectedStoreIdChanged)
public: public:
static SettingsService *instance() static SettingsService *instance()
@@ -18,20 +19,24 @@ public:
return &i; return &i;
} }
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
void setValue(const QString &key, const QVariant &value);
QString serverAddress() const; QString serverAddress() const;
void setServerAddress(const QString &address); void setServerAddress(const QString &address);
QString selectedUserId() const; QString selectedUserId() const;
void setSelectedUserId(const QString &userId); void setSelectedUserId(const QString &userId);
QString selectedStoreId() const;
void setSelectedStoreId(const QString &storeId);
signals: signals:
void serverAddressChanged(); void serverAddressChanged();
void selectedUserIdChanged(); void selectedUserIdChanged();
void selectedStoreIdChanged();
private: private:
QVariant value(const QString &key, const QVariant &defaultValue = QVariant{}) const;
void setValue(const QString &key, const QVariant &value);
SettingsService() = default; SettingsService() = default;
~SettingsService() = default; ~SettingsService() = default;

View File

@@ -0,0 +1,72 @@
#include "ordersviewmodel.h"
#include <QDateTime>
OrdersViewModel::OrdersViewModel(QObject *parent)
: QAbstractListModel{parent}
{
connect(&m_ordersModel, &BaseModel::dataChanged, this, &OrdersViewModel::reload);
connect(&m_usersModel, &BaseModel::dataChanged, this, &OrdersViewModel::reload);
connect(&m_productsModel, &BaseModel::dataChanged, this, &OrdersViewModel::reload);
connect(&m_storesModel, &BaseModel::dataChanged, this, &OrdersViewModel::reload);
reload();
}
int OrdersViewModel::rowCount(const QModelIndex &) const
{
return m_model.count();
}
QVariant OrdersViewModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return {};
}
return m_model.at(index.row()).toMap()[roleNames().value(role)];
}
QHash<int, QByteArray> OrdersViewModel::roleNames() const
{
return QHash<int, QByteArray> {
{ Roles::UserName, "userName" },
{ Roles::StoreName, "storeName" },
{ Roles::Date, "date" },
{ Roles::Time, "time"},
{ Roles::Amount, "amount" },
{ Roles::Products, "products" }
};
}
void OrdersViewModel::reload()
{
beginResetModel();
m_model.clear();
for (const QVariant &vOrder : m_ordersModel.orders()) {
QVariantMap order = vOrder.toMap();
QDateTime orderTime = QDateTime::fromSecsSinceEpoch(order.value("ts", 0).toDouble());
order["date"] = orderTime.date();
order["time"] = orderTime.time();
order["userName"] = m_usersModel.itemProperty(order["userId"].toString(), "name").toString();
order["storeName"] = m_storesModel.itemProperty(order["storeId"].toString(), "name").toString();
QVariantList prodModel;
for (const QVariant &prod : order["products"].toList()) {
QVariantMap product = prod.toMap();
product["product"] = m_productsModel.itemProperty(product.value("productId").toString(), "name");
prodModel << product;
}
order["products"] = prodModel;
m_model << order;
}
std::sort(m_model.begin(), m_model.end(), [](const QVariant &l, const QVariant &r) {
return l.toMap().value("ts").toDouble() < r.toMap().value("ts").toDouble();
});
endResetModel();
}

View File

@@ -0,0 +1,43 @@
#ifndef ORDERSVIEWMODEL_H
#define ORDERSVIEWMODEL_H
#include <QAbstractListModel>
#include "models/ordersmodel.h"
#include "models/usersmodel.h"
class OrdersViewModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit OrdersViewModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
signals:
void ordersChanged();
private:
enum Roles {
UserName = Qt::UserRole + 1,
StoreName,
Date,
Time,
Amount,
Products
};
void reload();
OrdersModel m_ordersModel;
UsersModel m_usersModel;
BaseModel m_productsModel = BaseModel("products", this);
BaseModel m_storesModel = BaseModel("stores", this);
QVariantList m_model;
};
#endif // ORDERSVIEWMODEL_H

View File

@@ -0,0 +1,78 @@
#include "productsviewmodel.h"
#include "services/settingsservice.h"
#include "models/ordersmodel.h"
ProductsViewModel::ProductsViewModel(QObject *parent)
: QObject{parent}
{
connect(&m_productsModel, &BaseModel::dataChanged, this, &ProductsViewModel::productsChanged);
}
QVariantList ProductsViewModel::products() const
{
return m_productsModel.items();
}
QVariantList ProductsViewModel::order() const
{
QVariantList res;
for (auto it = m_order.constBegin(); it != m_order.constEnd(); ++it) {
QVariantMap product = m_productsModel.item(it.key());
product["count"] = it.value().toMap().value("quantity").toDouble();
res << product;
}
return res;
}
float ProductsViewModel::orderSum() const
{
float res = 0.0;
for (auto it = m_order.constBegin(); it != m_order.constEnd(); ++it) {
QVariantMap product = m_productsModel.item(it.key());
float price = product.value("price", 0.0).toFloat();
float quantity = it.value().toMap().value("quantity").toDouble();
res += quantity * price;
}
return res;
}
void ProductsViewModel::setOrderValue(const QString &productId, float value)
{
if (value) {
float price = m_productsModel.itemProperty(productId, "price", 0.0).toFloat();
m_order[productId] = QVariantMap {
{ "productId", productId },
{ "quantity", value},
{ "price", price }
};
} else {
m_order.remove(productId);
}
emit orderChanged();
}
void ProductsViewModel::submitOrder()
{
OrdersModel model;
model.submitOrder(QVariantMap {
{ "userId", settings()->selectedUserId() },
{ "storeId", settings()->selectedStoreId() },
{ "products", m_order.values() },
{ "amount", orderSum() }
});
m_order.clear();
emit orderChanged();
}
SettingsService *ProductsViewModel::settings() const
{
return SettingsService::instance();
}

View File

@@ -0,0 +1,38 @@
#ifndef PRODUCTSVIEWMODEL_H
#define PRODUCTSVIEWMODEL_H
#include <QObject>
#include "models/basemodel.h"
class SettingsService;
class ProductsViewModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList products READ products NOTIFY productsChanged)
Q_PROPERTY(QVariantList order READ order NOTIFY orderChanged)
Q_PROPERTY(float orderSum READ orderSum NOTIFY orderChanged)
public:
explicit ProductsViewModel(QObject *parent = nullptr);
QVariantList products() const;
QVariantList order() const;
float orderSum() const;
Q_INVOKABLE void setOrderValue(const QString &productId, float value);
Q_INVOKABLE void submitOrder();
signals:
void productsChanged();
void orderChanged();
private:
SettingsService *settings() const;
BaseModel m_productsModel = BaseModel("products", this);
QVariantMap m_order;
};
#endif // PRODUCTSVIEWMODEL_H

View File

@@ -0,0 +1,57 @@
#include "settingsviewmodel.h"
SettingsViewModel::SettingsViewModel(QObject *parent)
: QAbstractListModel{parent}
{
m_items << SettingItem { tr("BeerLog service address"), "serverAddress", "text" }
<< SettingItem { tr("Selected user id"), "selectedUserId", "choice", "users" }
<< SettingItem { tr("Selected store"), "selectedStoreId", "choice", "stores" };
m_models["users"] = new BaseModel("users", this);
m_models["stores"] = new BaseModel("stores", this);
}
int SettingsViewModel::rowCount(const QModelIndex &) const
{
return m_items.count();
}
QVariant SettingsViewModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return {};
}
SettingItem item = m_items.at(index.row());
switch (role) {
case Roles::Title: return item.title;
case Roles::PropertyName: return item.propertyName;
case Roles::ControlType: return item.controlType;
case Roles::Model: return model(item.modelName);
default:
break;
}
return {};
}
QHash<int, QByteArray> SettingsViewModel::roleNames() const
{
return QHash<int, QByteArray> {
{ Roles::Title, "title" },
{ Roles::PropertyName, "name" },
{ Roles::ControlType, "control" },
{ Roles::Model, "choiceModel" }
};
}
QVariant SettingsViewModel::model(const QString &modelName) const
{
if (!m_models.contains(modelName)) {
return {};
}
return m_models[modelName]->items();
}

View File

@@ -0,0 +1,47 @@
#ifndef SETTINGSVIEWMODEL_H
#define SETTINGSVIEWMODEL_H
#include <QAbstractListModel>
#include "models/basemodel.h"
class SettingsViewModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit SettingsViewModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
private:
enum Roles {
Title = Qt::UserRole + 1,
PropertyName,
ControlType,
Model
};
struct SettingItem
{
QString title;
QString propertyName;
QString controlType;
QString modelName;
SettingItem(const QString &title, const QString &propertyName, const QString &controlType, const QString &modelName = {}) :
title(title),
propertyName(propertyName),
controlType(controlType),
modelName(modelName) {}
};
QVariant model(const QString &modelName) const;
QList<SettingItem> m_items;
QMap<QString, BaseModel *> m_models;
};
#endif // SETTINGSVIEWMODEL_H

View File

@@ -0,0 +1,38 @@
#include "storesviewmodel.h"
#include "services/settingsservice.h"
StoresViewModel::StoresViewModel(QObject *parent)
: QObject{parent}
{
connect(&m_storesModel, &BaseModel::dataChanged, this, &StoresViewModel::storesChanged);
connect(&m_storesModel, &BaseModel::dataChanged, this, &StoresViewModel::selectedStoreNameChanged);
connect(settings(), &SettingsService::selectedStoreIdChanged, this, &StoresViewModel::selectedStoreChanged);
connect(settings(), &SettingsService::selectedStoreIdChanged, this, &StoresViewModel::selectedStoreNameChanged);
}
QVariantList StoresViewModel::stores() const
{
return m_storesModel.items();
}
QString StoresViewModel::selectedStore() const
{
return settings()->selectedStoreId();
}
void StoresViewModel::setSelectedStore(const QString &newSelectedStore)
{
settings()->setSelectedStoreId(newSelectedStore);
}
QString StoresViewModel::selectedStoreName() const
{
return m_storesModel.itemProperty(selectedStore(), "name").toString();
}
SettingsService *StoresViewModel::settings() const
{
return SettingsService::instance();
}

View File

@@ -0,0 +1,36 @@
#ifndef STORESVIEWMODEL_H
#define STORESVIEWMODEL_H
#include <QObject>
#include "models/basemodel.h"
class SettingsService;
class StoresViewModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList stores READ stores NOTIFY storesChanged)
Q_PROPERTY(QString selectedStore READ selectedStore WRITE setSelectedStore NOTIFY selectedStoreChanged)
Q_PROPERTY(QString selectedStoreName READ selectedStoreName NOTIFY selectedStoreNameChanged)
public:
explicit StoresViewModel(QObject *parent = nullptr);
QVariantList stores() const;
QString selectedStore() const;
void setSelectedStore(const QString &newSelectedStore);
QString selectedStoreName() const;
signals:
void storesChanged();
void selectedStoreChanged();
void selectedStoreNameChanged();
private:
SettingsService *settings() const;
BaseModel m_storesModel = BaseModel("stores", this);
};
#endif // STORESVIEWMODEL_H

View File

@@ -5,8 +5,8 @@
UsersViewModel::UsersViewModel(QObject *parent) UsersViewModel::UsersViewModel(QObject *parent)
: QObject{parent} : QObject{parent}
{ {
connect(&m_usersModel, &AbstractModel::dataChanged, this, &UsersViewModel::usersChanged); connect(&m_usersModel, &BaseModel::dataChanged, this, &UsersViewModel::usersChanged);
connect(&m_usersModel, &AbstractModel::dataChanged, this, &UsersViewModel::selectedUserNameChanged); connect(&m_usersModel, &BaseModel::dataChanged, this, &UsersViewModel::selectedUserNameChanged);
connect(settings(), &SettingsService::selectedUserIdChanged, this, &UsersViewModel::selectedUserChanged); connect(settings(), &SettingsService::selectedUserIdChanged, this, &UsersViewModel::selectedUserChanged);
connect(settings(), &SettingsService::selectedUserIdChanged, this, &UsersViewModel::selectedUserNameChanged); connect(settings(), &SettingsService::selectedUserIdChanged, this, &UsersViewModel::selectedUserNameChanged);
@@ -24,16 +24,12 @@ QString UsersViewModel::selectedUser() const
void UsersViewModel::setSelectedUser(const QString &newSelectedUser) void UsersViewModel::setSelectedUser(const QString &newSelectedUser)
{ {
if (selectedUser() == newSelectedUser) {
return;
}
settings()->setSelectedUserId(newSelectedUser); settings()->setSelectedUserId(newSelectedUser);
} }
QString UsersViewModel::selectedUserName() const QString UsersViewModel::selectedUserName() const
{ {
return m_usersModel.userName(selectedUser()); return m_usersModel.itemProperty(selectedUser(), "name").toString();
} }
SettingsService *UsersViewModel::settings() const SettingsService *UsersViewModel::settings() const