From 53db09412af4efc27ed72d8dcf0aa516119b207b Mon Sep 17 00:00:00 2001 From: Alexandra Betouni <31625338+alexandraB99@users.noreply.github.com> Date: Tue, 27 Feb 2024 19:11:03 +0200 Subject: [PATCH] feat(ProfileShowcasePopup): Adding interactions in delegates Closes #13702 --- .../shared_models/collectibles_model.nim | 5 + storybook/pages/ProfileDialogViewPage.qml | 45 +- storybook/src/Models/AssetsModel.qml | 34 +- .../src/Models/ManageCollectiblesModel.qml | 14 + .../Components/StatusCommunityCard.qml | 21 +- .../statusModal/StatusImageWithTitle.qml | 4 +- .../Communities/controls/PermissionsRow.qml | 7 + .../helpers/ProfileShowcaseModelAdapter.qml | 4 + .../ProfileShowcaseSettingsModelAdapter.qml | 7 + ui/app/mainui/Popups.qml | 10 +- ui/imports/assets/png/profile/gradient.png | Bin 0 -> 4160 bytes ui/imports/assets/png/profile/gradient2x.png | Bin 0 -> 13928 bytes ui/imports/shared/controls/ExpandableTag.qml | 112 +++ .../shared/controls/delegates/InfoCard.qml | 51 +- ui/imports/shared/controls/qmldir | 1 + ui/imports/shared/views/ProfileDialogView.qml | 3 +- .../profile/ProfileShowcaseAccountsView.qml | 201 +++++ .../profile/ProfileShowcaseAssetsView.qml | 131 ++++ .../ProfileShowcaseCollectiblesView.qml | 209 +++++ .../ProfileShowcaseCommunitiesView.qml | 181 +++++ .../ProfileShowcaseSocialLinksView.qml | 134 ++++ .../views/profile/ProfileShowcaseView.qml | 722 ++++-------------- ui/imports/utils/Global.qml | 1 + 23 files changed, 1300 insertions(+), 597 deletions(-) create mode 100644 ui/imports/assets/png/profile/gradient.png create mode 100644 ui/imports/assets/png/profile/gradient2x.png create mode 100644 ui/imports/shared/controls/ExpandableTag.qml create mode 100644 ui/imports/shared/views/profile/ProfileShowcaseAccountsView.qml create mode 100644 ui/imports/shared/views/profile/ProfileShowcaseAssetsView.qml create mode 100644 ui/imports/shared/views/profile/ProfileShowcaseCollectiblesView.qml create mode 100644 ui/imports/shared/views/profile/ProfileShowcaseCommunitiesView.qml create mode 100644 ui/imports/shared/views/profile/ProfileShowcaseSocialLinksView.qml diff --git a/src/app/modules/shared_models/collectibles_model.nim b/src/app/modules/shared_models/collectibles_model.nim index 4e6f85d216..831c1d09eb 100644 --- a/src/app/modules/shared_models/collectibles_model.nim +++ b/src/app/modules/shared_models/collectibles_model.nim @@ -25,6 +25,7 @@ type CommunityName CommunityColor CommunityPrivilegesLevel + CommunityImage TokenType QtObject: @@ -146,6 +147,7 @@ QtObject: CollectibleRole.CommunityName.int:"communityName", CollectibleRole.CommunityColor.int:"communityColor", CollectibleRole.CommunityPrivilegesLevel.int:"communityPrivilegesLevel", + CollectibleRole.CommunityImage.int:"communityImage", CollectibleRole.TokenType.int:"tokenType", }.toTable @@ -197,6 +199,8 @@ QtObject: result = newQVariant(item.getCommunityColor()) of CollectibleRole.CommunityPrivilegesLevel: result = newQVariant(item.getCommunityPrivilegesLevel()) + of CollectibleRole.CommunityImage: + result = newQVariant(item.getCommunityImage()) of CollectibleRole.TokenType: result = newQVariant(item.getTokenType()) @@ -222,6 +226,7 @@ QtObject: of "communityName": result = item.getCommunityName() of "communityColor": result = item.getCommunityColor() of "communityPrivilegesLevel": result = $item.getCommunityPrivilegesLevel() + of "communityImage": result = item.getCommunityImage() proc resetCollectibleItems(self: Model, newItems: seq[CollectiblesEntry] = @[]) = self.beginResetModel() diff --git a/storybook/pages/ProfileDialogViewPage.qml b/storybook/pages/ProfileDialogViewPage.qml index 10e5dcd8d9..8946115ae2 100644 --- a/storybook/pages/ProfileDialogViewPage.qml +++ b/storybook/pages/ProfileDialogViewPage.qml @@ -8,6 +8,7 @@ import shared.stores 1.0 import mainui 1.0 import StatusQ 0.1 +import StatusQ.Core.Utils 0.1 as StatusQUtils import AppLayouts.Wallet.stores 1.0 @@ -127,29 +128,56 @@ SplitView { ListModel { id: linksModel ListElement { - uuid: "0001" text: "__github" url: "https://github.com/caybro" + showcaseVisibility: Constants.ShowcaseVisibility.Everyone } ListElement { - uuid: "0002" text: "__twitter" url: "https://twitter.com/caybro" + showcaseVisibility: Constants.ShowcaseVisibility.Everyone } ListElement { - uuid: "0003" text: "__personal_site" url: "https://status.im" + showcaseVisibility: Constants.ShowcaseVisibility.Everyone } ListElement { - uuid: "0004" text: "__youtube" url: "https://www.youtube.com/@LukasTinkl" + showcaseVisibility: Constants.ShowcaseVisibility.Everyone } ListElement { - uuid: "0006" text: "__telegram" url: "https://t.me/ltinkl" + showcaseVisibility: Constants.ShowcaseVisibility.Everyone + } + } + + ManageCollectiblesModel { + id: manageCollectiblesModel + Component.onCompleted: { + for (let i = 0; i < this.count; i++) { + setProperty(i, "showcaseVisibility", Constants.ShowcaseVisibility.Everyone) + } + } + } + + WalletAccountsModel { + id: walletAccountsModel + Component.onCompleted: { + for (let i = 0; i < this.count; i++) { + setProperty(i, "showcaseVisibility", Constants.ShowcaseVisibility.Everyone) + } + } + } + + CommunitiesModel { + id: communitiesModel + Component.onCompleted: { + for (let i = 0; i < this.count; i++) { + setProperty(i, "showcaseVisibility", Constants.ShowcaseVisibility.Everyone) + } } } @@ -310,7 +338,6 @@ SplitView { sourceComponent: ProfileDialogView { implicitWidth: 640 - enabledNetworks: NetworksModel.allNetworks readOnly: ctrlReadOnly.checked publicKey: switchOwnProfile.checked ? "0xdeadbeef" : "0xrandomguy" @@ -319,9 +346,9 @@ SplitView { sendToAccountEnabled: true - showcaseCommunitiesModel: CommunitiesModel {} - showcaseAccountsModel: WalletAccountsModel {} - showcaseCollectiblesModel: ManageCollectiblesModel {} + showcaseCommunitiesModel: communitiesModel + showcaseAccountsModel: walletAccountsModel + showcaseCollectiblesModel: manageCollectiblesModel showcaseSocialLinksModel: linksModel // TODO: showcaseAssetsModel diff --git a/storybook/src/Models/AssetsModel.qml b/storybook/src/Models/AssetsModel.qml index 378738db64..c85b17ea8d 100644 --- a/storybook/src/Models/AssetsModel.qml +++ b/storybook/src/Models/AssetsModel.qml @@ -11,7 +11,9 @@ ListModel { shortName: "SOCKS", symbol: "SOCKS", category: TokenCategories.Category.Community, - communityId: "" + communityId: "", + communityImage: "", + address: "23534tlgtu90345t" }, { key: "zrx", @@ -20,7 +22,9 @@ ListModel { shortName: "ZRX", symbol: "ZRX", category: TokenCategories.Category.Community, - communityId: "" + communityId: "", + communityImage: "", + address: "23534tlgtu90345t" }, { key: "1inch", @@ -29,7 +33,9 @@ ListModel { shortName: "1INCH", symbol: "1INCH", category: TokenCategories.Category.Own, - communityId: "" + communityId: "", + communityImage: "", + address: "23534tlgtu90345t" }, { key: "Aave", @@ -38,7 +44,9 @@ ListModel { shortName: "AAVE", symbol: "AAVE", category: TokenCategories.Category.Own, - communityId: "" + communityId: "", + communityImage: "", + address: "23534tlgtu90345t" }, { key: "Amp", @@ -47,7 +55,9 @@ ListModel { shortName: "AMP", symbol: "AMP", category: TokenCategories.Category.Own, - communityId: "" + communityId: "", + communityImage: "", + address: "23534tlgtu90345t" }, { key: "Dai", @@ -56,7 +66,9 @@ ListModel { shortName: "DAI", symbol: "DAI", category: TokenCategories.Category.General, - communityId: "" + communityId: "0x1", + communityImage: "https://pbs.twimg.com/profile_images/1599347398769143808/C6qG3RQv_400x400.jpg", + address: "stgdrswaE2q" }, { key: "snt", @@ -65,7 +77,8 @@ ListModel { shortName: "snt", symbol: "SNT", category: TokenCategories.Category.General, - communityId: "" + communityId: "", + address: "321312wdsadas" }, { key: "stt", @@ -74,7 +87,8 @@ ListModel { shortName: "stt", symbol: "STT", category: TokenCategories.Category.Own, - communityId: "" + communityId: "", + address: "rwr32e1wqdscdwe43r34r" }, { key: "eth", @@ -83,7 +97,9 @@ ListModel { shortName: "eth", symbol: "ETH", category: TokenCategories.Category.General, - communityId: "" + communityId: "000", + communityImage: ModelsData.icons.status, + address: "rwr43r34r" } ] diff --git a/storybook/src/Models/ManageCollectiblesModel.qml b/storybook/src/Models/ManageCollectiblesModel.qml index e4b6d6cf15..14656fe328 100644 --- a/storybook/src/Models/ManageCollectiblesModel.qml +++ b/storybook/src/Models/ManageCollectiblesModel.qml @@ -29,6 +29,8 @@ ListModel { imageUrl: ModelsData.collectibles.cryptoPunks, isLoading: false, backgroundColor: "", + permalink:"opensea.com", + domain:"opensea", ownership: [ { accountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", @@ -74,6 +76,8 @@ ListModel { imageUrl: "https://i.seadn.io/s/raw/files/ba2811bb5cd0bed67529d69fa92ef5aa.jpg?auto=format&dpr=1&w=1000", isLoading: false, backgroundColor: "", + permalink:"opensea.com", + domain:"opensea", ownership: [ { accountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", @@ -114,6 +118,8 @@ ListModel { imageUrl: ModelsData.collectibles.kitty1Big, isLoading: true, backgroundColor: "", + permalink:"opensea.com", + domain:"opensea", ownership: [ { accountAddress: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", @@ -154,6 +160,8 @@ ListModel { imageUrl: ModelsData.collectibles.kitty2Big, isLoading: false, backgroundColor: "", + permalink:"opensea.com", + domain:"opensea", ownership: [ { accountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", @@ -197,6 +205,8 @@ ListModel { imageUrl: ModelsData.collectibles.kitty3Big, isLoading: false, backgroundColor: "", + permalink:"opensea.com", + domain:"opensea", ownership: [ { accountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", @@ -242,6 +252,8 @@ ListModel { imageUrl: "https://i.seadn.io/s/raw/files/cfa559bb63e4378f17649c1e3b8f18fe.jpg?auto=format&dpr=1&w=1000", isLoading: false, backgroundColor: "", + permalink:"opensea.com", + domain:"opensea", ownership: [ { accountAddress: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", @@ -274,6 +286,8 @@ ListModel { imageUrl: "", isLoading: false, backgroundColor: "ivory", + permalink:"opensea.com", + domain:"opensea", ownership: [ { accountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", diff --git a/ui/StatusQ/src/StatusQ/Components/StatusCommunityCard.qml b/ui/StatusQ/src/StatusQ/Components/StatusCommunityCard.qml index 481ab95817..516956ea79 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusCommunityCard.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusCommunityCard.qml @@ -168,7 +168,7 @@ Rectangle { \qmlsignal StatusCommunityCard::clicked(string communityId) This signal is emitted when the card item is clicked. */ - signal clicked(string communityId) + signal clicked(var mouse, string communityId) QtObject { id: d @@ -177,13 +177,14 @@ Rectangle { readonly property int cardHeigth: (root.cardSize === StatusCommunityCard.Size.Big) ? 190 : 119 readonly property int totalHeigth: (root.cardSize === StatusCommunityCard.Size.Big) ? 230 : 144 readonly property int margins: 12 - readonly property int bannerRadius: (root.cardSize === StatusCommunityCard.Size.Big) ? 20 : 16 + readonly property int bannerRadius: (root.cardSize === StatusCommunityCard.Size.Big) ? 20 : 8 readonly property int bannerRadiusHovered: (root.cardSize === StatusCommunityCard.Size.Big) ? 30 : 16 - readonly property int cardRadius: 16 + readonly property int cardRadius: (root.cardSize === StatusCommunityCard.Size.Big) ? 16 : 8 readonly property color cardColor: Theme.palette.name === "light" ? Theme.palette.indirectColor1 : Theme.palette.baseColor2 readonly property color fontColor: Theme.palette.directColor1 readonly property color loadingColor1: Theme.palette.baseColor5 readonly property color loadingColor2: Theme.palette.baseColor4 + readonly property int titleFontWeight: (root.cardSize === StatusCommunityCard.Size.Big) ? Font.Bold : Font.Medium function numberFormat(number) { var res = number @@ -287,11 +288,12 @@ Rectangle { z: banner.z + 1 visible: root.loaded anchors.top: parent.top - anchors.topMargin: (root.cardSize === StatusCommunityCard.Size.Big) ? 40 : 23 + anchors.topMargin: (root.cardSize === StatusCommunityCard.Size.Big) ? 40 : 25 width: parent.width height: d.cardHeigth color: d.cardColor radius: d.cardRadius + border.color: root.border.color clip: true // Right header extra info component @@ -314,16 +316,18 @@ Rectangle { spacing: (root.cardSize === StatusCommunityCard.Size.Big) ? 6 : 0 StatusBaseText { Layout.alignment: Qt.AlignVCenter - Layout.fillWidth: true + Layout.fillWidth: (root.cardSize === StatusCommunityCard.Size.Big) + Layout.preferredHeight: 22 text: root.name - font.weight: Font.Bold + font.weight: d.titleFontWeight font.pixelSize: root.titleFontSize color: d.fontColor elide: Text.ElideRight } StatusBaseText { Layout.fillWidth: true - Layout.fillHeight: true + Layout.fillHeight: (root.cardSize === StatusCommunityCard.Size.Big) + Layout.preferredHeight: 16 text: root.description font.pixelSize: root.descriptionFontSize lineHeight: 1.2 @@ -516,7 +520,8 @@ Rectangle { anchors.fill: parent cursorShape: root.loaded ? Qt.PointingHandCursor : Qt.ArrowCursor hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: root.clicked(root.communityId) + onClicked: root.clicked(mouse ,root.communityId) } } diff --git a/ui/StatusQ/src/StatusQ/Popups/statusModal/StatusImageWithTitle.qml b/ui/StatusQ/src/StatusQ/Popups/statusModal/StatusImageWithTitle.qml index f1c7fcdf56..e56b20e925 100644 --- a/ui/StatusQ/src/StatusQ/Popups/statusModal/StatusImageWithTitle.qml +++ b/ui/StatusQ/src/StatusQ/Popups/statusModal/StatusImageWithTitle.qml @@ -22,6 +22,7 @@ Row { height: 40 isLetterIdenticon: false imgIsIdenticon: false + bgBorderWidth: 1 } spacing: 8 @@ -63,7 +64,7 @@ Row { height: statusImageWithTitle.asset.height radius: statusImageWithTitle.asset.bgRadius || width/2 color: Theme.palette.statusRoundedImage.backgroundColor - border.width: 1 + border.width: statusImageWithTitle.asset.bgBorderWidth border.color: Theme.palette.directColor7 showLoadingIndicator: true } @@ -118,6 +119,7 @@ Row { width: !iconOrImage.active ? parent.width : parent.width - iconOrImage.width - parent.spacing anchors.verticalCenter: parent.verticalCenter + Row { id: headerTitleRow width: parent.width diff --git a/ui/app/AppLayouts/Communities/controls/PermissionsRow.qml b/ui/app/AppLayouts/Communities/controls/PermissionsRow.qml index 914c7b148f..accccfac5e 100644 --- a/ui/app/AppLayouts/Communities/controls/PermissionsRow.qml +++ b/ui/app/AppLayouts/Communities/controls/PermissionsRow.qml @@ -79,6 +79,12 @@ Control { */ property color backgroundColor: Theme.palette.baseColor4 + /*! + \qmlproperty color PermissionsRow::backgroundColor + This property holds the control background color, including border color of overlapped elements. + */ + property color backgroundBorderColor: Theme.palette.baseColor4 + /*! \qmlproperty int PermissionsRow::backgroundRadius This property holds the background radius. @@ -169,6 +175,7 @@ Control { background: Rectangle { color: root.backgroundColor radius: root.backgroundRadius + border.color: root.backgroundBorderColor } contentItem: RowLayout { diff --git a/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModelAdapter.qml b/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModelAdapter.qml index c1abceae44..a479b14640 100644 --- a/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModelAdapter.qml +++ b/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseModelAdapter.qml @@ -93,6 +93,10 @@ QObject { function getShowcaseVisibility() { return Constants.ShowcaseVisibility.Everyone } + }, + FastExpressionRole { + name: "canReceiveFromMyAccounts" + expression: true } ] } diff --git a/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseSettingsModelAdapter.qml b/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseSettingsModelAdapter.qml index 3907241adf..1237d5eb51 100644 --- a/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseSettingsModelAdapter.qml +++ b/ui/app/AppLayouts/Profile/helpers/ProfileShowcaseSettingsModelAdapter.qml @@ -80,6 +80,13 @@ QObject { name: "showcaseKey" expression: model.address expectedRoles: ["address"] + }, + FastExpressionRole { + function canReceiveFromMyAccounts() { + return accountsSourceModel.count > 1 + } + name: "canReceiveFromMyAccounts" + expression: canReceiveFromMyAccounts() } ] } diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 69a08dd10d..c8331074a4 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -52,6 +52,7 @@ QtObject { Global.openOutgoingIDRequestPopup.connect(openOutgoingIDRequestPopup) Global.openIncomingIDRequestPopup.connect(openIncomingIDRequestPopup) Global.openInviteFriendsToCommunityPopup.connect(openInviteFriendsToCommunityPopup) + Global.openInviteFriendsToCommunityByIdPopup.connect(openInviteFriendsToCommunityByIdPopup) Global.openContactRequestPopup.connect(openContactRequestPopup) Global.openReviewContactRequestPopup.connect(openReviewContactRequestPopup) Global.openChooseBrowserPopup.connect(openChooseBrowserPopup) @@ -215,6 +216,14 @@ QtObject { openPopup(inviteFriendsToCommunityPopup, { community: community, communitySectionModule: communitySectionModule }, cb) } + function openInviteFriendsToCommunityByIdPopup(communityId, cb) { + root.rootStore.mainModuleInst.prepareCommunitySectionModuleForCommunityId(communityId) + const communitySectionModuleData = root.rootStore.mainModuleInst.getCommunitySectionModule() + const communityData = root.communitiesStore.getCommunityDetails(communityId) + + openPopup(inviteFriendsToCommunityPopup, { community: communityData, communitySectionModule: communitySectionModuleData }, cb) + } + function openContactRequestPopup(publicKey, contactDetails, cb) { let details = contactDetails ?? Utils.getContactDetailsAsJson(publicKey, false) const popupProperties = { @@ -478,7 +487,6 @@ QtObject { onClosed: destroy() } }, - Component { id: sendContactRequestPopupComponent diff --git a/ui/imports/assets/png/profile/gradient.png b/ui/imports/assets/png/profile/gradient.png new file mode 100644 index 0000000000000000000000000000000000000000..1bcca8be2047ee6485bbcffbd6f4924f84db616f GIT binary patch literal 4160 zcmb_f`#Tei`yLfSO1(l6m8g*{Z-o`5CEIFUTiwPM;!j5L|Vsn_V zIZr-W8|IMnc@8;jPT$`D;rm?I{oMC+U-$L=^8EB9+_yGAAbm_40011YxO23W^qwgIKu(p}g6#tU z4pv*-GIe+@s{7Op+)%o5U0|9OER&lo4OEqZXrc~W$=k_ zz^PM3*6W*e(qR$1tgWL3Z-llDqK8h1yL?6TRy=CYI{*J9Qf*k1BoeM>EZDRC{rSpQ zZ)h*Y8S*|J9E{A2XGD|YJ4O;gM34E=t0`mCTMi1^ZdTn`HI^;fv;4|#J>(~2XGJXa zA5ukM25%*D`*O=o*5;=O4egZGcV*YKLOjEN;q{nvqjNu8p|}?iD2rkwSu(Ga@}yA3 zeMcx)ry>L@Pb7x@SPsvuf#tWUTk(Y>Gi_!Om3H$O;ziXojK7k;JNjW*tE+yNS+^;V zVjw4=j)w9oZnwxp)m&w2;BblkS{&>g8`Ro6o>aJ#>E;l+Ha{`pyR)7j&R4>1&yPrM zRDHH?;+ocxCv#S0)A1WA`_;n$UH8k}INX=@BAO|vFS&Tl*SoRYc~xAf@@1QH;=62f zps$?!1yua`)O2%DjEj@U7unUnim+khh>vU4oRR|bf}2pbx$YN2jphkBTUl^b(fAVW zV(gxcZ2opc14=k)&Rd>p+kG63F}1c$UMg+9b!5GU81T^qe0Gk;m?`j^Z!LnNIyt>s zHsRSp&YxlhqI zOYIzCm%$+=Ig-f3a0u|)8hq4E`Cvm$kM-h&>@pJ3d+MQg+Vg|4-Cgs~AwcN;Is0(u zGqf7@a%iA#rG|BbnNR;`?Mtja+Nt!Rzs9ob?(rS9DJ^-U*9$pOMbMS zd?_WyNuP4ggK6AhHtnd9&>jW|w?@ttk5Dex77QqVUK^k=KK%*0)kKRCMf4qqAt8Q4 zONbkBXVNUzF+F3iC4x6Ayd;9sNiVIiZi+IRTIwd0K=@q10?T9mZ89kPz_BoNKdwX8 z?wf1cYqw1-N!02`bjZeO#+Zl?5ls>#N0IJoZ+GwJTX9$IYhSC4)dVy2k4SQ24El+L zKx}=cTjajCL(C8ph}x~8r!E-N?5Y^RqC(qm&J`Fs^B<#RG_pY_uDgpq7D{-YPuIWA z%t3l4i_5Eh_n}Iw8*BS{(ll>WDp2Pm7#SFT7pn{VKUd-lpi6Y5eP0rf2L{kx`zjpzS5F^u$wAGVvOO z5+)o$J{OG@R|bw2<>lx;(;lcAM%w7AT8^X`v?>z$c|ezm_xud@Ak&caA_=E^s4}GU zGNfe95<(>wB=p6>M7|8HS=v?g5t{#v-)9VgtkOEiTcG=j>(tJjG~IG{P6rA1fHTCj z!j-mbEgAMBmLsTP#4+BlVwsY57uLNlb=|X?4ChJUzb>8Xmm`QBpZj9FDPh7FnYI}htlt>$M7$gd?VqRPKE+7O+=REyuL+8x{4vcJ5__YJ{3a-z_oLQUwl z&X+xOA1Y2Gy(IPZe1IJ)nWjDmVQWvG8wYP1T4f!NSd+~fn;N@ET%}~oP1)kMr9d2T z(Tv$mUPS@s+A(lks~+FU&m7D|j$+p;H&;vn^u$@9)tP|5wy?9<1aee6g*Cq%#BEb- z8Nsa0ZEIX%r0k1J*eCC!?!$LsJw}R_s#Nbh1xMFI$(!!ffN?p-MNlqZ1P9 z%~P&ji|T<)L!YeLZh~;0-X+U9+q@@r{ngdHLq=I1SB`oYVIMH3`kv#jzmfPPEEh!K z@ojwzP%SHbk0NY~c(Iz-wjiZv7i=*Rrt2B!UrcK2U{#*i#GG|p#Y?+h0+sOqK2)M_ zT&wc{U-zd9X>;&geU8p6`cxP_kMlBPHn(xsX3aQ0ojCIYYnqn#v|1V}`qvIue!-rz zLwo>1a0TVcu?c-fsJ>u_wN3zm%G5oC31>C|@tS64tjj+hNN(b09Bn zvpwSySA=AVi#A6CPOPJH+eL>potYMTIjQ}2+2LOASw3IQA9G=;6D7;YXLP@6#=(X) zZHVnaYc2TQo~N7sA;z|aEdRezm)M8G_pd-qq}&t_4hyUGrS1;lL)TkTqIJ`mRW#5N z`|R%>L}oEw_}+n0A7r zq^3Ei7*U`gDv(nXPzTtm+rJ3(`8u%kVXPIwqozj_Vizr)gV^h3WM!DU_R6b2$(O*#0ynU>p!WE_?AP5zL|mVE%=8j0i@IhS^}gN*@s%I` z4ZvRDl%Nay9{JqJWm-s5jde_^Ihj+{P|(+d*f5A%&kXv>I)QNTTI6*8?td$5^uK`x=eanoyrdX^yZ%a z4)TpIH$et7V3WY}-~4Fe0nSJbP3<$hmr!N4418ivnw03r={A2K3;vCq40c2bR^N}C2WB6yS?aw*)<^IKgK zlSQ66=ueoCYG_`i@wD;o{V99%`S!9%14lf?oa`g}X{)+*Zc#-y`Fypz^7{%OWbgd; zb;fGo%I7)NANOOoPN6QaVCQx}|4+$!U2VyMAy+wT|0<7GMR+H~-d+IKtlU15@e4RX z*&Y4Mb4_zfqvyoHFEZMdY#LZnCn_t!qy&8*JMi-Bor&i0&WRKz0l^L^s{(4pgoC3> zrB96YJyYOV!>XQgE3uZ^1Hy{g&oeoLWDPJvbA8V#s8>nY^Ny=;a%a!l*~)8%Zy#61 z&d7B;j$(by5^#vrq*0}bTyiwrGAlFyu6h&XZ&Xh)ObUhn5J50I#LogyY z1}P~$rtv~+U`QT`ub3Hq7#*GM?AsCwoBbAC&e1v&n)>)N-QO4;mv->%M9#D1nFdiD%rV$taW-sq_i+>Obtj09Rh7-49PD*61l^C4U zcNhS&eQ%R0-qDSZ22A_eTTG^{g)p4Ckkfmt=Le+CmV%OUNO`6X z5QhPdufZ`lZpd2Hm}9sK4)D}Q0>WjQeMBOH)f>a(@%g3;WqL6g8y;nJwvSHVr)igj zT3<$+^uY{KD5!Pj;pQCY2Nhf83+_KF5jEvucoW2+buI@Bi$MxRaF^`MFUYFL%M4hp zPTKA@MjiOvQ$T$E$|@Sdhm=u5jN#EW5t$rGV4@H z;817S*v=Fd-zQdAsIu1P{OJexIQQBU*Bp}k45>-TZ5J2zugtz5#-XX*Q;s>X+pcKC ze)Nt!i(y_+@%{Ap-8}BTW$q-G^qmriTvQmg zv%Mv1UzzRdA~CuWclS`$^Q7k5rT*Z*`k#Hqe`Imn`WEqq HXXO6@BEFzt literal 0 HcmV?d00001 diff --git a/ui/imports/assets/png/profile/gradient2x.png b/ui/imports/assets/png/profile/gradient2x.png new file mode 100644 index 0000000000000000000000000000000000000000..888e6eace598592a09f67546af7f533a61d06d0e GIT binary patch literal 13928 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 F{{ZagkCOlZ literal 0 HcmV?d00001 diff --git a/ui/imports/shared/controls/ExpandableTag.qml b/ui/imports/shared/controls/ExpandableTag.qml new file mode 100644 index 0000000000..5daf746769 --- /dev/null +++ b/ui/imports/shared/controls/ExpandableTag.qml @@ -0,0 +1,112 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Components 0.1 +import StatusQ.Core.Theme 0.1 + +import utils 1.0 + +Control { + id: root + width: 120 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.bottom: parent.bottom + anchors.bottomMargin: 12 + + property string tagHeaderText + property string tagName + property string tagImage + property bool isIcon: false + property color backgroundColor: Theme.palette.baseColor4 + + property bool expanded: root.hovered + + signal tagClicked() + + contentItem: Item { + + StatusBaseText { + id: textLabel + width: parent.width + visible: root.expanded && !!root.tagHeaderText + opacity: visible ? 1 : 0 + color: Theme.palette.indirectColor1 + Behavior on opacity { NumberAnimation {} } + verticalAlignment: Text.AlignVCenter + text: root.tagHeaderText + elide: Text.ElideRight + font.weight: Font.Medium + } + Rectangle { + id: tagBackground + width: Math.min(Math.max(tagRowLayout.implicitWidth + tagRowLayout.anchors.margins * 2, 24), parent.width) + Behavior on width { NumberAnimation {} } + height: 24 + anchors.top: textLabel.bottom + anchors.topMargin: 4 + radius: parent.width/2 + color: root.backgroundColor + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: hovered ? Qt.PointingHandCursor : undefined + onClicked: { + root.tagClicked(); + } + } + + RowLayout { + id: tagRowLayout + anchors.fill: parent + anchors.margins: 2 + spacing: 4 + + Loader { + id: tagImageLoader + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignVCenter + sourceComponent: root.isIcon ? tagStatusRoundIcon : tagStatusRoundedImage + } + + StatusBaseText { + id: tagName + Layout.preferredHeight: 20 + Layout.fillWidth: true + Layout.rightMargin: 2 + visible: (root.expanded && !!root.tagName) + opacity: visible ? 1 : 0 + Behavior on opacity { NumberAnimation {} } + verticalAlignment: Text.AlignVCenter + font.pixelSize: Style.current.tertiaryTextFontSize + text: root.tagName + elide: Text.ElideRight + } + } + } + } + + Component { + id: tagStatusRoundedImage + StatusRoundedImage { + image.fillMode: Image.PreserveAspectFit + image.source: root.tagImage + } + } + + Component { + id: tagStatusRoundIcon + StatusRoundIcon { + asset.width: 16 + asset.height: 16 + color: "transparent" + asset.name: root.tagImage + asset.color: tagName.color + } + } +} diff --git a/ui/imports/shared/controls/delegates/InfoCard.qml b/ui/imports/shared/controls/delegates/InfoCard.qml index 33e66644b8..ddd9e0b4a6 100644 --- a/ui/imports/shared/controls/delegates/InfoCard.qml +++ b/ui/imports/shared/controls/delegates/InfoCard.qml @@ -1,6 +1,7 @@ import QtQuick 2.14 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import QtGraphicalEffects 1.0 import StatusQ.Core 0.1 import StatusQ.Controls 0.1 import StatusQ.Core.Theme 0.1 @@ -15,12 +16,16 @@ Control { padding: 12 + property bool highlight: false property string title: "" property string subTitle: "" property string tagIcon: "" property var enabledNetworks property bool loading: false property alias rightSideButtons: rightSideButtonsLoader.sourceComponent + signal clicked(var mouse) + signal communityTagClicked(var mouse) + property StatusAssetSettings asset: StatusAssetSettings { height: 32 @@ -29,14 +34,34 @@ Control { } background: Rectangle { + id: background anchors.fill: parent color: Style.current.background radius: Style.current.radius border.width: 1 border.color: Theme.palette.baseColor2 + layer.enabled: mouseArea.containsMouse || root.highlight + layer.effect: DropShadow { + source: background + horizontalOffset: 0 + verticalOffset: 2 + radius: 16 + samples: 25 + spread: 0 + color: Theme.palette.backdropColor + } } contentItem: Item { + MouseArea { + id: mouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton|Qt.RightButton + hoverEnabled: true + onClicked: { + root.clicked(mouse); + } + } ColumnLayout { id: titleColumn anchors.fill: parent @@ -55,15 +80,15 @@ Control { Item { Layout.fillWidth: true } Loader { id: rightSideButtonsLoader - Layout.alignment: Qt.AlignRight + Layout.alignment: Qt.AlignRight | Qt.AlignTop } } StatusTextWithLoadingState { - text: root.title + Layout.fillWidth: true Layout.preferredHeight: 22 Layout.topMargin: Style.current.halfPadding - Layout.fillWidth: true + text: root.title elide: Text.ElideRight font.weight: Font.Medium loading: root.loading @@ -94,12 +119,18 @@ Control { Repeater { id: chainRepeater model: root.enabledNetworks - delegate: StatusRoundedImage { + delegate: StatusRoundedComponent { width: 20 height: 20 - visible: image.source !== "" - image.source: Style.svg(model.iconUrl) + visible: model.iconUrl !== "" + color: Theme.palette.baseColor3 z: index + 1 + border.color: Style.current.background + StatusIcon { + anchors.fill:parent + anchors.margins: 1 + icon: Style.svg(model.iconUrl) + } } } } @@ -111,9 +142,15 @@ Control { verticalPadding: 0 spacing: 0 visible: !!root.tagIcon - asset.name: root.tagIcon + communityImage: root.tagIcon asset.width: 20 asset.height: 20 + MouseArea { + anchors.fill: parent + onClicked: { + root.communityTagClicked(mouse); + } + } } } } diff --git a/ui/imports/shared/controls/qmldir b/ui/imports/shared/controls/qmldir index 00ee697c70..ba80d1d752 100644 --- a/ui/imports/shared/controls/qmldir +++ b/ui/imports/shared/controls/qmldir @@ -49,3 +49,4 @@ TransactionDetailsHeader.qml 1.0 TransactionDetailsHeader.qml MockedKeycardReaderStateSelector 1.0 MockedKeycardReaderStateSelector.qml MockedKeycardStateSelector 1.0 MockedKeycardStateSelector.qml AssetsSectionDelegate 1.0 AssetsSectionDelegate.qml +ExpandableTag 1.0 ExpandableTag.qml diff --git a/ui/imports/shared/views/ProfileDialogView.qml b/ui/imports/shared/views/ProfileDialogView.qml index 666e5d8cbd..10cbae9f5e 100644 --- a/ui/imports/shared/views/ProfileDialogView.qml +++ b/ui/imports/shared/views/ProfileDialogView.qml @@ -35,7 +35,6 @@ Pane { property var contactsStore property alias sendToAccountEnabled: showcaseView.sendToAccountEnabled - property alias enabledNetworks: showcaseView.enabledNetworks property var dirtyValues: ({}) property bool dirty: false @@ -632,6 +631,8 @@ Pane { socialLinksModel: root.showcaseSocialLinksModel // assetsModel: root.showcaseAssetsModel + walletStore: WalletNS.RootStore + onCloseRequested: root.closeRequested() onCopyToClipboard: root.profileStore.copyToClipboard(text) } diff --git a/ui/imports/shared/views/profile/ProfileShowcaseAccountsView.qml b/ui/imports/shared/views/profile/ProfileShowcaseAccountsView.qml new file mode 100644 index 0000000000..5b8c723cb6 --- /dev/null +++ b/ui/imports/shared/views/profile/ProfileShowcaseAccountsView.qml @@ -0,0 +1,201 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Controls 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Core.Utils 0.1 as StatusQUtils +import StatusQ.Popups 0.1 + +import shared.controls.delegates 1.0 +import utils 1.0 + +Item { + id: root + + required property string mainDisplayName + required property bool sendToAccountEnabled + required property var accountsModel + required property var walletStore + + property alias cellWidth: accountsView.cellWidth + property alias cellHeight: accountsView.cellHeight + + signal copyToClipboard(string text) + + StatusBaseText { + anchors.centerIn: parent + visible: (accountsView.count === 0) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Theme.palette.directColor1 + text: qsTr("%1 has not shared any accounts").arg(root.mainDisplayName) + } + + StatusGridView { + id: accountsView + + anchors.fill: parent + topMargin: Style.current.bigPadding + bottomMargin: Style.current.bigPadding + leftMargin: Style.current.bigPadding + + visible: count + ScrollBar.vertical: StatusScrollBar { anchors.right: parent.right; anchors.rightMargin: width / 2 } + model: root.accountsModel + delegate: InfoCard { + id: accountInfoDelegate + implicitWidth: GridView.view.cellWidth - Style.current.padding + implicitHeight: GridView.view.cellHeight - Style.current.padding + title: model.name + subTitle: StatusQUtils.Utils.elideText(model.address, 6, 4).replace("0x", "0×") + asset.color: Utils.getColorForId(model.colorId) + 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 + enabledNetworks: root.walletStore.filteredFlatModel // TODO: https://github.com/status-im/status-desktop/issues/14227 + rightSideButtons: RowLayout { + StatusFlatRoundButton { + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + visible: accountInfoDelegate.hovered && model.canReceiveFromMyAccounts + type: StatusFlatRoundButton.Type.Secondary + icon.name: "send" + icon.color: !hovered ? Theme.palette.baseColor1 : Theme.palette.directColor1 + enabled: root.sendToAccountEnabled + onClicked: { + Global.openSendModal(model.address) + } + onHoveredChanged: accountInfoDelegate.highlight = hovered + } + StatusFlatRoundButton { + id: moreButton + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + visible: accountInfoDelegate.hovered + type: StatusFlatRoundButton.Type.Secondary + icon.name: "more" + icon.color: (hovered || d.menuOpened) ? Theme.palette.directColor1 : Theme.palette.baseColor1 + highlighted: d.menuOpened + onClicked: { + const preferredChains = StatusQUtils.ModelUtils.modelToArray(accountInfoDelegate.enabledNetworks, ["chainId"]).map((item) => item.chainId).join(":") + Global.openMenu(delegatesActionsMenu, this, { + x: moreButton.x, + y : moreButton.y, + accountAddress: model.address, + accountName: model.name, + accountColorId: model.colorId, + accountPrefferedChains: preferredChains + }); + } + onHoveredChanged: accountInfoDelegate.highlight = hovered + } + } + onClicked: { + if (mouse.button === Qt.RightButton) { + const preferredChains = StatusQUtils.ModelUtils.modelToArray(accountInfoDelegate.enabledNetworks, ["chainId"]).map((item) => item.chainId).join(":") + Global.openMenu(delegatesActionsMenu, this, { + accountAddress: model.address, + accountName: model.name, + accountColorId: model.colorId, + accountPrefferedChains: preferredChains + }); + } + } + } + } + + Component { + id: delegatesActionsMenu + StatusMenu { + id: contextMenu + property string accountAddress: "" + property string accountName: "" + property string accountColorId: "" + property var accountPrefferedChains: [] + + onOpened: { d.menuOpened = true; } + onClosed: { d.menuOpened = false; } + + StatusSuccessAction { + id: copyAddressAction + successText: qsTr("Copied") + text: qsTr("Copy adress") + icon.name: "copy" + onTriggered: { + root.copyToClipboard(accountAddress) + } + } + + StatusAction { + text: qsTr("Show address QR") + icon.name: "qr" + onTriggered: { + Global.openShowQRPopup({ + showSingleAccount: true, + switchingAccounsEnabled: false, + changingPreferredChainsEnabled: false, + hasFloatingButtons: false, + name: contextMenu.accountName, + address: contextMenu.accountAddress, + preferredSharingChainIds: contextMenu.accountPrefferedChains, + colorId: contextMenu.accountColorId + }) + } + } + + StatusAction { + text: qsTr("Save address") + icon.name: "favourite" + onTriggered: { + Global.openAddEditSavedAddressesPopup({ addAddress: true, address: contextMenu.accountAddress }) + } + } + + StatusAction { + text: qsTr("View on Etherscan") + icon.name: "link" + onTriggered: { + let link = Utils.getUrlForAddressOnNetwork(Constants.networkShortChainNames.mainnet, + root.walletStore.areTestNetworksEnabled, + root.walletStore.isGoerliEnabled, + contextMenu.accountAddress); + Global.openLinkWithConfirmation(link, StatusQUtils.StringUtils.extractDomainFromLink(link)); + } + } + + StatusAction { + text: qsTr("View on Optimism Explorer") + icon.name: "link" + onTriggered: { + let link = Utils.getUrlForAddressOnNetwork(Constants.networkShortChainNames.optimism, + root.walletStore.areTestNetworksEnabled, + root.walletStore.isGoerliEnabled, + contextMenu.accountAddress); + Global.openLinkWithConfirmation(link, StatusQUtils.StringUtils.extractDomainFromLink(link)); + } + } + + StatusAction { + text: qsTr("View on Arbiscan") + icon.name: "link" + onTriggered: { + let link = Utils.getUrlForAddressOnNetwork(Constants.networkShortChainNames.arbitrum, + root.walletStore.areTestNetworksEnabled, + root.walletStore.isGoerliEnabled, + contextMenu.accountAddress); + Global.openLinkWithConfirmation(link, StatusQUtils.StringUtils.extractDomainFromLink(link)); + } + } + } + } + + QtObject { + id: d + property bool menuOpened: false + } +} diff --git a/ui/imports/shared/views/profile/ProfileShowcaseAssetsView.qml b/ui/imports/shared/views/profile/ProfileShowcaseAssetsView.qml new file mode 100644 index 0000000000..6e9410c441 --- /dev/null +++ b/ui/imports/shared/views/profile/ProfileShowcaseAssetsView.qml @@ -0,0 +1,131 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Controls 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Popups 0.1 + +import shared.controls 1.0 +import shared.controls.delegates 1.0 + +import utils 1.0 + +Item { + id: root + + required property string mainDisplayName + required property var assetsModel + required property bool sendToAccountEnabled + + property alias cellWidth: accountsView.cellWidth + property alias cellHeight: accountsView.cellHeight + + signal closeRequested() + signal visitCommunity(var model) + + StatusBaseText { + anchors.centerIn: parent + visible: (assetsView.count === 0) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Theme.palette.directColor1 + text: qsTr("%1 has not shared any assets").arg(root.mainDisplayName) + } + StatusGridView { + id: assetsView + + anchors.fill: parent + topMargin: Style.current.bigPadding + bottomMargin: Style.current.bigPadding + leftMargin: Style.current.bigPadding + + visible: count + + model: root.assetsModel + ScrollBar.vertical: StatusScrollBar { anchors.right: parent.right; anchors.rightMargin: width / 2 } + delegate: InfoCard { + id: assetsInfoDelegate + width: GridView.view.cellWidth - Style.current.padding + height: GridView.view.cellHeight - Style.current.padding + title: model.name + //TODO show balance & symbol + subTitle: model.decimals + " " + model.symbol + asset.name: Constants.tokenIcon(model.symbol) + asset.isImage: true + + ExpandableTag { + id: communityTag + visible: !!model.communityImage + tagName: model.communityName + tagImage: model.communityImage + onTagClicked: { + Global.switchToCommunity(model.communityId); + root.closeRequested(); + } + } + + rightSideButtons: RowLayout { + StatusFlatRoundButton { + implicitWidth: 24 + implicitHeight: 24 + visible: (assetsInfoDelegate.hovered && !communityTag.hovered && model.communityId === "") + type: StatusFlatRoundButton.Type.Secondary + icon.name: "external" + icon.width: 16 + icon.height: 16 + radius: width/2 + icon.color: assetsInfoDelegate.hovered && !hovered ? Theme.palette.baseColor1 : Theme.palette.directColor1 + enabled: root.sendToAccountEnabled + onClicked: { + //TODO check this open on CoinGecko + Global.openLink(model.url); + } + } + } + onCommunityTagClicked: { + Global.switchToCommunity(model.communityId); + root.closeRequested(); + } + onClicked: { + if ((mouse.button === Qt.LeftButton) && (model.communityId !== "")) { + root.visitCommunity(model) + } else if (mouse.button === Qt.RightButton) { + Global.openMenu(delegatesActionsMenu, this, { accountAddress: model.address, communityId: model.communityId }); + } + } + } + } + + Component { + id: delegatesActionsMenu + StatusMenu { + id: contextMenu + + property string communityId + property string accountAddress: "" + + StatusAction { + text: qsTr("Visit community") + enabled: !!contextMenu.communityId + icon.name: "communities" + onTriggered: { + Global.switchToCommunity(contextMenu.communityId); + root.closeRequested(); + } + } + + StatusAction { + text: qsTr("View on CoinGecko") + enabled: false //contextMenu.communityId === "" + icon.name: "link" + onTriggered: { + //TODO: Get coingecko link for token + // let link = ""; + // Global.openLinkWithConfirmation(link, StatusQUtils.StringUtils.extractDomainFromLink(link)); + } + } + } + } +} \ No newline at end of file diff --git a/ui/imports/shared/views/profile/ProfileShowcaseCollectiblesView.qml b/ui/imports/shared/views/profile/ProfileShowcaseCollectiblesView.qml new file mode 100644 index 0000000000..25c80a130d --- /dev/null +++ b/ui/imports/shared/views/profile/ProfileShowcaseCollectiblesView.qml @@ -0,0 +1,209 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQml.Models 2.15 + +import StatusQ.Core 0.1 +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Popups.Dialog 0.1 +import StatusQ.Core.Utils 0.1 as StatusQUtils +import StatusQ.Popups 0.1 + +import shared.controls 1.0 +import utils 1.0 + +Item { + id: root + + required property string mainDisplayName + required property var collectiblesModel + required property var walletStore + + property alias cellWidth: collectiblesView.cellWidth + property alias cellHeight: collectiblesView.cellHeight + + signal closeRequested() + signal visitCommunity(var model) + + StatusBaseText { + anchors.centerIn: parent + visible: (collectiblesView.count === 0) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Theme.palette.directColor1 + text: qsTr("%1 has not shared any collectibles").arg(root.mainDisplayName) + } + StatusGridView { + id: collectiblesView + + anchors.fill: parent + topMargin: Style.current.bigPadding + bottomMargin: Style.current.bigPadding + leftMargin: Style.current.bigPadding + + visible: count + + model: root.collectiblesModel + ScrollBar.vertical: StatusScrollBar { anchors.right: parent.right; anchors.rightMargin: width / 2 } + delegate: Item { + id: delegateItem + function getCollectibleURL() { + const networkShortName = root.walletStore.getNetworkShortNames(model.chainId); + return root.walletStore.getOpenSeaCollectibleUrl(networkShortName, model.contractAddress, model.tokenId) + } + function openCollectibleURL() { + const link = getCollectibleURL(); + Global.openLinkWithConfirmation(link, StatusQUtils.StringUtils.extractDomainFromLink(link)); + } + + function openCollectionURL() { + let networkShortName = root.walletStore.getNetworkShortNames(model.chainId); + let link = root.walletStore.getOpenSeaCollectionUrl(networkShortName, model.contractAddress) + Global.openLinkWithConfirmation(link, StatusQUtils.StringUtils.extractDomainFromLink(link)); + } + + width: GridView.view.cellWidth - Style.current.padding + height: GridView.view.cellHeight - Style.current.padding + + HoverHandler { + id: hoverHandler + cursorShape: hovered ? Qt.PointingHandCursor : undefined + } + StatusRoundedImage { + id: collectibleImage + anchors.fill: parent + color: !!model.backgroundColor ? model.backgroundColor : "transparent" + radius: Style.current.radius + showLoadingIndicator: true + isLoading: image.isLoading || !model.imageUrl + image.fillMode: Image.PreserveAspectCrop + image.source: model.imageUrl ?? "" + TapHandler { + acceptedButtons: Qt.LeftButton | Qt.RightButton + onSingleTapped: { + if ((eventPoint.event.button === Qt.LeftButton) && (model.communityId !== "")) { + root.visitCommunity(model) + } else { + if (eventPoint.event.button === Qt.LeftButton) { + delegateItem.openCollectibleURL() + } else { + Global.openMenu(delegatesActionsMenu, collectibleImage, { communityId: model.communityId, url: getCollectibleURL()}); + } + } + } + } + } + + Image { + id: gradient + anchors.fill: collectibleImage + visible: hoverHandler.hovered + source: Style.png("profile/gradient") + } + + //TODO Add drop shadow + + Control { + id: amountControl + width: (amountText.contentWidth + Style.current.padding) + height: 24 + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.top: parent.top + anchors.topMargin: 12 + //TODO TBD, https://github.com/status-im/status-desktop/issues/13782 + visible: (model.userHas > 1) + + background: Rectangle { + radius: 30 + color: amountControl.hovered ? Theme.palette.indirectColor1 : Theme.palette.indirectColor2 + } + + contentItem: StatusBaseText { + id: amountText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: Style.current.asideTextFontSize + text: "x"+model.userHas + } + } + + StatusRoundButton { + implicitWidth: 24 + implicitHeight: 24 + anchors.right: parent.right + anchors.rightMargin: 12 + anchors.top: parent.top + anchors.topMargin: 12 + visible: (hoverHandler.hovered && model.communityId === "") + type: StatusFlatRoundButton.Type.Secondary + icon.name: "external" + icon.width: 16 + icon.height: 16 + radius: width/2 + icon.color: Theme.palette.directColor1 + icon.hoverColor: icon.color + color: hovered ? Theme.palette.indirectColor1 : Theme.palette.indirectColor2 + onClicked: { + delegateItem.openCollectibleURL() + } + } + + ExpandableTag { + id: expandableTag + + readonly property bool isCommunity: model.communityId != "" + readonly property bool isCollection: model.collectionUid != "" + + visible: isCommunity || (isCollection && hoverHandler.hovered) + tagHeaderText: model.name ?? "" + tagName: isCommunity ? (model.communityName ?? "") + : (model.collectionName ?? "") + tagImage: isCommunity ? (model.communityImage ?? "") + : (hovered ? "external" : "gallery") + isIcon: !isCommunity + backgroundColor: hovered ? Style.current.background : Theme.palette.indirectColor2 + expanded: hoverHandler.hovered || hovered + onTagClicked: { + if (isCommunity) { + Global.switchToCommunity(model.communityId); + root.closeRequested(); + } else { + delegateItem.openCollectionURL() + } + } + } + } + } + + Component { + id: delegatesActionsMenu + StatusMenu { + id: contextMenu + + property string url + property string communityId + + StatusAction { + text: qsTr("Visit community") + enabled: !!contextMenu.communityId + icon.name: "communities" + onTriggered: { + Global.switchToCommunity(contextMenu.communityId); + root.closeRequested(); + } + } + + StatusAction { + text: qsTr("View on Opensea") + enabled: contextMenu.communityId === "" + icon.name: "link" + onTriggered: { + Global.openLinkWithConfirmation(contextMenu.url, StatusQUtils.StringUtils.extractDomainFromLink(contextMenu.url)); + } + } + } + } +} \ No newline at end of file diff --git a/ui/imports/shared/views/profile/ProfileShowcaseCommunitiesView.qml b/ui/imports/shared/views/profile/ProfileShowcaseCommunitiesView.qml new file mode 100644 index 0000000000..b6b636c49c --- /dev/null +++ b/ui/imports/shared/views/profile/ProfileShowcaseCommunitiesView.qml @@ -0,0 +1,181 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +import AppLayouts.Communities.controls 1.0 + +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Popups 0.1 + +import utils 1.0 + +Item { + id: root + + required property string mainDisplayName + required property bool readOnly + required property var communitiesProxyModel + required property var globalAssetsModel + required property var globalCollectiblesModel + + property alias cellWidth: communitiesView.cellWidth + property alias cellHeight: communitiesView.cellHeight + + signal copyToClipboard(string text) + signal closeRequested() + + StatusBaseText { + anchors.centerIn: parent + visible: (communitiesView.count === 0) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Theme.palette.directColor1 + text: qsTr("%1 has not shared any communities").arg(root.mainDisplayName) + } + StatusGridView { + id: communitiesView + + anchors.fill: parent + topMargin: Style.current.bigPadding + bottomMargin: Style.current.bigPadding + leftMargin: Style.current.bigPadding + + visible: count + model: root.communitiesProxyModel + ScrollBar.vertical: StatusScrollBar { anchors.right: parent.right; anchors.rightMargin: width / 2 } + delegate: StatusCommunityCard { + id: profileDialogCommunityCard + readonly property var permissionsList: model.permissionsModel + readonly property bool requirementsMet: !!model.allTokenRequirementsMet ? model.allTokenRequirementsMet : false + cardSize: StatusCommunityCard.Size.Small + implicitWidth: GridView.view.cellWidth - Style.current.padding + implicitHeight: GridView.view.cellHeight - Style.current.padding + titleFontSize: 15 + communityId: model.id ?? "" + loaded: !!model.id + asset.source: model.image ?? "" + asset.isImage: !!model.image + asset.width: 32 + asset.height: 32 + name: model.name ?? "" + memberCountVisible: false + layer.enabled: hovered + border.width: hovered ? 0 : 1 + border.color: Theme.palette.baseColor2 + banner: model.bannerImageData ?? "" + descriptionFontSize: 12 + descriptionFontColor: Theme.palette.baseColor1 + description: { + switch (model.memberRole) { + case (Constants.memberRole.owner): + return qsTr("Owner"); + case (Constants.memberRole.admin) : + return qsTr("Admin"); + case (Constants.memberRole.tokenMaster): + return qsTr("Token Master"); + default: + return qsTr("Member"); + } + } + communityColor: model.color ?? "" + // Community restrictions + bottomRowComponent: (model.joined && !root.readOnly) ? + communityMembershipComponent : + !!profileDialogCommunityCard.permissionsList && profileDialogCommunityCard.permissionsList.count > 0 ? + permissionsRowComponent : null + + Component { + id: communityMembershipComponent + Item { + width: 125 + height: 24 + Rectangle { + anchors.fill: parent + radius: 20 + color: Theme.palette.successColor1 + opacity: .1 + border.color: Theme.palette.successColor1 + } + Row { + anchors.centerIn: parent + spacing: 2 + StatusIcon { + width: 16 + height: 16 + color: Theme.palette.successColor1 + icon: "tiny/checkmark" + } + StatusBaseText { + font.pixelSize: Theme.tertiaryTextFontSize + color: Theme.palette.successColor1 + text: qsTr("You’re there too") + } + } + } + } + + Component { + id: permissionsRowComponent + PermissionsRow { + hoverEnabled: false + assetsModel: root.globalAssetsModel + collectiblesModel: root.globalCollectiblesModel + model: profileDialogCommunityCard.permissionsList + requirementsMet: profileDialogCommunityCard.requirementsMet + backgroundBorderColor: Theme.palette.baseColor2 + backgroundRadius: 20 + } + } + + onClicked: { + if (root.readOnly) + return + if (mouse.button === Qt.LeftButton) { + Global.switchToCommunity(model.id); + root.closeRequested(); + } else { + Global.openMenu(delegatesActionsMenu, this, { communityId: model.id, url: Utils.getCommunityShareLink(model.id) }); + } + } + } + } + + Component { + id: delegatesActionsMenu + StatusMenu { + id: contextMenu + + property string url + property string communityId + + StatusAction { + text: qsTr("Visit community") + icon.name: "arrow-right" + onTriggered: { + Global.switchToCommunity(contextMenu.communityId); + root.closeRequested(); + } + } + + StatusAction { + text: qsTr("Invite People") + icon.name: "share-ios" + onTriggered: { + Global.openInviteFriendsToCommunityByIdPopup(contextMenu.communityId, null); + } + } + + StatusSuccessAction { + id: copyAddressAction + successText: qsTr("Copied") + text: qsTr("Copy link to community") + icon.name: "copy" + onTriggered: { + root.copyToClipboard(contextMenu.url) + } + } + } + } +} diff --git a/ui/imports/shared/views/profile/ProfileShowcaseSocialLinksView.qml b/ui/imports/shared/views/profile/ProfileShowcaseSocialLinksView.qml new file mode 100644 index 0000000000..be761e315e --- /dev/null +++ b/ui/imports/shared/views/profile/ProfileShowcaseSocialLinksView.qml @@ -0,0 +1,134 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Controls 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Core.Utils 0.1 as StatusQUtils +import StatusQ.Popups 0.1 + +import shared.controls.delegates 1.0 +import utils 1.0 + +Item { + id: root + + required property string mainDisplayName + required property var socialLinksModel + + property alias cellWidth: webView.cellWidth + property alias cellHeight: webView.cellHeight + + signal copyToClipboard(string text) + + StatusBaseText { + anchors.centerIn: parent + visible: (webView.count === 0) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Theme.palette.directColor1 + text: qsTr("%1 has not shared any links").arg(root.mainDisplayName) + } + StatusGridView { + id: webView + + anchors.fill: parent + topMargin: Style.current.bigPadding + bottomMargin: Style.current.bigPadding + leftMargin: Style.current.bigPadding + + visible: count + + model: root.socialLinksModel + ScrollBar.vertical: StatusScrollBar { anchors.right: parent.right; anchors.rightMargin: width / 2 } + delegate: InfoCard { + id: socialLinksInfoDelegate + readonly property int linkType: ProfileUtils.linkTextToType(model.text) + width: GridView.view.cellWidth - Style.current.padding + height: GridView.view.cellHeight - Style.current.padding + title: !!ProfileUtils.linkTypeToText(linkType) ? ProfileUtils.linkTypeToText(linkType) : model.text + asset.bgColor: ProfileUtils.linkTypeBgColor(linkType) + asset.name: ProfileUtils.linkTypeToIcon(linkType) + asset.color: ProfileUtils.linkTypeColor(linkType) + asset.width: 20 + asset.height: 20 + asset.bgWidth: 32 + asset.bgHeight: 32 + asset.isImage: false + subTitle: model.url + onClicked: { + if (mouse.button === Qt.RightButton) { + Global.openMenu(delegatesActionsMenu, this, { url: model.url }); + } + } + highlight: hovered + rightSideButtons: RowLayout { + StatusFlatRoundButton { + implicitWidth: 24 + implicitHeight: 24 + type: StatusFlatRoundButton.Type.Secondary + icon.name: "external" + icon.width: 16 + icon.height: 16 + radius: width/2 + highlighted: true + visible: socialLinksInfoDelegate.hovered + icon.color: socialLinksInfoDelegate.hovered && !hovered ? Theme.palette.baseColor1 : Theme.palette.directColor1 + + onClicked: { + Global.openLinkWithConfirmation(model.url, StatusQUtils.StringUtils.extractDomainFromLink(model.url)); + } + } + } + } + } + + Item { + width: 279 + height: 32 + anchors.bottom: parent.bottom + anchors.bottomMargin: 20 + anchors.horizontalCenter: parent.horizontalCenter + visible: (webView.count > 0) + Rectangle { + anchors.fill: parent + color: Style.current.background + radius: 30 + border.color: Theme.palette.baseColor2 + } + Row { + anchors.centerIn: parent + spacing: 4 + StatusIcon { + width: 16 + height: 16 + icon: "info" + color: Theme.palette.directColor1 + } + StatusBaseText { + font.pixelSize: 13 + text: qsTr("Social handles and links are unverified") + } + } + } + + Component { + id: delegatesActionsMenu + StatusMenu { + id: contextMenu + + property string url + + StatusSuccessAction { + id: copyAddressAction + successText: qsTr("Copied") + text: qsTr("Copy link") + icon.name: "copy" + onTriggered: { + root.copyToClipboard(contextMenu.url); + } + } + } + } +} diff --git a/ui/imports/shared/views/profile/ProfileShowcaseView.qml b/ui/imports/shared/views/profile/ProfileShowcaseView.qml index c6e31be1c2..be501598f8 100644 --- a/ui/imports/shared/views/profile/ProfileShowcaseView.qml +++ b/ui/imports/shared/views/profile/ProfileShowcaseView.qml @@ -1,6 +1,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import QtQml.Models 2.15 import StatusQ 0.1 import StatusQ.Core 0.1 @@ -13,9 +14,6 @@ import StatusQ.Core.Utils 0.1 as StatusQUtils import SortFilterProxyModel 0.2 import utils 1.0 -import shared.controls 1.0 // Timer -import shared.controls.delegates 1.0 -import AppLayouts.Communities.controls 1.0 Control { id: root @@ -33,10 +31,11 @@ Control { property var globalAssetsModel property var globalCollectiblesModel + property var walletStore + required property string mainDisplayName required property bool readOnly required property bool sendToAccountEnabled - property var enabledNetworks signal closeRequested() signal copyToClipboard(string text) @@ -51,8 +50,6 @@ Control { property int delegateHeightS: 152 property int delegateWidthM: 202 property int delegateHeightM: 160 - - readonly property string copyLiteral: qsTr("Copy") } component PositionSFPM: SortFilterProxyModel { @@ -115,594 +112,197 @@ Control { anchors.top: parent.top height: 1 color: Theme.palette.baseColor2 - visible: ((communitiesView.contentY + accountsView.contentY + collectiblesView.contentY - /*+ assetsView.contentY*/ + webView.contentY) > Style.current.xlPadding) } } contentItem: StackLayout { id: stackLayout - // communities + anchors.fill:parent - ColumnLayout { + + ProfileShowcaseCommunitiesView { width: parent.width height: parent.height - 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 has not shared any communities").arg(root.mainDisplayName) - } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.topMargin: (communitiesView.contentY > Style.current.padding) ? 1 : Style.current.padding - Behavior on Layout.topMargin { NumberAnimation { duration: 50 } } - clip: true - StatusGridView { - id: communitiesView - width: 606 - height: parent.height - anchors.top: parent.top - anchors.topMargin: Style.current.halfPadding - anchors.bottom: parent.bottom - anchors.bottomMargin: Style.current.halfPadding - anchors.horizontalCenter: parent.horizontalCenter - anchors.horizontalCenterOffset: Style.current.halfPadding - clip: false - cellWidth: d.delegateWidthM - cellHeight: d.delegateHeightM - visible: count - model: communitiesProxyModel - ScrollBar.vertical: StatusScrollBar { } - delegate: StatusCommunityCard { - id: profileDialogCommunityCard - readonly property var permissionsList: model.permissionsModel //TODO: Add permissions model in the community model - readonly property bool requirementsMet: !!model.allTokenRequirementsMet ? model.allTokenRequirementsMet : false - cardSize: StatusCommunityCard.Size.Small - width: GridView.view.cellWidth - Style.current.padding - height: GridView.view.cellHeight - Style.current.padding - titleFontSize: 15 - descriptionFontSize: 12 - communityId: model.id ?? "" - loaded: !!model.id - asset.source: model.image ?? "" - asset.isImage: !!model.image - asset.width: 32 - asset.height: 32 - name: model.name ?? "" - memberCountVisible: false - layer.enabled: hovered - border.width: hovered ? 0 : 1 - border.color: Theme.palette.baseColor2 - descriptionFontColor: Theme.palette.baseColor1 - description: { - switch (model.memberRole) { - case (Constants.memberRole.owner): - return qsTr("Owner"); - case (Constants.memberRole.admin) : - return qsTr("Admin"); - case (Constants.memberRole.tokenMaster): - return qsTr("Token Master"); - default: - return qsTr("Member"); - } - } - communityColor: model.color ?? "" - // Community restrictions - bottomRowComponent: model.memberRole ?? -1 === Constants.memberRole.tokenMaster ? - communityMembershipComponent : - !!profileDialogCommunityCard.permissionsList && profileDialogCommunityCard.permissionsList.count > 0 ? - permissionsRowComponent : null - Component { - id: communityMembershipComponent - Item { - width: 125 - height: 24 - Rectangle { - anchors.fill: parent - radius: 20 - color: Theme.palette.successColor1 - opacity: .1 - border.color: Theme.palette.successColor1 - } - Row { - anchors.centerIn: parent - spacing: 2 - StatusIcon { - width: 16 - height: 16 - color: Theme.palette.successColor1 - icon: "tiny/checkmark" - } - StatusBaseText { - font.pixelSize: Theme.tertiaryTextFontSize - color: Theme.palette.successColor1 - text: qsTr("You’re there too") - } - } - } - } + cellWidth: d.delegateWidthM + cellHeight: d.delegateHeightM - Component { - id: permissionsRowComponent - PermissionsRow { - hoverEnabled: false - assetsModel: root.globalAssetsModel - collectiblesModel: root.globalCollectiblesModel - model: profileDialogCommunityCard.permissionsList - requirementsMet: profileDialogCommunityCard.requirementsMet - } - } + mainDisplayName: root.mainDisplayName + readOnly: root.readOnly + globalAssetsModel: root.globalAssetsModel + globalCollectiblesModel: root.globalCollectiblesModel - onClicked: { - if (root.readOnly) - return - root.closeRequested() - Global.switchToCommunity(model.id) - //TODO https://github.com/status-im/status-desktop/issues/13702 - //on right click add menu - } - } - } - } + communitiesProxyModel: communitiesProxyModel + + onCloseRequested: root.closeRequested() + onCopyToClipboard: root.copyToClipboard(text) } - // wallets/accounts - ColumnLayout { + ProfileShowcaseAccountsView { width: parent.width height: parent.height - 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 has not shared any accounts").arg(root.mainDisplayName) - } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.topMargin: (accountsView.contentY > Style.current.padding) ? 1 : Style.current.padding - Behavior on Layout.topMargin { NumberAnimation { duration: 50 } } - clip: true - StatusGridView { - id: accountsView - width: 606 - height: parent.height - anchors.top: parent.top - anchors.topMargin: Style.current.halfPadding - anchors.bottom: parent.bottom - anchors.bottomMargin: Style.current.halfPadding - anchors.horizontalCenter: parent.horizontalCenter - cellWidth: d.delegateWidthM - cellHeight: d.delegateHeightM - visible: count - clip: false - ScrollBar.vertical: StatusScrollBar { } - model: accountsProxyModel - delegate: InfoCard { - implicitWidth: GridView.view.cellWidth - Style.current.padding - implicitHeight: GridView.view.cellHeight - Style.current.padding - title: model.name - subTitle: StatusQUtils.Utils.elideText(model.address, 6, 4).replace("0x", "0×") - asset.color: Utils.getColorForId(model.colorId) - 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 - enabledNetworks: root.enabledNetworks - rightSideButtons: RowLayout { - StatusFlatRoundButton { - Layout.preferredWidth: 32 - Layout.preferredHeight: 32 - type: StatusFlatRoundButton.Type.Secondary - icon.name: "send" - icon.color: Theme.palette.baseColor1 - enabled: root.sendToAccountEnabled - onClicked: { - Global.openSendModal(model.address) - } - } - StatusFlatRoundButton { - Layout.preferredWidth: 32 - Layout.preferredHeight: 32 - type: StatusFlatRoundButton.Type.Secondary - icon.name: "more" - icon.color: Theme.palette.baseColor1 - onClicked: { - //TODO https://github.com/status-im/status-desktop/issues/13702 - //open menu - } - } - } - } - //TODO remove when https://github.com/status-im/status-desktop/issues/13702 - // delegate: StatusListItem { - // id: accountDelegate - // property bool saved: { - // let savedAddress = root.walletStore.getSavedAddress(model.address) - // if (savedAddress.name !== "") - // return true - // if (!!root.walletStore.lastCreatedSavedAddress) { - // if (root.walletStore.lastCreatedSavedAddress.address.toLowerCase() === model.address.toLowerCase()) { - // return !!root.walletStore.lastCreatedSavedAddress.error - // } - // } + mainDisplayName: root.mainDisplayName + sendToAccountEnabled: root.sendToAccountEnabled + accountsModel: accountsProxyModel + walletStore: root.walletStore - // return false - // } - // 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: Utils.getColorForId(model.colorId) - // 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 - // 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: { - // // From here, we should just run add saved address popup - // Global.openAddEditSavedAddressesPopup({ - // addAddress: true, - // address: model.address - // }) - // } - // }, - // StatusFlatRoundButton { - // anchors.verticalCenter: parent.verticalCenter - // type: StatusFlatRoundButton.Type.Secondary - // icon.name: "send" - // tooltip.text: qsTr("Send") - // enabled: root.sendToAccountEnabled - // onClicked: { - // 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); - // } - // } - // ] - // } - } + cellWidth: d.delegateWidthM + cellHeight: d.delegateHeightM + + onCopyToClipboard: root.copyToClipboard(text) + } + + ProfileShowcaseCollectiblesView { + width: parent.width + height: parent.height + + cellWidth: d.delegateWidthS + cellHeight: d.delegateHeightS + + mainDisplayName: root.mainDisplayName + collectiblesModel: collectiblesProxyModel + walletStore: root.walletStore + + onCloseRequested: root.closeRequested() + onVisitCommunity: { + Global.openPopup(visitComunityPopupComponent, {communityId: model.communityId, communityName: model.communityName, + communityLogo: model.communityImage, tokenName: model.name, + tokenImage: model.imageUrl, isAssetType: false }); } } - // 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 has not shared any collectibles").arg(root.mainDisplayName) - } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.topMargin: (collectiblesView.contentY > Style.current.padding) ? 1 : Style.current.padding - Behavior on Layout.topMargin { NumberAnimation { duration: 50 } } - clip: true - StatusGridView { - id: collectiblesView - width: 608 - height: parent.height - anchors.top: parent.top - anchors.topMargin: Style.current.halfPadding - anchors.bottom: parent.bottom - anchors.bottomMargin: Style.current.halfPadding - anchors.horizontalCenter: parent.horizontalCenter - anchors.horizontalCenterOffset: Style.current.halfPadding - cellWidth: d.delegateWidthS - cellHeight: d.delegateHeightS - visible: count - clip: false - // TODO Issue #11637: Dedicated controller for user's list of collectibles (no watch-only entries) - model: collectiblesProxyModel - ScrollBar.vertical: StatusScrollBar { } - delegate: StatusRoundedImage { - width: GridView.view.cellWidth - Style.current.padding - height: GridView.view.cellHeight - Style.current.padding - border.width: 1 - border.color: Theme.palette.directColor7 - color: !!model.backgroundColor ? model.backgroundColor : "transparent" - radius: Style.current.radius - showLoadingIndicator: true - isLoading: image.isLoading || !model.imageUrl - image.fillMode: Image.PreserveAspectCrop - image.source: model.imageUrl ?? "" + // ProfileShowcaseAssetsView { + // width: parent.width + // height: parent.height - Control { - width: (amountText.contentWidth + Style.current.padding) - height: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.top: parent.top - anchors.topMargin: 12 - //TODO TBD, we need to show the number if the user has more than 1 of each collectible - //not sure how to name the role - visible: (model.userHas > 1) + // mainDisplayName: root.mainDisplayName + // assetsModel: assetsProxyModel + // sendToAccountEnabled: root.sendToAccountEnabled + // delegatesActionsMenu: delegatesActionsMenu - background: Rectangle { - radius: 30 - color: Theme.palette.indirectColor2 - } + // cellHeight: d.delegateHeightS + // cellWidth: d.delegateWidthS - contentItem: StatusBaseText { - id: amountText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.pixelSize: Style.current.asideTextFontSize - text: "x"+model.userHas - } - } - - Control { - width: 24 - height: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 12 - visible: !!model.communityImage - - background: Rectangle { - radius: parent.width/2 - color: Theme.palette.indirectColor2 - } - contentItem: StatusRoundedImage { - anchors.fill: parent - anchors.margins: 4 - image.fillMode: Image.PreserveAspectFit - image.source: model.communityImage - } - } - HoverHandler { - id: hhandler - cursorShape: hovered ? Qt.PointingHandCursor : undefined - } - - TapHandler { - onSingleTapped: { - //TODO https://github.com/status-im/status-desktop/issues/13702 - 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 has not shared any assets").arg(root.mainDisplayName) - // } - // Item { - // Layout.fillWidth: true - // Layout.fillHeight: true - // Layout.topMargin: (assetsView.contentY > Style.current.padding) ? 1 : Style.current.padding - // Behavior on Layout.topMargin { NumberAnimation { duration: 50 } } - // clip: true - // StatusGridView { - // id: assetsView - // width: 608 - // height: parent.height - // anchors.top: parent.top - // anchors.topMargin: Style.current.halfPadding - // anchors.bottom: parent.bottom - // anchors.bottomMargin: Style.current.halfPadding - // anchors.horizontalCenter: parent.horizontalCenter - // anchors.horizontalCenterOffset: Style.current.halfPadding - // cellWidth: d.delegateWidthS - // cellHeight: d.delegateHeightS - // visible: count - // clip: false - // model: assetsProxyModel - // ScrollBar.vertical: StatusScrollBar { } - // delegate: InfoCard { - // width: GridView.view.cellWidth - Style.current.padding - // height: GridView.view.cellHeight - Style.current.padding - // title: model.name - // subTitle: LocaleUtils.currencyAmountToLocaleString(model.enabledNetworkBalance) - // asset.name: Constants.tokenIcon(model.symbol) - // asset.isImage: true - // tagIcon: !!model.communityImage ? model.communityImage : "" - // rightSideButtons: RowLayout { - // StatusFlatRoundButton { - // implicitWidth: 24 - // implicitHeight: 24 - // type: StatusFlatRoundButton.Type.Secondary - // icon.name: "external" - // icon.width: 16 - // icon.height: 16 - // icon.color: Theme.palette.baseColor1 - // enabled: root.sendToAccountEnabled - // onClicked: { - // //TODO https://github.com/status-im/status-desktop/issues/13702 - // //Global.openSendModal(model.address) - // //on right click open menu - // } - // } - // } - // } - // //TODO remove when https://github.com/status-im/status-desktop/issues/13702 - // // 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: model.name - // // //subTitle: 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: Constants.tokenIcon(model.symbol) - // // } - // // ] - // // onClicked: { - // // if (root.readOnly) - // // return - // // // TODO what to do here? - // // } - // // } - // } - // } + // onCloseRequested: root.closeRequested() + // onVisitCommunity: { + // Global.openPopup(visitComunityPopupComponent, {communityId: model.communityId, communityName: model.communityName, + // communityLogo: model.communityImage, tokenName: model.name, + // tokenImage: Constants.tokenIcon(model.symbol), isAssetType: false }); + // } // } - // social links - ColumnLayout { - StatusBaseText { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.alignment: Qt.AlignCenter - visible: webView.count == 0 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - color: Theme.palette.directColor1 - text: qsTr("%1 has not shared any links").arg(root.mainDisplayName) - } + ProfileShowcaseSocialLinksView { + width: parent.width + height: parent.height - Item { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.topMargin: (webView.contentY > Style.current.padding) ? 1 : Style.current.padding - Behavior on Layout.topMargin { NumberAnimation { duration: 50 } } - clip: true + cellWidth: d.delegateWidthS + cellHeight: d.delegateHeightS - StatusGridView { - id: webView - width: 608 - height: parent.height - anchors.top: parent.top - anchors.topMargin: Style.current.halfPadding - anchors.bottom: parent.bottom - anchors.bottomMargin: Style.current.halfPadding - anchors.horizontalCenter: parent.horizontalCenter - anchors.horizontalCenterOffset: Style.current.halfPadding - cellWidth: d.delegateWidthS - cellHeight: d.delegateHeightS - visible: count - clip: false - model: socialLinksProxyModel - ScrollBar.vertical: StatusScrollBar { } - delegate: InfoCard { - readonly property int linkType: ProfileUtils.linkTextToType(model.text) - width: GridView.view.cellWidth - Style.current.padding - height: GridView.view.cellHeight - Style.current.padding - title: ProfileUtils.linkTypeToText(linkType) - asset.bgColor: Style.current.translucentBlue - asset.name: ProfileUtils.linkTypeToIcon(linkType) - asset.color: ProfileUtils.linkTypeColor(linkType) - asset.width: 20 - asset.height: 20 - asset.bgWidth: 32 - asset.bgHeight: 32 - asset.isImage: false - subTitle: ProfileUtils.stripSocialLinkPrefix(model.url, linkType) - rightSideButtons: RowLayout { - StatusFlatRoundButton { - implicitWidth: 24 - implicitHeight: 24 - type: StatusFlatRoundButton.Type.Secondary - icon.name: "external" - icon.width: 16 - icon.height: 16 - icon.color: Theme.palette.baseColor1 - enabled: root.sendToAccountEnabled - onClicked: { - //TODO https://github.com/status-im/status-desktop/issues/13702 - //on right click open menu + mainDisplayName: root.mainDisplayName + socialLinksModel: socialLinksProxyModel + + onCopyToClipboard: root.copyToClipboard(text) + } + } + + Component { + id: visitComunityPopupComponent + StatusDialog { + id: visitComunityPopup + // Community related props: + property string communityId + property string communityName + property string communityLogo + + // Token related props: + property string tokenName + property string tokenImage + property bool isAssetType: false + + width: 521 // by design + padding: 0 + + contentItem: StatusScrollView { + id: scrollView + padding: Style.current.padding + contentWidth: availableWidth + + ColumnLayout { + width: scrollView.availableWidth + spacing: Style.current.padding + + StatusBaseText { + Layout.fillWidth: true + + text: visitComunityPopup.isAssetType ? qsTr("%1 is a community minted asset. Would you like to visit the community that minted it?").arg(visitComunityPopup.tokenName) : + qsTr("%1 is a community minted collectible. Would you like to visit the community that minted it?").arg(visitComunityPopup.tokenName) + textFormat: Text.RichText + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + lineHeight: 1.2 + } + + // Navigate to community button + StatusListItem { + Layout.fillWidth: true + Layout.bottomMargin: Style.current.halfPadding + + title: visitComunityPopup.communityName + border.color: Theme.palette.baseColor2 + asset.name: visitComunityPopup.communityLogo + asset.isImage: true + asset.isLetterIdenticon: !asset.name + components: [ + RowLayout { + StatusIcon { + Layout.alignment: Qt.AlignVCenter + icon: "arrow-right" + color: Theme.palette.primaryColor1 + } + + StatusBaseText { + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: Style.current.padding + + text: visitComunityPopup.tokenName + font.pixelSize: Style.current.additionalTextSize + color: Theme.palette.primaryColor1 } } + ] + + onClicked: { + Global.switchToCommunity(visitComunityPopup.communityId); + visitComunityPopup.close(); + root.closeRequested(); } } } - Item { - width: 279 - height: 32 - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - Rectangle { - anchors.fill: parent - color: Style.current.background - radius: 30 - border.color: Theme.palette.baseColor2 - } - Row { - anchors.centerIn: parent - spacing: 4 - StatusIcon { - width: 16 - height: 16 - icon: "info" - color: Theme.palette.directColor1 - } + } - StatusBaseText { - font.pixelSize: 13 - text: qsTr("Social handles and links are unverified") + header: StatusDialogHeader { + leftComponent: StatusRoundedImage { + Layout.alignment: Qt.AlignHCenter + Layout.margins: Style.current.padding + Layout.preferredWidth: 68 + Layout.preferredHeight: Layout.preferredWidth + radius: visitComunityPopup.isAssetType ? width / 2 : 8 + image.source: visitComunityPopup.tokenImage + showLoadingIndicator: false + image.fillMode: Image.PreserveAspectCrop + } + headline.title: visitComunityPopup.tokenName + headline.subtitle: qsTr("Minted by %1").arg(visitComunityPopup.communityName) + actions.closeButton.onClicked: { visitComunityPopup.close(); } + } + + footer: StatusDialogFooter { + spacing: Style.current.padding + rightButtons: ObjectModel { + StatusFlatButton { + text: qsTr("Cancel") + onClicked: { + visitComunityPopup.close(); } } } diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index 9cbca6857b..4d0628d975 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -46,6 +46,7 @@ QtObject { signal markAsUntrustedRequested(string publicKey, var contactDetails) signal removeContactRequested(string publicKey, var contactDetails) signal openInviteFriendsToCommunityPopup(var community, var communitySectionModule, var cb) + signal openInviteFriendsToCommunityByIdPopup(string communityId, var cb) signal openIncomingIDRequestPopup(string publicKey, var contactDetails, var cb) signal openOutgoingIDRequestPopup(string publicKey, var contactDetails, var cb) signal openDeleteMessagePopup(string messageId, var messageStore)