From ac266bb9979ffd3bb0147c0aeab2ecb09161d615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Cie=C5=9Blak?= Date: Fri, 16 Feb 2024 13:08:45 +0100 Subject: [PATCH] feat(ProfileShowcase): Component managing all models required by Profile Showcase settings UI Closes: #13435 Closes: #13490 Closes: #13494 --- storybook/pages/ProfileShowcaseModelsPage.qml | 324 ++++++++++++++++++ .../Profile/helpers/ProfileShowcaseModels.qml | 163 +++++++++ ui/app/AppLayouts/Profile/helpers/qmldir | 1 + 3 files changed, 488 insertions(+) create mode 100644 storybook/pages/ProfileShowcaseModelsPage.qml create mode 100644 ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModels.qml diff --git a/storybook/pages/ProfileShowcaseModelsPage.qml b/storybook/pages/ProfileShowcaseModelsPage.qml new file mode 100644 index 0000000000..9c066371f4 --- /dev/null +++ b/storybook/pages/ProfileShowcaseModelsPage.qml @@ -0,0 +1,324 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQml 2.15 + +import StatusQ 0.1 +import StatusQ.Core.Utils 0.1 + +import Storybook 1.0 + +import utils 1.0 + +import AppLayouts.Profile.helpers 1.0 + +ColumnLayout { + ListModel { + id: accountsModel + + ListElement { key: "1"; name: "Crypto Kitties" } + ListElement { key: "2"; name: "Status" } + ListElement { key: "3"; name: "Fun Stuff" } + ListElement { key: "4"; name: "Other Stuff" } + } + + ListModel { + id: accountsShowcaseModel + + ListElement { key: "1"; visibility: 1; position: 0 } + ListElement { key: "3"; visibility: 2; position: 9 } + } + + ListModel { + id: collectiblesModel + + ListElement { key: "1"; name: "Collectible 1"; accounts: "1:3" } + ListElement { key: "2"; name: "Collectible 2"; accounts: "3" } + ListElement { key: "3"; name: "Collectible 3"; accounts: "1:2:3" } + ListElement { key: "4"; name: "Collectible 4"; accounts: "1:4" } + } + + ListModel { + id: collectiblesShowcaseModel + + ListElement { key: "1"; visibility: 1; position: 0 } + ListElement { key: "2"; visibility: 2; position: 2 } + ListElement { key: "3"; visibility: 2; position: 1 } + } + + ProfileShowcaseModels { + id: showcaseModels + + accountsSourceModel: accountsModel + accountsShowcaseModel: accountsShowcaseModel + + collectiblesSourceModel: collectiblesModel + collectiblesShowcaseModel: collectiblesShowcaseModel + } + + MovableModel { + id: accountsMovableModel + + sourceModel: showcaseModels.accountsVisibleModel + } + + MovableModel { + id: collectiblesMovableModel + + sourceModel: showcaseModels.collectiblesVisibleModel + } + + component VisibilityComboBox: ComboBox { + model: ListModel { + ListElement { text: "contacts"; value: 1 } + ListElement { text: "verified"; value: 2 } + ListElement { text: "all"; value: 3 } + } + + textRole: "text" + valueRole: "value" + } + + Flickable { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.margins: 10 + + contentWidth: grid.width + contentHeight: grid.height + + clip: true + + Grid { + id: grid + + rows: 3 + columns: 4 + + spacing: 10 + + flow: Grid.TopToBottom + + Label { + text: "Backend models" + font.pixelSize: 22 + padding: 10 + } + + GenericListView { + width: 200 + height: 300 + + model: accountsModel + label: "ACCOUNTS MODEL" + } + + GenericListView { + width: 200 + height: 300 + + model: accountsShowcaseModel + label: "SHOWCASE MODEL" + roles: ["key", "visibility", "position"] + } + + Label { + text: "Display models" + font.pixelSize: 22 + padding: 10 + } + + GenericListView { + width: 420 + height: 300 + + model: accountsMovableModel + label: "IN SHOWCASE" + movable: true + roles: ["key", "visibility", "position"] + + onMoveRequested: { + accountsMovableModel.move(from, to) + + const key = ModelUtils.get(accountsMovableModel, to, "key") + showcaseModels.changeAccountPosition(key, to); + } + + insetComponent: RowLayout { + readonly property var topModel: model + + RoundButton { + text: "❌" + onClicked: showcaseModels.setAccountVisibility( + model.key, 0) + } + + VisibilityComboBox { + property bool completed: false + + onCurrentValueChanged: { + if (!completed || topModel.index < 0) + return + + showcaseModels.setAccountVisibility( + topModel.key, currentValue) + } + + Component.onCompleted: { + currentIndex = indexOfValue(topModel.visibility) + completed = true + } + } + } + } + + GenericListView { + width: 420 + height: 300 + + model: showcaseModels.accountsHiddenModel + + label: "HIDDEN" + + roles: ["key", "visibility", "position"] + + insetComponent: Button { + text: "unhide" + + onClicked: showcaseModels.setAccountVisibility( + model.key, 1) + } + } + + Label { + text: "Backend models" + font.pixelSize: 22 + padding: 10 + } + + GenericListView { + width: 270 + height: 300 + + model: collectiblesModel + label: "COLLECTIBLES MODEL" + + roles: ["key", "name", "accounts"] + } + + GenericListView { + width: 270 + height: 300 + + model: collectiblesShowcaseModel + label: "SHOWCASE MODEL" + roles: ["key", "visibility", "position"] + } + + Label { + text: "Display models" + font.pixelSize: 22 + padding: 10 + } + + GenericListView { + width: 610 + height: 300 + + model: collectiblesMovableModel + label: "IN SHOWCASE" + movable: true + roles: ["key", "visibility", "position", "accounts", "maxVisibility"] + + onMoveRequested: { + collectiblesMovableModel.move(from, to) + + const key = ModelUtils.get(collectiblesMovableModel, to, "key") + showcaseModels.changeCollectiblePosition(key, to); + } + + insetComponent: RowLayout { + readonly property var topModel: model + + RoundButton { + text: "❌" + onClicked: showcaseModels.setCollectibleVisibility( + model.key, 0) + } + + VisibilityComboBox { + property bool completed: false + + onCurrentValueChanged: { + if (!completed || topModel.index < 0) + return + + showcaseModels.setCollectibleVisibility( + topModel.key, currentValue) + } + + Component.onCompleted: { + currentIndex = indexOfValue(topModel.visibility) + completed = true + } + } + } + } + + GenericListView { + width: 610 + height: 300 + + model: showcaseModels.collectiblesHiddenModel + + label: "HIDDEN" + + roles: ["key", "visibility", "position", + "accounts", "maxVisibility"] + + insetComponent: Button { + text: "unhide" + + onClicked: showcaseModels.setCollectibleVisibility( + model.key, 1) + } + } + } + } + + Label { + text: `accounts in showcase: [${showcaseModels.visibleAccountsList}]` + Layout.alignment: Qt.AlignHCenter + } + + Label { + readonly property string visibilities: + JSON.stringify(showcaseModels.accountsVisibilityMap) + + text: `accounts visibilities: [${visibilities}]` + Layout.alignment: Qt.AlignHCenter + } + + Button { + text: "SAVE" + + onClicked: { + const accountsToBeSaved = showcaseModels.accountsCurrentState() + const collectiblesToBeSaved = showcaseModels.collectiblesCurrentState() + + accountsMovableModel.syncOrder() + collectiblesMovableModel.syncOrder() + + accountsShowcaseModel.clear() + accountsShowcaseModel.append(accountsToBeSaved) + + collectiblesShowcaseModel.clear() + collectiblesShowcaseModel.append(collectiblesToBeSaved) + } + + Layout.alignment: Qt.AlignHCenter + Layout.margins: 10 + } +} + +// category: Models diff --git a/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModels.qml b/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModels.qml new file mode 100644 index 0000000000..e0a98bec91 --- /dev/null +++ b/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModels.qml @@ -0,0 +1,163 @@ +import QtQml 2.15 + +import StatusQ 0.1 +import StatusQ.Core.Utils 0.1 + +import SortFilterProxyModel 0.2 + +QObject { + id: root + + // COMMUNITIES + + // Input models + property alias communitiesSourceModel: communities.sourceModel + property alias communitiesShowcaseModel: communities.showcaseModel + + // Output models + readonly property alias communitiesVisibleModel: communities.visibleModel + readonly property alias communitiesHiddenModel: communities.hiddenModel + + // Methods + function communitiesCurrentState() { + return communities.currentState() + } + + function setCommunityVisibility(key, visibility) { + communities.setVisibility(key, visibility) + } + + function changeCommunityPosition(key, to) { + communities.changePosition(key, to) + } + + // ACCOUNTS + + // Input models + property alias accountsSourceModel: accounts.sourceModel + property alias accountsShowcaseModel: accounts.showcaseModel + + // Output models + readonly property alias accountsVisibleModel: accounts.visibleModel + readonly property alias accountsHiddenModel: accounts.hiddenModel + + // Methods + function accountsCurrentState() { + return accounts.currentState() + } + + function setAccountVisibility(key, visibility) { + accounts.setVisibility(key, visibility) + } + + function changeAccountPosition(key, to) { + accounts.changePosition(key, to) + } + + // Other + readonly property alias visibleAccountsList: + visibleAccountsConnections.visibleAccountsList + + readonly property alias accountsVisibilityMap: + visibleAccountsConnections.accountsVisibilityMap + + // COLLECTIBLES + + // Input models + property alias collectiblesSourceModel: collectiblesFilter.sourceModel + property alias collectiblesShowcaseModel: collectibles.showcaseModel + + // Output models + readonly property alias collectiblesVisibleModel: collectibles.visibleModel + readonly property alias collectiblesHiddenModel: collectibles.hiddenModel + + // Methods + function collectiblesCurrentState() { + return collectibles.currentState() + } + + function setCollectibleVisibility(key, visibility) { + collectibles.setVisibility(key, visibility) + } + + function changeCollectiblePosition(key, to) { + collectibles.changePosition(key, to) + } + + ProfileShowcaseDirtyState { + id: communities + } + + ProfileShowcaseDirtyState { + id: accounts + } + + ProfileShowcaseDirtyState { + id: collectibles + + sourceModel: collectiblesFilter + } + + SortFilterProxyModel { + id: collectiblesFilter + + delayed: true + + proxyRoles: FastExpressionRole { + name: "maxVisibility" + + expression: { + const m = root.accountsVisibilityMap + const accounts = model.accounts.split(":") + const visibilities = accounts.map(e => m[e]).filter(e => e) + + return visibilities.length ? Math.min(...visibilities) : 0 + } + + expectedRoles: ["accounts"] + } + + filters: RangeFilter { + roleName: "maxVisibility" + minimumValue: 1 + } + } + + Connections { + id: visibleAccountsConnections + + target: accounts.visibleModel + + property var visibleAccountsList: [] + property var accountsVisibilityMap: ({}) + + function updateAccountsList() { + const keysAndVisibility = ModelUtils.modelToArray( + accounts.visibleModel, ["key", "visibility"]) + + visibleAccountsList = keysAndVisibility.map(e => e.key) + + accountsVisibilityMap = keysAndVisibility.reduce( + (acc, val) => Object.assign( + acc, {[val.key]: val.visibility}), {}) + } + + function onDataChanged() { + updateAccountsList() + } + + function onRowsInserted() { + updateAccountsList() + } + + function onRowsRemoved() { + updateAccountsList() + } + + function onModelReset() { + updateAccountsList() + } + + Component.onCompleted: updateAccountsList() + } +} diff --git a/ui/app/AppLayouts/Profile/helpers/qmldir b/ui/app/AppLayouts/Profile/helpers/qmldir index 6cfb4c7399..87106dd06a 100644 --- a/ui/app/AppLayouts/Profile/helpers/qmldir +++ b/ui/app/AppLayouts/Profile/helpers/qmldir @@ -1,2 +1,3 @@ ProfileShowcaseDirtyState 1.0 ProfileShowcaseDirtyState.qml +ProfileShowcaseModels 1.0 ProfileShowcaseModels.qml VisibilityAndPositionDirtyStateModel 1.0 VisibilityAndPositionDirtyStateModel.qml