diff --git a/HanoiTowers/Protockol/src/profiledata.cpp b/HanoiTowers/Protockol/src/profiledata.cpp index 58a0538..7b5b617 100644 --- a/HanoiTowers/Protockol/src/profiledata.cpp +++ b/HanoiTowers/Protockol/src/profiledata.cpp @@ -23,6 +23,14 @@ void ProfileData::setRecord(int rec) { emit recordChanged(rec); } +void ProfileData::setAvatarHash(int avatarHash) { + if (_avatarHash == avatarHash) + return; + + _avatarHash = avatarHash; + emit avatarHashChanged(_avatarHash); +} + ProfileData::ProfileData(const QByteArray &userID): QObject(nullptr) { _userId = userID; @@ -93,3 +101,7 @@ QString ProfileData::userId() const{ QByteArray ProfileData::userIdRaw() const { return _userId; } + +int ProfileData::avatarHash() const { + return _avatarHash; +} diff --git a/HanoiTowers/Protockol/src/profiledata.h b/HanoiTowers/Protockol/src/profiledata.h index e7018ef..05bcba5 100644 --- a/HanoiTowers/Protockol/src/profiledata.h +++ b/HanoiTowers/Protockol/src/profiledata.h @@ -16,8 +16,9 @@ class HANOITOWERSPROTOCOL_EXPORT ProfileData : public QObject, public QH::Stream Q_PROPERTY(QObject* gameState READ gameState NOTIFY gameStateChanged) Q_PROPERTY(QString userId READ userId NOTIFY userIdChanged) - Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(int record READ record WRITE setRecord NOTIFY recordChanged) + Q_PROPERTY(int avatarHash READ avatarHash WRITE setAvatarHash NOTIFY avatarHashChanged) Q_PROPERTY(bool onlineUser READ isOnline WRITE setOnline NOTIFY onlineChanged) @@ -44,10 +45,14 @@ public: QString userId() const; QByteArray userIdRaw() const; + int avatarHash() const; + public slots: void setOnline(bool onlineUser); void setRecord(int record); + void setAvatarHash(int avatarHash); + signals: void gameStateChanged(QObject* gameState); void onlineChanged(bool onlineUser); @@ -57,12 +62,15 @@ signals: void userIdChanged(QString userId); + void avatarHashChanged(int avatarHash); + private: GameState _state; QString _name; int _record = 0; bool _online = false; QByteArray _userId; + int _avatarHash; }; diff --git a/HanoiTowers/Protockol/src/useravatar.cpp b/HanoiTowers/Protockol/src/useravatar.cpp new file mode 100644 index 0000000..bdb0d85 --- /dev/null +++ b/HanoiTowers/Protockol/src/useravatar.cpp @@ -0,0 +1,64 @@ +#include "useravatar.h" + +UserAvatar::UserAvatar(): QH::PKG::DBObject("Avatars") { + +} + +bool UserAvatar::copyFrom(const QH::PKG::AbstractData *other) { + if (!DBObject::copyFrom(other)) + return false; + + auto otherObject = dynamic_cast(other); + if (!otherObject) + return false; + + _image = otherObject->_image; + + return true; +} + +QH::PKG::DBObject *UserAvatar::createDBObject() const { + return create(); +} + +bool UserAvatar::fromSqlRecord(const QSqlRecord &q) { + if (!DBObject::fromSqlRecord(q)) { + return false; + } + + _image = q.value("data").toByteArray(); + + return isValid(); +} + +bool UserAvatar::isValid() const { + return DBObject::isValid(); +} + +QDataStream &UserAvatar::fromStream(QDataStream &stream) { + DBObject::toStream(stream); + stream >> _image; + return stream; +} + +QDataStream &UserAvatar::toStream(QDataStream &stream) const { + DBObject::toStream(stream); + stream << _image; + return stream; +} + +QH::BaseId UserAvatar::generateId() const { + return getId(); +} + +QH::PKG::DBVariantMap UserAvatar::variantMap() const { + return {{"data", {_image, QH::PKG::MemberType::InsertUpdate}}}; +} + +QByteArray UserAvatar::image() const { + return _image; +} + +void UserAvatar::setImage(const QByteArray &image) { + _image = image; +} diff --git a/HanoiTowers/Protockol/src/useravatar.h b/HanoiTowers/Protockol/src/useravatar.h new file mode 100644 index 0000000..ac0ce18 --- /dev/null +++ b/HanoiTowers/Protockol/src/useravatar.h @@ -0,0 +1,30 @@ +#ifndef USERAVATAR_H +#define USERAVATAR_H + +#include + +class UserAvatar: public QH::PKG::DBObject +{ +public: + UserAvatar(); + bool copyFrom(const QH::PKG::AbstractData *other) override; + QH::PKG::DBObject *createDBObject() const override; + bool fromSqlRecord(const QSqlRecord &q) override; + bool isValid() const override; + + // StreamBase interface + QByteArray image() const; + void setImage(const QByteArray &image); + +protected: + QDataStream &fromStream(QDataStream &stream) override; + QDataStream &toStream(QDataStream &stream) const override; + + QH::BaseId generateId() const override; + QH::PKG::DBVariantMap variantMap() const override; + +private: + QByteArray _image; +}; + +#endif // USERAVATAR_H diff --git a/HanoiTowers/Server/sql/database.sql b/HanoiTowers/Server/sql/database.sql index 3694ffa..7b0bfef 100644 --- a/HanoiTowers/Server/sql/database.sql +++ b/HanoiTowers/Server/sql/database.sql @@ -4,6 +4,7 @@ CREATE TABLE IF NOT EXISTS UsersData ( points INTEGER default 0, userdata BLOB default NULL, updateTime INTEGER default 0, + userAvatar INTEGER default 0, FOREIGN KEY(id) REFERENCES NetworkMembers(id) ON UPDATE CASCADE @@ -11,10 +12,11 @@ CREATE TABLE IF NOT EXISTS UsersData ( ); CREATE TABLE IF NOT EXISTS Avatars ( - id VARCHAR(64) NOT NULL, + id INTEGER PRIMARY KEY NOT NULL + user_id VARCHAR(64) NOT NULL, data BLOB default NULL, - FOREIGN KEY(id) REFERENCES NetworkMembers(id) + FOREIGN KEY(user_id) REFERENCES NetworkMembers(id) ON UPDATE CASCADE ON DELETE CASCADE ); diff --git a/HanoiTowers/client/Fog.qml b/HanoiTowers/client/Fog.qml index 3c44766..394c1c7 100644 --- a/HanoiTowers/client/Fog.qml +++ b/HanoiTowers/client/Fog.qml @@ -7,7 +7,7 @@ Item { property real spread: 0.1 readonly property int duration: 10000 - visible: backEnd.fog + visible: backEnd && backEnd.fog Image { @@ -71,7 +71,7 @@ Item { id: tim repeat: true; - running: backEnd.fogAnimation + running: backEnd && backEnd.fogAnimation interval: root.duration onTriggered: { diff --git a/HanoiTowers/client/backEnd.cpp b/HanoiTowers/client/backEnd.cpp index 2b607a4..32f3f02 100644 --- a/HanoiTowers/client/backEnd.cpp +++ b/HanoiTowers/client/backEnd.cpp @@ -11,10 +11,12 @@ #include #include #include "gamestate.h" +#include "hanoiimageprovider.h" #include #include #include #include +#include #define DEFAULT_USER_ID QByteArray("DefaultUser") #define DEFAULT_USER_NAME "User" @@ -48,6 +50,9 @@ BackEnd::BackEnd(QQmlApplicationEngine *engine): _recordsTable = new RecordListModel(this); _recordsTable->setSource(_client.localUsersPreview()); + _imageProvider = new HanoiImageProvider(&_client); + engine->addImageProvider("HanoiImages", _imageProvider); + connect(_loginModel , &LoginView::LVMainModel::sigLoginRequest, this, &BackEnd::handleOnlineRequest); @@ -69,6 +74,9 @@ ProfileData* BackEnd::initProfile(const QByteArray& userId, const QString &userN connect(_profile, &ProfileData::onlineRequest, this, &BackEnd::handleOnlineRequestfromProfile); + connect(_profile, &ProfileData::nameChanged, + this, &BackEnd::handleChangeName); + if (!_client.login(userId)) { _profile->setName(userName); @@ -149,6 +157,10 @@ void BackEnd::handleOnlineRequestfromProfile(const QString &name) { emit showOnlinePage(); } +void BackEnd::handleChangeName(const QString &) { + _client.updateProfile(*_profile); +} + void BackEnd::handleOnlineRequest(const LoginView::UserData & user) { if (!_client.login(user.nickname().toLatin1(), user.rawPassword().toLatin1())) { @@ -210,6 +222,16 @@ void BackEnd::setShowHelp(bool state) { _settings->setValue(FIRST_RUN_KEY, state); } +void BackEnd::setNewAvatar(const QString &pathToAvatar) { + QImage image; + if (pathToAvatar.contains("file://")) { + image = QImage(pathToAvatar.right(pathToAvatar.size() - 7)); + } else { + image = QImage(pathToAvatar); + } + _client.setNewAvatar(_profile->userId().toLocal8Bit(), image); +} + bool BackEnd::fog() const { return _settings->getValue(FOG, true).toBool(); } diff --git a/HanoiTowers/client/backEnd.h b/HanoiTowers/client/backEnd.h index 58f5286..16048e6 100644 --- a/HanoiTowers/client/backEnd.h +++ b/HanoiTowers/client/backEnd.h @@ -28,6 +28,7 @@ class UserData; } class QQmlApplicationEngine; class RecordListModel; +class HanoiImageProvider; class BackEnd: public QObject { @@ -66,6 +67,7 @@ public: * @param state - a new state of show help message */ Q_INVOKABLE void setShowHelp(bool state); + Q_INVOKABLE void setNewAvatar(const QString& pathToAvatar); bool fog() const; @@ -151,6 +153,8 @@ signals: private slots: void handleOnlineRequestfromProfile(const QString&); + void handleChangeName(const QString&); + void handleOnlineRequest(const LoginView::UserData&); void handleOnlineRequestError(const QString&Errr); @@ -167,6 +171,7 @@ private: HanoiClient _client; SettingsData _settingsData; + HanoiImageProvider *_imageProvider = nullptr; }; diff --git a/HanoiTowers/client/hanoiclient.cpp b/HanoiTowers/client/hanoiclient.cpp index d9625ec..9f07707 100644 --- a/HanoiTowers/client/hanoiclient.cpp +++ b/HanoiTowers/client/hanoiclient.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "hanoierrorcodes.h" #include "localrecordstable.h" @@ -141,11 +142,47 @@ void HanoiClient::setStatus(const Status &status) { } } +bool HanoiClient::setNewAvatar(const QByteArray &userId, const QImage &image) { + UserAvatar avatarData; + avatarData.setId(userId); + + QByteArray array; + QDataStream stram(&array, QIODevice::WriteOnly); + + stram << image; + avatarData.setImage(array); + + if (!db()->saveObject(&avatarData)) { + return false; + } + + auto profile = currentProfile(); + + if(profile && profile->isOnline()) { + return sendData(&avatarData, _serverAddress); + } + + return true; +} + +QImage HanoiClient::userAvatar(const QByteArray &userId) const { + UserAvatar avatarData; + avatarData.setId(userId); + + auto result = db()->getObject(avatarData); + + if (result) { + return QImage::fromData(result->image()); + } + + return {}; +} + QByteArray HanoiClient::currentUserId() const { return _currentUserId; } -const ProfileData* HanoiClient::currentProfile() { +const ProfileData* HanoiClient::currentProfile() const { auto userData = getLocalUser(_currentUserId); diff --git a/HanoiTowers/client/hanoiclient.h b/HanoiTowers/client/hanoiclient.h index c2a5c12..016ddfa 100644 --- a/HanoiTowers/client/hanoiclient.h +++ b/HanoiTowers/client/hanoiclient.h @@ -14,6 +14,7 @@ #define REMOTE_HOST "127.0.0.1" #endif #define REMOTE_PORT 7770 +#include #include #include #include @@ -46,7 +47,7 @@ public: QByteArray currentUserId() const; - const ProfileData *currentProfile(); + const ProfileData *currentProfile() const; bool updateProfile(const ProfileData& profile); bool addProfile(const ProfileData& profile); @@ -64,6 +65,9 @@ public: Status getStatus() const; void setStatus(const Status &status); + bool setNewAvatar(const QByteArray &userId, const QImage& image); + QImage userAvatar(const QByteArray &userId) const; + protected: void nodeConfirmend(const QH::HostAddress &node) override; void nodeConnected(const QH::HostAddress &node) override; diff --git a/HanoiTowers/client/hanoiimageprovider.cpp b/HanoiTowers/client/hanoiimageprovider.cpp new file mode 100644 index 0000000..f2ff069 --- /dev/null +++ b/HanoiTowers/client/hanoiimageprovider.cpp @@ -0,0 +1,48 @@ +#include "hanoiclient.h" +#include "hanoiimageprovider.h" + +#include +#include + +HanoiImageProvider::HanoiImageProvider(const HanoiClient *client) { + _pool = new QThreadPool(); + _client = client; +} + +HanoiImageProvider::~HanoiImageProvider() { + _pool->deleteLater(); +} + +QQuickImageResponse *HanoiImageProvider::requestImageResponse(const QString &id, + const QSize &requestedSize) { + + AsyncImageResponse *response = new AsyncImageResponse(id, requestedSize, _client); + _pool->start(response); + return response; + +} + +AsyncImageResponse::AsyncImageResponse(const QString &id, const QSize &requestedSize, + const HanoiClient *client) + : m_id(id), m_requestedSize(requestedSize), m_texture(0), _client(client) { + setAutoDelete(false); +} + +QQuickTextureFactory *AsyncImageResponse::textureFactory() const { + return m_texture; +} + +void AsyncImageResponse::run() { + + QImage image = _client->userAvatar(m_id.toLocal8Bit());; + + if (image.isNull()) { + image = QImage(":/img/DefaultAvatar"); + } + + if (m_requestedSize.isValid()) + image = image.scaled(m_requestedSize); + + m_texture = QQuickTextureFactory::textureFactoryForImage(image); + emit finished(); +} diff --git a/HanoiTowers/client/hanoiimageprovider.h b/HanoiTowers/client/hanoiimageprovider.h new file mode 100644 index 0000000..afe43e1 --- /dev/null +++ b/HanoiTowers/client/hanoiimageprovider.h @@ -0,0 +1,41 @@ +#ifndef HANOIIMAGEPROVIDER_H +#define HANOIIMAGEPROVIDER_H + +#include +#include + +class QThreadPool; +class HanoiClient; + +class AsyncImageResponse : public QQuickImageResponse, public QRunnable +{ + public: + AsyncImageResponse(const QString &id, const QSize &requestedSize, const HanoiClient *client); + + QQuickTextureFactory *textureFactory() const override; + + void run() override; + + QString m_id; + QSize m_requestedSize; + QQuickTextureFactory *m_texture; + +private: + const HanoiClient *_client = nullptr; +}; + + +class HanoiImageProvider: public QQuickAsyncImageProvider +{ +public: + HanoiImageProvider(const HanoiClient* client); + ~HanoiImageProvider() override; + + QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; + +private: + QThreadPool *_pool = nullptr; + const HanoiClient *_client = nullptr; +}; + +#endif // HANOIIMAGEPROVIDER_H diff --git a/HanoiTowers/client/menu/UserView.qml b/HanoiTowers/client/menu/UserView.qml index 1432491..ab69a63 100644 --- a/HanoiTowers/client/menu/UserView.qml +++ b/HanoiTowers/client/menu/UserView.qml @@ -3,6 +3,7 @@ import QtQuick.Controls 2.15 import QtQuick.Controls.Material 2.15 import QtQuick.Layouts 1.15 import QtQuick.Window 2.15 +import QtQuick.Dialogs 1.0 GridLayout { id: mainLayout @@ -10,16 +11,37 @@ GridLayout { columns: 2 clip: true - Image { - id: userAvatar - source: "qrc:/qtquickplugin/images/template_image.png" + property var userModel: null + + signal newAvatar(var path); + + Button { Layout.preferredHeight: 50 * Screen.pixelDensity Layout.preferredWidth: 50 * Screen.pixelDensity Layout.rowSpan: 1 - fillMode: Image.PreserveAspectFit - - Button { + Image { + id: userAvatar + source: "image://HanoiImages/" + ((userModel)? userModel.userId: "") anchors.fill: parent + + fillMode: Image.PreserveAspectFit + + opacity: 0.1 + } + + onClicked: { + fileDialog.open() + } + } + + FileDialog { + id: fileDialog + title: qsTr("Please choose a new Avatar") + folder: shortcuts.home + onAccepted: { + if (fileDialog.fileUrls.length) { + newAvatar(fileDialog.fileUrls[0]) + } } } @@ -60,21 +82,27 @@ GridLayout { id: eid readOnly: true - text: "" + text: (userModel)? userModel.userId: "" horizontalAlignment: Text.AlignHCenter } TextField { id: ename - text: qsTr("") + text: (userModel)? userModel.name: "" horizontalAlignment: Text.AlignHCenter + maximumLength: 64 + + onEditingFinished: { + if (userModel) + userModel.name = text + } } TextField { id: erecord - text: "0" + text: (userModel)? userModel.record: "" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter readOnly: true @@ -83,7 +111,13 @@ GridLayout { Switch { id: eonline - text: qsTr("") + checked: (userModel)? userModel.onlineUser: false + + onCheckedChanged: { + if (userModel) + userModel.onlineUser = checked + } + text: "" } } diff --git a/HanoiTowers/client/menu/UsersTable.qml b/HanoiTowers/client/menu/UsersTable.qml index 895b30d..b2b7cc4 100644 --- a/HanoiTowers/client/menu/UsersTable.qml +++ b/HanoiTowers/client/menu/UsersTable.qml @@ -29,6 +29,11 @@ Item { UserView { Layout.rowSpan: 3 + userModel: (backEnd)? backEnd.profileObject: null + + onNewAvatar: { + backEnd.setNewAvatar(path); + } } Base.BaseButton { diff --git a/HanoiTowers/client/qml.qrc b/HanoiTowers/client/qml.qrc index 5255711..079455d 100644 --- a/HanoiTowers/client/qml.qrc +++ b/HanoiTowers/client/qml.qrc @@ -31,6 +31,7 @@ res/help.png + res/DefaultAvatar.png languages/ru.qm diff --git a/HanoiTowers/client/res/DefaultAvatar.png b/HanoiTowers/client/res/DefaultAvatar.png new file mode 100644 index 0000000..193f0ac Binary files /dev/null and b/HanoiTowers/client/res/DefaultAvatar.png differ diff --git a/HanoiTowers/client/sql/database.sql b/HanoiTowers/client/sql/database.sql index 2542d6c..0b520d2 100644 --- a/HanoiTowers/client/sql/database.sql +++ b/HanoiTowers/client/sql/database.sql @@ -4,13 +4,16 @@ CREATE TABLE IF NOT EXISTS Users ( token BLOB default NULL, userdata BLOB default NULL, updateTime INTEGER default 0 + userAvatar INTEGER default 0, + ); CREATE TABLE IF NOT EXISTS Avatars ( - id VARCHAR(64) NOT NULL, + id INTEGER PRIMARY KEY NOT NULL + user_id VARCHAR(64) NOT NULL, data BLOB default NULL, - FOREIGN KEY(id) REFERENCES Users(id) + FOREIGN KEY(user_id) REFERENCES Users(id) ON UPDATE CASCADE ON DELETE CASCADE ); diff --git a/Heart b/Heart index 894fa6e..acd1276 160000 --- a/Heart +++ b/Heart @@ -1 +1 @@ -Subproject commit 894fa6efcfcbf69ed8e2b39b2ba90b16b3fbfe93 +Subproject commit acd1276f2a7dd8a8646e00737433146f2beb5ea6