added an image provider and the tble of users avatars

This commit is contained in:
Andrei Yankovich 2020-11-08 23:13:05 +03:00
parent 6c6676d752
commit 0faeb4fdbd
18 changed files with 336 additions and 20 deletions

View File

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

View File

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

View File

@ -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<const UserAvatar*>(other);
if (!otherObject)
return false;
_image = otherObject->_image;
return true;
}
QH::PKG::DBObject *UserAvatar::createDBObject() const {
return create<UserAvatar>();
}
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;
}

View File

@ -0,0 +1,30 @@
#ifndef USERAVATAR_H
#define USERAVATAR_H
#include <dbobject.h>
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

View File

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

View File

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

View File

@ -11,10 +11,12 @@
#include <QDir>
#include <qmlnotifyservice.h>
#include "gamestate.h"
#include "hanoiimageprovider.h"
#include <QQmlApplicationEngine>
#include <lvmainmodel.h>
#include <recordlistmodel.h>
#include <execution>
#include <QQmlContext>
#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();
}

View File

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

View File

@ -14,6 +14,7 @@
#include <authrequest.h>
#include <userdatarequest.h>
#include <sqldbwriter.h>
#include <useravatar.h>
#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);

View File

@ -14,6 +14,7 @@
#define REMOTE_HOST "127.0.0.1"
#endif
#define REMOTE_PORT 7770
#include <QImage>
#include <databasenode.h>
#include <profiledata.h>
#include <userpreview.h>
@ -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;

View File

@ -0,0 +1,48 @@
#include "hanoiclient.h"
#include "hanoiimageprovider.h"
#include <QThread>
#include <QThreadPool>
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();
}

View File

@ -0,0 +1,41 @@
#ifndef HANOIIMAGEPROVIDER_H
#define HANOIIMAGEPROVIDER_H
#include <QQuickAsyncImageProvider>
#include <QRunnable>
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

View File

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

View File

@ -29,6 +29,11 @@ Item {
UserView {
Layout.rowSpan: 3
userModel: (backEnd)? backEnd.profileObject: null
onNewAvatar: {
backEnd.setNewAvatar(path);
}
}
Base.BaseButton {

View File

@ -31,6 +31,7 @@
</qresource>
<qresource prefix="/img">
<file alias="Help">res/help.png</file>
<file alias="DefaultAvatar">res/DefaultAvatar.png</file>
</qresource>
<qresource prefix="/languages">
<file alias="ru">languages/ru.qm</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

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

2
Heart

@ -1 +1 @@
Subproject commit 894fa6efcfcbf69ed8e2b39b2ba90b16b3fbfe93
Subproject commit acd1276f2a7dd8a8646e00737433146f2beb5ea6