feat: New design flows to integrate Revealing addresses...

... when joining Community functionality

Closes #11138
This commit is contained in:
Lukáš Tinkl 2023-07-04 17:11:41 +02:00 committed by Lukáš Tinkl
parent fe94bd0c69
commit 02e40adfca
53 changed files with 1673 additions and 178 deletions

View File

@ -249,7 +249,7 @@ QtObject:
let addressesArray = map(parseJson(addressesToShare).getElems(), proc(x:JsonNode):string = x.getStr()) let addressesArray = map(parseJson(addressesToShare).getElems(), proc(x:JsonNode):string = x.getStr())
self.delegate.requestToJoinCommunityWithAuthentication(ensName, addressesArray, airdropAddress) self.delegate.requestToJoinCommunityWithAuthentication(ensName, addressesArray, airdropAddress)
except Exception as e: except Exception as e:
echo "Error requesting to join community with authetication and shared addresses: ", e.msg echo "Error requesting to join community with authentication and shared addresses: ", e.msg
proc joinGroupChatFromInvitation*(self: View, groupName: string, chatId: string, adminPK: string) {.slot.} = proc joinGroupChatFromInvitation*(self: View, groupName: string, chatId: string, adminPK: string) {.slot.} =
self.delegate.joinGroupChatFromInvitation(groupName, chatId, adminPK) self.delegate.joinGroupChatFromInvitation(groupName, chatId, adminPK)
@ -421,4 +421,4 @@ QtObject:
read = getAllTokenRequirementsMet read = getAllTokenRequirementsMet
notify = allTokenRequirementsMetChanged notify = allTokenRequirementsMetChanged
proc userAuthenticationCanceled*(self: View) {.signal.} proc userAuthenticationCanceled*(self: View) {.signal.}

View File

@ -57,7 +57,6 @@ QtObject:
let enumRole = role.ModelRole let enumRole = role.ModelRole
case enumRole: case enumRole:
of ModelRole.Key: of ModelRole.Key:
if item.getType() == ord(TokenType.ENS): if item.getType() == ord(TokenType.ENS):
result = newQVariant(item.getEnsPattern()) result = newQVariant(item.getEnsPattern())
else: else:
@ -69,7 +68,7 @@ QtObject:
of ModelRole.ShortName: of ModelRole.ShortName:
result = newQVariant(item.getSymbol()) result = newQVariant(item.getSymbol())
of ModelRole.Name: of ModelRole.Name:
result = newQVariant(item.getSymbol()) result = newQVariant(item.getName())
of ModelRole.Amount: of ModelRole.Amount:
result = newQVariant(item.getAmount()) result = newQVariant(item.getAmount())
of ModelRole.CriteriaMet: of ModelRole.CriteriaMet:

View File

@ -162,7 +162,7 @@ QtObject:
of "description": result = $item.getDescription() of "description": result = $item.getDescription()
of "assetWebsiteUrl": result = $item.getAssetWebsiteUrl() of "assetWebsiteUrl": result = $item.getAssetWebsiteUrl()
of "builtOn": result = $item.getBuiltOn() of "builtOn": result = $item.getBuiltOn()
of "Address": result = $item.getAddress() of "address": result = $item.getAddress()
of "marketCap": result = $item.getMarketCap() of "marketCap": result = $item.getMarketCap()
of "highDay": result = $item.getHighDay() of "highDay": result = $item.getHighDay()
of "lowDay": result = $item.getLowDay() of "lowDay": result = $item.getLowDay()

View File

@ -89,7 +89,7 @@ proc buildTokenPermissionItem*(tokenPermission: CommunityTokenPermissionDto): To
tokenCriteriaItems, tokenCriteriaItems,
tokenPermissionChatListItems, tokenPermissionChatListItems,
tokenPermission.isPrivate, tokenPermission.isPrivate,
false # allTokenCriteriaMet will be update by a call to checkPermissinosToJoin false # allTokenCriteriaMet will be updated by a call to checkPermissionsToJoin
) )
return tokenPermissionItem return tokenPermissionItem

View File

@ -253,6 +253,10 @@ ListModel {
title: "RemotelyDestructPopup" title: "RemotelyDestructPopup"
section: "Popups" section: "Popups"
} }
ListElement {
title: "SharedAddressesPopup"
section: "Popups"
}
ListElement { ListElement {
title: "AlertPopup" title: "AlertPopup"
section: "Popups" section: "Popups"

View File

@ -242,6 +242,13 @@
], ],
"EditOwnerTokenView": [ "EditOwnerTokenView": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?type=design&node-id=34794-590207&mode=design&t=ZnwK9yenS5oSgwws-0" "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?type=design&node-id=34794-590207&mode=design&t=ZnwK9yenS5oSgwws-0"
],
"CommunityIntroDialog": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=31461%3A563897&mode=dev"
],
"SharedAddressesPopup": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=31461%3A564367&mode=dev",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=31461%3A563905&mode=dev",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=31461%3A579875&mode=dev"
] ]
} }

View File

@ -44,10 +44,16 @@ SplitView {
5. 🚗 consectetur adipiscing elit 5. 🚗 consectetur adipiscing elit
Nemo enim 😋 ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.".arg(dialog.name) Nemo enim 😋 ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.".arg(dialog.name)
loginType: ctrlLoginType.currentIndex
onJoined: logs.logEvent("CommunityIntroDialog::onJoined()") walletAccountsModel: WalletAccountsModel {}
permissionsModel: dialog.accessType === Constants.communityChatOnRequestAccess ? PermissionsModel.complexPermissionsModel
: null
assetsModel: AssetsModel {}
collectiblesModel: CollectiblesModel {}
onJoined: logs.logEvent("CommunityIntroDialog::onJoined", ["airdropAddress", "sharedAddresses"], arguments)
onCancelMembershipRequest: logs.logEvent("CommunityIntroDialog::onCancelMembershipRequest()") onCancelMembershipRequest: logs.logEvent("CommunityIntroDialog::onCancelMembershipRequest()")
} }
} }
@ -146,6 +152,20 @@ Nemo enim 😋 ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
} }
} }
ColumnLayout {
visible: dialog.accessType == Constants.communityChatOnRequestAccess && !dialog.isInvitationPending
Label {
Layout.fillWidth: true
text: "Login type"
}
ComboBox {
id: ctrlLoginType
Layout.fillWidth: true
model: ["Password","Biometrics","Keycard"]
}
}
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
} }

View File

