fix: Optimize ContactsView & MembersTabPanel settings pages

- removed nested ListViews inside StackLayouts, in order to reduce the
memory footprint and improve performance, and also to be able to better
manage the scrolling
- no more unrolled multiple listviews, which again hurt the performance;
now the views instantiate the delegates dynamically on the fly
- the tab bar and the search fields now stick to the top of the page,
with the users list view scrolling independently
- both views now uniformly use the common `ContactListItemDelegate`
- the received/sent CRs are now combined into one `pendingContacts`
model
- factored out common search/filter criteria into a new, separate SFPM
`UserFilterContainer` component
- fix an issue where StatusContactVerificationIcons wasn't properly
displaying the "blocked" state/icon
- fix documentation comments, removed relative imports, and updated some

Fixes #16612
Fixes #16958
This commit is contained in:
Lukáš Tinkl 2024-12-18 21:00:08 +01:00
parent 564e91496a
commit 77975e9039
No known key found for this signature in database
31 changed files with 729 additions and 858 deletions

View File

@ -0,0 +1,68 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import StatusQ 0.1
import Models 1.0
import Storybook 1.0
import SortFilterProxyModel 0.2
import utils 1.0
import shared.stores 1.0 as SharedStores
import AppLayouts.Profile.views 1.0
import AppLayouts.Profile.stores 1.0
import mainui.adaptors 1.0
Item {
id: root
ContactsView {
sectionTitle: "Contacts"
anchors.fill: parent
anchors.leftMargin: 64
anchors.topMargin: 16
contentWidth: 560
contactsStore: ContactsStore {
function joinPrivateChat(pubKey) {}
function acceptContactRequest(pubKey, contactRequestId) {}
function dismissContactRequest(pubKey, contactRequestId) {}
}
utilsStore: SharedStores.UtilsStore {
function getEmojiHash(publicKey) {
if (publicKey === "")
return ""
return JSON.stringify(["👨🏻‍🍼", "🏃🏿‍♂️", "🌇", "🤶🏿", "🏮","🤷🏻‍♂️", "🤦🏻", "📣", "🤎", "👷🏽", "😺", "🥞", "🔃", "🧝🏽‍♂️"])
}
}
mutualContactsModel: adaptor.mutualContacts
blockedContactsModel: adaptor.blockedContacts
pendingContactsModel: adaptor.pendingContacts
pendingReceivedContactsCount: adaptor.pendingReceivedRequestContacts.count
}
ContactsModelAdaptor {
id: adaptor
allContacts: SortFilterProxyModel {
sourceModel: UsersModel {}
proxyRoles: [
FastExpressionRole {
function displayNameProxy(localNickname, ensName, displayName, aliasName) {
return ProfileUtils.displayName(localNickname, ensName, displayName, aliasName)
}
name: "preferredDisplayName"
expectedRoles: ["localNickname", "displayName", "ensName", "alias"]
expression: displayNameProxy(model.localNickname, model.ensName, model.displayName, model.alias)
}
]
}
}
}
// category: Views
// status: good

View File

