Make QML code more declarative by using properties
authorNicolas Fella <nicolas.fella@gmx.de>
Tue, 21 Jul 2020 11:24:59 +0000 (13:24 +0200)
committerKevin Ottens (Rebase PR Action) <er-vin@users.noreply.github.com>
Mon, 12 Oct 2020 11:00:20 +0000 (11:00 +0000)
By using properties and property bindings the QML code gets more declarative rather than imperative, which is considered better.

This patch:
- Introduces a currentUserId property in UserModel that replaces the equivalent Q_INVOKABLE call
- Introduces an avatar property in User that contains the avatar's image provider url without any fallback
- Introduces new image provider urls for fallback images
- Moves the fallback image selection to QML since we want different fallbacks according to where it is used
- Wires up the necessary signals to propagate a changing avatar

Signed-off-by: Nicolas Fella <nicolas.fella@gmx.de>
src/gui/tray/UserLine.qml
src/gui/tray/UserModel.cpp
src/gui/tray/UserModel.h
src/gui/tray/Window.qml

index d4e8e2edd3b5b7db9bd3e34784385d6b52f1b12c..90cf8acf17d11135c9fc93099a1e7f26aca50f5d 100644 (file)
@@ -67,7 +67,7 @@ MenuItem {
                         Layout.leftMargin: 4\r
                         verticalAlignment: Qt.AlignCenter\r
                         cache: false\r
-                        source: ("image://avatars/" + id)\r
+                        source: model.avatar != "" ? model.avatar : "image://avatars/fallbackBlack"\r
                         Layout.preferredHeight: (userLineLayout.height -16)\r
                         Layout.preferredWidth: (userLineLayout.height -16)\r
                         Rectangle {\r
index 6c6b5f49c72306bf7a11181d6bf0856d9e3598f2..1a76a50c62c33b9e229ff0d81d7959bb221ddbe4 100644 (file)
@@ -48,6 +48,8 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
     connect(FolderMan::instance(), &FolderMan::folderListChanged, this, &User::hasLocalFolderChanged);
 
     connect(this, &User::guiLog, Logger::instance(), &Logger::guiLog);
+
+    connect(_account->account().data(), &Account::accountChangedAvatar, this, &User::avatarChanged);
 }
 
 void User::slotBuildNotificationDisplay(const ActivityList &list)
@@ -463,21 +465,18 @@ QString User::server(bool shortened) const
     return serverUrl;
 }
 
-QImage User::avatar(bool whiteBg) const
+QImage User::avatar() const
 {
-    QImage img = AvatarJob::makeCircularAvatar(_account->account()->avatar());
-    if (img.isNull()) {
-        QImage image(128, 128, QImage::Format_ARGB32);
-        image.fill(Qt::GlobalColor::transparent);
-        QPainter painter(&image);
-
-        QSvgRenderer renderer(QString(whiteBg ? ":/client/theme/black/user.svg" : ":/client/theme/white/user.svg"));
-        renderer.render(&painter);
+    return AvatarJob::makeCircularAvatar(_account->account()->avatar());
+}
 
-        return image;
-    } else {
-        return img;
+QString User::avatarUrl() const
+{
+    if (avatar().isNull()) {
+        return QString();
     }
+
+    return QStringLiteral("image://avatars/") + _account->account()->id();
 }
 
 bool User::hasLocalFolder() const
@@ -575,27 +574,12 @@ Q_INVOKABLE bool UserModel::isUserConnected(const int &id)
     return _users[id]->isConnected();
 }
 
-Q_INVOKABLE QImage UserModel::currentUserAvatar()
-{
-    if (!_users.isEmpty()) {
-        return _users[_currentUserId]->avatar();
-    } else {
-        QImage image(128, 128, QImage::Format_ARGB32);
-        image.fill(Qt::GlobalColor::transparent);
-        QPainter painter(&image);
-        QSvgRenderer renderer(QString(":/client/theme/white/user.svg"));
-        renderer.render(&painter);
-
-        return image;
-    }
-}
-
 QImage UserModel::avatarById(const int &id)
 {
     if (_users.isEmpty())
         return {};
 
-    return _users[id]->avatar(true);
+    return _users[id]->avatar();
 }
 
 Q_INVOKABLE QString UserModel::currentUserServer()
