fix(RenameGroupPopup): fix name validation and scrolling

- increase the length limit to 30 and allow `&`, as per the spec
- wrap the popup in a scroll view
- pls some minor cleanups

Fixes #16523
This commit is contained in:
Lukáš Tinkl 2024-10-22 16:14:43 +02:00 committed by Lukáš Tinkl
parent d317df032d
commit 158154bcca
14 changed files with 104 additions and 144 deletions

View File

@ -61,22 +61,21 @@ Control {
function getTagColor(isReadonly) { function getTagColor(isReadonly) {
if(isReadonly) if(isReadonly)
return Theme.palette.baseColor1 return Theme.palette.baseColor1
return mouseArea.containsMouse ? Theme.palette.miscColor1 : Theme.palette.primaryColor1 return root.hovered ? Theme.palette.miscColor1 : Theme.palette.primaryColor1
} }
} }
implicitHeight: 30 implicitHeight: 30
horizontalPadding: d.tagMargins horizontalPadding: d.tagMargins
font.pixelSize: 15 font.pixelSize: Theme.primaryTextFontSize
font.family: Theme.baseFont.name font.family: Theme.baseFont.name
background: Rectangle { background: Rectangle {
color: d.getTagColor(root.isReadonly) color: d.getTagColor(root.isReadonly)
radius: 8 radius: Theme.radius
} }
contentItem: RowLayout { contentItem: RowLayout {
id: tagRow
spacing: 2 spacing: 2
StatusIcon { StatusIcon {
@ -99,7 +98,6 @@ Control {
height: d.tagIconsSize height: d.tagIconsSize
icon: "close" icon: "close"
MouseArea { MouseArea {
id: mouseArea
enabled: !root.isReadonly enabled: !root.isReadonly
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true

View File

@ -73,7 +73,6 @@ Item {
Layout.leftMargin: Theme.padding Layout.leftMargin: Theme.padding
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: text !== "" visible: text !== ""
font.pixelSize: 15
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }
Item { Item {
@ -115,7 +114,7 @@ Item {
Layout.minimumWidth: 4 Layout.minimumWidth: 4
Layout.fillHeight: true Layout.fillHeight: true
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
font.pixelSize: 15 font.pixelSize: Theme.primaryTextFontSize
color: Theme.palette.directColor1 color: Theme.palette.directColor1
selectByMouse: true selectByMouse: true
@ -193,7 +192,7 @@ Item {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.rightMargin: Theme.padding Layout.rightMargin: Theme.padding
visible: text !== "" visible: text !== ""
font.pixelSize: 10 font.pixelSize: Theme.asideTextFontSize
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
} }
} }
@ -210,14 +209,12 @@ Item {
StatusButton { StatusButton {
objectName: "inlineSelectorConfirmButton" objectName: "inlineSelectorConfirmButton"
Layout.alignment: Qt.AlignVCenter
enabled: (listView.count > 0) enabled: (listView.count > 0)
text: qsTr("Confirm") text: qsTr("Confirm")
onClicked: root.confirmed() onClicked: root.confirmed()
} }
StatusButton { StatusButton {
Layout.alignment: Qt.AlignVCenter
text: qsTr("Cancel") text: qsTr("Cancel")
type: StatusBaseButton.Type.Danger type: StatusBaseButton.Type.Danger
onClicked: root.rejected() onClicked: root.rejected()

View File

@ -216,7 +216,6 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: accountImage.right anchors.left: accountImage.right
anchors.leftMargin: Theme.smallPadding anchors.leftMargin: Theme.smallPadding
font.pixelSize: 15
} }
MouseArea { MouseArea {

View File

@ -1,3 +1,4 @@
SuggestionBoxPanel 1.0 SuggestionBoxPanel.qml SuggestionBoxPanel 1.0 SuggestionBoxPanel.qml
UserListPanel 1.0 UserListPanel.qml UserListPanel 1.0 UserListPanel.qml
ChatAnchorButtonsPanel 1.0 ChatAnchorButtonsPanel.qml ChatAnchorButtonsPanel 1.0 ChatAnchorButtonsPanel.qml
InlineSelectorPanel 1.0 InlineSelectorPanel.qml

View File

@ -290,7 +290,6 @@ Item {
&& !d.sendingInProgress && !d.sendingInProgress
} }
store: root.rootStore
usersModel: d.activeUsersStore.usersModel usersModel: d.activeUsersStore.usersModel
sharedStore: root.sharedRootStore sharedStore: root.sharedRootStore

View File

@ -2,7 +2,6 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtQml.Models 2.15 import QtQml.Models 2.15
import QtGraphicalEffects 1.15
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
@ -46,7 +45,6 @@ Page {
Behavior on anchors.bottomMargin { NumberAnimation { duration: 30 }} Behavior on anchors.bottomMargin { NumberAnimation { duration: 30 }}
background: Rectangle { background: Rectangle {
anchors.fill: parent
color: Theme.palette.statusAppLayout.rightPanelBackgroundColor color: Theme.palette.statusAppLayout.rightPanelBackgroundColor
} }
@ -73,14 +71,14 @@ Page {
const ensName = member.displayName.includes(".eth") ? member.displayName : "" const ensName = member.displayName.includes(".eth") ? member.displayName : ""
root.rootStore.chatCommunitySectionModule.createOneToOneChat("", member.pubKey, ensName) root.rootStore.chatCommunitySectionModule.createOneToOneChat("", member.pubKey, ensName)
} else { } else {
var groupName = ""; var groupName = []
var pubKeys = []; var pubKeys = []
for (var i = 0; i < model.count; i++) { for (var i = 0; i < model.count; i++) {
const member = model.get(i) const member = model.get(i)
groupName += (member.displayName + (i === model.count - 1 ? "" : "&")) groupName.push(member.displayName)
pubKeys.push(member.pubKey) pubKeys.push(member.pubKey)
} }
root.rootStore.chatCommunitySectionModule.createGroupChat("", groupName, JSON.stringify(pubKeys)) root.rootStore.chatCommunitySectionModule.createGroupChat("", groupName.join("&"), JSON.stringify(pubKeys))
} }
} }
@ -131,7 +129,6 @@ Page {
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Layout.leftMargin: Theme.halfPadding Layout.leftMargin: Theme.halfPadding
visible: contactsList.visible visible: contactsList.visible
font.pixelSize: 15
text: qsTr("Contacts") text: qsTr("Contacts")
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }
@ -182,7 +179,6 @@ Page {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 15
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
text: qsTr("You can only send direct messages to your Contacts.\n text: qsTr("You can only send direct messages to your Contacts.\n
Send a contact request to the person you would like to chat with, you will be able to chat with them once they have accepted your contact request.") Send a contact request to the person you would like to chat with, you will be able to chat with them once they have accepted your contact request.")

View File

@ -1,6 +1,6 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
@ -8,14 +8,14 @@ import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import "../panels" import AppLayouts.Chat.stores 1.0
import "../stores"
import "private"
import utils 1.0 import utils 1.0
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
import "private"
MembersSelectorBase { MembersSelectorBase {
id: root id: root
@ -55,7 +55,7 @@ MembersSelectorBase {
readonly property string _pubKey: model.pubKey readonly property string _pubKey: model.pubKey
height: ListView.view.height height: ListView.view.height
text: root.tagText(model.localNickname, model.displayName, model.alias) text: model.preferredDisplayName
isReadonly: { isReadonly: {
if (model.memberRole === Constants.memberRole.owner) return true if (model.memberRole === Constants.memberRole.owner) return true

View File

@ -1,6 +1,6 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import QtQml.Models 2.2 import QtQml.Models 2.15
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
@ -9,8 +9,6 @@ import "private"
import utils 1.0 import utils 1.0
import SortFilterProxyModel 0.2
MembersSelectorBase { MembersSelectorBase {
id: root id: root
@ -39,20 +37,18 @@ MembersSelectorBase {
} }
onTextPasted: (text) => { onTextPasted: (text) => {
// When pated, process text immediately // When pasted, process text immediately
contactLookupDelayTimer.stop() // when pasting, textChanged is still emited first contactLookupDelayTimer.stop() // when pasting, textChanged is still emitted first
d.lookupContact(text) d.lookupContact(text)
} }
model: SortFilterProxyModel { model: d.selectedMembers
sourceModel: d.selectedMembers
}
delegate: StatusTagItem { delegate: StatusTagItem {
readonly property string _pubKey: model.pubKey readonly property string _pubKey: model.pubKey
height: ListView.view.height height: ListView.view.height
text: root.tagText(model.localNickname, model.displayName, model.alias) text: model.localNickname || model.displayName
onClosed: root.entryRemoved(this) onClosed: root.entryRemoved(this)
} }
@ -129,11 +125,7 @@ MembersSelectorBase {
return false return false
} }
d.selectedMembers.append({ d.selectedMembers.append({pubKey, displayName, localNickname})
"pubKey": pubKey,
"displayName": displayName,
"localNickname": localNickname
})
return true return true
} }

View File

@ -1,16 +1,13 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.15
import StatusQ 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import "../../panels"
import AppLayouts.Chat.stores 1.0 as ChatStores import AppLayouts.Chat.stores 1.0 as ChatStores
import AppLayouts.Chat.panels 1.0
import utils 1.0 import utils 1.0
import shared.controls.delegates 1.0 import shared.controls.delegates 1.0
@ -25,10 +22,6 @@ InlineSelectorPanel {
readonly property int membersLimit: 20 // see: https://github.com/status-im/status-mobile/issues/13066 readonly property int membersLimit: 20 // see: https://github.com/status-im/status-mobile/issues/13066
property bool limitReached: model.count >= membersLimit property bool limitReached: model.count >= membersLimit
function tagText(localNickname, displayName, aliasName) {
return localNickname || displayName || aliasName
}
property string pastedChatKey: "" property string pastedChatKey: ""
label.text: qsTr("To:") label.text: qsTr("To:")
@ -54,41 +47,32 @@ InlineSelectorPanel {
return true return true
} }
function isPastedProfileLinkToContact(pubkey) {
return root.pastedChatKey === pubkey
}
filters: [ filters: [
ExpressionFilter { FastExpressionFilter {
enabled: root.edit.text !== "" && root.pastedChatKey == "" enabled: root.edit.text !== "" && root.pastedChatKey == ""
expression: { expression: {
root.edit.text // ensure expression is reevaluated when edit.text changes root.edit.text // ensure expression is reevaluated when edit.text changes
return _suggestionsModel.searchPredicate(model.displayName, model.localNickname, model.alias) return _suggestionsModel.searchPredicate(model.displayName, model.localNickname, model.alias)
} }
expectedRoles: ["displayName", "localNickname", "alias"]
}, },
ExpressionFilter { FastExpressionFilter {
expression: { expression: {
root.model.count // ensure expression is reevaluated when members model changes root.model.count // ensure expression is reevaluated when members model changes
return _suggestionsModel.notAMemberPredicate(model.pubKey) return _suggestionsModel.notAMemberPredicate(model.pubKey)
} }
expectedRoles: ["pubKey"]
}, },
ExpressionFilter { ValueFilter {
enabled: root.pastedChatKey != "" roleName: "pubKey"
expression: { value: root.pastedChatKey
root.pastedChatKey // ensure expression is reevaluated when members model changes enabled: root.pastedChatKey !== ""
return _suggestionsModel.isPastedProfileLinkToContact(model.pubKey)
}
} }
] ]
proxyRoles: ExpressionRole {
name: "title"
expression: model.localNickname || model.displayName || model.alias
}
sorters: StringSorter { sorters: StringSorter {
roleName: "title" roleName: "preferredDisplayName"
numericMode: true caseSensitivity: Qt.CaseInsensitive
} }
} }

View File

@ -1,7 +1,7 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Controls 2.13 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.15
import QtQml.Models 2.14 import QtQml.Models 2.15
import shared.controls 1.0 import shared.controls 1.0
import shared.panels 1.0 import shared.panels 1.0
@ -26,11 +26,11 @@ StatusDialog {
title: qsTr("Edit group name and image") title: qsTr("Edit group name and image")
width: 480 width: 480
height: 610 padding: 0
QtObject { QtObject {
id: d id: d
readonly property int nameCharLimit: 24 readonly property int nameCharLimit: 30 // cf spec: https://github.com/status-im/feature-specs/blob/d66c586f13cb1fa0486544030148df68e06928f0/content/raw/chat/group_chat.md
} }
onOpened: { onOpened: {
@ -47,66 +47,71 @@ StatusDialog {
imageEditor.dataImage = activeGroupImageData imageEditor.dataImage = activeGroupImageData
} }
ColumnLayout { StatusScrollView {
id: scrollView
anchors.fill: parent anchors.fill: parent
spacing: 20 contentWidth: availableWidth
StatusInput { ColumnLayout {
id: groupName width: scrollView.availableWidth
input.edit.objectName: "groupChatEdit_name" spacing: 20
Layout.alignment: Qt.AlignHCenter
label: qsTr("Name the group")
charLimit: d.nameCharLimit
validators: [ StatusInput {
StatusMinLengthValidator { id: groupName
minLength: 1 input.edit.objectName: "groupChatEdit_name"
errorMessage: Utils.getErrorMessage(groupName.errors, qsTr("group name")) Layout.alignment: Qt.AlignHCenter
}, label: qsTr("Name the group")
StatusRegularExpressionValidator { charLimit: d.nameCharLimit
regularExpression: Constants.regularExpressions.alphanumericalExpanded
errorMessage: Constants.errorMessages.alphanumericalExpandedRegExp
}
]
}
StatusBaseText { validators: [
id: imgText StatusMinLengthValidator {
text: qsTr("Group image") minLength: 1
leftPadding: groupName.leftPadding - root.padding errorMessage: Utils.getErrorMessage(groupName.errors, qsTr("group name"))
} },
StatusRegularExpressionValidator {
regularExpression: Constants.regularExpressions.alphanumericalExpanded2
errorMessage: Constants.errorMessages.alphanumericalExpandedRegExp
}
]
}
EditCroppedImagePanel { StatusBaseText {
id: imageEditor id: imgText
objectName: "groupChatEdit_image" text: qsTr("Group image")
Layout.preferredWidth: 128 }
Layout.preferredHeight: Layout.preferredWidth / aspectRatio
Layout.alignment: Qt.AlignHCenter
imageFileDialogTitle: qsTr("Choose an image as logo") EditCroppedImagePanel {
title: qsTr("Edit group name and image") id: imageEditor
acceptButtonText: qsTr("Use as an icon for this group chat") objectName: "groupChatEdit_image"
} Layout.preferredWidth: 128
Layout.preferredHeight: Layout.preferredWidth / aspectRatio
Layout.alignment: Qt.AlignHCenter
StatusBaseText { imageFileDialogTitle: qsTr("Choose an image as logo")
id: colorText title: qsTr("Edit group name and image")
text: qsTr("Standard colours") acceptButtonText: qsTr("Use as an icon for this group chat")
leftPadding: groupName.leftPadding - root.padding }
}
StatusColorSelectorGrid { StatusBaseText {
id: colorSelectionGrid id: colorText
objectName: "groupChatEdit_color" text: qsTr("Standard colours")
Layout.alignment: Qt.AlignHCenter }
diameter: 40
selectorDiameter: 16
columns: 6
selectedColorIndex: -1
}
Item { StatusColorSelectorGrid {
id: spacerItem id: colorSelectionGrid
height: 10 objectName: "groupChatEdit_color"
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: -(parent.spacing / 3)
diameter: 40
selectorDiameter: 16
columns: 6
selectedColorIndex: -1
}
Item {
id: spacerItem
height: 10
}
} }
} }

View File

@ -39,7 +39,6 @@ Rectangle {
signal dismissLinkPreview(int index) signal dismissLinkPreview(int index)
property var usersModel property var usersModel
property ChatStores.RootStore store
property SharedStores.RootStore sharedStore property SharedStores.RootStore sharedStore
property var emojiPopup: null property var emojiPopup: null

View File

@ -54,16 +54,6 @@ StatusMenu {
onTriggered: root.displayProfilePopup(root.chatId) onTriggered: root.displayProfilePopup(root.chatId)
} }
StatusAction {
objectName: "viewMembersStatusAction"
text: qsTr("View Members")
icon.name: "group-chat"
enabled: root.chatType === Constants.chatType.privateGroupChat
onTriggered: {
localAccountSensitiveSettings.expandUsersList = !localAccountSensitiveSettings.expandUsersList;
}
}
StatusAction { StatusAction {
objectName: "addRemoveFromGroupStatusAction" objectName: "addRemoveFromGroupStatusAction"
text: root.amIChatAdmin ? qsTr("Add / remove from group") : qsTr("Add to group") text: root.amIChatAdmin ? qsTr("Add / remove from group") : qsTr("Add to group")

View File

@ -933,7 +933,6 @@ Loader {
suggestionsOpened = false suggestionsOpened = false
} }
store: root.rootStore
usersModel: root.usersStore.usersModel usersModel: root.usersStore.usersModel
sharedStore: root.sharedRootStore sharedStore: root.sharedRootStore
emojiPopup: root.emojiPopup emojiPopup: root.emojiPopup
@ -957,7 +956,7 @@ Loader {
linkPreviewModel: root.linkPreviewModel linkPreviewModel: root.linkPreviewModel
gifLinks: root.gifLinks gifLinks: root.gifLinks
playAnimations: root.Window.window.active && root.messageStore.isChatActive playAnimations: root.Window.active && root.messageStore.isChatActive
isOnline: root.rootStore.mainModuleInst.isOnline isOnline: root.rootStore.mainModuleInst.isOnline
highlightLink: delegate.hoveredLink highlightLink: delegate.hoveredLink
onImageClicked: (image, mouse, imageSource, url) => { onImageClicked: (image, mouse, imageSource, url) => {

View File

@ -659,6 +659,7 @@ QtObject {
readonly property var alphanumerical: /^$|^[a-zA-Z0-9]+$/ readonly property var alphanumerical: /^$|^[a-zA-Z0-9]+$/
readonly property var alphanumericalExpanded: /^$|^[a-zA-Z0-9\-_\.\u0020]+$/ readonly property var alphanumericalExpanded: /^$|^[a-zA-Z0-9\-_\.\u0020]+$/
readonly property var alphanumericalExpanded1: /^[a-zA-Z0-9\-_]+(?: [a-zA-Z0-9\-_]+)*$/ readonly property var alphanumericalExpanded1: /^[a-zA-Z0-9\-_]+(?: [a-zA-Z0-9\-_]+)*$/
readonly property var alphanumericalExpanded2: /^$|^[a-zA-Z0-9\-_\.\u0020\&]+$/
readonly property var alphanumericalWithSpace: /^$|^[a-zA-Z0-9\s]+$/ readonly property var alphanumericalWithSpace: /^$|^[a-zA-Z0-9\s]+$/
readonly property var asciiPrintable: /^$|^[!-~]+$/ readonly property var asciiPrintable: /^$|^[!-~]+$/
readonly property var ascii: /^$|^[\x00-\x7F]+$/ readonly property var ascii: /^$|^[\x00-\x7F]+$/