@ -4,6 +4,8 @@ import QtQuick.Layouts 1.14
import StatusQ.Core.Utils 0.1 import StatusQ.Core.Utils 0.1
import AppLayouts.Communities.controls 1.0
Flickable { Flickable {
id: root id: root

View File

@ -18,7 +18,7 @@ SplitView {
id: d id: d
property string name: "Uniswap" property string name: "Uniswap"
property string channelName: "#vip" property string channelName: "vip"
property bool joinCommunity: true // Otherwise, enter channel property bool joinCommunity: true // Otherwise, enter channel
property bool requirementsMet: true property bool requirementsMet: true
property bool isInvitationPending: false property bool isInvitationPending: false
@ -40,8 +40,6 @@ SplitView {
orientation: Qt.Vertical orientation: Qt.Vertical
SplitView.fillWidth: true SplitView.fillWidth: true
Item { Item {
SplitView.fillWidth: true SplitView.fillWidth: true
SplitView.fillHeight: true SplitView.fillHeight: true

View File

@ -59,6 +59,7 @@ SplitView {
readonly property string image: ModelsData.icons.socks readonly property string image: ModelsData.icons.socks
readonly property string color: "red" readonly property string color: "red"
readonly property bool owner: isOwnerCheckBox.checked readonly property bool owner: isOwnerCheckBox.checked
readonly property bool admin: isAdminCheckBox.checked
} }
function log(method, index) { function log(method, index) {
@ -94,6 +95,12 @@ SplitView {
text: "Is owner" text: "Is owner"
} }
CheckBox {
id: isAdminCheckBox
text: "Is admin"
}
CheckBox { CheckBox {
id: emptyModelCheckBox id: emptyModelCheckBox

View File

@ -0,0 +1,155 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml 2.15
import Storybook 1.0
import Models 1.0
import AppLayouts.Communities.popups 1.0
SplitView {
id: root
Logs { id: logs }
ListModel {
id: emptyModel
}
Component {
id: dlgComponent
SharedAddressesPopup {
//anchors.centerIn: parent
isEditMode: ctrlEditMode.checked
communityName: "Decentraland"
communityIcon: ModelsData.assets.uni
loginType: ctrlLoginType.currentIndex
walletAccountsModel: WalletAccountsModel {}
permissionsModel: {
if (ctrlPermissions.checked && ctrlTokenGatedChannels.checked)
return PermissionsModel.complexPermissionsModel
if (ctrlPermissions.checked)
return PermissionsModel.permissionsModel
if (ctrlTokenGatedChannels.checked)
return PermissionsModel.channelsOnlyPermissionsModel
return emptyModel
}
assetsModel: AssetsModel {}
collectiblesModel: CollectiblesModel {}
visible: true
onShareSelectedAddressesClicked: logs.logEvent("::shareSelectedAddressesClicked", ["airdropAddress", "sharedAddresses"], arguments)
onClosed: destroy()
}
}
property var dialog
function createAndOpenDialog() {
dialog = dlgComponent.createObject(root)
dialog.open()
}
Component.onCompleted: createAndOpenDialog()
SplitView {
orientation: Qt.Vertical
SplitView.fillWidth: true
Pane {
id: pane
SplitView.fillWidth: true
SplitView.fillHeight: true
PopupBackground {
anchors.fill: parent
}
Button {
anchors.centerIn: parent
text: "Reopen"
onClicked: createAndOpenDialog()
}
}
LogsAndControlsPanel {
id: logsAndControlsPanel
SplitView.minimumHeight: 100
SplitView.preferredHeight: 150
logsView.logText: logs.logText
}
}
Pane {
SplitView.minimumWidth: 300
SplitView.preferredWidth: 300
ColumnLayout {
anchors.fill: parent
Switch {
id: ctrlPermissions
text: "With permissions"
checked: true
}
Switch {
id: ctrlTokenGatedChannels
text: "With token gated channels"
checked: true
}
Switch {
id: ctrlEditMode
text: "Edit mode"
}
ColumnLayout {
visible: ctrlEditMode.checked
Label {
Layout.fillWidth: true
text: "Login type"
}
ComboBox {
id: ctrlLoginType
Layout.fillWidth: true
model: ["Password","Biometrics","Keycard"]
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: "lightgray"
}
Text {
text: "Info"
font.bold: true
}
Text {
Layout.fillWidth: true
text: "Shared addresses: %1".arg(!!dialog ? dialog.selectedSharedAddresses.join(";") : "")
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
Text {
Layout.fillWidth: true
text: "Airdrop address: %1".arg(!!dialog ? dialog.selectedAirdropAddress : "")
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
}
Item {
Layout.fillHeight: true
}
}
}
}

View File

@ -65,8 +65,8 @@ SplitView {
errorText: errorTextField.text errorText: errorTextField.text
totalFeeText: "0.01 ETH ($265.43)" totalFeeText: "0.01 ETH ($265.43)"
onSignTransactionClicked: logs.logEvent("SignTokenTransactionsPopup::onSignTransactionClicked") onSignTransactionClicked: logs.logEvent("SignMultiTokenTransactionsPopup::onSignTransactionClicked")
onCancelClicked: logs.logEvent("SignTokenTransactionsPopup::onCancelClicked") onCancelClicked: logs.logEvent("SignMultiTokenTransactionsPopup::onCancelClicked")
} }
} }

View File

@ -100,7 +100,7 @@ SplitView {
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: "Network name" text: "Network fee"
} }
TextField { TextField {

View File

@ -50,7 +50,7 @@ SplitView {
id: editor id: editor
isOnlyChannelPanelEditor: true isOnlyChannelPanelEditor: true
channelName: "#vip" channelName: "vip"
joinCommunity: false joinCommunity: false
} }
} }

View File

@ -63,7 +63,7 @@ ListModel {
iconSource: ModelsData.assets.snt, iconSource: ModelsData.assets.snt,
name: "snt", name: "snt",
shortName: "snt", shortName: "snt",
symbol: "snt", symbol: "SNT",
category: TokenCategories.Category.General, category: TokenCategories.Category.General,
communityId: "" communityId: ""
} }

View File

@ -8,7 +8,7 @@ ListModel {
key: "Anniversary", key: "Anniversary",
iconSource: ModelsData.collectibles.anniversary, iconSource: ModelsData.collectibles.anniversary,
name: "Anniversary", name: "Anniversary",
symbol: "Anniversary", symbol: "ANN",
category: TokenCategories.Category.Community, category: TokenCategories.Category.Community,
imageUrl: ModelsData.collectibles.anniversary, imageUrl: ModelsData.collectibles.anniversary,
id: 1767698, id: 1767698,
@ -18,7 +18,7 @@ ListModel {
key: "Anniversary2", key: "Anniversary2",
iconSource: ModelsData.collectibles.anniversary, iconSource: ModelsData.collectibles.anniversary,
name: "Anniversary2", name: "Anniversary2",
symbol: "Anniversary2", symbol: "ANN2",
category: TokenCategories.Category.Community, category: TokenCategories.Category.Community,
imageUrl: ModelsData.collectibles.anniversary, imageUrl: ModelsData.collectibles.anniversary,
id: 1767699, id: 1767699,
@ -28,7 +28,7 @@ ListModel {
key: "CryptoKitties", key: "CryptoKitties",
iconSource: ModelsData.collectibles.cryptoKitties, iconSource: ModelsData.collectibles.cryptoKitties,
name: "CryptoKitties", name: "CryptoKitties",
symbol: "CryptoKitties", symbol: "CK",
category: TokenCategories.Category.Own, category: TokenCategories.Category.Own,
subItems: [ subItems: [
{ {
@ -82,7 +82,7 @@ ListModel {
key: "SuperRare", key: "SuperRare",
iconSource: ModelsData.collectibles.superRare, iconSource: ModelsData.collectibles.superRare,
name: "SuperRare", name: "SuperRare",
symbol: "SuperRare", symbol: "SR",
category: TokenCategories.Category.Own, category: TokenCategories.Category.Own,
imageUrl: ModelsData.collectibles.superRare, imageUrl: ModelsData.collectibles.superRare,
id: 1767701, id: 1767701,
@ -92,7 +92,7 @@ ListModel {
key: "Custom", key: "Custom",
iconSource: ModelsData.collectibles.custom, iconSource: ModelsData.collectibles.custom,
name: "Custom Collectible", name: "Custom Collectible",
symbol: "Custom", symbol: "CUS",
category: TokenCategories.Category.General, category: TokenCategories.Category.General,
imageUrl: ModelsData.collectibles.custom, imageUrl: ModelsData.collectibles.custom,
id: 1767764, id: 1767764,

View File

@ -14,13 +14,15 @@ QtObject {
holdingsListModel: root.createHoldingsModel1(), holdingsListModel: root.createHoldingsModel1(),
channelsListModel: root.createChannelsModel1(), channelsListModel: root.createChannelsModel1(),
permissionType: PermissionTypes.Type.Admin, permissionType: PermissionTypes.Type.Admin,
isPrivate: true isPrivate: true,
tokenCriteriaMet: false
}, },
{ {
holdingsListModel: root.createHoldingsModel2(), holdingsListModel: root.createHoldingsModel2(),
channelsListModel: root.createChannelsModel2(), channelsListModel: root.createChannelsModel2(),
permissionType: PermissionTypes.Type.Member, permissionType: PermissionTypes.Type.Member,
isPrivate: false isPrivate: false,
tokenCriteriaMet: true
} }
] ]
@ -29,7 +31,7 @@ QtObject {
holdingsListModel: root.createHoldingsModel4(), holdingsListModel: root.createHoldingsModel4(),
channelsListModel: root.createChannelsModel1(), channelsListModel: root.createChannelsModel1(),
permissionType: PermissionTypes.Type.Admin, permissionType: PermissionTypes.Type.Admin,
isPrivate: true, isPrivate: true
} }
] ]
@ -138,6 +140,124 @@ QtObject {
} }
] ]
readonly property var complexPermissionsModelData: [
{
id: "admin1",
holdingsListModel: root.createHoldingsModel2b(),
channelsListModel: root.createChannelsModel2(),
permissionType: PermissionTypes.Type.Admin,
isPrivate: false,
tokenCriteriaMet: true
},
{
id: "admin2",
holdingsListModel: root.createHoldingsModel3(),
channelsListModel: root.createChannelsModel2(),
permissionType: PermissionTypes.Type.Admin,
isPrivate: false,
tokenCriteriaMet: false
},
{
id: "member1",
holdingsListModel: root.createHoldingsModel2(),
channelsListModel: root.createChannelsModel2(),
permissionType: PermissionTypes.Type.Member,
isPrivate: false,
tokenCriteriaMet: true
},
{
id: "member2",
holdingsListModel: root.createHoldingsModel3(),
channelsListModel: root.createChannelsModel2(),
permissionType: PermissionTypes.Type.Member,
isPrivate: false,
tokenCriteriaMet: false
}
]
readonly property var channelsOnlyPermissionsModelData: [
{
id: "read1a",
holdingsListModel: root.createHoldingsModel1b(),
channelsListModel: root.createChannelsModel1(),
permissionType: PermissionTypes.Type.Read,
isPrivate: false,
tokenCriteriaMet: true
},
{
id: "read1b",
holdingsListModel: root.createHoldingsModel1(),
channelsListModel: root.createChannelsModel1(),
permissionType: PermissionTypes.Type.Read,
isPrivate: false,
tokenCriteriaMet: false
},
{
id: "read1c",
holdingsListModel: root.createHoldingsModel3(),
channelsListModel: root.createChannelsModel1(),
permissionType: PermissionTypes.Type.Read,
isPrivate: false,
tokenCriteriaMet: false
},
{
id: "read2a",
holdingsListModel: root.createHoldingsModel2(),
channelsListModel: root.createChannelsModel3(),
permissionType: PermissionTypes.Type.Read,
isPrivate: false,
tokenCriteriaMet: true
},
{
id: "read2b",
holdingsListModel: root.createHoldingsModel5(),
channelsListModel: root.createChannelsModel3(),
permissionType: PermissionTypes.Type.Read,
isPrivate: false,
tokenCriteriaMet: false
},
{
id: "viewAndPost1a",
holdingsListModel: root.createHoldingsModel3(),
channelsListModel: root.createChannelsModel1(),
permissionType: PermissionTypes.Type.ViewAndPost,
isPrivate: false,
tokenCriteriaMet: false
},
{
id: "viewAndPost1b",
holdingsListModel: root.createHoldingsModel2b(),
channelsListModel: root.createChannelsModel1(),
permissionType: PermissionTypes.Type.ViewAndPost,
isPrivate: false,
tokenCriteriaMet: true
},
{
id: "viewAndPost2a",
holdingsListModel: root.createHoldingsModel3(),
channelsListModel: root.createChannelsModel3(),
permissionType: PermissionTypes.Type.ViewAndPost,
isPrivate: false,
tokenCriteriaMet: false
},
{
id: "viewAndPost2b",
holdingsListModel: root.createHoldingsModel5(),
channelsListModel: root.createChannelsModel3(),
permissionType: PermissionTypes.Type.ViewAndPost,
isPrivate: false,
tokenCriteriaMet: false
},
{
id: "viewAndPost2c",
holdingsListModel: root.createHoldingsModel1(),
channelsListModel: root.createChannelsModel3(),
permissionType: PermissionTypes.Type.ViewAndPost,
isPrivate: false,
tokenCriteriaMet: false
}
]
readonly property ListModel permissionsModel: ListModel { readonly property ListModel permissionsModel: ListModel {
readonly property ModelChangeGuard guard: ModelChangeGuard { readonly property ModelChangeGuard guard: ModelChangeGuard {
model: root.permissionsModel model: root.permissionsModel
@ -215,6 +335,29 @@ QtObject {
} }
} }
readonly property var complexPermissionsModel: ListModel {
readonly property ModelChangeGuard guard: ModelChangeGuard {
model: root.complexPermissionsModel
}
Component.onCompleted: {
append(complexPermissionsModelData)
append(channelsOnlyPermissionsModelData)
guard.enabled = true
}
}
readonly property var channelsOnlyPermissionsModel: ListModel {
readonly property ModelChangeGuard guard: ModelChangeGuard {
model: root.channelsOnlyPermissionsModel
}
Component.onCompleted: {
append(channelsOnlyPermissionsModelData)
guard.enabled = true
}
}
function createHoldingsModel1() { function createHoldingsModel1() {
return [ return [
{ {
@ -230,7 +373,7 @@ QtObject {
return [ return [
{ {
type: HoldingTypes.Type.Ens, type: HoldingTypes.Type.Ens,
key: "Ens", key: "*.eth",
amount: 1, amount: 1,
available: true available: true
} }
@ -249,7 +392,24 @@ QtObject {
type: HoldingTypes.Type.Asset, type: HoldingTypes.Type.Asset,
key: "Dai", key: "Dai",
amount: 11, amount: 11,
available: false available: true
}
]
}
function createHoldingsModel2b() {
return [
{
type: HoldingTypes.Type.Collectible,
key: "Anniversary2",
amount: 1,
available: true
},
{
type: HoldingTypes.Type.Asset,
key: "snt",
amount: 666,
available: true
} }
] ]
} }
@ -293,7 +453,7 @@ QtObject {
}, },
{ {
type: HoldingTypes.Type.Ens, type: HoldingTypes.Type.Ens,
key: "ENS", key: "foo.bar.eth",
amount: 1, amount: 1,
available: false available: false
}, },
@ -317,7 +477,7 @@ QtObject {
{ {
type: HoldingTypes.Type.Asset, type: HoldingTypes.Type.Asset,
key: "zrx", key: "zrx",
amount: 1, amount: 10,
available: false available: false
}, },
{ {
@ -355,4 +515,8 @@ QtObject {
function createChannelsModel2() { function createChannelsModel2() {
return [] return []
} }
function createChannelsModel3() {
return [{ key: "_vip" } ]
}
} }

View File

@ -1,9 +1,122 @@
import QtQuick 2.15 import QtQuick 2.15
import utils 1.0
ListModel { ListModel {
ListElement { name: "Test account"; emoji: "😋"; colorId: "primary"; address: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"; walletType: "" } readonly property var data: [
ListElement { name: "Another account - generated"; emoji: "🚗"; colorId: "army"; address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8888"; walletType: "generated" } {
ListElement { name: "Another account - seed"; emoji: "🎨"; colorId: "army"; address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8888"; walletType: "seed" } name: "helloworld",
ListElement { name: "Another account - watch"; emoji: "🔗"; colorId: "army"; address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8888"; walletType: "watch" } emoji: "😋",
ListElement { name: "Another account - key"; emoji: "💼"; colorId: "army"; address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8888"; walletType: "key" } colorId: "primary",
address: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240",
walletType: "",
position: 0,
assets: [
{
symbol: "socks",
enabledNetworkBalance: {
displayDecimals: 2,
stripTrailingZeroes: true,
amount: 15.0,
symbol: "SOX"
}
},
{
symbol: "snt",
enabledNetworkBalance: {
displayDecimals: 2,
stripTrailingZeroes: true,
amount: 670.2345,
symbol: "SNT"
}
},
{
symbol: "zrx",
enabledNetworkBalance: {
displayDecimals: 4,
stripTrailingZeroes: true,
amount: 7.456000,
symbol: "ZRX"
}
}
]
},
{
name: "Hot wallet (generated)",
emoji: "🚗",
colorId: "army",
address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881",
walletType: Constants.generatedWalletType,
position: 3,
assets: [
{
symbol: "deadbeef",
enabledNetworkBalance: {
displayDecimals: 1,
stripTrailingZeroes: true,
amount: 1,
symbol: "DBF"
}
}
]
},
{
name: "Family (seed)",
emoji: "🎨", colorId: "magenta",
address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8882",
walletType: Constants.seedWalletType,
position: 1,
assets: [
{
symbol: "Aave",
enabledNetworkBalance: {
displayDecimals: 6,
stripTrailingZeroes: true,
amount: 42,
symbol: "AAVE"
}
},
{
symbol: "dai",
enabledNetworkBalance: {
displayDecimals: 2,
stripTrailingZeroes: true,
amount: 120.123,
symbol: "DAI"
}
}
]
},
{
name: "Tag Heuer (watch)",
emoji: "⌚",
colorId: "copper",
address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8883",
walletType: Constants.watchWalletType,
position: 2,
assets: [
]
},
{
name: "Fab (key)",
emoji: "⌚",
colorId: Constants.walletAccountColors.camel,
address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8884",
walletType: Constants.keyWalletType,
position: 4,
assets: [
{
symbol: "socks",
enabledNetworkBalance: {
displayDecimals: 2,
stripTrailingZeroes: false,
amount: 3.5,
symbol: "SOX"
}
}
]
}
]
Component.onCompleted: append(data)
} }

View File

@ -91,11 +91,13 @@ add_library(StatusQ SHARED
src/plugin.cpp src/plugin.cpp
include/StatusQ/QClipboardProxy.h include/StatusQ/QClipboardProxy.h
include/StatusQ/modelutilsinternal.h include/StatusQ/modelutilsinternal.h
include/StatusQ/permissionutilsinternal.h
include/StatusQ/rxvalidator.h include/StatusQ/rxvalidator.h
include/StatusQ/statussyntaxhighlighter.h include/StatusQ/statussyntaxhighlighter.h
include/StatusQ/statuswindow.h include/StatusQ/statuswindow.h
src/QClipboardProxy.cpp src/QClipboardProxy.cpp
src/modelutilsinternal.cpp src/modelutilsinternal.cpp
src/permissionutilsinternal.cpp
src/rxvalidator.cpp src/rxvalidator.cpp
src/statussyntaxhighlighter.cpp src/statussyntaxhighlighter.cpp
src/statuswindow.cpp src/statuswindow.cpp

View File

@ -0,0 +1,19 @@
#pragma once
#include <QObject>
class QAbstractItemModel;
class PermissionUtilsInternal : public QObject
{
Q_OBJECT
public:
explicit PermissionUtilsInternal(QObject* parent = nullptr);
//!< traverse the permissions @p model, and look for unique token keys recursively under holdingsListModel->key
Q_INVOKABLE QStringList getUniquePermissionTokenKeys(QAbstractItemModel *model) const;
//!< traverse the permissions @p model, and look for unique channel keys recursively under channelsListModel->key; filtering out @permissionTypes ([PermissionTypes.Type.FOO])
Q_INVOKABLE QStringList getUniquePermissionChannels(QAbstractItemModel *model, const QList<int> &permissionTypes = {}) const;
};

View File

@ -359,8 +359,8 @@ Rectangle {
id: tagsScrollView id: tagsScrollView
visible: tagsRepeater.count > 0 visible: tagsRepeater.count > 0
anchors.top: statusListItemTertiaryTitle.bottom anchors.top: statusListItemTertiaryTitle.bottom
anchors.topMargin: visible ? 8 : 0 anchors.topMargin: visible ? 2 : 0
width: Math.min(statusListItemTagsSlotInline.width, statusListItemTagsSlotInline.availableWidth) width: Math.min(statusListItemTagsSlotInline.width, statusListItemTagsSlotInline.availableWidth, parent.width)
height: visible ? contentHeight : 0 height: visible ? contentHeight : 0
padding: 0 padding: 0
@ -378,7 +378,7 @@ Rectangle {
RowLayout { RowLayout {
anchors.top: tagsScrollView.bottom anchors.top: tagsScrollView.bottom
anchors.topMargin: visible ? 8 : 0 anchors.topMargin: visible ? 4 : 0
width: parent.width width: parent.width
visible: !!root.beneathTagsIcon || !!root.beneathTagsTitle visible: !!root.beneathTagsIcon || !!root.beneathTagsTitle
spacing: 4 spacing: 4

View File

@ -2,4 +2,3 @@ module StatusQ.Components.private
StatusImageMessage 0.1 statusMessage/StatusImageMessage.qml StatusImageMessage 0.1 statusMessage/StatusImageMessage.qml
StatusMessageImageAlbum 0.1 statusMessage/StatusMessageImageAlbum.qml StatusMessageImageAlbum 0.1 statusMessage/StatusMessageImageAlbum.qml
StatusBaseDateInput 0.1 dateInput/StatusBaseDateInput.qml

View File

@ -79,6 +79,7 @@ CheckBox {
: root.indicator.width) : 0 : root.indicator.width) : 0
rightPadding: !root.leftSide? (!!root.text ? root.indicator.width + root.spacing rightPadding: !root.leftSide? (!!root.text ? root.indicator.width + root.spacing
: root.indicator.width) : 0 : root.indicator.width) : 0
visible: !!text
} }
HoverHandler { HoverHandler {

View File

@ -21,6 +21,7 @@ RadioButton {
Large Large
} }
opacity: enabled ? 1.0 : 0.3
font.family: Theme.palette.baseFont.name font.family: Theme.palette.baseFont.name
indicator: Rectangle { indicator: Rectangle {
@ -47,5 +48,6 @@ RadioButton {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
leftPadding: root.indicator && !root.mirrored ? root.indicator.width + root.spacing : 0 leftPadding: root.indicator && !root.mirrored ? root.indicator.width + root.spacing : 0
rightPadding: root.indicator && root.mirrored ? root.indicator.width + root.spacing : 0 rightPadding: root.indicator && root.mirrored ? root.indicator.width + root.spacing : 0
visible: !!text
} }
} }

View File

@ -65,6 +65,7 @@ QtObject {
return num.toString().split('.')[1].length return num.toString().split('.')[1].length
} }
function stripTrailingZeroes(numStr, locale) { function stripTrailingZeroes(numStr, locale) {
let regEx = locale.decimalPoint == "." ? /(\.[0-9]*[1-9])0+$|\.0*$/ : /(\,[0-9]*[1-9])0+$|\,0*$/ let regEx = locale.decimalPoint == "." ? /(\.[0-9]*[1-9])0+$|\.0*$/ : /(\,[0-9]*[1-9])0+$|\,0*$/
return numStr.replace(regEx, '$1') return numStr.replace(regEx, '$1')
@ -101,8 +102,6 @@ QtObject {
} }
function currencyAmountToLocaleString(currencyAmount, options = null, locale = null) { function currencyAmountToLocaleString(currencyAmount, options = null, locale = null) {
locale = locale || Qt.locale()
if (!currencyAmount) { if (!currencyAmount) {
return qsTr("N/A") return qsTr("N/A")
} }
@ -114,6 +113,8 @@ QtObject {
if (typeof currencyAmount.amount === "undefined") if (typeof currencyAmount.amount === "undefined")
return qsTr("N/A") return qsTr("N/A")
locale = locale || Qt.locale()
// Parse options // Parse options
var optNoSymbol = false var optNoSymbol = false
var optRawAmount = false var optRawAmount = false
@ -141,7 +142,7 @@ QtObject {
if (currencyAmount.amount > 0 && currencyAmount.amount < minAmount && !optRawAmount) if (currencyAmount.amount > 0 && currencyAmount.amount < minAmount && !optRawAmount)
{ {
// Handle amounts smaller than resolution // Handle amounts smaller than resolution
amountStr = "<%1".arg(numberToLocaleString(minAmount, displayDecimals, locale)) amountStr = "<%1".arg(numberToLocaleString(minAmount, optDisplayDecimals, locale))
} else { } else {
var amount var amount
var displayDecimals var displayDecimals
@ -158,11 +159,10 @@ QtObject {
// For normal numbers, we show the whole integral part and as many decimal places not // For normal numbers, we show the whole integral part and as many decimal places not
// not to exceed the maximum // not to exceed the maximum
amount = currencyAmount.amount amount = currencyAmount.amount
// For numbers over 1M , dont show decimal places // For numbers over 1M , dont show decimal places
if(numIntegerDigits > maxDigitsToShowDecimal) { if(numIntegerDigits > maxDigitsToShowDecimal) {
displayDecimals = 0 displayDecimals = 0
} } else {
else {
displayDecimals = Math.min(optDisplayDecimals, Math.max(0, maxDigits - numIntegerDigits)) displayDecimals = Math.min(optDisplayDecimals, Math.max(0, maxDigits - numIntegerDigits))
} }
} }

View File

@ -16,9 +16,8 @@ Image {
if(icon.startsWith("data:image/") || icon.startsWith("https://") || icon.startsWith("qrc:/") || icon.startsWith("file:/")) { if(icon.startsWith("data:image/") || icon.startsWith("https://") || icon.startsWith("qrc:/") || icon.startsWith("file:/")) {
//raw image data //raw image data
source = icon source = icon
objectName = "custom-icon" objectName = "custom-icon"
} } else if (icon !== "") {
else if (icon !== "") {
source = "../../assets/img/icons/" + icon+ ".svg"; source = "../../assets/img/icons/" + icon+ ".svg";
objectName = icon + "-icon" objectName = icon + "-icon"
} }

View File

@ -45,6 +45,10 @@ QtObject {
return array return array
} }
function modelToFlatArray(model, role) {
return modelToArray(model, [role]).map(entry => entry[role])
}
function indexOf(model, role, key) { function indexOf(model, role, key) {
const count = model.rowCount() const count = model.rowCount()

View File

@ -32,7 +32,6 @@ QtObject {
case OperatorsUtils.Operators.None: case OperatorsUtils.Operators.None:
default: default:
return "" return ""
} }
} }
} }

View File

@ -1,7 +1,8 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.15
import QtQml.Models 2.14 import QtQml.Models 2.15
import QtQml 2.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
@ -28,6 +29,14 @@ Dialog {
margins: 64 margins: 64
modal: true modal: true
// workaround for https://bugreports.qt.io/browse/QTBUG-87804
Binding on margins {
id: workaroundBinding
when: false
restoreMode: Binding.RestoreBindingOrValue
}
standardButtons: Dialog.Cancel | Dialog.Ok standardButtons: Dialog.Cancel | Dialog.Ok
Overlay.modal: Rectangle { Overlay.modal: Rectangle {

View File

@ -23,7 +23,7 @@ StatusModal {
visible: replaceItem || stackLayout.currentIndex > 0 visible: replaceItem || stackLayout.currentIndex > 0
onClicked: { onClicked: {
if (replaceItem) { if (replaceItem) {
replaceItem = null; replaceItem = undefined; // unload the replaceItem
} else { } else {
let prevAction = stackLayout.currentItem.prevAction let prevAction = stackLayout.currentItem.prevAction
stackLayout.currentIndex--; stackLayout.currentIndex--;

View File

@ -1,6 +1,7 @@
#include "StatusQ/modelutilsinternal.h" #include "StatusQ/modelutilsinternal.h"
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QDebug>
ModelUtilsInternal::ModelUtilsInternal(QObject* parent) ModelUtilsInternal::ModelUtilsInternal(QObject* parent)
: QObject(parent) : QObject(parent)
@ -16,7 +17,6 @@ QStringList ModelUtilsInternal::roleNames(QAbstractItemModel *model) const
return {roles.cbegin(), roles.cend()}; return {roles.cbegin(), roles.cend()};
} }
int ModelUtilsInternal::roleByName(QAbstractItemModel* model, int ModelUtilsInternal::roleByName(QAbstractItemModel* model,
const QString &roleName) const const QString &roleName) const
{ {

View File

@ -0,0 +1,109 @@
#include "StatusQ/permissionutilsinternal.h"
#include <QAbstractItemModel>
#include <QDebug>
#include <set>
namespace {
int roleByName(QAbstractItemModel* model, const QString &roleName)
{
if (!model)
return -1;
return model->roleNames().key(roleName.toUtf8(), -1);
}
}
PermissionUtilsInternal::PermissionUtilsInternal(QObject* parent)
: QObject(parent)
{
}
QStringList PermissionUtilsInternal::getUniquePermissionTokenKeys(QAbstractItemModel* model) const
{
if (!model)
return {};
const auto role = roleByName(model, QStringLiteral("holdingsListModel"));
if (role == -1) {
qWarning() << Q_FUNC_INFO << "Requested roleName 'holdingsListModel' not found!";
return {};
}
std::set<QString> result; // unique, sorted by default
const auto permissionsCount = model->rowCount();
for (int i = 0; i < permissionsCount; i++) {
const auto isPrivate = model->data(model->index(i, 0), roleByName(model, QStringLiteral("isPrivate"))).toBool();
if (isPrivate)
continue;
const auto holdings = model->data(model->index(i, 0), role);
if (holdings.isValid() && !holdings.isNull()) {
const auto holdingItems = holdings.value<QAbstractItemModel*>();
if (!holdingItems) {
qWarning() << Q_FUNC_INFO << "Unable to cast 'holdingsListModel' to QAbstractItemModel *!";
continue;
}
const auto holdingItemsCount = holdingItems->rowCount();
for (int j = 0; j < holdingItemsCount; j++) {
const auto keyRole = roleByName(holdingItems, QStringLiteral("key"));
if (keyRole == -1) {
qWarning() << Q_FUNC_INFO << "Requested roleName 'key' not found!";
continue;
}
result.insert(holdingItems->data(holdingItems->index(j, 0), keyRole).toString().toUpper());
}
}
}
return {result.cbegin(), result.cend()};
}
// TODO return a QVariantMap (https://github.com/status-im/status-desktop/issues/11481) with key->channelName
QStringList PermissionUtilsInternal::getUniquePermissionChannels(QAbstractItemModel* model, const QList<int> &permissionTypes) const
{
if (!model)
return {};
const auto role = roleByName(model, QStringLiteral("channelsListModel"));
if (role == -1) {
qWarning() << Q_FUNC_INFO << "Requested roleName 'channelsListModel' not found!";
return {};
}
const auto permissionTypeRole = roleByName(model, QStringLiteral("permissionType"));
std::set<QString> result; // unique, sorted by default
const auto permissionsCount = model->rowCount();
for (int i = 0; i < permissionsCount; i++) {
if (!permissionTypes.isEmpty()) {
const auto permissionType = model->data(model->index(i, 0), permissionTypeRole).toInt();
if (!permissionTypes.contains(permissionType))
continue;
}
const auto channels = model->data(model->index(i, 0), role);
if (channels.isValid() && !channels.isNull()) {
const auto channelItems = channels.value<QAbstractItemModel *>();
if (!channelItems) {
qWarning() << Q_FUNC_INFO << "Unable to cast 'channelsListModel' to QAbstractItemModel *!";
continue;
}
const auto channelItemsCount = channelItems->rowCount();
for (int j = 0; j < channelItemsCount; j++) {
const auto keyRole = roleByName(channelItems, QStringLiteral("key"));
if (keyRole == -1) {
qWarning() << Q_FUNC_INFO << "Requested roleName 'key' not found!";
continue;
}
result.insert(channelItems->data(channelItems->index(j, 0), keyRole).toString());
}
}
}
return {result.cbegin(), result.cend()};
}

View File

@ -5,6 +5,7 @@
#include "StatusQ/QClipboardProxy.h" #include "StatusQ/QClipboardProxy.h"
#include "StatusQ/modelutilsinternal.h" #include "StatusQ/modelutilsinternal.h"
#include "StatusQ/permissionutilsinternal.h"
#include "StatusQ/rxvalidator.h" #include "StatusQ/rxvalidator.h"
#include "StatusQ/statussyntaxhighlighter.h" #include "StatusQ/statussyntaxhighlighter.h"
#include "StatusQ/statuswindow.h" #include "StatusQ/statuswindow.h"
@ -26,6 +27,10 @@ public:
qmlRegisterSingletonType<ModelUtilsInternal>( qmlRegisterSingletonType<ModelUtilsInternal>(
"StatusQ.Internal", 0, 1, "ModelUtils", &ModelUtilsInternal::qmlInstance); "StatusQ.Internal", 0, 1, "ModelUtils", &ModelUtilsInternal::qmlInstance);
qmlRegisterSingletonType<PermissionUtilsInternal>("StatusQ.Internal", 0, 1, "PermissionUtils", [](QQmlEngine *, QJSEngine *) {
return new PermissionUtilsInternal;
});
QZXing::registerQMLTypes(); QZXing::registerQMLTypes();
qqsfpm::registerTypes(); qqsfpm::registerTypes();
} }

View File

@ -11,6 +11,7 @@ import "stores"
import AppLayouts.Communities.popups 1.0 import AppLayouts.Communities.popups 1.0
import AppLayouts.Chat.stores 1.0 import AppLayouts.Chat.stores 1.0
import AppLayouts.Wallet.stores 1.0 as WalletStore
StackLayout { StackLayout {
id: root id: root
@ -176,8 +177,14 @@ StackLayout {
property string communityId property string communityId
loginType: root.rootStore.loginType
walletAccountsModel: WalletStore.RootStore.receiveAccounts
permissionsModel: root.permissionsStore.permissionsModel
assetsModel: root.rootStore.assetsModel
collectiblesModel: root.rootStore.collectiblesModel
onJoined: { onJoined: {
root.rootStore.requestToJoinCommunityWithAuthentication(root.rootStore.userProfileInst.name) root.rootStore.requestToJoinCommunityWithAuthentication(root.rootStore.userProfileInst.name, sharedAddresses, airdropAddress)
} }
onCancelMembershipRequest: { onCancelMembershipRequest: {

View File

@ -378,8 +378,8 @@ QtObject {
return communitiesModuleInst.spectateCommunity(id, ensName) return communitiesModuleInst.spectateCommunity(id, ensName)
} }
function requestToJoinCommunityWithAuthentication(ensName) { function requestToJoinCommunityWithAuthentication(ensName, addressesToShare = [], airdropAddress = "") {
chatCommunitySectionModule.requestToJoinCommunityWithAuthentication(ensName) chatCommunitySectionModule.requestToJoinCommunityWithAuthenticationWithSharedAddresses(ensName, JSON.stringify(addressesToShare), airdropAddress)
} }
function userCanJoin(id) { function userCanJoin(id) {
@ -475,7 +475,7 @@ QtObject {
const userCanJoin = userCanJoin(result.communityId) const userCanJoin = userCanJoin(result.communityId)
// TODO find what to do when you can't join // TODO find what to do when you can't join
if (userCanJoin) { if (userCanJoin) {
requestToJoinCommunityWithAuthentication(userProfileInst.preferredName) requestToJoinCommunityWithAuthentication(userProfileInst.preferredName) // FIXME what addresses to share?
} }
} }
return result return result
@ -608,9 +608,9 @@ QtObject {
if(userProfileInst.usingBiometricLogin) if(userProfileInst.usingBiometricLogin)
return Constants.LoginType.Biometrics return Constants.LoginType.Biometrics
else if(userProfileInst.isKeycardUser) if(userProfileInst.isKeycardUser)
return Constants.LoginType.Keycard return Constants.LoginType.Keycard
else return Constants.LoginType.Password return Constants.LoginType.Password
} }
readonly property Connections communitiesModuleConnections: Connections { readonly property Connections communitiesModuleConnections: Connections {

View File

@ -26,7 +26,7 @@ StatusSectionLayout {
id: root id: root
property var contactsStore property var contactsStore
property bool hasAddedContacts: root.contactsStore.myContactsModel.count > 0 property bool hasAddedContacts: contactsStore.myContactsModel.count > 0
property RootStore rootStore property RootStore rootStore
property var createChatPropertiesStore property var createChatPropertiesStore
@ -36,7 +36,7 @@ StatusSectionLayout {
property var stickersPopup property var stickersPopup
property bool stickersLoaded: false property bool stickersLoaded: false
readonly property var chatContentModule: root.rootStore.currentChatContentModule() || null readonly property var chatContentModule: rootStore.currentChatContentModule() || null
readonly property bool viewOnlyPermissionsSatisfied: chatContentModule.viewOnlyPermissionsSatisfied readonly property bool viewOnlyPermissionsSatisfied: chatContentModule.viewOnlyPermissionsSatisfied
readonly property bool viewAndPostPermissionsSatisfied: chatContentModule.viewAndPostPermissionsSatisfied readonly property bool viewAndPostPermissionsSatisfied: chatContentModule.viewAndPostPermissionsSatisfied
property bool hasViewOnlyPermissions: false property bool hasViewOnlyPermissions: false

View File

@ -6,7 +6,7 @@ import StatusQ.Core.Theme 0.1
QtObject { QtObject {
enum Type { enum Type {
None, Admin, Member, Read, ViewAndPost None, Admin, Member, Read, ViewAndPost, Moderator
} }
function getName(type) { function getName(type) {
@ -15,12 +15,12 @@ QtObject {
return qsTr("Become admin") return qsTr("Become admin")
case PermissionTypes.Type.Member: case PermissionTypes.Type.Member:
return qsTr("Become member") return qsTr("Become member")
case PermissionTypes.Type.Moderator:
return qsTr("Moderate")
case PermissionTypes.Type.ViewAndPost:
return qsTr("View and post")
case PermissionTypes.Type.Read: case PermissionTypes.Type.Read:
return qsTr("View only") return qsTr("View only")
case PermissionTypes.Type.ViewAndPost:
return qsTr("View and post")
case PermissionTypes.Type.Moderator:
return qsTr("Moderate")
} }
return "" return ""
@ -32,12 +32,12 @@ QtObject {
return "admin" return "admin"
case PermissionTypes.Type.Member: case PermissionTypes.Type.Member:
return "in-contacts" return "in-contacts"
case PermissionTypes.Type.Moderator:
return "arbitrator"
case PermissionTypes.Type.ViewAndPost: case PermissionTypes.Type.ViewAndPost:
return "edit" return "edit"
case PermissionTypes.Type.Read: case PermissionTypes.Type.Read:
return "show" return "show"
case PermissionTypes.Type.Moderator:
return "arbitrator"
} }
return "" return ""

View File

@ -4,6 +4,7 @@ import QtQml 2.14
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 import StatusQ.Core.Utils 0.1
import StatusQ.Internal 0.1 as Internal
import AppLayouts.Communities.controls 1.0 import AppLayouts.Communities.controls 1.0
@ -63,6 +64,15 @@ QtObject {
return "" return ""
} }
function getUniquePermissionTokenKeys(model) {
return Internal.PermissionUtils.getUniquePermissionTokenKeys(model)
}
function getUniquePermissionChannels(model, permissionsTypesArray = []) {
// TODO return a QVariantMap (https://github.com/status-im/status-desktop/issues/11481)
return Internal.PermissionUtils.getUniquePermissionChannels(model, permissionsTypesArray)
}
function setHoldingsTextFormat(type, name, amount) { function setHoldingsTextFormat(type, name, amount) {
switch (type) { switch (type) {
case HoldingTypes.Type.Asset: case HoldingTypes.Type.Asset:

View File

@ -27,10 +27,6 @@ Control {
// By design values: // By design values:
readonly property int defaultHoldingsSpacing: 8 readonly property int defaultHoldingsSpacing: 8
function holdingsTextFormat(name, amount) {
return PermissionsHelpers.setHoldingsTextFormat(HoldingTypes.Type.Asset, name, amount)
}
} }
contentItem: ColumnLayout { contentItem: ColumnLayout {
@ -83,7 +79,7 @@ Control {
asset.color: asset.isImage ? "transparent" : titleText.color asset.color: asset.isImage ? "transparent" : titleText.color
closeButtonVisible: false closeButtonVisible: false
titleText.color: model.available ? Theme.palette.primaryColor1 : Theme.palette.dangerColor1 titleText.color: model.available ? Theme.palette.primaryColor1 : Theme.palette.dangerColor1
bgColor: model.available ? Theme.palette.primaryColor2 :Theme.palette.dangerColor2 bgColor: model.available ? Theme.palette.primaryColor2 : Theme.palette.dangerColor2
titleText.font.pixelSize: 15 titleText.font.pixelSize: 15
} }
} }

View File

@ -92,15 +92,23 @@ Control {
] ]
} }
readonly property var moderatePermissionsModel: SortFilterProxyModel {
sourceModel: root.moderateHoldingsModel
filters: [
ExpressionFilter {
expression: d.filterPermissions(model)
}
]
}
} }
padding: 35 // default by design padding: 35 // default by design
spacing: 32 // default by design spacing: 32 // default by design
contentItem: ColumnLayout { contentItem: ColumnLayout {
id: column id: column
spacing: root.spacing spacing: root.spacing
component CustomHoldingsListPanel: HoldingsListPanel { component CustomHoldingsListPanel: HoldingsListPanel {
Layout.fillWidth: true Layout.fillWidth: true
@ -123,25 +131,23 @@ Control {
CustomHoldingsListPanel { CustomHoldingsListPanel {
visible: !root.joinCommunity && d.viewOnlyPermissionsModel.count > 0 visible: !root.joinCommunity && d.viewOnlyPermissionsModel.count > 0
introText: root.requiresRequest ? introText: root.requiresRequest ?
qsTr("To view the #<b>%1</b> channel you need to join <b>%2</b> and prove that you hold").arg(root.channelName).arg(root.communityName) : qsTr("To view the <b>#%1</b> channel you need to join <b>%2</b> and prove that you hold").arg(root.channelName).arg(root.communityName) :
qsTr("To view the #<b>%1</b> channel you need to hold").arg(root.channelName) qsTr("To view the <b>#%1</b> channel you need to hold").arg(root.channelName)
model: d.viewOnlyPermissionsModel model: d.viewOnlyPermissionsModel
} }
CustomHoldingsListPanel { CustomHoldingsListPanel {
visible: !root.joinCommunity && d.viewAndPostPermissionsModel.count > 0 visible: !root.joinCommunity && d.viewAndPostPermissionsModel.count > 0
introText: root.requiresRequest ? introText: root.requiresRequest ?
qsTr("To view and post in the #<b>%1</b> channel you need to join <b>%2</b> and prove that you hold").arg(root.channelName).arg(root.communityName) : qsTr("To view and post in the <b>#%1</b> channel you need to join <b>%2</b> and prove that you hold").arg(root.channelName).arg(root.communityName) :
qsTr("To view and post in the #<b>%1</b> channel you need to hold").arg(root.channelName) qsTr("To view and post in the <b>#%1</b> channel you need to hold").arg(root.channelName)
model: d.viewAndPostPermissionsModel model: d.viewAndPostPermissionsModel
} }
HoldingsListPanel { CustomHoldingsListPanel {
Layout.fillWidth: true visible: !root.joinCommunity && d.moderatePermissionsModel.count > 0
spacing: root.spacing introText: qsTr("To moderate in the <b>#%1</b> channel you need to hold").arg(root.channelName)
visible: !root.joinCommunity && !!d.moderateHoldings model: d.moderatePermissionsModel
introText: qsTr("To moderate in the <b>%1</b> channel you need to hold").arg(root.channelName)
model: d.moderateHoldingsModel
} }
StatusButton { StatusButton {
@ -150,7 +156,7 @@ Control {
text: root.isInvitationPending ? d.getInvitationPendingText() : d.getRevealAddressText() text: root.isInvitationPending ? d.getInvitationPendingText() : d.getRevealAddressText()
icon.name: root.isInvitationPending ? "" : Constants.authenticationIconByType[root.loginType] icon.name: root.isInvitationPending ? "" : Constants.authenticationIconByType[root.loginType]
font.pixelSize: 13 font.pixelSize: 13
enabled: root.requirementsMet || d.communityPermissionsModel.count == 0 enabled: root.requirementsMet || d.communityPermissionsModel.count === 0
onClicked: root.isInvitationPending ? root.invitationPendingClicked() : root.revealAddressClicked() onClicked: root.isInvitationPending ? root.invitationPendingClicked() : root.revealAddressClicked()
} }
@ -163,4 +169,3 @@ Control {
} }
} }
} }

View File

@ -0,0 +1,141 @@
import QtQuick 2.15
import QtQuick.Controls 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.Core.Utils 0.1
import SortFilterProxyModel 0.2
import utils 1.0
StatusListView {
id: root
property bool hasPermissions
property var uniquePermissionTokenKeys
// read/write properties
property string selectedAirdropAddress: selectedSharedAddresses.length ? selectedSharedAddresses[0] : ""
property var selectedSharedAddresses: count ? ModelUtils.modelToFlatArray(model, "address") : []
leftMargin: d.absLeftMargin
topMargin: Style.current.padding
rightMargin: Style.current.padding
bottomMargin: Style.current.padding
QtObject {
id: d
// UI
readonly property int absLeftMargin: 12
readonly property ButtonGroup airdropGroup: ButtonGroup {
exclusive: true
}
readonly property ButtonGroup addressesGroup: ButtonGroup {
exclusive: false
}
function selectFirstAvailableAirdropAddress() {
root.selectedAirdropAddress = ModelUtils.modelToFlatArray(root.model, "address").find(address => selectedSharedAddresses.includes(address))
}
}
spacing: Style.current.halfPadding
delegate: StatusListItem {
width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin
statusListItemTitle.font.weight: Font.Medium
title: model.name
tertiaryTitle: !walletAccountAssetsModel.count && root.hasPermissions ? qsTr("No relevant tokens") : ""
tagsModel: SortFilterProxyModel {
id: walletAccountAssetsModel
sourceModel: model.assets
function filterPredicate(modelData) {
return root.uniquePermissionTokenKeys.includes(modelData.symbol.toUpperCase())
}
filters: ExpressionFilter {
expression: walletAccountAssetsModel.filterPredicate(model)
}
sorters: ExpressionSorter {
expression: {
return modelLeft.enabledNetworkBalance.amount > modelRight.enabledNetworkBalance.amount // descending, biggest first
}
}
}
statusListItemInlineTagsSlot.spacing: Style.current.padding
tagsDelegate: Row {
spacing: 4
StatusRoundedImage {
anchors.verticalCenter: parent.verticalCenter
width: 16
height: 16
image.source: Constants.tokenIcon(model.symbol.toUpperCase())
}
StatusBaseText {
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: Theme.tertiaryTextFontSize
text: LocaleUtils.currencyAmountToLocaleString(enabledNetworkBalance)
}
}
asset.color: !!model.colorId ? Utils.getColorForId(model.colorId): ""
asset.emoji: model.emoji
asset.name: !model.emoji ? "filled-account": ""
asset.letterSize: 14
asset.isLetterIdenticon: !!model.emoji
asset.isImage: asset.isLetterIdenticon
components: [
StatusFlatButton {
ButtonGroup.group: d.airdropGroup
anchors.verticalCenter: parent.verticalCenter
icon.name: "airdrop"
icon.color: hovered ? Theme.palette.primaryColor3 :
checked ? Theme.palette.primaryColor1 : disabledTextColor
checkable: true
checked: model.address === root.selectedAirdropAddress
enabled: shareAddressCheckbox.checked && root.selectedSharedAddresses.length > 1 // last cannot be unchecked
visible: shareAddressCheckbox.checked
opacity: enabled ? 1.0 : 0.3
onCheckedChanged: if (checked) root.selectedAirdropAddress = model.address
StatusToolTip {
text: qsTr("Use this address for any Community airdrops")
visible: parent.hovered
delay: 500
}
},
StatusCheckBox {
id: shareAddressCheckbox
ButtonGroup.group: d.addressesGroup
anchors.verticalCenter: parent.verticalCenter
checkable: true
checked: root.selectedSharedAddresses.includes(model.address)
enabled: !(root.selectedSharedAddresses.length === 1 && checked) // last cannot be unchecked
onToggled: {
// handle selected addresses
const index = root.selectedSharedAddresses.indexOf(model.address)
const selectedSharedAddressesCopy = Object.assign([], root.selectedSharedAddresses) // deep copy
if (index === -1) {
selectedSharedAddressesCopy.push(model.address)
} else {
selectedSharedAddressesCopy.splice(index, 1)
}
root.selectedSharedAddresses = selectedSharedAddressesCopy
// switch to next available airdrop address when unchecking
if (!checked && model.address === root.selectedAirdropAddress) {
d.selectFirstAvailableAirdropAddress()
}
}
}
]
}
}

View File

@ -0,0 +1,160 @@
import QtQuick 2.15
import QtQml.Models 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import StatusQ.Core 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import SortFilterProxyModel 0.2
import AppLayouts.Profile.controls 1.0
import AppLayouts.Communities.controls 1.0
import AppLayouts.Communities.views 1.0
import AppLayouts.Communities.helpers 1.0
import utils 1.0
Control {
id: root
property bool isEditMode
required property string communityName
required property string communityIcon
property int loginType: Constants.LoginType.Password
required property var walletAccountsModel // name, address, emoji, colorId, assets
required property var permissionsModel // id, key, permissionType, holdingsListModel, channelsListModel, isPrivate, tokenCriteriaMet
required property var assetsModel
required property var collectiblesModel
readonly property string title: isEditMode ? qsTr("Edit which addresses you share with %1").arg(communityName)
: qsTr("Select addresses to share with %1").arg(communityName)
readonly property var buttons: ObjectModel {
StatusFlatButton {
visible: root.isEditMode
borderColor: Theme.palette.baseColor2
text: qsTr("Cancel")
onClicked: root.close()
}
StatusButton {
enabled: root.selectedSharedAddresses.length && root.selectedAirdropAddress
visible: root.isEditMode
icon.name: Constants.authenticationIconByType[root.loginType]
text: qsTr("Save changes")
onClicked: {
// TODO connect to backend
root.close()
}
}
StatusButton {
visible: !root.isEditMode
enabled: root.selectedAirdropAddress && root.selectedSharedAddresses.length
text: qsTr("Share selected addresses to join")
onClicked: {
root.shareSelectedAddressesClicked(root.selectedAirdropAddress, root.selectedSharedAddresses)
root.close()
}
}
// NB no more buttons after this, see property `rightButtons` below
}
readonly property var rightButtons: [buttons.get(buttons.count-1)] // "magically" used by CommunityIntroDialog StatusStackModal impl
readonly property string selectedAirdropAddress: accountSelector.selectedAirdropAddress
readonly property var selectedSharedAddresses: accountSelector.selectedSharedAddresses
signal close()
signal shareSelectedAddressesClicked(string airdropAddress, var sharedAddresses)
spacing: Style.current.padding
QtObject {
id: d
// internal logic
readonly property bool hasPermissions: root.permissionsModel && root.permissionsModel.count
}
padding: 0
contentItem: ColumnLayout {
spacing: 0
// addresses
SharedAddressesAccountSelector {
id: accountSelector
hasPermissions: d.hasPermissions
uniquePermissionTokenKeys: PermissionsHelpers.getUniquePermissionTokenKeys(root.permissionsModel)
Layout.fillWidth: true
Layout.preferredHeight: contentHeight + topMargin + bottomMargin
Layout.maximumHeight: hasPermissions ? permissionsView.implicitHeight > root.availableHeight / 2 ? root.availableHeight / 2 : root.availableHeight : -1
Layout.fillHeight: !hasPermissions
model: SortFilterProxyModel {
sourceModel: root.walletAccountsModel
filters: ValueFilter {
roleName: "walletType"
value: Constants.watchWalletType
inverted: true
}
sorters: [
ExpressionSorter {
function isGenerated(modelData) {
return modelData.walletType === Constants.generatedWalletType
}
expression: {
return isGenerated(modelLeft)
}
},
RoleSorter {
roleName: "position"
},
RoleSorter {
roleName: "name"
}
]
}
}
// divider with top rounded corners + drop shadow
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Style.current.padding * 2
color: Theme.palette.baseColor2
radius: Style.current.padding
border.width: 1
border.color: Theme.palette.baseColor3
visible: d.hasPermissions
layer.enabled: true
layer.effect: DropShadow {
horizontalOffset: 0
verticalOffset: -9
radius: 14
samples: 29
color: Qt.rgba(0, 0, 0, 0.04)
}
}
// permissions
SharedAddressesPermissionsPanel {
id: permissionsView
permissionsModel: root.permissionsModel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
communityName: root.communityName
communityIcon: root.communityIcon
Layout.fillHeight: true
Layout.fillWidth: true
Layout.topMargin: -Style.current.padding // compensate for the half-rounded divider above
visible: d.hasPermissions
}
}
}

View File

@ -0,0 +1,437 @@
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 StatusQ.Core.Utils 0.1
import SortFilterProxyModel 0.2
import AppLayouts.Communities.controls 1.0
import AppLayouts.Communities.views 1.0
import AppLayouts.Communities.helpers 1.0
import utils 1.0
Rectangle {
id: root
property var permissionsModel
property var assetsModel
property var collectiblesModel
property string communityName
property string communityIcon
implicitHeight: permissionsScrollView.contentHeight - permissionsScrollView.anchors.topMargin
color: Theme.palette.baseColor2
QtObject {
id: d
// UI
readonly property int absLeftMargin: 12
readonly property color tableBorderColor: Theme.palette.directColor7
// internal logic
readonly property var uniquePermissionChannels:
root.permissionsModel && root.permissionsModel.count ?
PermissionsHelpers.getUniquePermissionChannels(root.permissionsModel, [PermissionTypes.Type.Read, PermissionTypes.Type.ViewAndPost])
: []
// models
readonly property var adminPermissionsModel: SortFilterProxyModel {
id: adminPermissionsModel
sourceModel: root.permissionsModel
function filterPredicate(modelData) {
return (modelData.permissionType === Constants.permissionType.admin) &&
(modelData.tokenCriteriaMet && !modelData.isPrivate) // admin privs are hidden if criteria not met
}
filters: ExpressionFilter {
expression: adminPermissionsModel.filterPredicate(model)
}
}
readonly property var joinPermissionsModel: SortFilterProxyModel {
id: joinPermissionsModel
sourceModel: root.permissionsModel
function filterPredicate(modelData) {
return (modelData.permissionType === Constants.permissionType.member) &&
(modelData.tokenCriteriaMet || !modelData.isPrivate)
}
filters: ExpressionFilter {
expression: joinPermissionsModel.filterPredicate(model)
}
}
}
StatusScrollView {
id: permissionsScrollView
anchors.fill: parent
anchors.topMargin: -Style.current.padding
contentWidth: availableWidth
ColumnLayout {
width: parent.width
spacing: Style.current.halfPadding
// header
RowLayout {
Layout.fillWidth: true
Layout.bottomMargin: 4
spacing: Style.current.padding
StatusRoundedImage {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Layout.leftMargin: d.absLeftMargin
image.source: root.communityIcon
}
StatusBaseText {
font.weight: Font.Medium
text: qsTr("Permissions")
}
}
// permission types
PermissionPanel {
permissionType: PermissionTypes.Type.Member
permissionsModel: d.joinPermissionsModel
}
PermissionPanel {
permissionType: PermissionTypes.Type.Admin
permissionsModel: d.adminPermissionsModel
}
Repeater { // channel repeater
model: d.uniquePermissionChannels // TODO get channelName in addition (https://github.com/status-im/status-desktop/issues/11481)
delegate: ChannelPermissionPanel {}
}
}
}
component PanelBg: Rectangle {
color: Theme.palette.statusListItem.backgroundColor
border.width: 1
border.color: Theme.palette.baseColor2
radius: Style.current.radius
}
component PanelIcon: StatusRoundIcon {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignTop
asset.name: {
switch (permissionType) {
case PermissionTypes.Type.Admin:
return "admin"
case PermissionTypes.Type.Member:
return "communities"
default:
return "channel"
}
}
radius: height/2
}
component PanelHeading: StatusBaseText {
Layout.fillWidth: true
elide: Text.ElideRight
font.weight: Font.Medium
text: {
switch (permissionType) {
case PermissionTypes.Type.Admin:
return qsTr("Become an admin")
case PermissionTypes.Type.Member:
return qsTr("Join %1").arg(root.communityName)
default:
return modelData // TODO display channel name https://github.com/status-im/status-desktop/issues/11481
}
}
}
component SinglePermissionFlow: Flow {
width: parent.width
spacing: Style.current.halfPadding
Repeater {
model: HoldingsSelectionModel {
sourceModel: model.holdingsListModel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
}
delegate: Row {
spacing: 4
StatusRoundedImage {
anchors.verticalCenter: parent.verticalCenter
width: 16
height: 16
image.source: model.imageSource
}
StatusBaseText {
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: Theme.tertiaryTextFontSize
text: model.text
color: model.available ? Theme.palette.successColor1 : Theme.palette.directColor1
}
}
}
}
component PermissionPanel: Control {
id: permissionPanel
property int permissionType: PermissionTypes.Type.None
property var permissionsModel
readonly property bool tokenCriteriaMet: overallPermissionRow.tokenCriteriaMet
visible: permissionsModel.count
Layout.fillWidth: true
padding: d.absLeftMargin
background: PanelBg {}
contentItem: RowLayout {
spacing: Style.current.padding
PanelIcon {}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
PanelHeading {}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: grid.implicitHeight + grid.anchors.margins*2
border.width: 1
border.color: d.tableBorderColor
radius: Style.current.radius
color: "transparent"
GridLayout {
id: grid
anchors.fill: parent
anchors.margins: Style.current.halfPadding
rowSpacing: Style.current.halfPadding
columnSpacing: Style.current.halfPadding
columns: 2
Repeater {
id: permissionsRepeater
property int revision
model: permissionPanel.permissionsModel
delegate: Column {
Layout.column: 0
Layout.row: index
Layout.fillWidth: true
spacing: Style.current.halfPadding
readonly property bool tokenCriteriaMet: model.tokenCriteriaMet ?? false
onTokenCriteriaMetChanged: permissionsRepeater.revision++
SinglePermissionFlow {}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width + grid.anchors.margins*2
height: 1
color: d.tableBorderColor
visible: index < permissionsRepeater.count - 1
}
}
}
RowLayout {
id: overallPermissionRow
Layout.column: 1
Layout.rowSpan: permissionsRepeater.count || 1
Layout.preferredWidth: 110
Layout.fillHeight: true
readonly property bool tokenCriteriaMet: {
permissionsRepeater.revision // NB no let/const here b/c of https://bugreports.qt.io/browse/QTBUG-91917
for (var i = 0; i < permissionsRepeater.count; i++) {
var permissionItem = permissionsRepeater.itemAt(i);
if (permissionItem.tokenCriteriaMet)
return true
}
return false
}
Rectangle {
Layout.preferredWidth: 1
Layout.fillHeight: true
Layout.topMargin: -Style.current.halfPadding
Layout.bottomMargin: -Style.current.halfPadding
color: d.tableBorderColor
}
Row {
Layout.alignment: Qt.AlignCenter
StatusIcon {
anchors.verticalCenter: parent.verticalCenter
width: 16
height: 16
icon: overallPermissionRow.tokenCriteriaMet ? "tiny/checkmark" : "tiny/secure"
color: overallPermissionRow.tokenCriteriaMet ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: Theme.tertiaryTextFontSize
text: {
switch (permissionPanel.permissionType) {
case PermissionTypes.Type.Admin:
return qsTr("Admin")
case PermissionTypes.Type.Member:
return qsTr("Join")
case PermissionTypes.Type.Read:
return qsTr("View only")
case PermissionTypes.Type.ViewAndPost:
return qsTr("View & post")
default:
return "???"
}
}
color: overallPermissionRow.tokenCriteriaMet ? Theme.palette.directColor1 : Theme.palette.baseColor1
}
}
}
}
}
}
}
}
component ChannelPermissionPanel: Control {
id: channelPermsPanel
Layout.fillWidth: true
spacing: 10
padding: d.absLeftMargin
background: PanelBg {}
readonly property string channelKey: modelData
contentItem: RowLayout {
spacing: Style.current.padding
PanelIcon {}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: Style.current.smallPadding
PanelHeading {}
Repeater { // permissions repeater
model: [PermissionTypes.Type.Read, PermissionTypes.Type.ViewAndPost]
delegate: Rectangle {
id: channelPermsSubPanel
readonly property int permissionType: modelData
Layout.fillWidth: true
Layout.preferredHeight: grid2.implicitHeight + grid2.anchors.margins*2
border.width: 1
border.color: d.tableBorderColor
radius: Style.current.radius
color: "transparent"
GridLayout {
id: grid2
anchors.fill: parent
anchors.margins: Style.current.halfPadding
rowSpacing: Style.current.halfPadding
columnSpacing: Style.current.halfPadding
columns: 2
Repeater {
id: permissionsRepeater2
property int revision
model: SortFilterProxyModel {
id: channelPermissionsModel
sourceModel: root.permissionsModel
function filterPredicate(modelData) {
return modelData.permissionType === channelPermsSubPanel.permissionType &&
!modelData.isPrivate &&
ModelUtils.contains(modelData.channelsListModel, "key", channelPermsPanel.channelKey) // filter and group by channel "key"
}
filters: ExpressionFilter {
expression: channelPermissionsModel.filterPredicate(model)
}
}
delegate: Column {
Layout.column: 0
Layout.row: index
Layout.fillWidth: true
spacing: Style.current.halfPadding
readonly property bool tokenCriteriaMet: model.tokenCriteriaMet ?? false
onTokenCriteriaMetChanged: permissionsRepeater2.revision++
SinglePermissionFlow {}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width + grid2.anchors.margins*2
height: 1
color: d.tableBorderColor
visible: index < permissionsRepeater2.count - 1
}
}
}
RowLayout {
id: overallPermissionRow2
Layout.column: 1
Layout.rowSpan: channelPermissionsModel.count || 1
Layout.preferredWidth: 110
Layout.fillHeight: true
readonly property bool tokenCriteriaMet: {
permissionsRepeater2.revision
for (let i = 0; i < permissionsRepeater2.count; i++) {
const permissionItem = permissionsRepeater2.itemAt(i);
if (permissionItem.tokenCriteriaMet)
return true
}
return false
}
Rectangle {
Layout.preferredWidth: 1
Layout.fillHeight: true
Layout.topMargin: -Style.current.halfPadding
Layout.bottomMargin: -Style.current.halfPadding
color: d.tableBorderColor
}
Row {
Layout.alignment: Qt.AlignCenter
StatusIcon {
anchors.verticalCenter: parent.verticalCenter
width: 16
height: 16
icon: overallPermissionRow2.tokenCriteriaMet ? "tiny/checkmark" : "tiny/secure"
color: overallPermissionRow2.tokenCriteriaMet ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: Theme.tertiaryTextFontSize
text: {
switch (channelPermsSubPanel.permissionType) {
case PermissionTypes.Type.Read:
return qsTr("View only")
case PermissionTypes.Type.ViewAndPost:
return qsTr("View & post")
case PermissionTypes.Type.Moderator:
return qsTr("Moderate")
default:
return "???"
}
}
color: overallPermissionRow2.tokenCriteriaMet ? Theme.palette.directColor1 : Theme.palette.baseColor1
}
}
}
}
}
}
}
}
}
}

View File

@ -23,6 +23,7 @@ PrivilegedTokenArtworkPanel 1.0 PrivilegedTokenArtworkPanel.qml
ProfilePopupInviteFriendsPanel 1.0 ProfilePopupInviteFriendsPanel.qml ProfilePopupInviteFriendsPanel 1.0 ProfilePopupInviteFriendsPanel.qml
ProfilePopupInviteMessagePanel 1.0 ProfilePopupInviteMessagePanel.qml ProfilePopupInviteMessagePanel 1.0 ProfilePopupInviteMessagePanel.qml
ProfilePopupOverviewPanel 1.0 ProfilePopupOverviewPanel.qml ProfilePopupOverviewPanel 1.0 ProfilePopupOverviewPanel.qml
SharedAddressesPanel 1.0 SharedAddressesPanel.qml
SortableTokenHoldersList 1.0 SortableTokenHoldersList.qml SortableTokenHoldersList 1.0 SortableTokenHoldersList.qml
SortableTokenHoldersPanel 1.0 SortableTokenHoldersPanel.qml SortableTokenHoldersPanel 1.0 SortableTokenHoldersPanel.qml
TagsPanel 1.0 TagsPanel.qml TagsPanel 1.0 TagsPanel.qml

View File

@ -0,0 +1,50 @@
import QtQuick 2.15
import StatusQ.Popups.Dialog 0.1
import AppLayouts.Communities.panels 1.0
import utils 1.0
StatusDialog {
id: root
property bool isEditMode
required property string communityName
required property string communityIcon
property int loginType: Constants.LoginType.Password
required property var walletAccountsModel // name, address, emoji, colorId, assets
required property var permissionsModel // id, key, permissionType, holdingsListModel, channelsListModel, isPrivate, tokenCriteriaMet
required property var assetsModel
required property var collectiblesModel
readonly property string selectedAirdropAddress: panel.selectedAirdropAddress
readonly property var selectedSharedAddresses: panel.selectedSharedAddresses
signal shareSelectedAddressesClicked(string airdropAddress, var sharedAddresses)
title: panel.title
implicitWidth: 640 // by design
padding: 0
contentItem: SharedAddressesPanel {
id: panel
isEditMode: root.isEditMode
communityName: root.communityName
communityIcon: root.communityIcon
loginType: root.loginType
walletAccountsModel: root.walletAccountsModel
permissionsModel: root.permissionsModel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
onShareSelectedAddressesClicked: root.shareSelectedAddressesClicked(airdropAddress, sharedAddresses)
onClose: root.close()
}
footer: StatusDialogFooter {
spacing: Style.current.padding
rightButtons: panel.buttons
}
}

View File

@ -16,3 +16,4 @@ RemotelyDestructPopup 1.0 RemotelyDestructPopup.qml
SignMultiTokenTransactionsPopup 1.0 SignMultiTokenTransactionsPopup.qml SignMultiTokenTransactionsPopup 1.0 SignMultiTokenTransactionsPopup.qml
SignTokenTransactionsPopup 1.0 SignTokenTransactionsPopup.qml SignTokenTransactionsPopup 1.0 SignTokenTransactionsPopup.qml
TransferOwnershipPopup 1.0 TransferOwnershipPopup.qml TransferOwnershipPopup 1.0 TransferOwnershipPopup.qml
SharedAddressesPopup 1.0 SharedAddressesPopup.qml

View File

@ -20,6 +20,7 @@ import shared.views.chat 1.0
import AppLayouts.Communities.popups 1.0 import AppLayouts.Communities.popups 1.0
import AppLayouts.Communities.panels 1.0 import AppLayouts.Communities.panels 1.0
import AppLayouts.Wallet.stores 1.0 as WalletStore
// FIXME: Rework me to use ColumnLayout instead of anchors!! // FIXME: Rework me to use ColumnLayout instead of anchors!!
Item { Item {
@ -126,10 +127,15 @@ Item {
introMessage: communityData.introMessage introMessage: communityData.introMessage
imageSrc: communityData.image imageSrc: communityData.image
accessType: communityData.access accessType: communityData.access
loginType: root.store.loginType
walletAccountsModel: WalletStore.RootStore.receiveAccounts
permissionsModel: root.store.permissionsStore.permissionsModel
assetsModel: root.store.assetsModel
collectiblesModel: root.store.collectiblesModel
onJoined: { onJoined: {
joinCommunityButton.loading = true joinCommunityButton.loading = true
root.store.requestToJoinCommunityWithAuthentication(root.store.userProfileInst.name) root.store.requestToJoinCommunityWithAuthentication(root.store.userProfileInst.name, sharedAddresses, airdropAddress)
} }
onCancelMembershipRequest: { onCancelMembershipRequest: {
root.store.cancelPendingRequest(communityData.id) root.store.cancelPendingRequest(communityData.id)

View File

@ -18,6 +18,8 @@ import SortFilterProxyModel 0.2
import "../panels" import "../panels"
import AppLayouts.Communities.popups 1.0 import AppLayouts.Communities.popups 1.0
import AppLayouts.Communities.panels 1.0 import AppLayouts.Communities.panels 1.0
import AppLayouts.Wallet.stores 1.0 as WalletStore
import AppLayouts.Chat.stores 1.0 as ChatStore
SettingsContentBase { SettingsContentBase {
id: root id: root
@ -208,22 +210,23 @@ SettingsContentBase {
property string communityId property string communityId
readonly property var chatCommunitySectionModule: { readonly property var chatStore: ChatStore.RootStore {
root.rootStore.mainModuleInst.prepareCommunitySectionModuleForCommunityId(communityIntroDialog.communityId) chatCommunitySectionModule: {
return root.rootStore.mainModuleInst.getCommunitySectionModule() root.rootStore.mainModuleInst.prepareCommunitySectionModuleForCommunityId(communityIntroDialog.communityId)
return root.rootStore.mainModuleInst.getCommunitySectionModule()
}
} }
onJoined: { loginType: chatStore.loginType
chatCommunitySectionModule.requestToJoinCommunityWithAuthentication(root.rootStore.userProfileInst.name) walletAccountsModel: WalletStore.RootStore.receiveAccounts
} permissionsModel: chatStore.permissionsStore.permissionsModel
assetsModel: chatStore.assetsModel
collectiblesModel: chatStore.collectiblesModel
onCancelMembershipRequest: { onJoined: chatStore.requestToJoinCommunityWithAuthentication(root.rootStore.userProfileInst.name, JSON.stringify(sharedAddresses), airdropAddress)
root.rootStore.cancelPendingRequest(communityIntroDialog.communityId) onCancelMembershipRequest: root.rootStore.cancelPendingRequest(communityIntroDialog.communityId)
}
onClosed: { onClosed: destroy()
destroy()
}
} }
} }
} }

View File

@ -1,7 +1,6 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.15
import QtQml.Models 2.14
import utils 1.0 import utils 1.0
@ -9,9 +8,14 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Popups.Dialog 0.1 import StatusQ.Popups 0.1
import StatusQ.Core.Utils 0.1
StatusDialog { import AppLayouts.Communities.panels 1.0
import SortFilterProxyModel 0.2
StatusStackModal {
id: root id: root
property string name property string name
@ -19,74 +23,131 @@ StatusDialog {
property int accessType property int accessType
property url imageSrc property url imageSrc
property bool isInvitationPending: false property bool isInvitationPending: false
property int loginType: Constants.LoginType.Password
signal joined required property var walletAccountsModel // name, address, emoji, colorId
signal cancelMembershipRequest required property var permissionsModel // id, key, permissionType, holdingsListModel, channelsListModel, isPrivate, tokenCriteriaMet
required property var assetsModel
required property var collectiblesModel
signal joined(string airdropAddress, var sharedAddresses)
signal cancelMembershipRequest()
width: 640 // by design
padding: 0 padding: 0
title: qsTr("Welcome to %1").arg(name) stackTitle: root.accessType === Constants.communityChatOnRequestAccess ? qsTr("Request to join %1").arg(name) : qsTr("Welcome to %1").arg(name)
footer: StatusDialogFooter { rightButtons: [d.shareButton, finishButton]
rightButtons: ObjectModel {
StatusButton { finishButton: StatusButton {
text: root.isInvitationPending ? qsTr("Cancel Membership Request") text: root.isInvitationPending ? qsTr("Cancel Membership Request")
: (root.accessType === Constants.communityChatOnRequestAccess : (root.accessType === Constants.communityChatOnRequestAccess
? qsTr("Request to join %1").arg(root.name) ? qsTr("Share your addresses to join")
: qsTr("Join %1").arg(root.name) ) : qsTr("Join %1").arg(root.name) )
type: root.isInvitationPending ? StatusBaseButton.Type.Danger type: root.isInvitationPending ? StatusBaseButton.Type.Danger
: StatusBaseButton.Type.Normal : StatusBaseButton.Type.Normal
enabled: checkBox.checked || root.isInvitationPending icon.name: root.accessType === Constants.communityChatOnRequestAccess && !root.isInvitationPending ? Constants.authenticationIconByType[root.loginType] : ""
onClicked: { onClicked: {
if (root.isInvitationPending) { if (root.isInvitationPending) {
root.cancelMembershipRequest() root.cancelMembershipRequest()
} else { } else {
root.joined() root.joined(d.selectedAirdropAddress, d.selectedSharedAddresses)
}
root.close()
}
}
QtObject {
id: d
readonly property var tempAddressesModel: SortFilterProxyModel {
sourceModel: root.walletAccountsModel
filters: [
ValueFilter {
roleName: "walletType"
value: Constants.watchWalletType
inverted: true
}
]
sorters: [
ExpressionSorter {
function isGenerated(modelData) {
return modelData.walletType === Constants.generatedWalletType
} }
root.close() expression: {
return isGenerated(modelLeft)
}
},
RoleSorter {
roleName: "position"
},
RoleSorter {
roleName: "name"
}
]
}
// all non-watched addresses by default, unless selected otherwise below in SharedAddressesPanel
property var selectedSharedAddresses: tempAddressesModel.count ? ModelUtils.modelToFlatArray(tempAddressesModel, "address") : []
property string selectedAirdropAddress: selectedSharedAddresses.length ? selectedSharedAddresses[0] : ""
readonly property var shareButton: StatusFlatButton {
height: finishButton.height
visible: !root.isInvitationPending && !root.replaceItem
borderColor: Theme.palette.baseColor2
text: qsTr("Select addresses to share")
onClicked: root.replace(sharedAddressesPanelComponent)
}
}
Component {
id: sharedAddressesPanelComponent
SharedAddressesPanel {
communityName: root.name
communityIcon: root.imageSrc
loginType: root.loginType
walletAccountsModel: root.walletAccountsModel
permissionsModel: root.permissionsModel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
onShareSelectedAddressesClicked: {
d.selectedAirdropAddress = airdropAddress
d.selectedSharedAddresses = sharedAddresses
root.replaceItem = undefined // go back, unload us
}
}
}
stackItems: [
StatusScrollView {
id: scrollView
contentWidth: availableWidth
ColumnLayout {
spacing: 24
width: scrollView.availableWidth
StatusRoundedImage {
id: roundImage
Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: 64
Layout.preferredWidth: Layout.preferredHeight
visible: image.status == Image.Loading || image.status == Image.Ready
image.source: root.imageSrc
}
StatusBaseText {
id: introText
Layout.fillWidth: true
text: root.introMessage || qsTr("Community <b>%1</b> has no intro message...").arg(root.name)
color: Theme.palette.directColor1
wrapMode: Text.WordWrap
} }
} }
} }
} ]
StatusScrollView {
id: scrollView
anchors.fill: parent
implicitWidth: 640 // by design
contentWidth: availableWidth
ColumnLayout {
id: columnContent
spacing: 24
width: scrollView.availableWidth
StatusRoundedImage {
id: roundImage
Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: 64
Layout.preferredWidth: Layout.preferredHeight
visible: image.status == Image.Loading || image.status == Image.Ready
image.source: root.imageSrc
}
StatusBaseText {
id: introText
Layout.fillWidth: true
text: root.introMessage !== "" ? root.introMessage : qsTr("Community <b>%1</b> has no intro message...").arg(root.name)
color: Theme.palette.directColor1
wrapMode: Text.WordWrap
}
StatusCheckBox {
id: checkBox
Layout.alignment: Qt.AlignCenter
visible: !root.isInvitationPending
text: qsTr("I agree with the above")
}
}
}
} }

View File

@ -54,7 +54,6 @@ QtObject {
return (modelData.permissionType == Constants.permissionType.viewAndPost) && return (modelData.permissionType == Constants.permissionType.viewAndPost) &&
root.permissionsModel.belongsToChat(modelData.id, root.activeChannelId) && root.permissionsModel.belongsToChat(modelData.id, root.activeChannelId) &&
(modelData.tokenCriteriaMet || !modelData.isPrivate) (modelData.tokenCriteriaMet || !modelData.isPrivate)
} }
filters: [ filters: [
ExpressionFilter { ExpressionFilter {

View File

@ -987,7 +987,8 @@ QtObject {
enum TokenType { enum TokenType {
Unknown = 0, Unknown = 0,
ERC20 = 1, // Asset ERC20 = 1, // Asset
ERC721 = 2 // Collectible ERC721 = 2, // Collectible
ENS = 3
} }
// Mirrors src/backend/activity.nim ActivityStatus // Mirrors src/backend/activity.nim ActivityStatus

View File

@ -24,7 +24,7 @@ QtObject {
} }
function startsWith0x(value) { function startsWith0x(value) {
return value.startsWith('0x') return !!value && value.startsWith('0x')
} }
function isChatKey(value) { function isChatKey(value) {
@ -248,8 +248,8 @@ QtObject {
* Returns text in the format "✓ 12 words" for seed phrases input boxes * Returns text in the format "✓ 12 words" for seed phrases input boxes
*/ */
function seedPhraseWordCountText(text) { function seedPhraseWordCountText(text) {
let wordCount = countWords(text); const wordCount = countWords(text);
return getTick(wordCount) + wordCount.toString() + " " + qsTr("words") return getTick(wordCount) + qsTr("%n word(s)", "", wordCount)
} }
function uuid() { function uuid() {
@ -288,7 +288,7 @@ QtObject {
} else if (!/^\d+$/.test(firstPINField.pinInput)) { } else if (!/^\d+$/.test(firstPINField.pinInput)) {
return [false, qsTr("The PIN must contain only digits")]; return [false, qsTr("The PIN must contain only digits")];
} else if (firstPINField.pinInput.length != Constants.keycard.general.keycardPinLength) { } else if (firstPINField.pinInput.length != Constants.keycard.general.keycardPinLength) {
return [false, qsTr("The PIN must be exactly %1 digits").arg(Constants.keycard.general.keycardPinLength)]; return [false, qsTr("The PIN must be exactly %n digit(s)", "", Constants.keycard.general.keycardPinLength)];
} }
return [true, ""]; return [true, ""];
@ -296,7 +296,7 @@ QtObject {
if (repeatPINField.pinInput === "") { if (repeatPINField.pinInput === "") {
return [false, qsTr("You need to repeat your PIN")]; return [false, qsTr("You need to repeat your PIN")];
} else if (repeatPINField.pinInput !== firstPINField.pinInput) { } else if (repeatPINField.pinInput !== firstPINField.pinInput) {
return [false, qsTr("PIN don't match")]; return [false, qsTr("PINs don't match")];
} }
return [true, ""]; return [true, ""];
@ -373,7 +373,7 @@ QtObject {
} }
if(validation & Utils.Validate.TextLength && str.length > limit) { if(validation & Utils.Validate.TextLength && str.length > limit) {
errMsg = qsTr("The %1 cannot exceed %2 characters").arg(fieldName, limit) errMsg = qsTr("The %1 cannot exceed %n character(s)", "", limit).arg(fieldName)
} }
if(validation & Utils.Validate.TextHexColor && !isHexColor(str)) { if(validation & Utils.Validate.TextHexColor && !isHexColor(str)) {
@ -392,7 +392,7 @@ QtObject {
if (errors.minLength) { if (errors.minLength) {
return errors.minLength.min === 1 ? return errors.minLength.min === 1 ?
qsTr("You need to enter a %1").arg(fieldName) : qsTr("You need to enter a %1").arg(fieldName) :
qsTr("Value has to be at least %1 characters long").arg(errors.minLength.min) qsTr("Value has to be at least %n character(s) long", "", errors.minLength.min)
} }
} }
return "" return ""
@ -657,7 +657,7 @@ QtObject {
// special handling because on an index attached to the constant // special handling because on an index attached to the constant
if (key.startsWith(Constants.appTranslatableConstants.keycardAccountNameOfUnknownWalletAccount)) { if (key.startsWith(Constants.appTranslatableConstants.keycardAccountNameOfUnknownWalletAccount)) {
let num = key.substring(Constants.appTranslatableConstants.keycardAccountNameOfUnknownWalletAccount.length) let num = key.substring(Constants.appTranslatableConstants.keycardAccountNameOfUnknownWalletAccount.length)
return "%1%2".arg(qsTr("acc")).arg(num) //short name of an unknown (removed) wallet account return "%1%2".arg(qsTr("acc", "short for account")).arg(num) //short name of an unknown (removed) wallet account
} }
return key return key

@ -1 +1 @@
Subproject commit 6a471f1bef1288407751400d93b12b7fa911474d Subproject commit 70b76297fd074b4adda5e659d260c8cc18e51ef9