@ -8,6 +8,7 @@ import AppLayouts.Communities.panels 1.0
import AppLayouts.Chat.stores 1.0 as ChatStores
import AppLayouts.Profile.stores 1.0 as ProfileStores
import shared.stores 1.0
import utils 1.0
import Models 1.0
@ -15,8 +16,6 @@ import SortFilterProxyModel 0.2
import Storybook 1.0
import StatusQ 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
SplitView {
id: root
@ -24,46 +23,27 @@ SplitView {
orientation: Qt.Vertical
Logs { id: logs }
// Utils.globalUtilsInst mock
QtObject {
function getEmojiHashAsJson(publicKey) {
return JSON.stringify(["👨🏻‍🍼", "🏃🏿‍♂️", "🌇", "🤶🏿", "🏮","🤷🏻‍♂️", "🤦🏻", "📣", "🤎", "👷🏽", "😺", "🥞", "🔃", "🧝🏽‍♂️"])
}
function getColorId(publicKey) {
return SQUtils.ModelUtils.getByKey(usersModel, "pubKey", publicKey, "colorId")
}
function getCompressedPk(publicKey) { return "zx3sh" + publicKey }
function getColorHashAsJson(publicKey) {
return JSON.stringify([{colorId: 0, segmentLength: 1},
{colorId: 19, segmentLength: 2}])
}
function isCompressedPubKey(publicKey) { return true }
Component.onCompleted: {
Utils.globalUtilsInst = this
}
Component.onDestruction: {
Utils.globalUtilsInst = {}
}
}
MembersTabPanel {
id: membersTabPanelPage
SplitView.fillWidth: true
SplitView.fillHeight: true
placeholderText: "Search users"
model: usersModelWithMembershipState
panelType: viewStateSelector.currentValue
searchString: ctrlSearch.text
rootStore: ChatStores.RootStore {
contactsStore: ProfileStores.ContactsStore {
readonly property string myPublicKey: "0x000"
}
}
utilsStore: UtilsStore {
function getEmojiHash(publicKey) {
if (publicKey === "")
return ""
return JSON.stringify(["👨🏻‍🍼", "🏃🏿‍♂️", "🌇", "🤶🏿", "🏮","🤷🏻‍♂️", "🤦🏻", "📣", "🤎", "👷🏽", "😺", "🥞", "🔃", "🧝🏽‍♂️"])
}
}
onKickUserClicked: {
logs.logEvent("MembersTabPanel::onKickUserClicked", ["id", "name"], arguments)
@ -132,7 +112,7 @@ SplitView {
}
LogsAndControlsPanel {
SplitView.minimumHeight: 100
SplitView.minimumHeight: 200
SplitView.preferredHeight: 320
logsView.logText: logs.logText
@ -144,6 +124,7 @@ SplitView {
}
ComboBox {
Layout.preferredWidth: 300
id: viewStateSelector
textRole: "text"
valueRole: "value"
@ -155,6 +136,13 @@ SplitView {
ListElement { text: "Declined Members"; value: MembersTabPanel.TabType.DeclinedRequests }
}
}
Label { text: "Search" }
TextField {
id: ctrlSearch
Layout.preferredWidth: 300
placeholderText: "Search by member name or chat key"
}
}
}
@ -163,4 +151,6 @@ SplitView {
}
}
// category: Panels
// status: good
// https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/KubaDesktop?type=design&node-id=35909-605774&mode=design&t=KfrAekLfW5mTy68x-0

View File

@ -29,7 +29,7 @@ Item {
StatusTabButton {
width: implicitWidth
enabled: false
text: qsTr("Blocked & disabled")
text: "Blocked & disabled"
}
StatusTabButton {
width: implicitWidth

View File

@ -9,9 +9,10 @@ ListModel {
compressedPubKey: "zQ3shQBu4PRDX17vewYyvSczbTj344viTXxcMNvQLeyQsBDF4",
onlineStatus: Constants.onlineStatus.online,
isContact: true,
isBlocked: false,
isVerified: false,
isAdmin: false,
isUntrustworthy: true,
isUntrustworthy: false,
displayName: "Mike has a very long name that should elide " +
"eventually and result in a tooltip displayed instead",
alias: "",
@ -26,13 +27,15 @@ ListModel {
],
isAwaitingAddress: false,
memberRole: Constants.memberRole.none,
trustStatus: Constants.trustStatus.untrustworthy
trustStatus: Constants.trustStatus.unknown
},
{
pubKey: "0x04df12f12f12f12f1234",
compressedPubKey: "zQ3shQBAAPRDX17vewYyvSczbTj344viTXxcMNvQLeyQsBDF4",
onlineStatus: Constants.onlineStatus.inactive,
isContact: false,
contactRequest: Constants.ContactRequestState.Sent,
isBlocked: false,
isVerified: false,
isAdmin: false,
isUntrustworthy: false,
@ -49,13 +52,14 @@ ListModel {
],
isAwaitingAddress: false,
memberRole: Constants.memberRole.owner,
trustStatus: Constants.trustStatus.trusted
trustStatus: Constants.trustStatus.unknown
},
{
pubKey: "0x04d1b7cc0ef3f470f1238",
compressedPubKey: "zQ3shQ7u3PRDX17vewYyvSczbTj344viTXxcMNvQLeyQsCDF4",
onlineStatus: Constants.onlineStatus.inactive,
isContact: false,
isBlocked: true,
isVerified: false,
isAdmin: false,
isUntrustworthy: true,
@ -66,6 +70,10 @@ ListModel {
icon: ModelsData.icons.dragonereum,
colorId: 4,
isEnsVerified: false,
colorHash: [
{ colorId: 7, segmentLength: 3 },
{ colorId: 12, segmentLength: 1 }
],
isAwaitingAddress: false,
memberRole: Constants.memberRole.none,
trustStatus: Constants.trustStatus.untrustworthy
@ -75,16 +83,17 @@ ListModel {
compressedPubKey: "zQ3shQAL4PRDX17vewYyvSczbTj344viTXxcMNvQLeyQsBDF4",
onlineStatus: Constants.onlineStatus.online,
isContact: true,
isVerified: true,
isBlocked: false,
isVerified: false,
isAdmin: false,
isUntrustworthy: true,
displayName: "Maria",
alias: "meth",
localNickname: "86.eth",
ensName: "8⃣_6⃣.eth",
localNickname: "",
ensName: "",
icon: "",
colorId: 5,
isEnsVerified: true,
isEnsVerified: false,
isAwaitingAddress: false,
memberRole: Constants.memberRole.none,
trustStatus: Constants.trustStatus.untrustworthy
@ -93,8 +102,10 @@ ListModel {
pubKey: "0x04d1bed192343f470f1255",
compressedPubKey: "zQ3shQBu4PGDX17vewYyvSczbTj344viTXxcMNvQLeyQsBD1A",
onlineStatus: Constants.onlineStatus.online,
isContact: true,
isVerified: true,
isContact: false,
contactRequest: Constants.ContactRequestState.Received,
isBlocked: false,
isVerified: false,
isAdmin: true,
isUntrustworthy: true,
displayName: "",
@ -113,7 +124,8 @@ ListModel {
compressedPubKey: "zQ3shQBk4PRDX17vewYyvSczbTj344viTXxcMNvQLeyQsB994",
onlineStatus: Constants.onlineStatus.inactive,
isContact: true,
isVerified: false,
isBlocked: false,
isVerified: true,
isAdmin: false,
isUntrustworthy: false,
displayName: "",

View File

@ -1,10 +1,5 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core.Utils 0.1
import utils 1.0
Flow {
id: root

View File

@ -52,7 +52,7 @@ settingsContentBaseScrollView_ContactListPanel = {"container": mainWindow_Contac
settingsContentBaseScrollView_Item = {"container": mainWindow_ContactsView, "type": "Item", "unnamed": 1, "visible": True}
settingsContentBaseScrollView_sentRequests_ContactsListPanel = {"container": mainWindow_ContactsView, "objectName": "sentRequests_ContactsListPanel", "type": "ContactsListPanel", "visible": True}
contactsTabBar_Contacts_StatusTabButton = {"container": mainWindow_ContactsView, "id": "contactsBtn", "type": "StatusTabButton", "unnamed": 1, "visible": True}
settingsContentBaseScrollView_receivedRequests_ContactsListPanel = {"container": mainWindow_ContactsView, "objectName": "receivedRequests_ContactsListPanel", "type": "ContactsListPanel", "visible": True}
settingsContentBaseScrollView_receivedRequests_ContactsListPanel = {"container": mainWindow_ContactsView, "objectName": "ContactsListPanel", "type": "ContactsListPanel", "visible": True}
settingsContentBaseScrollView_mutualContacts_ContactsListPanel = {"container": mainWindow_ContactsView, "id": "mutualContacts", "type": "ContactsListPanel", "unnamed": 1, "visible": True}
settingsContentBaseScrollView_Invite_friends_StatusButton = {"container": mainWindow_ContactsView, "type": "StatusButton", "unnamed": 1, "visible": True}
settingsContentBaseScrollView_NoFriendsRectangle = {"container": mainWindow_ContactsView, "type": "NoFriendsRectangle", "unnamed": 1, "visible": True}

View File

@ -71,7 +71,7 @@ Row {
}
spacing: 4
visible: root.isContact || (root.trustIndicator !== StatusContactVerificationIcons.TrustedType.None)
visible: root.isContact || root.isBlocked || (root.trustIndicator !== StatusContactVerificationIcons.TrustedType.None)
HoverHandler {
id: hoverHandler
@ -104,7 +104,8 @@ Row {
// (un)trusted
StatusRoundIcon {
visible: !root.isBlocked && root.trustIndicator !== StatusContactVerificationIcons.TrustedType.None
visible: !root.isBlocked && (root.trustIndicator === StatusContactVerificationIcons.TrustedType.Untrustworthy ||
(root.isContact && trustIndicator === StatusContactVerificationIcons.TrustedType.Verified))
asset: root.trustContactIcon
}

View File

@ -58,22 +58,27 @@ ItemDelegate {
*/
property string pubKey: ""
/*!
\qmlproperty string StatusMemberListItem::isContact
\qmlproperty bool StatusMemberListItem::isContact
This property holds if the member represented is contact.
*/
property bool isContact: false
/*!
\qmlproperty string StatusMemberListItem::isVerified
\qmlproperty bool StatusMemberListItem::isVerified
This property holds if the member represented is verified contact.
*/
property bool isVerified: false
/*!
\qmlproperty string StatusMemberListItem::isUntrustworthy
\qmlproperty bool StatusMemberListItem::isUntrustworthy
This property holds if the member represented is untrustworthy.
*/
property bool isUntrustworthy: false
/*!
\qmlproperty string StatusMemberListItem::status
\qmlproperty bool StatusMemberListItem::isBlocked
This property holds if the member represented is blocked.
*/
property bool isBlocked: false
/*!
\qmlproperty int StatusMemberListItem::status
This property holds the connectivity status of the member represented.
int unknown: -1
@ -84,7 +89,7 @@ ItemDelegate {
// FIXME: move Constants.onlineStatus from status-desktop
property int status: 0
/*!
\qmlproperty string StatusMemberListItem::isAdmin
\qmlproperty bool StatusMemberListItem::isAdmin
This property holds the admin status of the member represented.
*/
property bool isAdmin: false
@ -126,7 +131,7 @@ ItemDelegate {
property alias badge: identicon.badge
/*!
\qmlsignal
\qmlsignal clicked
This signal is emitted when the StatusMemberListItem is clicked.
*/
signal clicked(var mouse)
@ -158,9 +163,9 @@ ItemDelegate {
}
}
horizontalPadding: 8
horizontalPadding: Theme.halfPadding
verticalPadding: 12
spacing: 8
spacing: Theme.halfPadding
icon.width: 32
icon.height: 32
@ -170,7 +175,7 @@ ItemDelegate {
background: Rectangle {
color: root.color
radius: 8
radius: Theme.radius
MouseArea {
anchors.fill: parent
@ -200,9 +205,8 @@ ItemDelegate {
// badge
badge.visible: true
badge.color: root.status === 1 ? Theme.palette.successColor1 : Theme.palette.baseColor1 // FIXME, see root.status
badge.anchors.top: undefined
badge.border.width: 2
badge.border.color: Theme.palette.statusListItem.backgroundColor
badge.border.color: root.hovered ? Theme.palette.statusBadge.hoverBorderColor : Theme.palette.statusBadge.borderColor
badge.implicitHeight: 12 // 8 px + 2 px * 2 borders
badge.implicitWidth: 12 // 8 px + 2 px * 2 borders
}
@ -243,7 +247,7 @@ ItemDelegate {
Layout.fillWidth: true
elide: Text.ElideRight
text: d.composeSubtitle()
font.pixelSize: 10
font.pixelSize: Theme.asideTextFontSize
color: Theme.palette.baseColor1
visible: !!text
@ -280,6 +284,7 @@ ItemDelegate {
id: statusContactVerificationIcons
StatusContactVerificationIcons {
isContact: root.isContact
isBlocked: root.isBlocked
trustIndicator: {
if (root.isVerified)
return StatusContactVerificationIcons.TrustedType.Verified

View File

@ -1,4 +1,4 @@
import QtQuick 2.14
import QtQuick 2.15
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
@ -12,4 +12,5 @@ StatusFlatRoundButton {
implicitHeight: 24
icon.color: Theme.palette.directColor9
backgroundHoverColor: "transparent"
tooltip.text: qsTr("Clear")
}

View File

@ -1,15 +1,15 @@
import QtQuick 2.14
import QtQuick 2.15
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
Rectangle {
id: statusFlatRoundButton
property StatusAssetSettings icon: StatusAssetSettings {
width: 23
height: 23
width: 24
height: 24
rotation: 0
color: {

View File

@ -1,6 +1,6 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Components 0.1
@ -17,8 +17,10 @@ Control {
signal toggled
padding: 4
contentItem: RowLayout {
spacing: 16
spacing: Theme.padding
StatusRoundIcon {
asset.name: root.icon
@ -26,22 +28,21 @@ Control {
ColumnLayout {
Layout.fillWidth: true
StatusBaseText {
text: root.title
color: Theme.palette.directColor1
font.pixelSize: 15
}
Item { Layout.fillWidth: true }
Layout.fillHeight: true
StatusBaseText {
Layout.fillWidth: true
text: root.title
visible: !!text
color: Theme.palette.directColor1
elide: Text.ElideRight
}
StatusBaseText {
Layout.fillWidth: true
Layout.fillHeight: true
text: root.subTitle
visible: !!text
color: Theme.palette.baseColor1
font.pixelSize: 15
lineHeight: 1.2
wrapMode: Text.WordWrap
elide: Text.ElideRight
@ -51,6 +52,7 @@ Control {
StatusSwitch {
id: switchItem
objectName: "switchItem"
padding: 0
onToggled: root.toggled()
}

View File

@ -1,13 +1,17 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import StatusQ.Core.Theme 0.1
import AppLayouts.Communities.controls 1.0
Page {
id: root
leftPadding: 64
topPadding: 16
leftPadding: Theme.xlPadding*2
topPadding: Theme.padding
readonly property int preferredContentWidth: 560
property alias buttons: pageHeader.buttons
property alias subtitle: pageHeader.subtitle
@ -18,8 +22,8 @@ Page {
id: pageHeader
height: 44
leftPadding: 64
rightPadding: width - 560 - leftPadding
leftPadding: root.leftPadding
rightPadding: width - root.preferredContentWidth - leftPadding
title: root.title
}

View File

@ -3,7 +3,9 @@ import QtQuick.Layouts 1.15
import StatusQ 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import shared.controls 1.0
import shared.stores 1.0 as SharedStores
import utils 1.0
@ -72,14 +74,15 @@ SettingsPage {
membersTabBar.currentIndex = tabButton.TabBar.index
}
spacing: 19
spacing: Theme.padding
StatusTabBar {
id: membersTabBar
Layout.fillWidth: true
Layout.topMargin: 5
Layout.preferredWidth: root.preferredContentWidth
StatusTabButton {
readonly property int subSection: MembersTabPanel.TabType.AllMembers
id: allMembersBtn
objectName: "allMembersButton"
width: implicitWidth
@ -87,6 +90,8 @@ SettingsPage {
}
StatusTabButton {
readonly property int subSection: MembersTabPanel.TabType.PendingRequests
id: pendingRequestsBtn
objectName: "pendingRequestsButton"
width: implicitWidth
@ -95,6 +100,8 @@ SettingsPage {
}
StatusTabButton {
readonly property int subSection: MembersTabPanel.TabType.DeclinedRequests
id: declinedRequestsBtn
objectName: "declinedRequestsButton"
width: implicitWidth
@ -103,6 +110,8 @@ SettingsPage {
}
StatusTabButton {
readonly property int subSection: MembersTabPanel.TabType.BannedMembers
id: bannedBtn
objectName: "bannedButton"
width: implicitWidth
@ -111,79 +120,53 @@ SettingsPage {
}
}
StackLayout {
id: stackLayout
Layout.fillWidth: true
SearchBox {
id: memberSearch
Layout.preferredWidth: root.preferredContentWidth
placeholderText: qsTr("Search by name or chat key")
enabled: membersTabBar.currentItem.enabled
}
MembersTabPanel {
Layout.preferredWidth: root.preferredContentWidth
Layout.fillHeight: true
currentIndex: membersTabBar.currentIndex
MembersTabPanel {
model: root.membersModel
rootStore: root.rootStore
utilsStore: root.utilsStore
memberRole: root.memberRole
panelType: MembersTabPanel.TabType.AllMembers
Layout.fillWidth: true
Layout.fillHeight: true
onKickUserClicked: {
kickBanPopup.mode = KickBanPopup.Mode.Kick
kickBanPopup.username = name
kickBanPopup.userId = id
kickBanPopup.open()
panelType: membersTabBar.currentItem.subSection
model: {
switch (panelType) {
case MembersTabPanel.TabType.PendingRequests:
return root.pendingMembersModel
case MembersTabPanel.TabType.DeclinedRequests:
return root.declinedMembersModel
case MembersTabPanel.TabType.BannedMembers:
return root.bannedMembersModel
case MembersTabPanel.TabType.AllMembers:
default:
return root.membersModel
}
onBanUserClicked: {
kickBanPopup.mode = KickBanPopup.Mode.Ban
kickBanPopup.username = name
kickBanPopup.userId = id
kickBanPopup.open()
}
onViewMemberMessagesClicked: root.viewMemberMessagesClicked(pubKey, displayName)
}
MembersTabPanel {
model: root.pendingMembersModel
rootStore: root.rootStore
utilsStore: root.utilsStore
memberRole: root.memberRole
panelType: MembersTabPanel.TabType.PendingRequests
searchString: memberSearch.text
rootStore: root.rootStore
utilsStore: root.utilsStore
memberRole: root.memberRole
Layout.fillWidth: true
Layout.fillHeight: true
onAcceptRequestToJoin: root.acceptRequestToJoin(id)
onDeclineRequestToJoin: root.declineRequestToJoin(id)
onKickUserClicked: {
kickBanPopup.mode = KickBanPopup.Mode.Kick
kickBanPopup.username = name
kickBanPopup.userId = id
kickBanPopup.open()
}
MembersTabPanel {
model: root.declinedMembersModel
rootStore: root.rootStore
utilsStore: root.utilsStore
memberRole: root.memberRole
panelType: MembersTabPanel.TabType.DeclinedRequests
Layout.fillWidth: true
Layout.fillHeight: true
onAcceptRequestToJoin: root.acceptRequestToJoin(id)
}
MembersTabPanel {
model: root.bannedMembersModel
rootStore: root.rootStore
utilsStore: root.utilsStore
memberRole: root.memberRole
panelType: MembersTabPanel.TabType.BannedMembers
Layout.fillWidth: true
Layout.fillHeight: true
onUnbanUserClicked: root.unbanUserClicked(id)
onViewMemberMessagesClicked: root.viewMemberMessagesClicked(pubKey, displayName)
onBanUserClicked: {
kickBanPopup.mode = KickBanPopup.Mode.Ban
kickBanPopup.username = name
kickBanPopup.userId = id
kickBanPopup.open()
}
onUnbanUserClicked: root.unbanUserClicked(id)
onAcceptRequestToJoin: root.acceptRequestToJoin(id)
onDeclineRequestToJoin: root.declineRequestToJoin(id)
onViewMemberMessagesClicked: root.viewMemberMessagesClicked(pubKey, displayName)
}
}

View File

@ -10,25 +10,27 @@ import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import shared.controls 1.0
import shared 1.0
import shared.controls.chat 1.0
import shared.controls.delegates 1.0
import shared.stores 1.0 as SharedStores
import shared.views.chat 1.0
import utils 1.0
import AppLayouts.Chat.stores 1.0
import AppLayouts.Communities.layouts 1.0
import SortFilterProxyModel 0.2
Item {
id: root
property string placeholderText: qsTr("Search by member name or chat key")
property var model
required property var model
property string searchString
property RootStore rootStore
property SharedStores.UtilsStore utilsStore
property int panelType: MembersTabPanel.TabType.AllMembers
property int memberRole: Constants.memberRole.none
readonly property bool isOwner: memberRole === Constants.memberRole.owner
@ -49,332 +51,279 @@ Item {
DeclinedRequests
}
property int panelType: MembersTabPanel.TabType.AllMembers
ColumnLayout {
StatusListView {
objectName: "CommunityMembersTabPanel_MembersListViews"
anchors.fill: parent
spacing: 30
SearchBox {
id: memberSearch
Layout.preferredWidth: 400
Layout.leftMargin: 12
placeholderText: root.placeholderText
enabled: !!root.model && !root.model.ModelCount.empty
model: SortFilterProxyModel {
sourceModel: root.model
sorters: [
StringSorter {
roleName: "preferredDisplayName"
caseSensitivity: Qt.CaseInsensitive
}
]
filters: [
UserFilterContainer {
searchString: root.searchString
}
]
}
StatusListView {
id: membersList
objectName: "CommunityMembersTabPanel_MembersListViews"
spacing: 0
Layout.fillWidth: true
Layout.fillHeight: true
delegate: ContactListItemDelegate {
id: memberItem
model: SortFilterProxyModel {
id: filteredModel
sourceModel: root.model
// Buttons visibility conditions:
// 1. Tab based buttons - only visible when the tab is selected
// a. All members tab
// - Kick; - Kick pending
// - Ban; - Ban pending
// b. Pending requests tab
// - Accept; - Accept pending
// - Reject; - Reject pending
// c. Rejected members tab
// - Accept; - Accept pending
// d. Banned members tab
// - Unban
// 2. Pending states - buttons in pending states are always visible in their specific tab. Other buttons are disabled if the request is in pending state
// - Accept button is visible when the user is hovered or when the request is in accepted pending state. This condition can be overriden by the ctaAllowed property
// - Reject button is visible when the user is hovered or when the request is in rejected pending state. This condition can be overriden by the ctaAllowed property
// - Kick and ban buttons are visible when the user is hovered or when the request is in kick or ban pending state. This condition can be overriden by the ctaAllowed property
// 3. Other conditions - buttons are visible when the user is hovered and is not himself or other privileged user
// 4. All members tab, member in AwaitingAddress state - buttons is not visible, sandwatch icon is shown
function searchPredicate(ensName, displayName, aliasName) {
const lowerCaseSearchString = memberSearch.text.toLowerCase()
const secondaryName = ProfileUtils.displayName("", ensName, displayName, aliasName)
/// Helpers ///
return secondaryName.toLowerCase().includes(lowerCaseSearchString)
}
// Tab based buttons
readonly property bool tabIsShowingKickBanButtons: root.panelType === MembersTabPanel.TabType.AllMembers
readonly property bool tabIsShowingUnbanButton: root.panelType === MembersTabPanel.TabType.BannedMembers
readonly property bool tabIsShowingRejectButton: root.panelType === MembersTabPanel.TabType.PendingRequests
readonly property bool tabIsShowingAcceptButton: root.panelType === MembersTabPanel.TabType.PendingRequests ||
root.panelType === MembersTabPanel.TabType.DeclinedRequests
readonly property bool tabIsShowingViewMessagesButton: model.membershipRequestState !== Constants.CommunityMembershipRequestState.BannedWithAllMessagesDelete &&
(root.panelType === MembersTabPanel.TabType.AllMembers ||
root.panelType === MembersTabPanel.TabType.BannedMembers)
sorters : [
StringSorter {
roleName: "preferredDisplayName"
caseSensitivity: Qt.CaseInsensitive
}
]
filters: AnyOf {
enabled: memberSearch.text !== ""
// substring search for either nickname or the other primary/secondary display name
SearchFilter {
roleName: "localNickname"
searchPhrase: memberSearch.text
}
FastExpressionFilter {
expression: {
memberSearch.text
return filteredModel.searchPredicate(model.ensName, model.displayName, model.alias)
}
expectedRoles: ["ensName", "displayName", "alias"]
}
// exact search for the full key
ValueFilter {
roleName: "compressedPubKey"
value: memberSearch.text
}
// Request states
readonly property bool isPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.Pending
readonly property bool isAccepted: model.membershipRequestState === Constants.CommunityMembershipRequestState.Accepted
readonly property bool isRejected: model.membershipRequestState === Constants.CommunityMembershipRequestState.Rejected
readonly property bool isRejectedPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.RejectedPending
readonly property bool isAcceptedPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.AcceptedPending
readonly property bool isBanPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.BannedPending
readonly property bool isUnbanPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.UnbannedPending
readonly property bool isKickPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.KickedPending
readonly property bool isBanned: model.membershipRequestState === Constants.CommunityMembershipRequestState.Banned ||
model.membershipRequestState === Constants.CommunityMembershipRequestState.BannedWithAllMessagesDelete
readonly property bool isKicked: model.membershipRequestState === Constants.CommunityMembershipRequestState.Kicked
// TODO: Connect to backend when available
// The admin that initited the pending state can change the state. Actions are not visible for other admins
readonly property bool ctaAllowed: !isRejectedPending && !isAcceptedPending && !isBanPending && !isUnbanPending && !isKickPending
readonly property bool canBeBanned: {
if (model.isCurrentUser)
return false
switch (model.memberRole) {
// Owner can't be banned
case Constants.memberRole.owner: return false
// TokenMaster can only be banned by owner
case Constants.memberRole.tokenMaster: return root.isOwner
// Admin can only be banned by owner and tokenMaster
case Constants.memberRole.admin: return root.isOwner || root.isTokenMaster
// All normal members can be banned by all privileged users
default: return true
}
}
spacing: 0
readonly property bool showOnHover: hovered && ctaAllowed
readonly property bool canDeleteMessages: model.isCurrentUser || model.memberRole !== Constants.memberRole.owner
delegate: StatusMemberListItem {
id: memberItem
/// Button visibility ///
readonly property bool acceptButtonVisible: tabIsShowingAcceptButton && (isPending || isRejected || isRejectedPending || isAcceptedPending) && showOnHover
readonly property bool rejectButtonVisible: tabIsShowingRejectButton && (isPending || isRejectedPending || isAcceptedPending) && showOnHover
readonly property bool acceptPendingButtonVisible: tabIsShowingAcceptButton && isAcceptedPending
readonly property bool rejectPendingButtonVisible: tabIsShowingRejectButton && isRejectedPending
readonly property bool kickButtonVisible: tabIsShowingKickBanButtons && isAccepted && showOnHover && canBeBanned
readonly property bool banButtonVisible: tabIsShowingKickBanButtons && isAccepted && showOnHover && canBeBanned
readonly property bool kickPendingButtonVisible: tabIsShowingKickBanButtons && isKickPending
readonly property bool banPendingButtonVisible: tabIsShowingKickBanButtons && isBanPending
readonly property bool unbanButtonVisible: tabIsShowingUnbanButton && isBanned && showOnHover
readonly property bool viewMessagesButtonVisible: tabIsShowingViewMessagesButton && showOnHover
readonly property bool messagesDeletedTextVisible: showOnHover &&
model.membershipRequestState === Constants.CommunityMembershipRequestState.BannedWithAllMessagesDelete
// Buttons visibility conditions:
// 1. Tab based buttons - only visible when the tab is selected
// a. All members tab
// - Kick; - Kick pending
// - Ban; - Ban pending
// b. Pending requests tab
// - Accept; - Accept pending
// - Reject; - Reject pending
// c. Rejected members tab
// - Accept; - Accept pending
// d. Banned members tab
// - Unban
// 2. Pending states - buttons in pending states are always visible in their specific tab. Other buttons are disabled if the request is in pending state
// - Accept button is visible when the user is hovered or when the request is in accepted pending state. This condition can be overriden by the ctaAllowed property
// - Reject button is visible when the user is hovered or when the request is in rejected pending state. This condition can be overriden by the ctaAllowed property
// - Kick and ban buttons are visible when the user is hovered or when the request is in kick or ban pending state. This condition can be overriden by the ctaAllowed property
// 3. Other conditions - buttons are visible when the user is hovered and is not himself or other privileged user
// 4. All members tab, member in AwaitingAddress state - buttons is not visible, sandwatch icon is shown
/// Pending states ///
readonly property bool isPendingState: isAcceptedPending || isRejectedPending || isBanPending || isUnbanPending || isKickPending
readonly property string pendingStateText: isAcceptedPending ? qsTr("Accept pending...") :
isRejectedPending ? qsTr("Reject pending...") :
isBanPending ? qsTr("Ban pending...") :
isUnbanPending ? qsTr("Unban pending...") :
isKickPending ? qsTr("Kick pending...") : ""
/// Helpers ///
isAwaitingAddress: model.membershipRequestState === Constants.CommunityMembershipRequestState.AwaitingAddress
// Tab based buttons
readonly property bool tabIsShowingKickBanButtons: root.panelType === MembersTabPanel.TabType.AllMembers
readonly property bool tabIsShowingUnbanButton: root.panelType === MembersTabPanel.TabType.BannedMembers
readonly property bool tabIsShowingRejectButton: root.panelType === MembersTabPanel.TabType.PendingRequests
readonly property bool tabIsShowingAcceptButton: root.panelType === MembersTabPanel.TabType.PendingRequests ||
root.panelType === MembersTabPanel.TabType.DeclinedRequests
readonly property bool tabIsShowingViewMessagesButton: model.membershipRequestState !== Constants.CommunityMembershipRequestState.BannedWithAllMessagesDelete &&
(root.panelType === MembersTabPanel.TabType.AllMembers ||
root.panelType === MembersTabPanel.TabType.BannedMembers)
// Request states
readonly property bool isPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.Pending
readonly property bool isAccepted: model.membershipRequestState === Constants.CommunityMembershipRequestState.Accepted
readonly property bool isRejected: model.membershipRequestState === Constants.CommunityMembershipRequestState.Rejected
readonly property bool isRejectedPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.RejectedPending
readonly property bool isAcceptedPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.AcceptedPending
readonly property bool isBanPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.BannedPending
readonly property bool isUnbanPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.UnbannedPending
readonly property bool isKickPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.KickedPending
readonly property bool isBanned: model.membershipRequestState === Constants.CommunityMembershipRequestState.Banned ||
model.membershipRequestState === Constants.CommunityMembershipRequestState.BannedWithAllMessagesDelete
readonly property bool isKicked: model.membershipRequestState === Constants.CommunityMembershipRequestState.Kicked
// TODO: Connect to backend when available
// The admin that initited the pending state can change the state. Actions are not visible for other admins
readonly property bool ctaAllowed: !isRejectedPending && !isAcceptedPending && !isBanPending && !isUnbanPending && !isKickPending
readonly property bool isHovered: memberItem.hovered
readonly property bool canBeBanned: {
if (model.isCurrentUser)
return false
switch (model.memberRole) {
// Owner can't be banned
case Constants.memberRole.owner: return false
// TokenMaster can only be banned by owner
case Constants.memberRole.tokenMaster: return root.isOwner
// Admin can only be banned by owner and tokenMaster
case Constants.memberRole.admin: return root.isOwner || root.isTokenMaster
// All normal members can be banned by all privileged users
default: return true
components: [
StatusBaseText {
id: pendingText
width: Math.max(implicitWidth, d.pendingTextMaxWidth)
onImplicitWidthChanged: {
d.pendingTextMaxWidth = Math.max(implicitWidth, d.pendingTextMaxWidth)
}
visible: !!text && isPendingState
rightPadding: isKickPending || isBanPending || isUnbanPending ? 0 : Theme.bigPadding
anchors.verticalCenter: parent.verticalCenter
text: pendingStateText
color: Theme.palette.baseColor1
StatusToolTip {
text: qsTr("Waiting for owner node to come online")
visible: hoverHandler.hovered
}
HoverHandler {
id: hoverHandler
enabled: pendingText.visible
}
},
StatusBaseText {
text: qsTr("Messages deleted")
color: Theme.palette.baseColor1
anchors.verticalCenter: parent.verticalCenter
visible: messagesDeletedTextVisible
},
StatusButton {
id: viewMessages
anchors.verticalCenter: parent.verticalCenter
objectName: "MemberListItem_ViewMessages"
text: qsTr("View Messages")
visible: viewMessagesButtonVisible
size: StatusBaseButton.Size.Small
onClicked: root.viewMemberMessagesClicked(model.pubKey, memberItem.title)
},
StatusButton {
anchors.verticalCenter: parent.verticalCenter
objectName: "MemberListItem_KickButton"
text: qsTr("Kick")
visible: kickButtonVisible
type: StatusBaseButton.Type.Danger
size: StatusBaseButton.Size.Small
onClicked: root.kickUserClicked(model.pubKey, memberItem.title)
},
StatusButton {
objectName: "MemberListItem_BanButton"
anchors.verticalCenter: parent.verticalCenter
visible: banButtonVisible
text: qsTr("Ban")
type: StatusBaseButton.Type.Danger
size: StatusBaseButton.Size.Small
onClicked: root.banUserClicked(model.pubKey, memberItem.title)
},
StatusButton {
objectName: "MemberListItem_UnbanButton"
anchors.verticalCenter: parent.verticalCenter
visible: unbanButtonVisible
text: qsTr("Unban")
type: StatusBaseButton.Type.Danger
size: StatusBaseButton.Size.Small
onClicked: root.unbanUserClicked(model.pubKey)
},
StatusButton {
id: acceptButton
anchors.verticalCenter: parent.verticalCenter
visible: acceptButtonVisible
text: qsTr("Accept")
type: StatusBaseButton.Type.Success
size: StatusBaseButton.Size.Small
icon.name: "checkmark-circle"
icon.color: enabled ? Theme.palette.successColor1 : disabledTextColor
loading: model.requestToJoinLoading
enabled: !acceptPendingButtonVisible
onClicked: root.acceptRequestToJoin(model.requestToJoinId)
},
StatusButton {
id: rejectButton
visible: rejectButtonVisible
text: qsTr("Reject")
type: StatusBaseButton.Type.Danger
size: StatusBaseButton.Size.Small
icon.name: "close-circle"
icon.color: enabled ? Theme.palette.dangerColor1 : disabledTextColor
enabled: !rejectPendingButtonVisible
onClicked: root.declineRequestToJoin(model.requestToJoinId)
}
readonly property bool showOnHover: isHovered && ctaAllowed
readonly property bool canDeleteMessages: model.isCurrentUser || model.memberRole !== Constants.memberRole.owner
]
/// Button visibility ///
readonly property bool acceptButtonVisible: tabIsShowingAcceptButton && (isPending || isRejected || isRejectedPending || isAcceptedPending) && showOnHover
readonly property bool rejectButtonVisible: tabIsShowingRejectButton && (isPending || isRejectedPending || isAcceptedPending) && showOnHover
readonly property bool acceptPendingButtonVisible: tabIsShowingAcceptButton && isAcceptedPending
readonly property bool rejectPendingButtonVisible: tabIsShowingRejectButton && isRejectedPending
readonly property bool kickButtonVisible: tabIsShowingKickBanButtons && isAccepted && showOnHover && canBeBanned
readonly property bool banButtonVisible: tabIsShowingKickBanButtons && isAccepted && showOnHover && canBeBanned
readonly property bool kickPendingButtonVisible: tabIsShowingKickBanButtons && isKickPending
readonly property bool banPendingButtonVisible: tabIsShowingKickBanButtons && isBanPending
readonly property bool unbanButtonVisible: tabIsShowingUnbanButton && isBanned && showOnHover
readonly property bool viewMessagesButtonVisible: tabIsShowingViewMessagesButton && showOnHover
readonly property bool messagesDeletedTextVisible: showOnHover &&
model.membershipRequestState === Constants.CommunityMembershipRequestState.BannedWithAllMessagesDelete
readonly property string title: model.preferredDisplayName
/// Pending states ///
readonly property bool isPendingState: isAcceptedPending || isRejectedPending || isBanPending || isUnbanPending || isKickPending
readonly property string pendingStateText: isAcceptedPending ? qsTr("Accept pending...") :
isRejectedPending ? qsTr("Reject pending...") :
isBanPending ? qsTr("Ban pending...") :
isUnbanPending ? qsTr("Unban pending...") :
isKickPending ? qsTr("Kick pending...") : ""
width: ListView.view.width
isAwaitingAddress: model.membershipRequestState === Constants.CommunityMembershipRequestState.AwaitingAddress
icon.width: 40
icon.height: 40
rightPadding: 75
leftPadding: 12
onClicked: {
if (mouse.button === Qt.RightButton) {
const profileType = Utils.getProfileType(model.isCurrentUser, false, model.isBlocked)
const contactType = Utils.getContactType(model.contactRequest, model.isContact)
components: [
StatusBaseText {
id: pendingText
width: Math.max(implicitWidth, d.pendingTextMaxWidth)
onImplicitWidthChanged: {
d.pendingTextMaxWidth = Math.max(implicitWidth, d.pendingTextMaxWidth)
}
visible: !!text && isPendingState
rightPadding: isKickPending || isBanPending || isUnbanPending ? 0 : Theme.bigPadding
anchors.verticalCenter: parent.verticalCenter
text: pendingStateText
color: Theme.palette.baseColor1
StatusToolTip {
text: qsTr("Waiting for owner node to come online")
visible: hoverHandler.hovered
}
HoverHandler {
id: hoverHandler
enabled: pendingText.visible
}
},
StatusBaseText {
text: qsTr("Messages deleted")
color: Theme.palette.baseColor1
anchors.verticalCenter: parent.verticalCenter
visible: messagesDeletedTextVisible
},
StatusButton {
id: viewMessages
anchors.verticalCenter: parent.verticalCenter
objectName: "MemberListItem_ViewMessages"
text: qsTr("View Messages")
visible: viewMessagesButtonVisible
size: StatusBaseButton.Size.Small
onClicked: root.viewMemberMessagesClicked(model.pubKey, memberItem.title)
},
StatusButton {
id: kickButton
anchors.verticalCenter: parent.verticalCenter
objectName: "MemberListItem_KickButton"
text: qsTr("Kick")
visible: kickButtonVisible
type: StatusBaseButton.Type.Danger
size: StatusBaseButton.Size.Small
onClicked: root.kickUserClicked(model.pubKey, memberItem.title)
},
StatusButton {
id: banButton
objectName: "MemberListItem_BanButton"
anchors.verticalCenter: parent.verticalCenter
visible: banButtonVisible
text: qsTr("Ban")
type: StatusBaseButton.Type.Danger
size: StatusBaseButton.Size.Small
onClicked: root.banUserClicked(model.pubKey, memberItem.title)
},
StatusButton {
objectName: "MemberListItem_UnbanButton"
anchors.verticalCenter: parent.verticalCenter
visible: unbanButtonVisible
text: qsTr("Unban")
type: StatusBaseButton.Type.Danger
size: StatusBaseButton.Size.Small
onClicked: root.unbanUserClicked(model.pubKey)
},
StatusButton {
id: acceptButton
anchors.verticalCenter: parent.verticalCenter
opacity: acceptButtonVisible
text: qsTr("Accept")
type: StatusBaseButton.Type.Success
icon.name: "checkmark-circle"
icon.color: enabled ? Theme.palette.successColor1 : disabledTextColor
loading: model.requestToJoinLoading
enabled: !acceptPendingButtonVisible
onClicked: root.acceptRequestToJoin(model.requestToJoinId)
},
StatusButton {
id: rejectButton
opacity: rejectButtonVisible
text: qsTr("Reject")
type: StatusBaseButton.Type.Danger
icon.name: "close-circle"
icon.color: enabled ? Theme.palette.dangerColor1 : disabledTextColor
enabled: !rejectPendingButtonVisible
onClicked: root.declineRequestToJoin(model.requestToJoinId)
const params = {
profileType, contactType,
pubKey: model.pubKey,
compressedPubKey: model.compressedPubKey,
emojiHash: root.utilsStore.getEmojiHash(model.pubKey),
colorHash: model.colorHash,
colorId: model.colorId,
displayName: memberItem.title || model.displayName,
userIcon: model.icon,
trustStatus: model.trustStatus,
onlineStatus: model.onlineStatus,
ensVerified: model.isEnsVerified,
hasLocalNickname: !!model.localNickname
}
]
readonly property string title: model.preferredDisplayName
width: membersList.width
color: "transparent"
pubKey: model.isEnsVerified ? "" : Utils.getElidedCompressedPk(model.pubKey)
nickName: model.localNickname
userName: ProfileUtils.displayName("", model.ensName, model.displayName, model.alias)
status: model.onlineStatus
icon.color: Utils.colorForColorId(model.colorId)
icon.name: model.icon
icon.width: 40
icon.height: 40
ringSettings.ringSpecModel: model.colorHash
badge.visible: (root.panelType === MembersTabPanel.TabType.AllMembers)
onClicked: {
if (mouse.button === Qt.RightButton) {
const profileType = Utils.getProfileType(model.isCurrentUser, false, model.isBlocked)
const contactType = Utils.getContactType(model.contactRequest, model.isContact)
const params = {
profileType, contactType,
pubKey: model.pubKey,
compressedPubKey: model.compressedPubKey,
emojiHash: root.utilsStore.getEmojiHash(model.pubKey),
colorHash: model.colorHash,
colorId: model.colorId,
displayName: memberItem.title || model.displayName,
userIcon: model.icon,
trustStatus: model.trustStatus,
onlineStatus: model.onlineStatus,
ensVerified: model.isEnsVerified,
hasLocalNickname: !!model.localNickname
}
Global.openMenu(memberContextMenuComponent, this, params)
} else if (mouse.button === Qt.LeftButton) {
Global.openProfilePopup(model.pubKey)
}
memberContextMenuComponent.createObject(root, params).popup(this)
} else if (mouse.button === Qt.LeftButton) {
Global.openProfilePopup(model.pubKey)
}
}
}
}
Component {
id: memberContextMenuComponent
Component {
id: memberContextMenuComponent
ProfileContextMenu {
id: memberContextMenuView
ProfileContextMenu {
id: memberContextMenuView
required property string pubKey
required property string pubKey
onOpenProfileClicked: Global.openProfilePopup(pubKey, null)
onCreateOneToOneChat: {
Global.changeAppSectionBySectionType(Constants.appSection.chat)
root.rootStore.chatCommunitySectionModule.createOneToOneChat("", pubKey, "")
onOpenProfileClicked: Global.openProfilePopup(pubKey, null)
onCreateOneToOneChat: {
Global.changeAppSectionBySectionType(Constants.appSection.chat)
root.rootStore.chatCommunitySectionModule.createOneToOneChat("", pubKey, "")
}
onReviewContactRequest: Global.openReviewContactRequestPopup(pubKey, null)
onSendContactRequest: Global.openContactRequestPopup(pubKey, null)
onEditNickname: Global.openNicknamePopupRequested(pubKey, null)
onRemoveNickname: root.rootStore.contactsStore.changeContactNickname(pubKey, "", displayName, true)
onUnblockContact: Global.unblockContactRequested(pubKey)
onMarkAsUntrusted: Global.markAsUntrustedRequested(pubKey)
onRemoveTrustStatus: root.rootStore.contactsStore.removeTrustStatus(pubKey)
onRemoveContact: Global.removeContactRequested(pubKey)
onBlockContact: Global.blockContactRequested(pubKey)
onMarkAsTrusted: Global.openMarkAsIDVerifiedPopup(pubKey, null)
onRemoveTrustedMark: Global.openRemoveIDVerificationDialog(pubKey, null)
onClosed: destroy()
}
onReviewContactRequest: Global.openReviewContactRequestPopup(pubKey, null)
onSendContactRequest: Global.openContactRequestPopup(pubKey, null)
onEditNickname: Global.openNicknamePopupRequested(pubKey, null)
onRemoveNickname: root.rootStore.contactsStore.changeContactNickname(pubKey, "", displayName, true)
onUnblockContact: Global.unblockContactRequested(pubKey)
onMarkAsUntrusted: Global.markAsUntrustedRequested(pubKey)
onRemoveTrustStatus: root.rootStore.contactsStore.removeTrustStatus(pubKey)
onRemoveContact: Global.removeContactRequested(pubKey)
onBlockContact: Global.blockContactRequested(pubKey)
onMarkAsTrusted: Global.openMarkAsIDVerifiedPopup(pubKey, null)
onRemoveTrustedMark: Global.openRemoveIDVerificationDialog(pubKey, null)
onClosed: destroy()
}
}
@ -384,5 +333,6 @@ Item {
// so that the text aligned on all rows (the text might be different on each row)
property real pendingTextMaxWidth: 0
}
onPanelTypeChanged: { d.pendingTextMaxWidth = 0 }
}

View File

@ -2,7 +2,6 @@ import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml.Models 2.15
import QtGraphicalEffects 1.15
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1

View File

@ -25,46 +25,32 @@ StatusDialog {
Kick, Ban
}
width: 400
width: 480
title: root.mode === KickBanPopup.Mode.Kick
? qsTr("Kick %1").arg(root.username)
: qsTr("Ban %1").arg(root.username)
contentItem: ColumnLayout {
anchors.centerIn: parent
StatusBaseText {
Layout.fillWidth: true
Layout.fillHeight: true
font.pixelSize: Theme.primaryTextFontSize
wrapMode: Text.Wrap
text: root.mode === KickBanPopup.Mode.Kick
? qsTr("Are you sure you want to kick <b>%1</b> from %2?")
.arg(root.username).arg(root.communityName)
: qsTr("Are you sure you want to ban <b>%1</b> from %2? This means that they will be kicked from this community and banned from re-joining.")
.arg(root.username).arg(root.communityName)
? qsTr("Are you sure you want to kick <b>%1</b> from %2?").arg(root.username).arg(root.communityName)
: qsTr("Are you sure you want to ban <b>%1</b> from %2? This means that they will be kicked from this community and banned from re-joining.").arg(root.username).arg(root.communityName)
}
RowLayout {
visible: root.mode === KickBanPopup.Mode.Ban
StatusBaseText {
Layout.fillWidth: true
text: qsTr("Delete all messages posted by the user")
font.pixelSize: Theme.primaryTextFontSize
}
StatusSwitch {
id: deleteAllMessagesSwitch
checked: false
}
}
StatusSwitch {
Layout.fillWidth: true
id: deleteAllMessagesSwitch
visible: root.mode === KickBanPopup.Mode.Ban
leftSide: false
text: qsTr("Delete all messages posted by the user")
}
}
footer: StatusDialogFooter {
rightButtons: ObjectModel {
@ -74,8 +60,6 @@ StatusDialog {
onClicked: root.close()
}
StatusButton {
id: banButton
objectName: root.mode === KickBanPopup.Mode.Kick
? "CommunityMembers_KickModal_KickButton"
: "CommunityMembers_BanModal_BanButton"

View File

@ -55,8 +55,8 @@ StatusSectionLayout {
property var mutualContactsModel
property var blockedContactsModel
property var pendingReceivedRequestContactsModel
property var pendingSentRequestContactsModel
property var pendingContactsModel
property int pendingReceivedContactsCount
required property bool isCentralizedMetricsEnabled
@ -116,7 +116,7 @@ StatusSectionLayout {
syncingBadgeCount: root.store.devicesStore.devicesModel.count -
root.store.devicesStore.devicesModel.pairedCount
messagingBadgeCount: root.pendingReceivedRequestContactsModel.ModelCount.count
messagingBadgeCount: root.pendingReceivedContactsCount
}
headerBackground: AccountHeaderGradient {
@ -244,8 +244,8 @@ StatusSectionLayout {
mutualContactsModel: root.mutualContactsModel
blockedContactsModel: root.blockedContactsModel
pendingReceivedRequestContactsModel: root.pendingReceivedRequestContactsModel
pendingSentRequestContactsModel: root.pendingSentRequestContactsModel
pendingContactsModel: root.pendingContactsModel
pendingReceivedContactsCount: root.pendingReceivedContactsCount
}
}
@ -280,7 +280,7 @@ StatusSectionLayout {
contentWidth: d.contentWidth
sectionTitle: settingsEntriesModel.getNameForSubsection(Constants.settingsSubsection.messaging)
requestsCount: root.pendingReceivedRequestContactsModel.ModelCount.count
requestsCount: root.pendingReceivedContactsCount
messagingStore: root.store.messagingStore
}
}

View File

@ -1,49 +1,26 @@
import QtQuick 2.15
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
import shared.controls.delegates 1.0
StatusListItem {
ContactListItemDelegate {
id: root
width: parent.width
height: visible ? implicitHeight : 0
title: root.name
property string name
property string iconSource
property color pubKeyColor
property var colorHash
property bool showSendMessageButton: false
property bool showRejectContactRequestButton: false
property bool showAcceptContactRequestButton: false
property bool showRemoveRejectionButton: false
property string contactText: ""
signal contextMenuRequested
signal sendMessageRequested
signal showVerificationRequestRequested
signal acceptContactRequested
signal rejectRequestRequested
signal removeRejectionRequested
asset.width: 40
asset.height: 40
asset.color: root.pubKeyColor
asset.letterSize: asset._twoLettersSize
asset.charactersLen: 2
asset.name: root.iconSource
asset.isLetterIdenticon: root.iconSource.toString() === ""
ringSettings {
ringSpecModel: root.colorHash
ringPxSize: Math.max(asset.width / 24.0)
}
icon.width: 40
icon.height: 40
components: [
StatusFlatRoundButton {
@ -53,6 +30,7 @@ StatusListItem {
height: visible ? 32 : 0
icon.name: "chat"
icon.color: Theme.palette.directColor1
tooltip.text: qsTr("Send message")
onClicked: root.sendMessageRequested()
},
StatusFlatRoundButton {
@ -62,6 +40,7 @@ StatusListItem {
height: visible ? 32 : 0
icon.name: "close-circle"
icon.color: Theme.palette.dangerColor1
tooltip.text: qsTr("Reject")
onClicked: root.rejectRequestRequested()
},
StatusFlatRoundButton {
@ -71,26 +50,16 @@ StatusListItem {
height: visible ? 32 : 0
icon.name: "checkmark-circle"
icon.color: Theme.palette.successColor1
tooltip.text: qsTr("Accept")
onClicked: root.acceptContactRequested()
},
StatusFlatRoundButton {
objectName: "removeRejectBtn"
visible: showRemoveRejectionButton
width: visible ? 32 : 0
height: visible ? 32 : 0
icon.name: "cancel"
icon.color: Theme.palette.dangerColor1
onClicked: root.removeRejectionRequested()
},
StatusBaseText {
text: root.contactText
anchors.verticalCenter: parent.verticalCenter
color: Theme.palette.baseColor1
},
StatusFlatRoundButton {
objectName: "moreBtn"
id: menuButton
width: 32
height: 32
icon.name: "more"

View File

@ -1,152 +1,71 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml.Models 2.15
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import shared 1.0
import shared.panels 1.0
import shared.popups 1.0
import utils 1.0
import SortFilterProxyModel 0.2
Item {
StatusListView {
id: root
implicitHeight: (title.height + contactsList.height)
property var contactsModel
required property var contactsModel
property int panelUsage: Constants.contactsPanelUsage.unknownPosition
property string title: ""
property string searchString: ""
readonly property int count: contactsList.count
signal openContactContextMenu(string publicKey)
signal sendMessageActionTriggered(string publicKey)
signal showVerificationRequest(string publicKey)
signal contactRequestAccepted(string publicKey)
signal contactRequestRejected(string publicKey)
signal rejectionRemoved(string publicKey)
StyledText {
id: title
height: visible ? contentHeight : 0
anchors.left: parent.left
anchors.leftMargin: Theme.padding
visible: contactsList.count > 0 && root.title !== ""
text: root.title
font.weight: Font.Medium
font.pixelSize: 15
color: Theme.palette.secondaryText
}
objectName: "ContactListPanel_ListView"
StatusListView {
id: contactsList
objectName: "ContactListPanel_ListView"
anchors.top: title.bottom
anchors.left: parent.left
anchors.right: parent.right
onCountChanged: {
height = (count*64);
}
interactive: false
model: SortFilterProxyModel {
id: filteredModel
model: SortFilterProxyModel {
id: filteredModel
sourceModel: root.contactsModel
sourceModel: root.contactsModel
function panelUsagePredicate(isVerified) {
if (panelUsage === Constants.contactsPanelUsage.verifiedMutualContacts)
return isVerified
if (panelUsage === Constants.contactsPanelUsage.mutualContacts)
return !isVerified
return true
filters: [
UserFilterContainer {
searchString: root.searchString
}
]
function searchPredicate(name, pubkey, compressedPubKey) {
const lowerCaseSearchString = root.searchString.toLowerCase()
return name.toLowerCase().includes(lowerCaseSearchString) ||
pubkey.toLowerCase().includes(lowerCaseSearchString) ||
compressedPubKey.toLowerCase().includes(lowerCaseSearchString)
}
filters: [
FastExpressionFilter {
expression: filteredModel.panelUsagePredicate(model.isVerified)
expectedRoles: ["isVerified"]
},
FastExpressionFilter {
enabled: root.searchString !== ""
expression: {
root.searchString // ensure expression is reevaluated when searchString changes
return filteredModel.searchPredicate(model.displayName, model.pubKey, model.compressedPubKey)
}
expectedRoles: ["displayName", "pubKey", "compressedPubKey"]
}
]
sorters: StringSorter {
sorters: [
FilterSorter { // Trusted contacts first
enabled: root.panelUsage === Constants.contactsPanelUsage.mutualContacts
ValueFilter { roleName: "isVerified"; value: true }
},
FilterSorter { // Received CRs first
id: pendingFilter
readonly property int received: Constants.ContactRequestState.Received
enabled: root.panelUsage === Constants.contactsPanelUsage.pendingContacts
ValueFilter { roleName: "contactRequest"; value: pendingFilter.received }
},
StringSorter {
roleName: "preferredDisplayName"
caseSensitivity: Qt.CaseInsensitive
}
}
]
}
delegate: ContactPanel {
id: panelDelegate
delegate: ContactPanel {
width: ListView.view.width
width: ListView.view.width
name: model.preferredDisplayName
iconSource: model.thumbnailImage
showSendMessageButton: model.isContact && !model.isBlocked
showRejectContactRequestButton: root.panelUsage === Constants.contactsPanelUsage.pendingContacts &&
model.contactRequest === Constants.ContactRequestState.Received
showAcceptContactRequestButton: showRejectContactRequestButton
subTitle: model.ensVerified ? "" : Utils.getElidedCompressedPk(model.pubKey)
pubKeyColor: Utils.colorForPubkey(model.pubKey)
colorHash: Utils.getColorHashAsJson(model.pubKey, model.ensVerified)
contactText: root.panelUsage === Constants.contactsPanelUsage.pendingContacts &&
model.contactRequest === Constants.ContactRequestState.Sent ? qsTr("Contact Request Sent")
: ""
showSendMessageButton: model.isContact && !model.isBlocked
showRejectContactRequestButton: {
if (root.panelUsage === Constants.contactsPanelUsage.receivedContactRequest
&& !model.verificationRequestStatus)
return true
return false
}
showAcceptContactRequestButton: {
if (root.panelUsage === Constants.contactsPanelUsage.receivedContactRequest
&& !model.verificationRequestStatus)
return true
return false
}
showRemoveRejectionButton: {
if (root.panelUsage === Constants.contactsPanelUsage.rejectedReceivedContactRequest)
return true
return false
}
contactText: {
if (root.panelUsage === Constants.contactsPanelUsage.sentContactRequest)
return qsTr("Contact Request Sent")
if (root.panelUsage === Constants.contactsPanelUsage.rejectedSentContactRequest)
return qsTr("Contact Request Rejected")
return ""
}
onContextMenuRequested: root.openContactContextMenu(model.pubKey)
onSendMessageRequested: root.sendMessageActionTriggered(model.pubKey)
onAcceptContactRequested: root.contactRequestAccepted(model.pubKey)
onRejectRequestRequested: root.contactRequestRejected(model.pubKey)
onRemoveRejectionRequested: root.rejectionRemoved(model.pubKey)
onShowVerificationRequestRequested: root.showVerificationRequest(model.pubKey)
}
onClicked: Global.openProfilePopup(model.pubKey)
onContextMenuRequested: root.openContactContextMenu(model.pubKey)
onSendMessageRequested: root.sendMessageActionTriggered(model.pubKey)
onAcceptContactRequested: root.contactRequestAccepted(model.pubKey)
onRejectRequestRequested: root.contactRequestRejected(model.pubKey)
}
}

View File

@ -1,4 +1,5 @@
ContactPanel 1.0 ContactPanel.qml
ContactsListPanel 1.0 ContactsListPanel.qml
ProfileDescriptionPanel 1.0 ProfileDescriptionPanel.qml
ProfileShowcaseAccountsPanel 1.0 ProfileShowcaseAccountsPanel.qml
ProfileShowcaseAssetsPanel 1.0 ProfileShowcaseAssetsPanel.qml

View File

@ -12,7 +12,7 @@ import StatusQ.Core.Backpressure 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import "../stores"
import AppLayouts.Profile.stores 1.0
StatusModal {
id: root

View File

@ -10,3 +10,4 @@ TokenListPopup 1.0 TokenListPopup.qml
WalletKeypairAccountMenu 1.0 WalletKeypairAccountMenu.qml
WalletAddressMenu 1.0 WalletAddressMenu.qml
ConfirmChangePasswordModal 1.0 ConfirmChangePasswordModal.qml
SendContactRequestModal 1.0 SendContactRequestModal.qml

View File

@ -18,9 +18,9 @@ import shared.stores 1.0 as SharedStores
import shared.views 1.0
import shared.views.chat 1.0
import "../stores"
import "../panels"
import "../popups"
import AppLayouts.Profile.stores 1.0
import AppLayouts.Profile.panels 1.0
import AppLayouts.Profile.popups 1.0
SettingsContentBase {
id: root
@ -28,20 +28,17 @@ SettingsContentBase {
property ContactsStore contactsStore
property SharedStores.UtilsStore utilsStore
property var mutualContactsModel
property var blockedContactsModel
property var pendingReceivedRequestContactsModel
property var pendingSentRequestContactsModel
required property var mutualContactsModel
required property var blockedContactsModel
required property var pendingContactsModel
required property int pendingReceivedContactsCount
property alias searchStr: searchBox.text
property bool isPending: false
titleRowComponentLoader.sourceComponent: StatusButton {
objectName: "ContactsView_ContactRequest_Button"
text: qsTr("Send contact request to chat key")
onClicked: {
Global.openPopup(sendContactRequest);
}
onClicked: sendContactRequestComponent.createObject(root).open()
}
function openContextMenu(model, pubKey) {
@ -67,11 +64,108 @@ SettingsContentBase {
Global.openMenu(contactContextMenuComponent, this, params)
}
Item {
id: contentItem
headerComponents: ColumnLayout {
width: root.contentWidth
height: (searchBox.height + contactsTabBar.height
+ stackLayout.height + (2 * Theme.bigPadding))
spacing: Theme.padding
StatusTabBar {
id: contactsTabBar
Layout.fillWidth: true
StatusTabButton {
readonly property int panelUsage: Constants.contactsPanelUsage.mutualContacts
width: implicitWidth
text: qsTr("Contacts")
}
StatusTabButton {
readonly property int panelUsage: Constants.contactsPanelUsage.pendingContacts
objectName: "ContactsView_PendingRequest_Button"
width: implicitWidth
enabled: !!root.pendingContactsModel && !root.pendingContactsModel.ModelCount.empty
text: qsTr("Pending Requests")
badge.value: root.pendingReceivedContactsCount
}
StatusTabButton {
readonly property int panelUsage: Constants.contactsPanelUsage.blockedContacts
objectName: "ContactsView_Blocked_Button"
width: implicitWidth
enabled: !!root.blockedContactsModel && !root.blockedContactsModel.ModelCount.empty
text: qsTr("Blocked")
}
}
SearchBox {
id: searchBox
Layout.fillWidth: true
placeholderText: qsTr("Search by name or chat key")
}
}
ContactsListPanel {
id: contactsListPanel
width: root.contentWidth
height: root.availableHeight
panelUsage: contactsTabBar.currentItem.panelUsage
contactsModel: {
switch (panelUsage) {
case Constants.contactsPanelUsage.pendingContacts:
return root.pendingContactsModel
case Constants.contactsPanelUsage.blockedContacts:
return root.blockedContactsModel
case Constants.contactsPanelUsage.mutualContacts:
default:
return root.mutualContactsModel
}
}
section.property: {
switch (contactsListPanel.panelUsage) {
case Constants.contactsPanelUsage.pendingContacts:
return "contactRequest"
case Constants.contactsPanelUsage.blockedContacts:
return ""
case Constants.contactsPanelUsage.mutualContacts:
default:
return "isVerified"
}
}
section.delegate: SectionComponent {
text: {
switch (contactsListPanel.panelUsage) {
case Constants.contactsPanelUsage.pendingContacts:
return section === `${Constants.ContactRequestState.Received}` ? qsTr("Received") : qsTr("Sent")
case Constants.contactsPanelUsage.blockedContacts:
return ""
case Constants.contactsPanelUsage.mutualContacts:
default:
return section === "true" ? qsTr("Trusted Contacts") : qsTr("Contacts")
}
}
}
section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
header: NoFriendsRectangle {
width: ListView.view.width
visible: ListView.view.count === 0
inviteButtonVisible: searchBox.text === ""
}
searchString: searchBox.text
onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey)
onSendMessageActionTriggered: root.contactsStore.joinPrivateChat(publicKey)
onContactRequestAccepted: root.contactsStore.acceptContactRequest(publicKey, "")
onContactRequestRejected: root.contactsStore.dismissContactRequest(publicKey, "")
Component {
id: sendContactRequestComponent
SendContactRequestModal {
contactsStore: root.contactsStore
onClosed: destroy()
}
}
Component {
id: contactContextMenuComponent
@ -98,191 +192,27 @@ SettingsContentBase {
onClosed: destroy()
}
}
SearchBox {
id: searchBox
anchors.left: parent.left
anchors.right: parent.right
placeholderText: qsTr("Search by a display name or chat key")
}
}
StatusTabBar {
id: contactsTabBar
anchors.left: parent.left
anchors.right: parent.right
anchors.top: searchBox.bottom
anchors.topMargin: Theme.padding
component SectionComponent: Rectangle {
required property string section
property alias text: sectionText.text
StatusTabButton {
id: contactsBtn
leftPadding: Theme.padding
width: implicitWidth
text: qsTr("Contacts")
}
StatusTabButton {
id: pendingRequestsBtn
objectName: "ContactsView_PendingRequest_Button"
width: implicitWidth
enabled: !root.pendingReceivedRequestContactsModel.ModelCount.empty ||
!root.pendingSentRequestContactsModel.ModelCount.empty
text: qsTr("Pending Requests")
badge.value: root.pendingReceivedRequestContactsModel.ModelCount.count
}
StatusTabButton {
id: blockedBtn
objectName: "ContactsView_Blocked_Button"
width: implicitWidth
enabled: !root.blockedContactsModel.ModelCount.empty
text: qsTr("Blocked")
}
}
width: ListView.view.width
height: sectionText.implicitHeight
color: Theme.palette.statusListItem.backgroundColor
StackLayout {
id: stackLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.top: contactsTabBar.bottom
currentIndex: contactsTabBar.currentIndex
anchors.topMargin: Theme.padding
// CONTACTS
ColumnLayout {
Layout.fillWidth: true
Layout.minimumHeight: 0
Layout.maximumHeight: (verifiedContacts.height + mutualContacts.height + noFriendsItem.height)
visible: (stackLayout.currentIndex === 0)
onVisibleChanged: {
if (visible) {
stackLayout.height = height+contactsTabBar.anchors.topMargin;
}
}
spacing: Theme.padding
ContactsListPanel {
id: verifiedContacts
StatusBaseText {
id: sectionText
width: parent.width
anchors.verticalCenter: parent.verticalCenter
topPadding: Theme.halfPadding
bottomPadding: Theme.halfPadding
Layout.fillWidth: true
title: qsTr("Trusted Contacts")
visible: !noFriendsItem.visible && count > 0
contactsModel: root.mutualContactsModel
searchString: searchBox.text
onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey)
panelUsage: Constants.contactsPanelUsage.verifiedMutualContacts
onSendMessageActionTriggered: {
root.contactsStore.joinPrivateChat(publicKey)
}
}
ContactsListPanel {
id: mutualContacts
Layout.fillWidth: true
visible: !noFriendsItem.visible && count > 0
title: qsTr("Contacts")
contactsModel: root.mutualContactsModel
searchString: searchBox.text
onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey)
panelUsage: Constants.contactsPanelUsage.mutualContacts
onSendMessageActionTriggered: {
root.contactsStore.joinPrivateChat(publicKey)
}
}
Item {
id: noFriendsItem
Layout.fillWidth: true
Layout.preferredHeight: visible ? (root.contentHeight - (2*searchBox.height) - contactsTabBar.height - contactsTabBar.anchors.topMargin) : 0
visible: root.mutualContactsModel.ModelCount.empty
NoFriendsRectangle {
anchors.centerIn: parent
text: qsTr("You don't have any contacts yet")
}
}
}
// PENDING REQUESTS
ColumnLayout {
Layout.fillWidth: true
Layout.minimumHeight: 0
Layout.maximumHeight: (receivedRequests.height + sentRequests.height)
spacing: Theme.padding
visible: (stackLayout.currentIndex === 1)
onVisibleChanged: {
if (visible) {
stackLayout.height = height+contactsTabBar.anchors.topMargin;
}
}
ContactsListPanel {
id: receivedRequests
objectName: "receivedRequests_ContactsListPanel"
Layout.fillWidth: true
title: qsTr("Received")
searchString: searchBox.text
visible: count > 0
onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey)
contactsModel: root.pendingReceivedRequestContactsModel
panelUsage: Constants.contactsPanelUsage.receivedContactRequest
onSendMessageActionTriggered: {
root.contactsStore.joinPrivateChat(publicKey)
}
onContactRequestAccepted: {
root.contactsStore.acceptContactRequest(publicKey, "")
}
onContactRequestRejected: {
root.contactsStore.dismissContactRequest(publicKey, "")
}
}
ContactsListPanel {
id: sentRequests
objectName: "sentRequests_ContactsListPanel"
Layout.fillWidth: true
title: qsTr("Sent")
searchString: searchBox.text
visible: count > 0
onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey)
contactsModel: root.pendingSentRequestContactsModel
panelUsage: Constants.contactsPanelUsage.sentContactRequest
}
}
// BLOCKED
ContactsListPanel {
id: blockedContacts
Layout.fillWidth: true
searchString: searchBox.text
onOpenContactContextMenu: root.openContextMenu(contactsModel, publicKey)
contactsModel: root.blockedContactsModel
panelUsage: Constants.contactsPanelUsage.blockedContacts
visible: (stackLayout.currentIndex === 2)
onVisibleChanged: {
if (visible) {
stackLayout.height = height;
}
}
}
}
Component {
id: loadingIndicator
StatusLoadingIndicator {
width: 12
height: 12
}
}
Component {
id: sendContactRequest
SendContactRequestModal {
contactsStore: root.contactsStore
onClosed: destroy()
}
color: Theme.palette.baseColor1
font.pixelSize: Theme.additionalTextSize
font.weight: Font.Medium
elide: Text.ElideRight
}
}
}

View File

@ -2,8 +2,10 @@ AboutView 1.0 AboutView.qml
AppearanceView 1.0 AppearanceView.qml
ChangePasswordView 1.0 ChangePasswordView.qml
CommunitiesView 1.0 CommunitiesView.qml
ContactsView 1.0 ContactsView.qml
CurrenciesModel 1.0 CurrenciesModel.qml
LanguageView 1.0 LanguageView.qml
NotificationsView 1.0 NotificationsView.qml
PrivacyAndSecurityView 1.0 PrivacyAndSecurityView.qml
SyncingView 1.0 SyncingView.qml
SettingsContentBase 1.0 SettingsContentBase.qml

View File

@ -538,7 +538,7 @@ Item {
Global.displayToastMessage(toastTitle, toastSubtitle, toastIcon, toastLoading, toastType, toastLink)
}
function onCommunityMemberStatusEphemeralNotification(communityName: string, memberName: string, state: CommunityMembershipRequestState) {
function onCommunityMemberStatusEphemeralNotification(communityName: string, memberName: string, state: int) {
var text = ""
switch (state) {
case Constants.CommunityMembershipRequestState.Banned:
@ -1724,8 +1724,8 @@ Item {
mutualContactsModel: contactsModelAdaptor.mutualContacts
blockedContactsModel: contactsModelAdaptor.blockedContacts
pendingReceivedRequestContactsModel: contactsModelAdaptor.pendingReceivedRequestContacts
pendingSentRequestContactsModel: contactsModelAdaptor.pendingSentRequestContacts
pendingContactsModel: contactsModelAdaptor.pendingContacts
pendingReceivedContactsCount: contactsModelAdaptor.pendingReceivedRequestContacts.count
Binding on settingsSubsection {
value: profileLoader.settingsSubsection

View File

@ -19,7 +19,7 @@ QObject {
localNickname [string] - local nickname set by the current user
alias [string] - generated 3 word name
icon [string] - thumbnail image of the user
colorId [string] - generated color ID for the user's profile
colorId [int] - generated color ID for the user's profile
colorHash [string] - generated color hash for the user's profile
onlineStatus [int] - the online status of the member
isContact [bool] - whether the user is a mutual contact or not
@ -75,4 +75,21 @@ QObject {
value: Constants.ContactRequestState.Sent
}
}
readonly property var pendingContacts: SortFilterProxyModel {
sourceModel: root.allContacts ?? null
filters: [
AnyOf {
ValueFilter {
roleName: "contactRequest"
value: Constants.ContactRequestState.Received
}
ValueFilter {
roleName: "contactRequest"
value: Constants.ContactRequestState.Sent
}
}
]
}
}

View File

@ -0,0 +1,39 @@
import StatusQ 0.1
import StatusQ.Core.Utils 0.1
import utils 1.0
import SortFilterProxyModel 0.2
AnyOf {
id: root
property string searchString
function searchPredicate(ensName, displayName, aliasName) {
const lowerCaseSearchString = root.searchString.toLowerCase()
const secondaryName = ProfileUtils.displayName("", ensName, displayName, aliasName)
return secondaryName.toLowerCase().includes(lowerCaseSearchString)
}
enabled: root.searchString !== ""
// substring search for either nickname or the other primary/secondary display name
SearchFilter {
roleName: "localNickname"
searchPhrase: root.searchString
}
FastExpressionFilter {
expression: {
root.searchString
return root.searchPredicate(model.ensName, model.displayName, model.alias)
}
expectedRoles: ["ensName", "displayName", "alias"]
}
// exact search for the full key
ValueFilter {
roleName: "compressedPubKey"
value: root.searchString
}
}

View File

@ -18,10 +18,11 @@ StatusMemberListItem {
pubKey: model.isEnsVerified ? "" : model.compressedPubKey
nickName: model.localNickname
userName: ProfileUtils.displayName("", model.ensName, model.displayName, model.alias)
isVerified: model.isVerified
isUntrustworthy: model.isUntrustworthy
isBlocked: model.isBlocked
isVerified: model.isVerified || model.trustStatus === Constants.trustStatus.trusted
isUntrustworthy: model.isUntrustworthy || model.trustStatus === Constants.trustStatus.untrustworthy
isContact: model.isContact
icon.name: model.icon
icon.name: model.thumbnailImage || model.icon
icon.color: Utils.colorForColorId(model.colorId)
status: model.onlineStatus
ringSettings.ringSpecModel: model.colorHash

View File

@ -3,3 +3,4 @@ module shared
DelegateModelGeneralized 1.0 DelegateModelGeneralized.qml
LoadingAnimation 1.0 LoadingAnimation.qml
MacTrafficLights 1.0 MacTrafficLights.qml
UserFilterContainer 1.0 UserFilterContainer.qml

View File

@ -1,31 +1,31 @@
import QtQuick 2.15
import utils 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import "../popups"
import utils 1.0
import shared.popups 1.0
Item {
id: noContactsRect
id: root
implicitWidth: 260
implicitHeight: visible ? 120 : 0
property string text: qsTr("You dont have any contacts yet. Invite your friends to start chatting.")
property string text: inviteButtonVisible ? qsTr("You dont have any contacts yet. Invite your friends to start chatting.")
: qsTr("No users match your search")
property alias textColor: noContacts.color
property bool inviteButtonVisible: true
StatusBaseText {
id: noContacts
text: noContactsRect.text
text: root.text
color: Theme.palette.baseColor1
anchors.top: parent.top
anchors.topMargin: Theme.padding
anchors.left: parent.left
anchors.right: parent.right
wrapMode: Text.WordWrap
font.pixelSize: 15
horizontalAlignment: Text.AlignHCenter
}
StatusButton {
@ -33,7 +33,8 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: noContacts.bottom
anchors.topMargin: Theme.padding
onClicked: Global.openPopup(inviteFriendsPopup);
visible: root.inviteButtonVisible
onClicked: inviteFriendsPopup.createObject(root).open()
}
Component {

View File

@ -498,12 +498,8 @@ QtObject {
readonly property QtObject contactsPanelUsage: QtObject {
readonly property int unknownPosition: -1
readonly property int mutualContacts: 0
readonly property int verifiedMutualContacts: 1
readonly property int sentContactRequest: 2
readonly property int receivedContactRequest: 3
readonly property int rejectedSentContactRequest: 4
readonly property int rejectedReceivedContactRequest: 5
readonly property int blockedContacts: 6
readonly property int pendingContacts: 1
readonly property int blockedContacts: 2
}
readonly property QtObject keypair: QtObject {