From 5c0f1981ada840644081fb1ecf1436d55f7ece40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Tue, 28 Feb 2023 16:00:10 +0100 Subject: [PATCH] feat: Profile Showcase: Proof of concept for own Profile Dialog - the goal of this PR is to get some bsais UI building blocks done for the followup PRs - the order of showcase tabs now is: Communities/Accounts/Collectibles/Assets - there will be further changes to accomodate for different types of backend models as those get developed (for other users' profiles) Fixes #9664 --- storybook/figma.json | 6 +- storybook/pages/ProfileDialogViewPage.qml | 226 +++++++++- .../StatusQ/Components/StatusToastMessage.qml | 2 +- .../Controls/StatusAccountSelector.qml | 4 +- .../src/StatusQ/Controls/StatusToolTip.qml | 9 +- .../CommunityPermissionsSettingsPanel.qml | 3 +- .../stores/CommunitiesStore.qml | 2 +- ui/app/AppLayouts/Profile/ProfileLayout.qml | 1 + ui/app/AppLayouts/Profile/controls/qmldir | 2 + .../Profile/views/MyProfileView.qml | 3 + .../views/profile/MyProfilePreview.qml | 1 + .../views/profile/MyProfileSettingsView.qml | 9 +- ui/app/AppLayouts/Wallet/stores/RootStore.qml | 8 + .../AppLayouts/Wallet/views/LeftTabView.qml | 4 +- ui/app/mainui/AppMain.qml | 13 +- ui/app/mainui/Popups.qml | 1 + .../shared/panels/StatusAssetSelector.qml | 2 - ui/imports/shared/popups/ProfileDialog.qml | 2 + ui/imports/shared/views/AssetsView.qml | 3 +- ui/imports/shared/views/ProfileDialogView.qml | 71 ++-- .../shared/views/chat/LinksMessageView.qml | 1 + .../views/profile/ProfileShowcaseView.qml | 395 ++++++++++++++++++ ui/imports/shared/views/profile/qmldir | 1 + ui/imports/utils/Global.qml | 3 + 24 files changed, 709 insertions(+), 63 deletions(-) create mode 100644 ui/app/AppLayouts/Profile/controls/qmldir create mode 100644 ui/imports/shared/views/profile/ProfileShowcaseView.qml create mode 100644 ui/imports/shared/views/profile/qmldir diff --git a/storybook/figma.json b/storybook/figma.json index 9003ae8cd5..78a4c6134c 100644 --- a/storybook/figma.json +++ b/storybook/figma.json @@ -67,7 +67,11 @@ "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=682%3A17655", "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=682%3A17087", "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=4%3A23525", - "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=4%3A23932" + "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=4%3A23932", + "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=4%3A23932&t=h8DUW6Eysawqe5u0-0", + "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=724%3A15511&t=h8DUW6Eysawqe5u0-0", + "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=6%3A16845&t=h8DUW6Eysawqe5u0-0", + "https://www.figma.com/file/ibJOTPlNtIxESwS96vJb06/%F0%9F%91%A4-Profile-%7C-Desktop?node-id=4%3A25437&t=h8DUW6Eysawqe5u0-0" ], "StatusCommunityCard": [ "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=8159%3A416159", diff --git a/storybook/pages/ProfileDialogViewPage.qml b/storybook/pages/ProfileDialogViewPage.qml index dee299078a..64169a9c46 100644 --- a/storybook/pages/ProfileDialogViewPage.qml +++ b/storybook/pages/ProfileDialogViewPage.qml @@ -2,13 +2,15 @@ import QtQuick 2.14 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 -import Storybook 1.0 - import utils 1.0 import shared.views 1.0 import mainui 1.0 import StatusQ 0.1 +import StatusQ.Core.Utils 0.1 as CoreUtils + +import Storybook 1.0 +import Models 1.0 SplitView { id: root @@ -55,7 +57,7 @@ SplitView { alias: "Mock Alias Triplet", lastUpdated: Date.now(), lastUpdatedLocally: Date.now(), - localNickname: "MockNickname", + localNickname: localNickname.text, thumbnailImage: "", largeImage: userImage.checked ? Style.png("status-logo") : "", isContact: isContact.checked, @@ -74,8 +76,8 @@ SplitView { text: "__twitter", url: "https://twitter.com/ethstatus", icon: "twitter" - }, - { + }, + { text: "__github", url: "https://github.com/status-im", icon: "github" @@ -122,6 +124,8 @@ SplitView { publicKey: switchOwnProfile.checked ? "0xdeadbeef" : "0xrandomguy" + onCloseRequested: logs.logEvent("closeRequested()") + profileStore: QtObject { readonly property string pubkey: "0xdeadbeef" readonly property string ensName: name.text @@ -169,6 +173,209 @@ SplitView { logs.logEvent("contactsStore::verifiedUntrustworthy", ["publicKey"], arguments) } } + + communitiesModel: ListModel { + ListElement { + name: "Not the cool gang" + amISectionAdmin: false + description: "Nothing to write home about" + color: "indigo" + image: "" + joined: true + members: [ + ListElement { displayName: "Joe" } + ] + } + ListElement { + name: "Awesome bunch" + amISectionAdmin: true + description: "Where the cool guys hang out & Nothing to write home about" + color: "green" + image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAlklEQVR4nOzW0QmDQBAG4SSkl7SUQlJGCrElq9F3QdjjVhh/5nv3cFhY9vUIYQiNITSG0BhCExPynn1gWf9bx498P7/ + nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2ImYgiNITTlTdG1nUZ5a92VITQxITFiJmIIjSE0htAYQrMHAAD//+wwFVpz+yqXAAAAAElFTkSuQmCC" + joined: true + members: [ + ListElement { displayName: "Alex" }, + ListElement { displayName: "AlexJb" }, + ListElement { displayName: "Michal" }, + ListElement { displayName: "Noelia" }, + ListElement { displayName: "Lukáš" } + ] + } + ListElement { + name: "Invisible community (should not display!)" + amISectionAdmin: false + description: "Get outta here" + color: "red" + image: "" + joined: false + members: [] + } + } + + walletStore: QtObject { + function switchAccountByAddress(address) { + logs.logEvent("walletStore::switchAccountByAddress", ["address"], arguments) + } + + function selectCollectible(slug, id) { + logs.logEvent("walletStore::selectCollectible", ["slug", "id"], arguments) + } + + readonly property var accounts: ListModel { + ListElement { + name: "My Status Account" + address: "0xcdc2ea3b6ba8fed3a3402f8db8b2fab53e7b7420" + color: "aliceblue" + emoji: "🇨🇿" + walletType: "" + } + ListElement { + name: "testing (no emoji, colored, saved, seed)" + address: "0xcdc2ea3b6ba8fed3a3402f8db8b2fab53e7b7000" + color: "olive" + walletType: "seed" + } + ListElement { + name: "My Bro's Account" + address: "0xcdc2ea3b6ba8fed3a3402f8db8b2fab53e7b7421" + color: "ghostwhite" + emoji: "🇸🇰" + walletType: "watch" + } + ListElement { + name: "Keycard" + address: "0xdeadbeef" + color: "red" + emoji: "" + walletType: "key" + } + } + + function getNameForSavedWalletAddress(address) { + return CoreUtils.ModelUtils.getByKey(savedAddresses, "address", address, "name") ?? "" + } + + function createOrUpdateSavedAddress(name, address, favourite) { + logs.logEvent("walletStore::createOrUpdateSavedAddress", ["name", "address", "favourite"], arguments) + savedAddresses.append({name, address, favourite, ens: false}) + return "" // no error + } + + readonly property var savedAddresses: ListModel { + ListElement { + name: "My Status Saved Account" + address: "0xcdc2ea3b6ba8fed3a3402f8db8b2fab53e7b7000" + favourite: true + ens: false + } + } + + readonly property var currentAccount: QtObject { + readonly property var assets: ListModel { + readonly property var data: [ + { + symbol: "MANA", + enabledNetworkBalance: { + amount: 301, + symbol: "MANA" + }, + changePct24hour: -2.1, + visibleForNetworkWithPositiveBalance: true + }, + { + symbol: "AAVE", + enabledNetworkBalance: { + amount: 23.3, + symbol: "AAVE" + }, + changePct24hour: 4.56, + visibleForNetworkWithPositiveBalance: true + }, + { + symbol: "POLY", + enabledNetworkBalance: { + amount: 3590, + symbol: "POLY" + }, + changePct24hour: -11.6789, + visibleForNetworkWithPositiveBalance: true + }, + { + symbol: "CDT", + enabledNetworkBalance: { + amount: 1000, + symbol: "CDT" + }, + changePct24hour: 0, + visibleForNetworkWithPositiveBalance: true + }, + { + symbol: "MKR", + enabledNetworkBalance: { + amount: 1.3, + symbol: "MKR" + }, + //changePct24hour: undefined // NB 'undefined' on purpose + visibleForNetworkWithPositiveBalance: true + }, + { + symbol: "InvisibleHere", + enabledNetworkBalance: {}, + changePct24hour: 0, + visibleForNetworkWithPositiveBalance: false + } + ] + Component.onCompleted: append(data) + } + } + + readonly property var flatCollectibles: ListModel { + readonly property var data: [ + { + //id: 123, + name: "Crypto Kitties", + description: "Super Crypto Kitty", + backgroundColor: "", + imageUrl: ModelsData.collectibles.cryptoKitties, + permalink: "" + }, + { + id: 34545656768, + name: "Kitty 1", + description: "", + backgroundColor: "green", + imageUrl: ModelsData.collectibles.kitty1Big, + permalink: "" + }, + { + id: 123456, + name: "Kitty 2", + description: "", + backgroundColor: "", + imageUrl: ModelsData.collectibles.kitty2Big, + permalink: "" + }, + { + id: 12345645459537432, + name: "", + description: "Kitty 3 description", + backgroundColor: "oink", + imageUrl: ModelsData.collectibles.kitty3Big, + permalink: "" + }, + { + id: 691, + name: "KILLABEAR #691", + description: "Please note that weapons are not yet reflected in the rarity stats.", + backgroundColor: "#807c56", + imageUrl: "https://assets.killabears.com/content/killabears/img/691-e81f892696a8ae700e0dbc62eb072060679a2046d1ef5eb2671bdb1fad1f68e3.png", + permalink: "https://opensea.io/assets/ethereum/0xc99c679c50033bbc5321eb88752e89a93e9e83c5/691" + } + ] + Component.onCompleted: append(data) + } + } } } } @@ -192,6 +399,12 @@ SplitView { } RowLayout { Layout.fillWidth: true + Label { text: "localNickname:" } + TextField { + id: localNickname + text: "MockNickname" + placeholderText: "Local Nickname" + } Label { text: "displayName:" } TextField { id: displayName @@ -237,7 +450,7 @@ SplitView { TextField { id: name enabled: ensVerified.checked - text: ensVerified.checked ? "mock-ens-name" : "" + text: ensVerified.checked ? "mock-ens-name.eth" : "" placeholderText: "ENS name" } } @@ -290,6 +503,7 @@ SplitView { TextField { Layout.fillWidth: true id: bio + selectByMouse: true text: "Hi, I am Alex. I'm an indie developer who mainly works on web products. I worked for several different companies and created a couple of my own products from scratch. Currently building Telescope and Prepacked. diff --git a/ui/StatusQ/src/StatusQ/Components/StatusToastMessage.qml b/ui/StatusQ/src/StatusQ/Components/StatusToastMessage.qml index ad7815edbf..904fc0d747 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusToastMessage.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusToastMessage.qml @@ -140,7 +140,7 @@ Control { This signal is emitted when the ToastMessage contains a url and this url is clicked by the user. */ - signal linkActivated(var link) + signal linkActivated(string link) QtObject { id: d diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusAccountSelector.qml b/ui/StatusQ/src/StatusQ/Controls/StatusAccountSelector.qml index ed012d2a5a..86cb431510 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusAccountSelector.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusAccountSelector.qml @@ -70,7 +70,7 @@ Item { textSelectedAddress.text = selectedAccount.address } if (selectedAccount.currencyBalance) { - textSelectedAddressFiatBalance.text = selectedAccount.currencyBalance + " " + currency.toUpperCase() + textSelectedAddressFiatBalance.text = LocaleUtils.currencyAmountToLocaleString(selectedAccount.currencyBalance) } if (selectedAccount.assets && showBalanceForAssetSymbol) { assetFound = Utils.findAssetByChainAndSymbol(root.chainId, selectedAccount.assets, showBalanceForAssetSymbol) @@ -239,7 +239,7 @@ Item { id: txtFiatBalance Layout.rightMargin: 4 font.pixelSize: 15 - text: currencyBalance + text: LocaleUtils.currencyAmountToLocaleString(currencyBalance, {onlyAmount: true}) color: Theme.palette.directColor1 } StatusBaseText { diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml b/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml index a2cbbd677a..3761f979ca 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml @@ -18,11 +18,8 @@ ToolTip { property int offset: 0 property alias arrow: arrow - implicitWidth: Math.min(maxWidth, contentItem.implicitWidth + 16) - leftPadding: 8 - rightPadding: 8 - topPadding: 8 - bottomPadding: 8 + implicitWidth: Math.min(maxWidth, implicitContentWidth + 16) + padding: 8 delay: 200 background: Item { id: statusToolTipBackground @@ -73,6 +70,6 @@ ToolTip { font.weight: Font.Medium horizontalAlignment: Text.AlignHCenter bottomPadding: 8 + textFormat: Text.RichText } } - diff --git a/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml index 003876c3bf..c4d4466218 100644 --- a/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml +++ b/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml @@ -71,8 +71,7 @@ SettingsPageLayout { } } - readonly property string initialState: root.permissionsModel.count > 0 - ? d.permissionsViewState : d.welcomeViewState + readonly property string initialState: d.permissionsExist ? d.permissionsViewState : d.welcomeViewState function initializeData() { holdingsToEditModel = emptyModel diff --git a/ui/app/AppLayouts/CommunitiesPortal/stores/CommunitiesStore.qml b/ui/app/AppLayouts/CommunitiesPortal/stores/CommunitiesStore.qml index 68210c62b8..3e86f7a5b8 100644 --- a/ui/app/AppLayouts/CommunitiesPortal/stores/CommunitiesStore.qml +++ b/ui/app/AppLayouts/CommunitiesPortal/stores/CommunitiesStore.qml @@ -101,7 +101,7 @@ QtObject { } function setActiveCommunity(communityId) { - mainModule.setActiveSectionById(communityId); + root.mainModuleInst.setActiveSectionById(communityId); } function navigateToCommunity(communityId) { diff --git a/ui/app/AppLayouts/Profile/ProfileLayout.qml b/ui/app/AppLayouts/Profile/ProfileLayout.qml index 577f0a1b4b..3a54895fbd 100644 --- a/ui/app/AppLayouts/Profile/ProfileLayout.qml +++ b/ui/app/AppLayouts/Profile/ProfileLayout.qml @@ -107,6 +107,7 @@ StatusSectionLayout { profileStore: root.store.profileStore privacyStore: root.store.privacyStore contactsStore: root.store.contactsStore + communitiesModel: root.store.communitiesList sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.profile) contentWidth: d.contentWidth } diff --git a/ui/app/AppLayouts/Profile/controls/qmldir b/ui/app/AppLayouts/Profile/controls/qmldir new file mode 100644 index 0000000000..7ba3436b2b --- /dev/null +++ b/ui/app/AppLayouts/Profile/controls/qmldir @@ -0,0 +1,2 @@ +CommunityDelegate 1.0 CommunityDelegate.qml +WalletAccountDelegate 1.0 WalletAccountDelegate.qml diff --git a/ui/app/AppLayouts/Profile/views/MyProfileView.qml b/ui/app/AppLayouts/Profile/views/MyProfileView.qml index 0c0ddc5c2f..13868f2e50 100644 --- a/ui/app/AppLayouts/Profile/views/MyProfileView.qml +++ b/ui/app/AppLayouts/Profile/views/MyProfileView.qml @@ -25,6 +25,7 @@ SettingsContentBase { property ProfileStore profileStore property PrivacyStore privacyStore property ContactsStore contactsStore + property var communitiesModel titleRowComponentLoader.sourceComponent: StatusButton { objectName: "profileSettingsChangePasswordButton" @@ -72,6 +73,7 @@ SettingsContentBase { profileStore: root.profileStore privacyStore: root.privacyStore walletStore: root.walletStore + communitiesModel: root.communitiesModel onVisibleChanged: if (visible) stackLayout.Layout.preferredHeight = settingsView.implicitHeight Component.onCompleted: stackLayout.Layout.preferredHeight = Qt.binding(() => settingsView.implicitHeight) @@ -82,6 +84,7 @@ SettingsContentBase { profileStore: root.profileStore contactsStore: root.contactsStore + communitiesModel: root.communitiesModel dirtyValues: settingsView.dirtyValues dirty: settingsView.dirty diff --git a/ui/app/AppLayouts/Profile/views/profile/MyProfilePreview.qml b/ui/app/AppLayouts/Profile/views/profile/MyProfilePreview.qml index 8c851f7358..0d1f8d8714 100644 --- a/ui/app/AppLayouts/Profile/views/profile/MyProfilePreview.qml +++ b/ui/app/AppLayouts/Profile/views/profile/MyProfilePreview.qml @@ -8,6 +8,7 @@ import StatusQ.Core.Theme 0.1 Item { property alias profileStore: profilePreview.profileStore property alias contactsStore: profilePreview.contactsStore + property alias communitiesModel: profilePreview.communitiesModel property alias dirtyValues: profilePreview.dirtyValues property alias dirty: profilePreview.dirty diff --git a/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml b/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml index 1006c4d6b8..8852f85c45 100644 --- a/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml +++ b/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml @@ -26,6 +26,7 @@ ColumnLayout { property PrivacyStore privacyStore property ProfileStore profileStore property WalletStore walletStore + property var communitiesModel property QtObject dirtyValues: QtObject { property string displayName: descriptionPanel.displayName.text @@ -158,7 +159,7 @@ ColumnLayout { StatusListItem { Layout.fillWidth: true - visible: Qt.platform.os == "osx" + visible: Qt.platform.os === Constants.mac title: qsTr("Biometric login and transaction authentication") asset.name: "touch-id" components: [ StatusSwitch { @@ -218,13 +219,13 @@ ColumnLayout { Repeater { id: communitiesRepeater - model: communitiesModule.model + model: root.communitiesModel CommunityDelegate { width: parent.width visible: joined community: model - enabled: false + sensor.enabled: false } } } @@ -246,7 +247,7 @@ ColumnLayout { width: parent.width account: model showShevronIcon: false - enabled: false + sensor.enabled: false } } } diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index 2b2cc77c0e..90f85ba7eb 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -141,6 +141,10 @@ QtObject { walletSection.switchAccount(newIndex) } + function switchAccountByAddress(address) { + walletSection.switchAccountByAddress(address) + } + function generateNewAccount(password, accountName, color, emoji, path, derivedFrom) { return walletSectionAccounts.generateNewAccount(password, accountName, color, emoji, path, derivedFrom) } @@ -198,6 +202,10 @@ QtObject { walletSectionCurrentCollectible.update(slug, id) } + function getNameForSavedWalletAddress(address) { + return walletSectionSavedAddresses.getNameByAddress(address) + } + function createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) { return walletSectionSavedAddresses.createOrUpdateSavedAddress(name, address, favourite, chainShortNames, ens) } diff --git a/ui/app/AppLayouts/Wallet/views/LeftTabView.qml b/ui/app/AppLayouts/Wallet/views/LeftTabView.qml index 3ead4045a0..f569780729 100644 --- a/ui/app/AppLayouts/Wallet/views/LeftTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/LeftTabView.qml @@ -169,9 +169,9 @@ Rectangle { components: [ StatusIcon { icon: { - if (model.walletType == "watch") + if (model.walletType === Constants.watchWalletType) return "show" - else if (model.walletType == "key") + if (model.walletType === Constants.keyWalletType) return "keycard" return "" diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index a4527cd7bd..a6ebe48830 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -163,6 +163,14 @@ Item { Global.settingsSubsection = subsection; } } + + function onOpenSendModal(address: string) { + sendModal.open(address) + } + + function onSwitchToCommunity(communityId: string) { + communitiesPortalLayoutContainer.communitiesStore.setActiveCommunity(communityId) + } } function changeAppSectionBySectionId(sectionId) { @@ -1119,7 +1127,10 @@ Item { this.open = false } onLinkActivated: { - Qt.openUrlExternally(link); + if (link.startsWith("#")) // internal link to section + globalConns.onAppSectionBySectionTypeChanged(link.substring(1)) + else + Global.openLink(link) } onClose: { diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index f250eb8d05..81cff2e5e5 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -287,6 +287,7 @@ QtObject { id: profilePopup profileStore: rootStore.profileSectionStore.profileStore contactsStore: rootStore.profileSectionStore.contactsStore + communitiesModel: rootStore.profileSectionStore.communitiesList onClosed: { if (profilePopup.parentPopup) { diff --git a/ui/imports/shared/panels/StatusAssetSelector.qml b/ui/imports/shared/panels/StatusAssetSelector.qml index d18b2c6736..f08bcac269 100644 --- a/ui/imports/shared/panels/StatusAssetSelector.qml +++ b/ui/imports/shared/panels/StatusAssetSelector.qml @@ -14,8 +14,6 @@ import StatusQ.Core.Backpressure 1.0 import shared.controls 1.0 import utils 1.0 -import utils 1.0 - import "../controls" Item { diff --git a/ui/imports/shared/popups/ProfileDialog.qml b/ui/imports/shared/popups/ProfileDialog.qml index 044a10cd95..9db8b7783e 100644 --- a/ui/imports/shared/popups/ProfileDialog.qml +++ b/ui/imports/shared/popups/ProfileDialog.qml @@ -13,6 +13,7 @@ StatusDialog { property var profileStore property var contactsStore + property var communitiesModel width: 640 padding: 0 @@ -24,6 +25,7 @@ StatusDialog { publicKey: root.publicKey profileStore: root.profileStore contactsStore: root.contactsStore + communitiesModel: root.communitiesModel onCloseRequested: root.close() } } diff --git a/ui/imports/shared/views/AssetsView.qml b/ui/imports/shared/views/AssetsView.qml index 15f626bc5c..f2a00df573 100644 --- a/ui/imports/shared/views/AssetsView.qml +++ b/ui/imports/shared/views/AssetsView.qml @@ -32,9 +32,8 @@ Item { id: assetListView objectName: "assetViewStatusListView" anchors.fill: parent - model: RootStore.tokensLoading ? 25 : filteredModel + model: RootStore.tokensLoading ? Constants.dummyModelItems : filteredModel delegate: RootStore.tokensLoading ? loadingTokenDelegate : tokenDelegate - ScrollBar.vertical: StatusScrollBar {} } SortFilterProxyModel { diff --git a/ui/imports/shared/views/ProfileDialogView.qml b/ui/imports/shared/views/ProfileDialogView.qml index e7404d8bef..81c78b1648 100644 --- a/ui/imports/shared/views/ProfileDialogView.qml +++ b/ui/imports/shared/views/ProfileDialogView.qml @@ -1,7 +1,7 @@ -import QtQuick 2.14 -import QtQuick.Controls 2.14 -import QtQuick.Layouts 1.14 -import QtGraphicalEffects 1.14 +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtGraphicalEffects 1.15 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 @@ -9,6 +9,7 @@ import StatusQ.Controls 0.1 import StatusQ.Components 0.1 import StatusQ.Popups 0.1 import StatusQ.Popups.Dialog 0.1 +import StatusQ.Core.Utils 0.1 as StatusQUtils import utils 1.0 import shared.controls 1.0 @@ -16,16 +17,23 @@ import shared.panels 1.0 import shared.popups 1.0 import shared.controls.chat 1.0 import shared.controls.chat.menuItems 1.0 +import shared.views.profile 1.0 + +import SortFilterProxyModel 0.2 + +import AppLayouts.Wallet.stores 1.0 as WalletNS Pane { id: root - property bool readOnly + property bool readOnly // inside settings/profile/preview property string publicKey: contactsStore.myPublicKey property var profileStore property var contactsStore + property var walletStore: WalletNS.RootStore + property var communitiesModel property QtObject dirtyValues: null property bool dirty: false @@ -547,8 +555,10 @@ Pane { StatusScrollView { id: scrollView + implicitWidth: contentWidth + leftPadding + rightPadding + implicitHeight: contentHeight + topPadding + bottomPadding Layout.fillWidth: true - Layout.preferredHeight: contentHeight + topPadding + bottomPadding + Layout.fillHeight: true Layout.leftMargin: -column.anchors.leftMargin Layout.rightMargin: -column.anchors.rightMargin Layout.topMargin: -column.spacing @@ -562,8 +572,7 @@ Pane { Layout.fillWidth: true Layout.leftMargin: column.anchors.leftMargin + Style.current.halfPadding Layout.rightMargin: column.anchors.rightMargin + Style.current.halfPadding - bio: root.dirty ? root.dirtyValues.bio - : d.contactDetails.bio + bio: root.dirty ? root.dirtyValues.bio : d.contactDetails.bio userSocialLinksJson: root.dirty ? root.profileStore.temporarySocialLinksJson : d.contactDetails.socialLinks } @@ -666,26 +675,17 @@ Pane { height: width mipmap: true smooth: false - source: root.profileStore.getQrCodeSource(root.profileStore.pubkey) + source: root.profileStore.getQrCodeSource(Utils.getCompressedPk(root.profileStore.pubkey)) } } } StatusTabBar { + id: showcaseTabBar Layout.fillWidth: true Layout.leftMargin: column.anchors.leftMargin Layout.rightMargin: column.anchors.rightMargin bottomPadding: -4 - - StatusTabButton { - leftPadding: 0 - width: implicitWidth - text: qsTr("Assets") - } - StatusTabButton { - width: implicitWidth - text: qsTr("Collectibles") - } StatusTabButton { width: implicitWidth text: qsTr("Communities") @@ -694,27 +694,32 @@ Pane { width: implicitWidth text: qsTr("Accounts") } + StatusTabButton { + width: implicitWidth + text: qsTr("Collectibles") + } + StatusTabButton { + leftPadding: 0 + width: implicitWidth + text: qsTr("Assets") + } } - StatusDialogBackground { + // Profile Showcase + ProfileShowcaseView { Layout.fillWidth: true Layout.topMargin: -column.spacing Layout.preferredHeight: 300 - color: Theme.palette.baseColor4 - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - height: parent.radius - color: parent.color - } + currentTabIndex: showcaseTabBar.currentIndex + isCurrentUser: d.isCurrentUser + mainDisplayName: d.mainDisplayName + readOnly: root.readOnly + profileStore: root.profileStore + walletStore: root.walletStore + communitiesModel: root.communitiesModel - StatusBaseText { - anchors.centerIn: parent - color: Theme.palette.baseColor1 - text: qsTr("More content to appear here soon...") - } + onCloseRequested: root.closeRequested() } } } diff --git a/ui/imports/shared/views/chat/LinksMessageView.qml b/ui/imports/shared/views/chat/LinksMessageView.qml index fc14096e79..323a4c4021 100644 --- a/ui/imports/shared/views/chat/LinksMessageView.qml +++ b/ui/imports/shared/views/chat/LinksMessageView.qml @@ -159,6 +159,7 @@ Column { anchors.centerIn: parent text: "GIF" font.pixelSize: 13 + color: "white" } } } diff --git a/ui/imports/shared/views/profile/ProfileShowcaseView.qml b/ui/imports/shared/views/profile/ProfileShowcaseView.qml new file mode 100644 index 0000000000..34d4599c0e --- /dev/null +++ b/ui/imports/shared/views/profile/ProfileShowcaseView.qml @@ -0,0 +1,395 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 +import StatusQ.Popups.Dialog 0.1 +import StatusQ.Core.Utils 0.1 as StatusQUtils + +import utils 1.0 +import shared.controls 1.0 // Timer + +import SortFilterProxyModel 0.2 + +Control { + id: root + + property alias currentTabIndex: stackLayout.currentIndex + property bool isCurrentUser + property string mainDisplayName + property bool readOnly + property var profileStore + property var walletStore + property var communitiesModel + + signal closeRequested() + + horizontalPadding: readOnly ? 20 : 40 // smaller in settings/preview + topPadding: Style.current.bigPadding + + QtObject { + id: d + + readonly property string copyLiteral: qsTr("Copy") + + readonly property var timer: Timer { + id: timer + } + } + + background: StatusDialogBackground { + color: Theme.palette.baseColor4 + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: parent.radius + color: parent.color + } + } + + contentItem: StackLayout { + id: stackLayout + + // communities + ColumnLayout { + StatusBaseText { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignCenter + visible: communitiesView.count == 0 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Theme.palette.directColor1 + text: qsTr("%1 hasn't joined any communities yet").arg(root.mainDisplayName) + } + + StatusGridView { + Layout.fillWidth: true + Layout.fillHeight: true + id: communitiesView + rightMargin: Style.current.halfPadding + cellWidth: (width-rightMargin)/2 + cellHeight: cellWidth/2 + visible: count + model: SortFilterProxyModel { + sourceModel: root.isCurrentUser ? root.communitiesModel : null // TODO show other users too + filters: ValueFilter { + roleName: "joined" + value: true + } + sorters: [ + RoleSorter { + roleName: "amISectionAdmin" + sortOrder: Qt.DescendingOrder // admin first + }, + StringSorter { + roleName: "name" + caseSensitivity: Qt.CaseInsensitive + } + ] + } + ScrollBar.vertical: StatusScrollBar { } + delegate: StatusListItem { // TODO custom delegate + width: GridView.view.cellWidth - Style.current.smallPadding + height: GridView.view.cellHeight - Style.current.smallPadding + title: model.name + statusListItemTitle.font.pixelSize: 17 + statusListItemTitle.font.bold: true + subTitle: model.description + tertiaryTitle: qsTr("%n member(s)", "", model.members.count) + asset.name: model.image ?? model.name + asset.isImage: asset.name.startsWith("data:image/") + asset.isLetterIdenticon: !model.image + asset.color: model.color + asset.width: 40 + asset.height: 40 + border.width: 1 + border.color: Theme.palette.baseColor2 + components: [ + StatusIcon { + visible: model.amISectionAdmin + anchors.verticalCenter: parent.verticalCenter + icon: "crown" + color: Theme.palette.directColor1 + } + ] + onClicked: { + if (root.readOnly) + return + root.closeRequested() + Global.switchToCommunity(model.id) + } + } + } + } + + // wallets/accounts + ColumnLayout { + StatusBaseText { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignCenter + visible: accountsView.count == 0 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Theme.palette.directColor1 + text: qsTr("%1 doesn't have any wallet accounts yet").arg(root.mainDisplayName) + } + + StatusListView { + Layout.fillWidth: true + Layout.fillHeight: true + id: accountsView + spacing: Style.current.halfPadding + visible: count + model: SortFilterProxyModel { + sourceModel: root.isCurrentUser ? root.walletStore.accounts : null // TODO show other users too + filters: ValueFilter { // everything except keycards + roleName: "walletType" + value: Constants.keyWalletType + inverted: true + } + } + delegate: StatusListItem { + id: accountDelegate + property bool saved: root.walletStore.getNameForSavedWalletAddress(model.address) !== "" + border.width: 1 + border.color: Theme.palette.baseColor2 + width: ListView.view.width + title: model.name + subTitle: StatusQUtils.Utils.elideText(model.address, 6, 4).replace("0x", "0×") + asset.color: model.color + asset.emoji: model.emoji ?? "" + asset.name: asset.emoji || "filled-account" + asset.isLetterIdenticon: asset.emoji + asset.letterSize: 14 + asset.bgColor: Theme.palette.primaryColor3 + asset.isImage: asset.emoji + components: [ + StatusIcon { + anchors.verticalCenter: parent.verticalCenter + visible: model.walletType === Constants.watchWalletType + icon: "show" + color: Theme.palette.directColor1 + }, + StatusFlatButton { + anchors.verticalCenter: parent.verticalCenter + size: StatusBaseButton.Size.Small + enabled: !accountDelegate.saved + text: accountDelegate.saved ? qsTr("Address saved") : qsTr("Save Address") + onClicked: { + accountDelegate.saved = root.walletStore.createOrUpdateSavedAddress(model.name, model.address, false) === "" + Global.displayToastMessage(qsTr("%1 saved to your wallet").arg(accountDelegate.subTitle), + qsTr("Go to your wallet"), + "wallet", + false, + Constants.ephemeralNotificationType.normal, + `#${Constants.appSection.wallet}` // internal link to wallet section + ) + } + }, + StatusFlatRoundButton { + anchors.verticalCenter: parent.verticalCenter + type: StatusFlatRoundButton.Type.Secondary + icon.name: "send" + tooltip.text: qsTr("Send") + onClicked: { + root.walletStore.switchAccountByAddress(model.address) + Global.openSendModal(model.address) + } + }, + StatusFlatRoundButton { + anchors.verticalCenter: parent.verticalCenter + type: StatusFlatRoundButton.Type.Secondary + icon.name: "copy" + tooltip.text: d.copyLiteral + onClicked: { + tooltip.text = qsTr("Copied") + root.profileStore.copyToClipboard(model.address) + d.timer.setTimeout(function() { + tooltip.text = d.copyLiteral + }, 2000); + } + } + ] + onClicked: { + if (root.readOnly) + return + root.walletStore.switchAccountByAddress(model.address) + } + } + } + } + + // collectibles/NFTs + ColumnLayout { + StatusBaseText { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignCenter + visible: collectiblesView.count == 0 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Theme.palette.directColor1 + text: qsTr("%1 doesn't have any collectibles/NFTs yet").arg(root.mainDisplayName) + } + + StatusGridView { + Layout.fillWidth: true + Layout.fillHeight: true + id: collectiblesView + rightMargin: Style.current.halfPadding + cellWidth: (width-rightMargin)/4 + cellHeight: cellWidth + visible: count + model: root.isCurrentUser ? root.walletStore.flatCollectibles : null // TODO show other users too + ScrollBar.vertical: StatusScrollBar { } + delegate: StatusRoundedImage { + width: GridView.view.cellWidth - Style.current.smallPadding + height: GridView.view.cellHeight - Style.current.smallPadding + border.width: 1 + border.color: Theme.palette.directColor7 + color: !!model.backgroundColor ? model.backgroundColor : "transparent" + radius: Style.current.radius + showLoadingIndicator: model.isLoading + image.fillMode: Image.PreserveAspectCrop + image.source: model.imageUrl ?? "" + + RowLayout { + anchors.left: parent.left + anchors.leftMargin: Style.current.halfPadding + anchors.bottom: parent.bottom + anchors.bottomMargin: Style.current.halfPadding + anchors.right: parent.right + anchors.rightMargin: Style.current.halfPadding + + Control { + Layout.maximumWidth: parent.width + horizontalPadding: Style.current.halfPadding + verticalPadding: Style.current.halfPadding/2 + visible: !!model.id + + background: Rectangle { + radius: Style.current.halfPadding/2 + color: Theme.palette.indirectColor2 + } + contentItem: StatusBaseText { + font.pixelSize: 13 + font.weight: Font.Medium + maximumLineCount: 1 + elide: Text.ElideRight + text: `#${model.id}` + } + } + } + + StatusToolTip { + visible: hhandler.hovered && (!!model.name || !!model.description) + text: { + const name = model.name + const descr = model.description + const sep = !!name && !!descr ? "
" : "" + return `${name}${sep}${descr}` + } + } + + HoverHandler { + id: hhandler + cursorShape: hovered ? Qt.PointingHandCursor : undefined + } + + TapHandler { + onSingleTapped: { + Global.openLink(model.permalink) + } + } + } + } + } + + // assets/tokens + ColumnLayout { + StatusBaseText { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignCenter + visible: assetsView.count == 0 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Theme.palette.directColor1 + text: qsTr("%1 doesn't have any assets/tokens yet").arg(root.mainDisplayName) + } + + StatusGridView { + Layout.fillWidth: true + Layout.fillHeight: true + id: assetsView + rightMargin: Style.current.halfPadding + cellWidth: (width-rightMargin)/3 + cellHeight: cellWidth/2.5 + visible: count + model: SortFilterProxyModel { + // TODO show assets for all accounts, not just the current one? + sourceModel: root.isCurrentUser ? root.walletStore.currentAccount.assets : null // TODO show other users too + filters: ValueFilter { + roleName: "visibleForNetworkWithPositiveBalance" + value: true + } + sorters: [ + StringSorter { + roleName: "name" + }, + StringSorter { + roleName: "symbol" + } + ] + } + ScrollBar.vertical: StatusScrollBar { } + delegate: StatusListItem { + readonly property double changePct24hour: model.changePct24hour ?? 0 + readonly property string textColor: changePct24hour === 0 + ? Theme.palette.baseColor1 : changePct24hour < 0 + ? Theme.palette.dangerColor1 : Theme.palette.successColor1 + readonly property string arrow: changePct24hour === 0 ? "" : changePct24hour < 0 ? "↓" : "↑" + + width: GridView.view.cellWidth - Style.current.halfPadding + height: GridView.view.cellHeight - Style.current.halfPadding + title: LocaleUtils.currencyAmountToLocaleString(model.enabledNetworkBalance) + statusListItemTitle.font.weight: Font.Medium + tertiaryTitle: qsTr("%1% today %2") + .arg(LocaleUtils.numberToLocaleString(changePct24hour, changePct24hour === 0 ? 0 : 2)).arg(arrow) + statusListItemTertiaryTitle.color: textColor + statusListItemTertiaryTitle.font.pixelSize: Theme.asideTextFontSize + statusListItemTertiaryTitle.anchors.topMargin: 6 + leftPadding: Style.current.halfPadding + rightPadding: Style.current.halfPadding + border.width: 1 + border.color: Theme.palette.baseColor2 + components: [ + Image { + width: 40 + height: 40 + anchors.verticalCenter: parent.verticalCenter + source: Style.png("tokens/" + model.symbol) + onStatusChanged: { + if (status === Image.Error) { + source = Style.png("tokens/DEFAULT-TOKEN") + } + } + } + ] + onClicked: { + if (root.readOnly) + return + // TODO what to do here? + } + } + } + } + } +} diff --git a/ui/imports/shared/views/profile/qmldir b/ui/imports/shared/views/profile/qmldir new file mode 100644 index 0000000000..c83591ed97 --- /dev/null +++ b/ui/imports/shared/views/profile/qmldir @@ -0,0 +1 @@ +ProfileShowcaseView 1.0 ProfileShowcaseView.qml diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index 1433515f20..3ee15253c2 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -47,6 +47,9 @@ QtObject { signal setNthEnabledSectionActive(int nthSection) signal appSectionBySectionTypeChanged(int sectionType, int subsection) + signal openSendModal(string address) + signal switchToCommunity(string communityId) + signal playSendMessageSound() signal playNotificationSound() signal playErrorSound()