diff --git a/storybook/pages/ProfileShowcaseModelsPage.qml b/storybook/pages/ProfileShowcaseModelsPage.qml index ef3f7ff977..bad4c10e76 100644 --- a/storybook/pages/ProfileShowcaseModelsPage.qml +++ b/storybook/pages/ProfileShowcaseModelsPage.qml @@ -24,6 +24,18 @@ ColumnLayout { ListElement { address: "4"; name: "Other Stuff" } } + ListModel { + id: socialLinksModel + + ListElement { uuid: "1"; text: "Twitter"; url: "https://twitter.com/status" } + ListElement { uuid: "2"; text: "Personal Site"; url: "https://status.im" } + ListElement { uuid: "3"; text: "Github"; url: "https://github.com" } + ListElement { uuid: "4"; text: "Youtube"; url: "https://youtube.com" } + ListElement { uuid: "5"; text: "Discord"; url: "https://discord.com" } + ListElement { uuid: "6"; text: "Telegram"; url: "https://t.me/status" } + ListElement { uuid: "7"; text: "Custom"; url: "https://status.im" } + } + ListModel { id: accountsShowcaseModel ListElement { @@ -173,6 +185,8 @@ ColumnLayout { collectiblesSourceModel: collectiblesModel collectiblesShowcaseModel: collectiblesShowcaseModel + + socialLinksSourceModel: socialLinksModel } ListModel { @@ -199,208 +213,277 @@ ColumnLayout { valueRole: "value" } - Flickable { + StackView { + id: stackView Layout.fillWidth: true Layout.fillHeight: true Layout.margins: 10 - contentWidth: grid.width - contentHeight: grid.height + initialItem: collectiblesView + + Component { + id: collectiblesView + Flickable { - clip: true + contentWidth: grid.width + contentHeight: grid.height - Grid { - id: grid + clip: true - rows: 3 - columns: 4 + Grid { + id: grid - spacing: 10 + rows: 3 + columns: 4 - flow: Grid.TopToBottom + spacing: 10 - Label { - text: "Backend models" - font.pixelSize: 22 - padding: 10 - } + flow: Grid.TopToBottom - GenericListView { - width: 300 - height: 300 - - model: accountsModel - label: "ACCOUNTS MODEL" - } - - GenericListView { - width: 300 - height: 300 - - model: accountsShowcaseModel - label: "SHOWCASE MODEL" - roles: ["showcaseKey", "showcaseVisibility", "showcasePosition"] - } - - Label { - text: "Display models" - font.pixelSize: 22 - padding: 10 - } - - GenericListView { - width: 420 - height: 300 - - model: showcaseModels.accountsVisibleModel - label: "IN SHOWCASE" - movable: true - roles: ["showcaseKey", "showcaseVisibility", "showcasePosition"] - - onMoveRequested: { - showcaseModels.changeAccountPosition(from, to); - } - - insetComponent: RowLayout { - readonly property var topModel: model - - RoundButton { - text: "❌" - onClicked: showcaseModels.setAccountVisibility( - model.showcaseKey, - Constants.ShowcaseVisibility.NoOne) + Label { + text: "Backend models" + font.pixelSize: 22 + padding: 10 } - VisibilityComboBox { - property bool completed: false + GenericListView { + width: 300 + height: 300 - onCurrentValueChanged: { - if (!completed || topModel.index < 0) - return + model: accountsModel + label: "ACCOUNTS MODEL" + } - showcaseModels.setAccountVisibility( - topModel.showcaseKey, currentValue) + GenericListView { + width: 300 + height: 300 + + model: accountsShowcaseModel + label: "SHOWCASE MODEL" + roles: ["showcaseKey", "showcaseVisibility", "showcasePosition"] + } + + Label { + text: "Display models" + font.pixelSize: 22 + padding: 10 + } + + GenericListView { + width: 420 + height: 300 + + model: showcaseModels.accountsVisibleModel + label: "IN SHOWCASE" + movable: true + roles: ["showcaseKey", "showcaseVisibility", "showcasePosition"] + + onMoveRequested: { + showcaseModels.changeAccountPosition(from, to); } - Component.onCompleted: { - currentIndex = indexOfValue(topModel.showcaseVisibility) - completed = true + insetComponent: RowLayout { + readonly property var topModel: model + + RoundButton { + text: "❌" + onClicked: showcaseModels.setAccountVisibility( + model.showcaseKey, + Constants.ShowcaseVisibility.NoOne) + } + + VisibilityComboBox { + property bool completed: false + + onCurrentValueChanged: { + if (!completed || topModel.index < 0) + return + + showcaseModels.setAccountVisibility( + topModel.showcaseKey, currentValue) + } + + Component.onCompleted: { + currentIndex = indexOfValue(topModel.showcaseVisibility) + completed = true + } + } + } + } + + GenericListView { + width: 420 + height: 300 + + model: showcaseModels.accountsHiddenModel + + label: "HIDDEN" + + roles: ["showcaseKey", "showcaseVisibility", "showcasePosition"] + + insetComponent: Button { + text: "unhide" + + onClicked: + showcaseModels.setAccountVisibility( + model.showcaseKey, + Constants.ShowcaseVisibility.IdVerifiedContacts) + } + } + + Label { + text: "Backend models" + font.pixelSize: 22 + padding: 10 + } + + GenericListView { + width: 270 + height: 300 + + model: collectiblesModel + label: "COLLECTIBLES MODEL" + } + + GenericListView { + width: 270 + height: 300 + + model: collectiblesShowcaseModel + label: "SHOWCASE MODEL" + roles: ["uid", "showcaseVisibility", "order"] + } + + Label { + text: "Display models" + font.pixelSize: 22 + padding: 10 + } + + GenericListView { + width: 610 + height: 300 + + model: showcaseModels.collectiblesVisibleModel + label: "IN SHOWCASE" + movable: true + roles: ["showcaseKey", "showcaseVisibility", "showcasePosition"] + + onMoveRequested: { + showcaseModels.changeCollectiblePosition(from, to); + } + + insetComponent: RowLayout { + readonly property var topModel: model + + RoundButton { + text: "❌" + onClicked: showcaseModels.setCollectibleVisibility( + model.showcaseKey, + Constants.ShowcaseVisibility.NoOne) + } + + VisibilityComboBox { + property bool completed: false + + onCurrentValueChanged: { + if (!completed || topModel.index < 0) + return + + showcaseModels.setCollectibleVisibility( + topModel.showcaseKey, currentValue) + } + + Component.onCompleted: { + currentIndex = indexOfValue(topModel.showcaseVisibility) + completed = true + } + } + } + } + + GenericListView { + width: 610 + height: 300 + + model: showcaseModels.collectiblesHiddenModel + + label: "HIDDEN" + + roles: ["showcaseKey", "showcaseVisibility", "showcasePosition", + "accounts", "maxVisibility"] + + insetComponent: Button { + text: "unhide" + + onClicked: + showcaseModels.setCollectibleVisibility( + model.showcaseKey, + Constants.ShowcaseVisibility.IdVerifiedContacts) } } } } + } + + Component { + id: webView + Flickable { - GenericListView { - width: 420 - height: 300 + contentWidth: webGrid.implicitWidth + contentHeight: webGrid.implicitHeight - model: showcaseModels.accountsHiddenModel + Grid { + id: webGrid - label: "HIDDEN" + rows: 3 + columns: 4 - roles: ["showcaseKey", "showcaseVisibility", "showcasePosition"] + spacing: 10 - insetComponent: Button { - text: "unhide" + flow: Grid.TopToBottom - onClicked: - showcaseModels.setAccountVisibility( - model.showcaseKey, - Constants.ShowcaseVisibility.IdVerifiedContacts) - } - } - - Label { - text: "Backend models" - font.pixelSize: 22 - padding: 10 - } - - GenericListView { - width: 270 - height: 300 - - model: collectiblesModel - label: "COLLECTIBLES MODEL" - } - - GenericListView { - width: 270 - height: 300 - - model: collectiblesShowcaseModel - label: "SHOWCASE MODEL" - roles: ["uid", "showcaseVisibility", "order"] - } - - Label { - text: "Display models" - font.pixelSize: 22 - padding: 10 - } - - GenericListView { - width: 610 - height: 300 - - model: showcaseModels.collectiblesVisibleModel - label: "IN SHOWCASE" - movable: true - roles: ["showcaseKey", "showcaseVisibility", "showcasePosition"] - - onMoveRequested: { - showcaseModels.changeCollectiblePosition(from, to); - } - - insetComponent: RowLayout { - readonly property var topModel: model - - RoundButton { - text: "❌" - onClicked: showcaseModels.setCollectibleVisibility( - model.showcaseKey, - Constants.ShowcaseVisibility.NoOne) + Label { + text: "Backend models" + font.pixelSize: 22 + padding: 10 } - VisibilityComboBox { - property bool completed: false + GenericListView { + width: 300 + height: 300 - onCurrentValueChanged: { - if (!completed || topModel.index < 0) - return + model: socialLinksModel + label: "SOCIAL LINKS MODEL" + } - showcaseModels.setCollectibleVisibility( - topModel.showcaseKey, currentValue) - } + Item { - Component.onCompleted: { - currentIndex = indexOfValue(topModel.showcaseVisibility) - completed = true + width: 300 + height: 300 + } + + Label { + text: "Display models" + font.pixelSize: 22 + padding: 10 + } + + GenericListView { + width: 610 + height: 300 + + model: showcaseModels.socialLinksVisibleModel + + label: "IN SHOWCASE" + movable: true + + onMoveRequested: { + showcaseModels.changeSocialLinkPosition(from, to); } } } } - - GenericListView { - width: 610 - height: 300 - - model: showcaseModels.collectiblesHiddenModel - - label: "HIDDEN" - - roles: ["showcaseKey", "showcaseVisibility", "showcasePosition", - "accounts", "maxVisibility"] - - insetComponent: Button { - text: "unhide" - - onClicked: - showcaseModels.setCollectibleVisibility( - model.showcaseKey, - Constants.ShowcaseVisibility.IdVerifiedContacts) - } - } } } @@ -417,6 +500,28 @@ ColumnLayout { Layout.alignment: Qt.AlignHCenter } + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignHCenter + + Button { + text: "Collectibles tab" + onClicked: { + stackView.replace(webView, collectiblesView) + } + } + + Button { + text: "Web tab" + onClicked: { + stackView.replace(collectiblesView, webView) + } + } + } + + + Button { text: "SAVE" //TODO: enable when showcaseModels backend APIs is integrated diff --git a/storybook/pages/ProfileSocialLinksPanelPage.qml b/storybook/pages/ProfileSocialLinksPanelPage.qml index e8895c6a6e..9932ab3f9e 100644 --- a/storybook/pages/ProfileSocialLinksPanelPage.qml +++ b/storybook/pages/ProfileSocialLinksPanelPage.qml @@ -25,56 +25,46 @@ SplitView { communityTokensStore: CommunityTokensStore {} } + ListModel { + id: emptyModel + } + ListModel { id: linksModel ListElement { uuid: "0001" text: "__github" url: "https://github.com/caybro" - linkType: 3 // Constants.socialLinkType.github - icon: "github" } ListElement { uuid: "0002" text: "__twitter" url: "https://twitter.com/caybro" - linkType: 1 // Constants.socialLinkType.twitter - icon: "twitter" } ListElement { uuid: "0003" text: "__personal_site" url: "https://status.im" - linkType: 2 // Constants.socialLinkType.personalSite - icon: "language" } ListElement { uuid: "0004" text: "__youtube" url: "https://www.youtube.com/@LukasTinkl" - linkType: 4 // Constants.socialLinkType.youtube - icon: "youtube" } ListElement { // NB: empty on purpose, for testing uuid: "" text: "" url: "" - linkType: -1 - icon: "" } ListElement { uuid: "0005" text: "Figma design very long URL link text that should elide" url: "https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=1223%3A124882&t=qvYeJ8grsZLyUS0V-0" - linkType: 0 // Constants.socialLinkType.custom - icon: "link" } ListElement { uuid: "0006" text: "__telegram" url: "https://t.me/ltinkl" - linkType: 6 // Constants.socialLinkType.telegram - icon: "telegram" } } @@ -89,47 +79,31 @@ SplitView { SplitView.fillWidth: true SplitView.preferredHeight: 300 ProfileSocialLinksPanel { + id: socialLinksPanel + property var linksModel: emptyModelCheck.checked ? emptyModel : linksModel width: 500 - profileStore: QtObject { - function createLink(text, url, linkType, icon) { - logs.logEvent("ProfileStore::createLink", ["text", "url", "linkType", "icon"], arguments) - linksModel.append({text, url, linkType, icon}) - } - function removeLink(uuid) { - logs.logEvent("ProfileStore::removeLink", ["uuid"], arguments) - const idx = CoreUtils.ModelUtils.indexOf(linksModel, "uuid", uuid) - if (idx === -1) - return - linksModel.remove(idx, 1) - } - - function updateLink(uuid, text, url) { - logs.logEvent("ProfileStore::updateLink", ["uuid", "text", "url"], arguments) - const idx = CoreUtils.ModelUtils.indexOf(linksModel, "uuid", uuid) - if (idx === -1) - return - if (!!text) - linksModel.setProperty(idx, "text", text) - if (!!url) - linksModel.setProperty(idx, "url", url) - } - - function moveLink(fromRow, toRow, count) { - logs.logEvent("ProfileStore::moveLink", ["fromRow", "toRow", "count"], arguments) - linksModel.move(fromRow, toRow, 1) - } - - function resetSocialLinks() { - logs.logEvent("ProfileStore::resetSocialLinks") - } - - function saveSocialLinks(silent = false) { - logs.logEvent("ProfileStore::saveSocialLinks", ["silent"], arguments) - } + onAddSocialLink: { + logs.logEvent("ProfileSocialLinksPanel::addSocialLink", ["url", "text"], arguments) + socialLinksPanel.linksModel.append({text: text, url: url}) + } + onUpdateSocialLink: { + logs.logEvent("ProfileSocialLinksPanel::updateSocialLink", ["index", "url", "text"], arguments) + if (!!text) + socialLinksPanel.linksModel.setProperty(index, "text", text) + if (!!url) + socialLinksPanel.linksModel.setProperty(index, "url", url) + } + onRemoveSocialLink: { + logs.logEvent("ProfileSocialLinksPanel::removeSocialLink", ["index"], arguments) + socialLinksPanel.linksModel.remove(index, 1) + } + onChangePosition: { + logs.logEvent("ProfileSocialLinksPanel::changePosition", ["from", "to"], arguments) + socialLinksPanel.linksModel.move(from, to, 1) } - socialLinksModel: linksModel + socialLinksModel: socialLinksPanel.linksModel } } @@ -140,6 +114,13 @@ SplitView { SplitView.preferredHeight: 200 logsView.logText: logs.logText + + + CheckBox { + id: emptyModelCheck + text: "emptyModel" + checked: false + } } } diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusSeedPhraseInput.qml b/ui/StatusQ/src/StatusQ/Controls/StatusSeedPhraseInput.qml index 5e8f516479..9e4e969132 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusSeedPhraseInput.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusSeedPhraseInput.qml @@ -122,17 +122,22 @@ Item { property bool isInputValidWord: false } - StatusInput { - id: seedWordInput - - implicitWidth: parent.width - input.leftComponent: StatusBaseText { + Component { + id: seedInputLeftComponent + StatusBaseText { rightPadding: 6 text: root.leftComponentText color: seedWordInput.input.edit.activeFocus ? Theme.palette.primaryColor1 : Theme.palette.baseColor1 font.pixelSize: 15 } + } + + StatusInput { + id: seedWordInput + + implicitWidth: parent.width + input.leftComponent: seedInputLeftComponent input.acceptReturn: true onTextChanged: { d.isInputValidWord = false diff --git a/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseDirtyState.qml b/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseDirtyState.qml index a06cc26a43..1ace22b483 100644 --- a/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseDirtyState.qml +++ b/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseDirtyState.qml @@ -21,6 +21,13 @@ QObject { property alias sourceModel: joined.leftModel property alias showcaseModel: joined.rightModel + /** + * True if the showcase model is in single model mode, i.e. the showcase + * model is part of the source model. False if the showcase model is a + * separate model. + */ + property bool singleModelMode: !joined.rightModel + /** * Model holding elements from 'sourceModel' intended to be visible in the * showcase, sorted by 'position' role. Includes roles from both input models. @@ -48,8 +55,8 @@ QObject { writable.revert() } - function currentState() { - return writable.currentState() + function currentState(roleNames = []) { + return writable.currentState(roleNames) } function setVisibility(key, visibility) { @@ -67,11 +74,25 @@ QObject { writableIndexes.push(visibleSFPM.mapToSource(newOrder[i])) } - for (var j = 0; j < newOrder.length; j++) { - writable.set(writableIndexes[j], { "showcasePosition": j}) + for (var i = 0; i < newOrder.length; i++) { + writable.set(writableIndexes[i], { "showcasePosition": i}) } } + function append(obj) { + writable.append(obj) + } + + function remove(index) { + const writableIndex = d.visibleIndexToWritable(index) + writable.remove(writableIndex) + } + + function update(index, obj) { + const writableIndex = d.visibleIndexToWritable(index) + writable.set(writableIndex, obj) + } + // internals, debug purpose only readonly property alias writable_: writable readonly property alias joined_: joined @@ -96,7 +117,7 @@ QObject { VisibilityAndPositionDirtyStateModel { id: writable - sourceModel: joined + sourceModel: root.singleModelMode ? root.sourceModel : joined visibilityHidden: Constants.ShowcaseVisibility.NoOne } @@ -140,4 +161,15 @@ QObject { filters: HiddenFilter {} } + + QtObject { + id: d + + function visibleIndexToWritable(index) { + const newOrder = visible.order() + const sfpmIndex = newOrder[index] + + return visibleSFPM.mapToSource(sfpmIndex) + } + } } diff --git a/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModelAdapter.qml b/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModelAdapter.qml index eef90b8701..8d24899354 100644 --- a/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModelAdapter.qml +++ b/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModelAdapter.qml @@ -1,10 +1,13 @@ import QtQml 2.15 +import QtQml.Models 2.15 import StatusQ 0.1 import StatusQ.Core.Utils 0.1 import SortFilterProxyModel 0.2 +import utils 1.0 + QObject { id: root @@ -32,6 +35,11 @@ QObject { readonly property alias adaptedCollectiblesSourceModel: collectiblesSFPM readonly property alias adaptedCollectiblesShowcaseModel: collectiblesRenamingShowcase + // Social links input models + property alias socialLinksSourceModel: socialLinksRoleRenaming.sourceModel + + // adapted models + readonly property alias adaptedSocialLinksSourceModel: socialLinksSFPM SortFilterProxyModel { id: communitySFPM @@ -192,4 +200,32 @@ QObject { } ] } -} \ No newline at end of file + + RolesRenamingModel { + id: socialLinksRoleRenaming + mapping: [ + RoleRename { + from: "uuid" + to: "showcaseKey" + } + ] + } + + SortFilterProxyModel { + id: socialLinksSFPM + sourceModel: socialLinksRoleRenaming + proxyRoles: [ + FastExpressionRole { + name: "showcasePosition" + expression: index + }, + FastExpressionRole { + name: "showcaseVisibility" + expression: getShowcaseVisibility() + function getShowcaseVisibility() { + return Constants.ShowcaseVisibility.Everyone + } + } + ] + } +} diff --git a/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModels.qml b/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModels.qml index 52f5d89887..be654e23a9 100644 --- a/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModels.qml +++ b/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModels.qml @@ -11,12 +11,13 @@ QObject { id: root // GENERAL - readonly property bool dirty: communities.dirty || accounts.dirty || collectibles.dirty + readonly property bool dirty: communities.dirty || accounts.dirty || collectibles.dirty || socialLinks.dirty function revert() { communities.revert() accounts.revert() collectibles.revert() + socialLinks.revert() } // COMMUNITIES @@ -98,14 +99,43 @@ QObject { collectibles.changePosition(from, to) } + // SOCIAL LINKS + + // Input models + property alias socialLinksSourceModel: modelAdapter.socialLinksSourceModel + + // Output models + readonly property alias socialLinksVisibleModel: socialLinks.visibleModel + + // Methods + function appendSocialLink(obj) { + socialLinks.append(obj) + } + + function updateSocialLink(index, obj) { + socialLinks.update(index, obj) + } + + function removeSocialLink(index) { + socialLinks.remove(index) + } + + function changeSocialLinkPosition(from, to) { + socialLinks.changePosition(from, to) + } + + function socialLinksCurrentState(roleNames) { + return socialLinks.currentState(roleNames) + } + // The complete preferences models json current state: function buildJSONModelsCurrentState() { return JSON.stringify({ "communities": communitiesCurrentState(), "accounts": accountsCurrentState(), - "collectibles": collectiblesCurrentState() + "collectibles": collectiblesCurrentState(), + "socialLinks": socialLinksCurrentState(["url", "text", "showcaseVisibility", "showcasePosition"]) // TODO: Assets --> Issue #13492 - // TODO: Web --> Issue #13495 }) } @@ -164,6 +194,14 @@ QObject { } } + ProfileShowcaseDirtyState { + id: socialLinks + + sourceModel: modelAdapter.adaptedSocialLinksSourceModel + singleModelMode: true + } + + SortFilterProxyModel { id: collectiblesFilter diff --git a/ui/app/AppLayouts/Profile/helpers/VisibilityAndPositionDirtyStateModel.qml b/ui/app/AppLayouts/Profile/helpers/VisibilityAndPositionDirtyStateModel.qml index cd132b6134..01ce253717 100644 --- a/ui/app/AppLayouts/Profile/helpers/VisibilityAndPositionDirtyStateModel.qml +++ b/ui/app/AppLayouts/Profile/helpers/VisibilityAndPositionDirtyStateModel.qml @@ -31,8 +31,8 @@ WritableProxyModel { * * The entries with visibility 0 (hidden) are not included in the list. */ - function currentState() { - const visible = d.getVisibleEntries() + function currentState(roleNames) { + const visible = d.getVisibleEntries(roleNames) const minPos = Math.min(...visible.map(e => e.showcasePosition)) return visible.map(e => { e.showcasePosition -= minPos; return e }) @@ -75,9 +75,14 @@ WritableProxyModel { return ModelUtils.indexOf(root, "showcaseKey", key) } - function getVisibleEntries() { - const roles = ["showcaseKey", "showcasePosition", "showcaseVisibility"] - const keysAndPos = ModelUtils.modelToArray(root, roles) + function getVisibleEntries(roleNames = ["showcaseKey", "showcasePosition", "showcaseVisibility"]) { + if (roleNames.length === 0) + roleNames = ["showcaseKey", "showcasePosition", "showcaseVisibility"] + + if (!roleNames.includes("showcaseVisibility")) + roleNames.push("showcaseVisibility") + + const keysAndPos = ModelUtils.modelToArray(root, roleNames) return keysAndPos.filter(p => p.showcaseVisibility && p.showcaseVisibility !== root.visibilityHidden) diff --git a/ui/app/AppLayouts/Profile/panels/ProfileSocialLinksPanel.qml b/ui/app/AppLayouts/Profile/panels/ProfileSocialLinksPanel.qml index 6895c36cc2..9129827157 100644 --- a/ui/app/AppLayouts/Profile/panels/ProfileSocialLinksPanel.qml +++ b/ui/app/AppLayouts/Profile/panels/ProfileSocialLinksPanel.qml @@ -3,6 +3,7 @@ import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import StatusQ.Core 0.1 +import StatusQ.Core.Utils 0.1 import StatusQ.Controls 0.1 import StatusQ.Components 0.1 import StatusQ.Core.Theme 0.1 @@ -17,27 +18,40 @@ import SortFilterProxyModel 0.2 Control { id: root - - property var profileStore + property var socialLinksModel property int showcaseLimit: 20 background: null + signal addSocialLink(string url, string text) + signal updateSocialLink(int index, string url, string text) + signal removeSocialLink(int index) + signal changePosition(int from, int to) + + QtObject { + id: d + + function containsSocialLink(text, url) { + return ModelUtils.contains(socialLinksModel, "text", text, Qt.CaseInsensitive) && + ModelUtils.contains(socialLinksModel, "url", url, Qt.CaseInsensitive) + } + } + Component { id: addSocialLinkModalComponent AddSocialLinkModal { - containsSocialLink: root.profileStore.containsSocialLink - onAddLinkRequested: root.profileStore.createLink(linkText, linkUrl, linkType, linkIcon) + containsSocialLink: d.containsSocialLink + onAddLinkRequested: root.addSocialLink(linkUrl, linkText) } } Component { id: modifySocialLinkModal ModifySocialLinkModal { - containsSocialLink: root.profileStore.containsSocialLink - onUpdateLinkRequested: root.profileStore.updateLink(uuid, linkText, linkUrl) - onRemoveLinkRequested: root.profileStore.removeLink(uuid) + containsSocialLink: d.containsSocialLink + onUpdateLinkRequested: root.updateSocialLink(index, linkUrl, linkText) + onRemoveLinkRequested: root.removeSocialLink(index) } } @@ -54,7 +68,7 @@ Control { } Item { Layout.fillWidth: true } StatusBaseText { - text: qsTr("%1 / %2").arg(root.profileStore.temporarySocialLinksModel.count).arg(root.showcaseLimit) + text: qsTr("%1 / %2").arg(linksView.count).arg(root.showcaseLimit) color: Theme.palette.baseColor1 font.pixelSize: Theme.tertiaryTextFontSize } @@ -62,7 +76,7 @@ Control { // empty placeholder when no links; dashed rounded rectangle ShapeRectangle { - readonly property bool maxReached: root.profileStore.temporarySocialLinksModel.count === root.showcaseLimit + readonly property bool maxReached: linksView.count === root.showcaseLimit Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: parent.width - 4 // the rectangular path is rendered outside @@ -115,18 +129,16 @@ Control { const to = draggableDelegate.visualIndex if (to === from) return - root.profileStore.moveLink(from, to, 1) + root.changePosition(from, to) drag.accept() } - onDropped: function(drop) { - root.profileStore.saveSocialLinks(true /*silent*/) - } - StatusDraggableListItem { id: draggableDelegate - readonly property string asideText: ProfileUtils.stripSocialLinkPrefix(model.url, model.linkType) + readonly property string asideText: ProfileUtils.stripSocialLinkPrefix(model.url, draggableDelegate.linkType) + readonly property int linkType: ProfileUtils.linkTextToType(model.text) + readonly property string iconName: ProfileUtils.linkTypeToIcon(draggableDelegate.linkType) visible: !!asideText width: parent.width @@ -142,11 +154,11 @@ Control { dragParent: linksView visualIndex: delegateRoot.visualIndex draggable: linksView.count > 1 - title: ProfileUtils.linkTypeToShortText(model.linkType) || model.text + title: ProfileUtils.linkTypeToShortText(draggableDelegate.linkType) || model.text hasIcon: true - icon.name: model.icon - icon.color: ProfileUtils.linkTypeColor(model.linkType) - assetBgColor: ProfileUtils.linkTypeBgColor(model.linkType) + icon.name: draggableDelegate.iconName + icon.color: ProfileUtils.linkTypeColor(draggableDelegate.linkType) + assetBgColor: ProfileUtils.linkTypeBgColor(draggableDelegate.linkType) actions: [ StatusLinkText { Layout.fillWidth: true @@ -167,7 +179,7 @@ Control { type: StatusFlatRoundButton.Type.Tertiary tooltip.text: qsTr("Edit link") onClicked: Global.openPopup(modifySocialLinkModal, - {linkType: model.linkType, icon: model.icon, uuid: model.uuid, + {linkType: draggableDelegate.linkType, icon: draggableDelegate.iconName, index: delegateRoot.visualIndex, linkText: model.text, linkUrl: draggableDelegate.asideText}) } ] diff --git a/ui/app/AppLayouts/Profile/popups/ModifySocialLinkModal.qml b/ui/app/AppLayouts/Profile/popups/ModifySocialLinkModal.qml index d197c3b7a3..f046f75087 100644 --- a/ui/app/AppLayouts/Profile/popups/ModifySocialLinkModal.qml +++ b/ui/app/AppLayouts/Profile/popups/ModifySocialLinkModal.qml @@ -21,12 +21,12 @@ StatusDialog { property int linkType: -1 property string icon - property string uuid + property int index property string linkText property string linkUrl - signal updateLinkRequested(string uuid, string linkText, string linkUrl) - signal removeLinkRequested(string uuid) + signal updateLinkRequested(string index, string linkText, string linkUrl) + signal removeLinkRequested(string index) implicitWidth: 480 // design @@ -38,7 +38,7 @@ StatusDialog { type: StatusButton.Danger text: qsTr("Delete") onClicked: { - root.removeLinkRequested(root.uuid) + root.removeLinkRequested(root.index) root.close() } } @@ -48,7 +48,7 @@ StatusDialog { text: qsTr("Update") enabled: linkTarget.valid && (!customTitle.visible || customTitle.valid) onClicked: { - root.updateLinkRequested(root.uuid, customTitle.text, ProfileUtils.addSocialLinkPrefix(linkTarget.text, root.linkType)) + root.updateLinkRequested(root.index, customTitle.text, ProfileUtils.addSocialLinkPrefix(linkTarget.text, root.linkType)) root.close() } } diff --git a/ui/app/AppLayouts/Profile/views/MyProfileView.qml b/ui/app/AppLayouts/Profile/views/MyProfileView.qml index e12f69d93a..f2d6afca4c 100644 --- a/ui/app/AppLayouts/Profile/views/MyProfileView.qml +++ b/ui/app/AppLayouts/Profile/views/MyProfileView.qml @@ -140,6 +140,8 @@ SettingsContentBase { collectiblesSourceModel: root.profileStore.collectiblesModel collectiblesShowcaseModel: root.profileStore.profileShowcaseCollectiblesModel collectiblesSearcherText: profileShowcaseCollectiblesPanel.searcherText + + socialLinksSourceModel: root.profileStore.socialLinksModel } function reset() { @@ -290,9 +292,24 @@ SettingsContentBase { // web ProfileSocialLinksPanel { - profileStore: root.profileStore - socialLinksModel: root.profileStore.temporarySocialLinksModel showcaseLimit: root.profileStore.getProfileShowcaseSocialLinksLimit() + socialLinksModel: priv.showcaseModels.socialLinksVisibleModel + + onAddSocialLink: function(url, text) { + priv.showcaseModels.appendSocialLink({ showcaseKey: "", text: text, url: url }) + } + + onUpdateSocialLink: function(index, url, text) { + priv.showcaseModels.updateSocialLink(index, { text: text, url: url }) + } + + onRemoveSocialLink: function(index) { + priv.showcaseModels.removeSocialLink(index) + } + + onChangePosition: function(from, to) { + priv.showcaseModels.changeSocialLinkPosition(from, to) + } } Component { diff --git a/ui/imports/utils/ProfileUtils.qml b/ui/imports/utils/ProfileUtils.qml index 4510c7bec6..931ff40639 100644 --- a/ui/imports/utils/ProfileUtils.qml +++ b/ui/imports/utils/ProfileUtils.qml @@ -78,6 +78,16 @@ QtObject { return Constants.socialLinkType.custom } + function linkTypeToIcon(linkType) { + if (linkType === Constants.socialLinkType.twitter) return "twitter" + if (linkType === Constants.socialLinkType.personalSite) return "language" + if (linkType === Constants.socialLinkType.github) return "github" + if (linkType === Constants.socialLinkType.youtube) return "youtube" + if (linkType === Constants.socialLinkType.discord) return "discord" + if (linkType === Constants.socialLinkType.telegram) return "telegram" + return "link" + } + // showcase function visibilityIcon(showcaseVisibility) { switch (showcaseVisibility) {