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())
self.delegate.requestToJoinCommunityWithAuthentication(ensName, addressesArray, airdropAddress)
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.} =
self.delegate.joinGroupChatFromInvitation(groupName, chatId, adminPK)
@ -421,4 +421,4 @@ QtObject:
read = getAllTokenRequirementsMet
notify = allTokenRequirementsMetChanged
proc userAuthenticationCanceled*(self: View) {.signal.}
proc userAuthenticationCanceled*(self: View) {.signal.}

View File

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

View File

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

View File

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

View File

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

View File

@ -242,6 +242,13 @@
],
"EditOwnerTokenView": [
"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
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()")
}
}
@ -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 {
Layout.fillHeight: true
}

View File

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

View File

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

View File

@ -59,6 +59,7 @@ SplitView {
readonly property string image: ModelsData.icons.socks
readonly property string color: "red"
readonly property bool owner: isOwnerCheckBox.checked
readonly property bool admin: isAdminCheckBox.checked
}
function log(method, index) {
@ -94,6 +95,12 @@ SplitView {
text: "Is owner"
}
CheckBox {
id: isAdminCheckBox
text: "Is admin"
}
CheckBox {
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
totalFeeText: "0.01 ETH ($265.43)"
onSignTransactionClicked: logs.logEvent("SignTokenTransactionsPopup::onSignTransactionClicked")
onCancelClicked: logs.logEvent("SignTokenTransactionsPopup::onCancelClicked")
onSignTransactionClicked: logs.logEvent("SignMultiTokenTransactionsPopup::onSignTransactionClicked")
onCancelClicked: logs.logEvent("SignMultiTokenTransactionsPopup::onCancelClicked")
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -14,13 +14,15 @@ QtObject {
holdingsListModel: root.createHoldingsModel1(),
channelsListModel: root.createChannelsModel1(),
permissionType: PermissionTypes.Type.Admin,
isPrivate: true
isPrivate: true,
tokenCriteriaMet: false
},
{
holdingsListModel: root.createHoldingsModel2(),
channelsListModel: root.createChannelsModel2(),
permissionType: PermissionTypes.Type.Member,
isPrivate: false
isPrivate: false,
tokenCriteriaMet: true
}
]
@ -29,7 +31,7 @@ QtObject {
holdingsListModel: root.createHoldingsModel4(),
channelsListModel: root.createChannelsModel1(),
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 ModelChangeGuard guard: ModelChangeGuard {
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() {
return [
{
@ -230,7 +373,7 @@ QtObject {
return [
{
type: HoldingTypes.Type.Ens,
key: "Ens",
key: "*.eth",
amount: 1,
available: true
}
@ -249,7 +392,24 @@ QtObject {
type: HoldingTypes.Type.Asset,
key: "Dai",
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,
key: "ENS",
key: "foo.bar.eth",
amount: 1,
available: false
},
@ -317,7 +477,7 @@ QtObject {
{
type: HoldingTypes.Type.Asset,
key: "zrx",
amount: 1,
amount: 10,
available: false
},
{
@ -355,4 +515,8 @@ QtObject {
function createChannelsModel2() {
return []
}
function createChannelsModel3() {
return [{ key: "_vip" } ]
}
}

View File

@ -1,9 +1,122 @@
import QtQuick 2.15
import utils 1.0
ListModel {
ListElement { name: "Test account"; emoji: "😋"; colorId: "primary"; address: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"; walletType: "" }
ListElement { name: "Another account - generated"; emoji: "🚗"; colorId: "army"; address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8888"; walletType: "generated" }
ListElement { name: "Another account - seed"; emoji: "🎨"; colorId: "army"; address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8888"; walletType: "seed" }
ListElement { name: "Another account - watch"; emoji: "🔗"; colorId: "army"; address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8888"; walletType: "watch" }
ListElement { name: "Another account - key"; emoji: "💼"; colorId: "army"; address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8888"; walletType: "key" }
readonly property var data: [
{
name: "helloworld",
emoji: "😋",
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
include/StatusQ/QClipboardProxy.h
include/StatusQ/modelutilsinternal.h
include/StatusQ/permissionutilsinternal.h
include/StatusQ/rxvalidator.h
include/StatusQ/statussyntaxhighlighter.h
include/StatusQ/statuswindow.h
src/QClipboardProxy.cpp
src/modelutilsinternal.cpp
src/permissionutilsinternal.cpp
src/rxvalidator.cpp
src/statussyntaxhighlighter.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
visible: tagsRepeater.count > 0
anchors.top: statusListItemTertiaryTitle.bottom
anchors.topMargin: visible ? 8 : 0
width: Math.min(statusListItemTagsSlotInline.width, statusListItemTagsSlotInline.availableWidth)
anchors.topMargin: visible ? 2 : 0
width: Math.min(statusListItemTagsSlotInline.width, statusListItemTagsSlotInline.availableWidth, parent.width)
height: visible ? contentHeight : 0
padding: 0
@ -378,7 +378,7 @@ Rectangle {
RowLayout {
anchors.top: tagsScrollView.bottom
anchors.topMargin: visible ? 8 : 0
anchors.topMargin: visible ? 4 : 0
width: parent.width
visible: !!root.beneathTagsIcon || !!root.beneathTagsTitle
spacing: 4

View File

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

View File

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

View File

@ -21,6 +21,7 @@ RadioButton {
Large
}
opacity: enabled ? 1.0 : 0.3
font.family: Theme.palette.baseFont.name
indicator: Rectangle {
@ -47,5 +48,6 @@ RadioButton {
verticalAlignment: Text.AlignVCenter
leftPadding: 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
}
function stripTrailingZeroes(numStr, locale) {
let regEx = locale.decimalPoint == "." ? /(\.[0-9]*[1-9])0+$|\.0*$/ : /(\,[0-9]*[1-9])0+$|\,0*$/
return numStr.replace(regEx, '$1')
@ -101,8 +102,6 @@ QtObject {
}
function currencyAmountToLocaleString(currencyAmount, options = null, locale = null) {
locale = locale || Qt.locale()
if (!currencyAmount) {
return qsTr("N/A")
}
@ -114,6 +113,8 @@ QtObject {
if (typeof currencyAmount.amount === "undefined")
return qsTr("N/A")
locale = locale || Qt.locale()
// Parse options
var optNoSymbol = false
var optRawAmount = false
@ -141,7 +142,7 @@ QtObject {
if (currencyAmount.amount > 0 && currencyAmount.amount < minAmount && !optRawAmount)
{
// Handle amounts smaller than resolution
amountStr = "<%1".arg(numberToLocaleString(minAmount, displayDecimals, locale))
amountStr = "<%1".arg(numberToLocaleString(minAmount, optDisplayDecimals, locale))
} else {
var amount
var displayDecimals
@ -158,11 +159,10 @@ QtObject {
// For normal numbers, we show the whole integral part and as many decimal places not
// not to exceed the maximum
amount = currencyAmount.amount
// For numbers over 1M , dont show decimal places
// For numbers over 1M , dont show decimal places
if(numIntegerDigits > maxDigitsToShowDecimal) {
displayDecimals = 0
}
else {
} else {
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:/")) {
//raw image data
source = icon
objectName = "custom-icon"
}
else if (icon !== "") {
objectName = "custom-icon"
} else if (icon !== "") {
source = "../../assets/img/icons/" + icon+ ".svg";
objectName = icon + "-icon"
}

View File

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

View File

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

View File

@ -1,7 +1,8 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import QtQml.Models 2.14
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml.Models 2.15
import QtQml 2.15
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
@ -28,6 +29,14 @@ Dialog {
margins: 64
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
Overlay.modal: Rectangle {

View File

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

View File

@ -1,6 +1,7 @@
#include "StatusQ/modelutilsinternal.h"
#include <QAbstractItemModel>
#include <QDebug>
ModelUtilsInternal::ModelUtilsInternal(QObject* parent)
: QObject(parent)
@ -16,7 +17,6 @@ QStringList ModelUtilsInternal::roleNames(QAbstractItemModel *model) const
return {roles.cbegin(), roles.cend()};
}
int ModelUtilsInternal::roleByName(QAbstractItemModel* model,
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/modelutilsinternal.h"
#include "StatusQ/permissionutilsinternal.h"
#include "StatusQ/rxvalidator.h"
#include "StatusQ/statussyntaxhighlighter.h"
#include "StatusQ/statuswindow.h"
@ -26,6 +27,10 @@ public:
qmlRegisterSingletonType<ModelUtilsInternal>(
"StatusQ.Internal", 0, 1, "ModelUtils", &ModelUtilsInternal::qmlInstance);
qmlRegisterSingletonType<PermissionUtilsInternal>("StatusQ.Internal", 0, 1, "PermissionUtils", [](QQmlEngine *, QJSEngine *) {
return new PermissionUtilsInternal;
});
QZXing::registerQMLTypes();
qqsfpm::registerTypes();
}

View File

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

View File

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

View File

@ -26,7 +26,7 @@ StatusSectionLayout {
id: root
property var contactsStore
property bool hasAddedContacts: root.contactsStore.myContactsModel.count > 0
property bool hasAddedContacts: contactsStore.myContactsModel.count > 0
property RootStore rootStore
property var createChatPropertiesStore
@ -36,7 +36,7 @@ StatusSectionLayout {
property var stickersPopup
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 viewAndPostPermissionsSatisfied: chatContentModule.viewAndPostPermissionsSatisfied
property bool hasViewOnlyPermissions: false

View File

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

View File

@ -4,6 +4,7 @@ import QtQml 2.14
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Internal 0.1 as Internal
import AppLayouts.Communities.controls 1.0
@ -63,6 +64,15 @@ QtObject {
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) {
switch (type) {
case HoldingTypes.Type.Asset:

View File

@ -27,10 +27,6 @@ Control {
// By design values:
readonly property int defaultHoldingsSpacing: 8
function holdingsTextFormat(name, amount) {
return PermissionsHelpers.setHoldingsTextFormat(HoldingTypes.Type.Asset, name, amount)
}
}
contentItem: ColumnLayout {
@ -83,7 +79,7 @@ Control {
asset.color: asset.isImage ? "transparent" : titleText.color
closeButtonVisible: false
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
}
}

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
spacing: 32 // default by design
contentItem: ColumnLayout {
id: column
spacing: root.spacing
component CustomHoldingsListPanel: HoldingsListPanel {
Layout.fillWidth: true
@ -123,25 +131,23 @@ Control {
CustomHoldingsListPanel {
visible: !root.joinCommunity && d.viewOnlyPermissionsModel.count > 0
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 hold").arg(root.channelName)
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)
model: d.viewOnlyPermissionsModel
}
CustomHoldingsListPanel {
visible: !root.joinCommunity && d.viewAndPostPermissionsModel.count > 0
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 hold").arg(root.channelName)
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)
model: d.viewAndPostPermissionsModel
}
HoldingsListPanel {
Layout.fillWidth: true
spacing: root.spacing
visible: !root.joinCommunity && !!d.moderateHoldings
introText: qsTr("To moderate in the <b>%1</b> channel you need to hold").arg(root.channelName)
model: d.moderateHoldingsModel
CustomHoldingsListPanel {
visible: !root.joinCommunity && d.moderatePermissionsModel.count > 0
introText: qsTr("To moderate in the <b>#%1</b> channel you need to hold").arg(root.channelName)
model: d.moderatePermissionsModel
}
StatusButton {
@ -150,7 +156,7 @@ Control {
text: root.isInvitationPending ? d.getInvitationPendingText() : d.getRevealAddressText()
icon.name: root.isInvitationPending ? "" : Constants.authenticationIconByType[root.loginType]
font.pixelSize: 13
enabled: root.requirementsMet || d.communityPermissionsModel.count == 0
enabled: root.requirementsMet || d.communityPermissionsModel.count === 0
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
ProfilePopupInviteMessagePanel 1.0 ProfilePopupInviteMessagePanel.qml
ProfilePopupOverviewPanel 1.0 ProfilePopupOverviewPanel.qml
SharedAddressesPanel 1.0 SharedAddressesPanel.qml
SortableTokenHoldersList 1.0 SortableTokenHoldersList.qml
SortableTokenHoldersPanel 1.0 SortableTokenHoldersPanel.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
SignTokenTransactionsPopup 1.0 SignTokenTransactionsPopup.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.panels 1.0
import AppLayouts.Wallet.stores 1.0 as WalletStore
// FIXME: Rework me to use ColumnLayout instead of anchors!!
Item {
@ -126,10 +127,15 @@ Item {
introMessage: communityData.introMessage
imageSrc: communityData.image
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: {
joinCommunityButton.loading = true
root.store.requestToJoinCommunityWithAuthentication(root.store.userProfileInst.name)
root.store.requestToJoinCommunityWithAuthentication(root.store.userProfileInst.name, sharedAddresses, airdropAddress)
}
onCancelMembershipRequest: {
root.store.cancelPendingRequest(communityData.id)

View File

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

View File

@ -1,7 +1,6 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.1
import QtQml.Models 2.14
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import utils 1.0
@ -9,9 +8,14 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Popups.Dialog 0.1
import StatusQ.Popups 0.1
import StatusQ.Core.Utils 0.1
StatusDialog {
import AppLayouts.Communities.panels 1.0
import SortFilterProxyModel 0.2
StatusStackModal {
id: root
property string name
@ -19,74 +23,131 @@ StatusDialog {
property int accessType
property url imageSrc
property bool isInvitationPending: false
property int loginType: Constants.LoginType.Password
signal joined
signal cancelMembershipRequest
required property var walletAccountsModel // name, address, emoji, colorId
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
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: ObjectModel {
StatusButton {
text: root.isInvitationPending ? qsTr("Cancel Membership Request")
: (root.accessType === Constants.communityChatOnRequestAccess
? qsTr("Request to join %1").arg(root.name)
: qsTr("Join %1").arg(root.name) )
type: root.isInvitationPending ? StatusBaseButton.Type.Danger
: StatusBaseButton.Type.Normal
enabled: checkBox.checked || root.isInvitationPending
onClicked: {
if (root.isInvitationPending) {
root.cancelMembershipRequest()
} else {
root.joined()
rightButtons: [d.shareButton, finishButton]
finishButton: StatusButton {
text: root.isInvitationPending ? qsTr("Cancel Membership Request")
: (root.accessType === Constants.communityChatOnRequestAccess
? qsTr("Share your addresses to join")
: qsTr("Join %1").arg(root.name) )
type: root.isInvitationPending ? StatusBaseButton.Type.Danger
: StatusBaseButton.Type.Normal
icon.name: root.accessType === Constants.communityChatOnRequestAccess && !root.isInvitationPending ? Constants.authenticationIconByType[root.loginType] : ""
onClicked: {
if (root.isInvitationPending) {
root.cancelMembershipRequest()
} else {
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) &&
root.permissionsModel.belongsToChat(modelData.id, root.activeChannelId) &&
(modelData.tokenCriteriaMet || !modelData.isPrivate)
}
filters: [
ExpressionFilter {

View File

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

View File

@ -24,7 +24,7 @@ QtObject {
}
function startsWith0x(value) {
return value.startsWith('0x')
return !!value && value.startsWith('0x')
}
function isChatKey(value) {
@ -248,8 +248,8 @@ QtObject {
* Returns text in the format "✓ 12 words" for seed phrases input boxes
*/
function seedPhraseWordCountText(text) {
let wordCount = countWords(text);
return getTick(wordCount) + wordCount.toString() + " " + qsTr("words")
const wordCount = countWords(text);
return getTick(wordCount) + qsTr("%n word(s)", "", wordCount)
}
function uuid() {
@ -288,7 +288,7 @@ QtObject {
} else if (!/^\d+$/.test(firstPINField.pinInput)) {
return [false, qsTr("The PIN must contain only digits")];
} 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, ""];
@ -296,7 +296,7 @@ QtObject {
if (repeatPINField.pinInput === "") {
return [false, qsTr("You need to repeat your PIN")];
} else if (repeatPINField.pinInput !== firstPINField.pinInput) {
return [false, qsTr("PIN don't match")];
return [false, qsTr("PINs don't match")];
}
return [true, ""];
@ -373,7 +373,7 @@ QtObject {
}
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)) {
@ -392,7 +392,7 @@ QtObject {
if (errors.minLength) {
return errors.minLength.min === 1 ?
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 ""
@ -657,7 +657,7 @@ QtObject {
// special handling because on an index attached to the constant
if (key.startsWith(Constants.appTranslatableConstants.keycardAccountNameOfUnknownWalletAccount)) {
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

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