@@ -617,11 +601,20 @@ void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent)
     }
 
     if (!containsUser) {
-        beginInsertRows(QModelIndex(), rowCount(), rowCount());
-        _users << new User(user, isCurrent);
+        int row = rowCount();
+        beginInsertRows(QModelIndex(), row, row);
+
+        User *u = new User(user, isCurrent);
+
+        connect(u, &User::avatarChanged, this, [this, row] {
+           emit dataChanged(index(row, 0), index(row, 0), {UserModel::AvatarRole});
+        });
+
+        _users << u;
         if (isCurrent) {
             _currentUserId = _users.indexOf(_users.last());
         }
+
         endInsertRows();
         ConfigFile cfg;
         _users.last()->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
@@ -748,7 +741,7 @@ QVariant UserModel::data(const QModelIndex &index, int role) const
     } else if (role == ServerRole) {
         return _users[index.row()]->server();
     } else if (role == AvatarRole) {
-        return _users[index.row()]->avatar();
+        return _users[index.row()]->avatarUrl();
     } else if (role == IsCurrentUserRole) {
         return _users[index.row()]->isCurrentUser();
     } else if (role == IsConnectedRole) {
@@ -829,12 +822,25 @@ QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize &
     Q_UNUSED(size)
     Q_UNUSED(requestedSize)
 
-    if (id == "currentUser") {
-        return UserModel::instance()->currentUserAvatar();
-    } else {
-        int uid = id.toInt();
-        return UserModel::instance()->avatarById(uid);
+    const auto makeIcon = [](const QString &path) {
+        QImage image(128, 128, QImage::Format_ARGB32);
+        image.fill(Qt::GlobalColor::transparent);
+        QPainter painter(&image);
+        QSvgRenderer renderer(path);
+        renderer.render(&painter);
+        return image;
+    };
+
+    if (id == QLatin1String("fallbackWhite")) {
+        return makeIcon(QStringLiteral(":/client/theme/white/user.svg"));
+    }
+
+    if (id == QLatin1String("fallbackBlack")) {
+        return makeIcon(QStringLiteral(":/client/theme/black/user.svg"));
     }
+
+    const int uid = id.toInt();
+    return UserModel::instance()->avatarById(uid);
 }
 
 /*-------------------------------------------------------------------------------------*/
index fd48f1110a730d256ab2875a64c3f306269b7804..09b42b175625e74ffc8f6434d224caaabf6053b5 100644 (file)
@@ -21,6 +21,7 @@ class User : public QObject
     Q_PROPERTY(QString server READ server CONSTANT)
     Q_PROPERTY(bool hasLocalFolder READ hasLocalFolder NOTIFY hasLocalFolderChanged)
     Q_PROPERTY(bool serverHasTalk READ serverHasTalk NOTIFY serverHasTalkChanged)
+    Q_PROPERTY(QString avatar READ avatarUrl NOTIFY avatarChanged)
 public:
     User(AccountStatePtr &account, const bool &isCurrent = false, QObject* parent = nullptr);
 
@@ -39,17 +40,18 @@ public:
     AccountApp *talkApp() const;
     bool hasActivities() const;
     AccountAppList appList() const;
-    QImage avatar(bool whiteBg = false) const;
-    QString id() const;
+    QImage avatar() const;
     void login() const;
     void logout() const;
     void removeAccount() const;
+    QString avatarUrl() const;
 
 signals:
     void guiLog(const QString &, const QString &);
     void nameChanged();
     void hasLocalFolderChanged();
     void serverHasTalkChanged();
+    void avatarChanged();
 
 public slots:
     void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
@@ -89,6 +91,7 @@ class UserModel : public QAbstractListModel
 {
     Q_OBJECT
     Q_PROPERTY(User* currentUser READ currentUser NOTIFY newUserSelected)
+    Q_PROPERTY(int currentUserId READ currentUserId NOTIFY newUserSelected)
 public:
     static UserModel *instance();
     virtual ~UserModel() = default;
@@ -108,12 +111,11 @@ public:
     Q_INVOKABLE void openCurrentAccountLocalFolder();
     Q_INVOKABLE void openCurrentAccountTalk();
     Q_INVOKABLE void openCurrentAccountServer();
-    Q_INVOKABLE QImage currentUserAvatar();
     Q_INVOKABLE int numUsers();
     Q_INVOKABLE QString currentUserServer();
     Q_INVOKABLE bool currentUserHasActivities();
     Q_INVOKABLE bool currentUserHasLocalFolder();
-    Q_INVOKABLE int currentUserId() const;
+    int currentUserId() const;
     Q_INVOKABLE bool isUserConnected(const int &id);
     Q_INVOKABLE void switchCurrentUser(const int &id);
     Q_INVOKABLE void login(const int &id);
index f5d23b4a6d3e620266b430a69bbee3439054b336..a73b3bfad90decaf062578931b2d8b9316badf77 100644 (file)
@@ -33,10 +33,8 @@ Window {
     }\r
 \r
     onVisibleChanged: {\r
-        currentAccountAvatar.source = ""\r
-        currentAccountAvatar.source = "image://avatars/currentUser"\r
         currentAccountStateIndicator.source = ""\r
-        currentAccountStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"\r
+        currentAccountStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"\r
 \r
         // HACK: reload account Instantiator immediately by restting it - could be done better I guess\r
         // see also id:accountMenu below\r
@@ -47,10 +45,8 @@ Window {
     Connections {\r
         target: UserModel\r
         onRefreshCurrentUserGui: {\r
-            currentAccountAvatar.source = ""\r
-            currentAccountAvatar.source = "image://avatars/currentUser"\r
             currentAccountStateIndicator.source = ""\r
-            currentAccountStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"\r
+            currentAccountStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"\r
         }\r
         onNewUserSelected: {\r
             accountMenu.close();\r
@@ -336,7 +332,7 @@ Window {
                             Layout.leftMargin: 8\r
                             verticalAlignment: Qt.AlignCenter\r
                             cache: false\r
-                            source: "image://avatars/currentUser"\r
+                            source: UserModel.currentUser.avatar != "" ? UserModel.currentUser.avatar : "image://avatars/fallbackWhite"\r
                             Layout.preferredHeight: Style.accountAvatarSize\r
                             Layout.preferredWidth: Style.accountAvatarSize\r
 \r
@@ -355,7 +351,7 @@ Window {
 \r
                             Image {\r
                                 id: currentAccountStateIndicator\r
-                                source: UserModel.isUserConnected(UserModel.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"\r
+                                source: UserModel.isUserConnected(UserModel.currentUserId) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"\r
                                 cache: false\r
                                 x: currentAccountStateIndicatorBackground.x + 1\r
                                 y: currentAccountStateIndicatorBackground.y + 1\r