refactor(ProfileView): Adapt the profile view to the new `ContactDetails` component

This commit is contained in:
Alex Jbanca 2024-06-03 11:57:02 +03:00 committed by Alex Jbanca
parent afcb7608e0
commit f943440915
6 changed files with 161 additions and 199 deletions

View File

@ -20,100 +20,102 @@ import QtTest 1.15
SplitView { SplitView {
id: root id: root
ColumnLayout { Pane {
SplitView.fillWidth: true SplitView.fillWidth: true
SplitView.fillHeight: true SplitView.fillHeight: true
clip: true contentItem: ColumnLayout {
spacing: 5 clip: true
Label { spacing: 5
Layout.fillWidth: true Label {
text: "publicKey: " + contactDetails.publicKey Layout.fillWidth: true
font.bold: true text: "publicKey: " + contactDetails.publicKey
} font.bold: true
Label { }
Layout.fillWidth: true Label {
text: "loading: " + contactDetails.loading Layout.fillWidth: true
font.bold: true text: "loading: " + contactDetails.loading
} font.bold: true
Label { }
Layout.fillWidth: true Label {
text: "displayName: " + contactDetails.displayName Layout.fillWidth: true
} text: "displayName: " + contactDetails.displayName
Label { }
Layout.fillWidth: true Label {
text: "ensName: " + contactDetails.ensName Layout.fillWidth: true
} text: "ensName: " + contactDetails.ensName
Label { }
Layout.fillWidth: true Label {
text: "ensVerified: " + contactDetails.ensVerified Layout.fillWidth: true
} text: "ensVerified: " + contactDetails.ensVerified
Label { }
Layout.fillWidth: true Label {
text: "localNickname: " + contactDetails.localNickname Layout.fillWidth: true
} text: "localNickname: " + contactDetails.localNickname
Label { }
Layout.fillWidth: true Label {
text: "alias: " + contactDetails.alias Layout.fillWidth: true
} text: "alias: " + contactDetails.alias
Label { }
Layout.fillWidth: true Label {
text: "icon: " + contactDetails.icon Layout.fillWidth: true
} text: "icon: " + contactDetails.icon
Label { }
Layout.fillWidth: true Label {
text: "colorId: " + contactDetails.colorId Layout.fillWidth: true
} text: "colorId: " + contactDetails.colorId
Label { }
Layout.fillWidth: true Label {
text: "colorHash: " + contactDetails.colorHash Layout.fillWidth: true
} text: "colorHash: " + contactDetails.colorHash
Label { }
Layout.fillWidth: true Label {
text: "onlineStatus: " + contactDetails.onlineStatus Layout.fillWidth: true
} text: "onlineStatus: " + contactDetails.onlineStatus
Label { }
Layout.fillWidth: true Label {
text: "isContact: " + contactDetails.isContact Layout.fillWidth: true
} text: "isContact: " + contactDetails.isContact
Label { }
Layout.fillWidth: true Label {
text: "isCurrentUser: " + contactDetails.isCurrentUser Layout.fillWidth: true
} text: "isCurrentUser: " + contactDetails.isCurrentUser
Label { }
Layout.fillWidth: true Label {
text: "isVerified: " + contactDetails.isVerified Layout.fillWidth: true
} text: "isVerified: " + contactDetails.isVerified
Label { }
Layout.fillWidth: true Label {
text: "isUntrustworthy: " + contactDetails.isUntrustworthy Layout.fillWidth: true
} text: "isUntrustworthy: " + contactDetails.isUntrustworthy
Label { }
Layout.fillWidth: true Label {
text: "isBlocked: " + contactDetails.isBlocked Layout.fillWidth: true
} text: "isBlocked: " + contactDetails.isBlocked
Label { }
Layout.fillWidth: true Label {
text: "contactRequestState: " + contactDetails.contactRequestState Layout.fillWidth: true
} text: "contactRequestState: " + contactDetails.contactRequestState
Label { }
Layout.fillWidth: true Label {
text: "incomingVerificationStatus: " + contactDetails.incomingVerificationStatus Layout.fillWidth: true
} text: "incomingVerificationStatus: " + contactDetails.incomingVerificationStatus
Label { }
Layout.fillWidth: true Label {
text: "outgoingVerificationStatus: " + contactDetails.outgoingVerificationStatus Layout.fillWidth: true
} text: "outgoingVerificationStatus: " + contactDetails.outgoingVerificationStatus
}
Pane { Pane {
contentItem: RowLayout { contentItem: RowLayout {
ComboBox { ComboBox {
id: pubKeySelector id: pubKeySelector
model: [...ModelUtils.modelToFlatArray(myContactsModel, "pubKey"), "myPubKey", "none"] model: [...ModelUtils.modelToFlatArray(myContactsModel, "pubKey"), "myPubKey", "none"]
ModelChangeTracker { ModelChangeTracker {
id: modelChangeTracker id: modelChangeTracker
model: myContactsModel model: myContactsModel
onRevisionChanged: { onRevisionChanged: {
pubKeySelector.model = [...ModelUtils.modelToFlatArray(myContactsModel, "pubKey"), "myPubKey", "none"] pubKeySelector.model = [...ModelUtils.modelToFlatArray(myContactsModel, "pubKey"), "myPubKey", "none"]
}
} }
} }
} }
@ -121,20 +123,22 @@ SplitView {
} }
} }
UsersModelEditor { Pane {
id: myContactsModelEditor
SplitView.fillHeight: true SplitView.fillHeight: true
SplitView.preferredWidth: 500 SplitView.preferredWidth: 500
model: myContactsModel contentItem: UsersModelEditor {
id: myContactsModelEditor
model: myContactsModel
onRemoveClicked: (index) => { onRemoveClicked: (index) => {
myContactsModel.remove(index, 1) myContactsModel.remove(index, 1)
} }
onRemoveAllClicked: () => { onRemoveAllClicked: () => {
myContactsModel.clear() myContactsModel.clear()
} }
onAddClicked: () => { onAddClicked: () => {
myContactsModel.append(getNewUser(myContactsModel.count)) myContactsModel.append(getNewUser(myContactsModel.count))
}
} }
} }

View File

@ -67,7 +67,7 @@ public:
// QAbstractItemModel interface // QAbstractItemModel interface
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; bool setData(const QModelIndex &index, const QVariant &value, int role) override;
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
// QQmlParserStatus interface // QQmlParserStatus interface

View File

@ -310,27 +310,27 @@ bool ConcatModel::setData(const QModelIndex &index, const QVariant &value, int r
if (!checkIndex(index, CheckIndexOption::IndexIsValid)) if (!checkIndex(index, CheckIndexOption::IndexIsValid))
return false; return false;
auto row = index.row(); const auto row = index.row();
auto source = sourceForIndex(row); const auto source = sourceForIndex(row);
if (source.first == nullptr) if (source.first == nullptr)
return false; return false;
auto model = source.first->model(); const auto model = source.first->model();
if (model == nullptr) if (model == nullptr)
return false; return false;
auto sourcePosition = m_sources.indexOf(source.first); const auto sourcePosition = m_sources.indexOf(source.first);
if (sourcePosition == -1) if (sourcePosition == -1)
return false; return false;
auto& mapping = m_rolesMappingToSource[sourcePosition]; const auto& mapping = m_rolesMappingToSource[sourcePosition];
auto it = mapping.find(role); const auto it = mapping.find(role);
if (it == mapping.end()) if (it == mapping.end())
return false; return false;
auto sourceIndex = model->index(source.second, 0); const auto sourceIndex = model->index(source.second, 0);
if(!sourceIndex.isValid()) if(!sourceIndex.isValid())
return false; return false;

View File

@ -67,7 +67,7 @@ QObject {
QObject { QObject {
id: d id: d
property bool loading: !itemData.available && !isMe readonly property bool loading: !itemData.available && !isMe
onLoadingChanged: { onLoadingChanged: {
if (loading) { if (loading) {
contactsStore.requestContactInfo(root.publicKey) contactsStore.requestContactInfo(root.publicKey)

View File

@ -8,23 +8,23 @@ QtObject {
property var profileModule property var profileModule
property string pubkey: !!Global.userProfile? Global.userProfile.pubKey : "" property string pubkey: userProfile.pubKey
property string name: !!Global.userProfile? Global.userProfile.name : "" property string name: userProfile.name
property string username: !!Global.userProfile? Global.userProfile.username : "" property string username: userProfile.username
property string displayName: !!Global.userProfile? Global.userProfile.displayName : "" property string displayName: userProfile.displayName
property string preferredName: !!Global.userProfile? Global.userProfile.preferredName : "" property string preferredName: userProfile.preferredName
property string profileLargeImage: !!Global.userProfile? Global.userProfile.largeImage : "" property string profileLargeImage: userProfile.largeImage
property string icon: !!Global.userProfile? Global.userProfile.icon : "" property string icon: userProfile.icon
property bool userDeclinedBackupBanner: Global.appIsReady? localAccountSensitiveSettings.userDeclinedBackupBanner : false property bool userDeclinedBackupBanner: Global.appIsReady? localAccountSensitiveSettings.userDeclinedBackupBanner : false
property var privacyStore: profileSectionModule.privacyModule property var privacyStore: profileSectionModule.privacyModule
readonly property string keyUid: !!Global.userProfile ? Global.userProfile.keyUid : "" readonly property string keyUid: userProfile.keyUid
readonly property bool isKeycardUser: !!Global.userProfile ? Global.userProfile.isKeycardUser : false readonly property bool isKeycardUser: userProfile.isKeycardUser
readonly property int currentUserStatus: !!Global.userProfile ? Global.userProfile.currentUserStatus : 0 readonly property int currentUserStatus: userProfile.currentUserStatus
readonly property var thumbnailImage: !!Global.userProfile ? Global.userProfile.thumbnailImage : "" readonly property var thumbnailImage: userProfile.thumbnailImage
readonly property var largeImage: !!Global.userProfile ? Global.userProfile.largeImage : "" readonly property var largeImage: userProfile.largeImage
readonly property int colorId: Utils.colorIdForPubkey(root.pubkey) readonly property int colorId: Utils.colorIdForPubkey(root.pubkey)
readonly property var colorHash: Utils.getColorHashAsJson(root.pubkey, name != "") readonly property var colorHash: Utils.getColorHashAsJson(root.pubkey, name != "")
readonly property string defaultDisplayName: Utils.getDefaultDisplayName("", name, displayName, username) readonly property string defaultDisplayName: ProfileUtils.displayName("", name, displayName, username)
readonly property string bio: profileModule.bio readonly property string bio: profileModule.bio
readonly property string socialLinksJson: profileModule.socialLinksJson readonly property string socialLinksJson: profileModule.socialLinksJson

View File

@ -22,6 +22,7 @@ import shared.views.profile 1.0
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
import AppLayouts.Wallet.stores 1.0 as WalletNS import AppLayouts.Wallet.stores 1.0 as WalletNS
import AppLayouts.Profile.helpers 1.0
Pane { Pane {
id: root id: root
@ -60,13 +61,15 @@ Pane {
id: background id: background
} }
ContactDetails {
id: contactDetails
publicKey: root.publicKey
contactsStore: root.contactsStore
profileStore: root.profileStore
}
QtObject { QtObject {
id: d id: d
property var contactDetails: Utils.getContactDetailsAsJson(root.publicKey, !isCurrentUser, !isCurrentUser, true)
function reload() {
contactDetails = Utils.getContactDetailsAsJson(root.publicKey, !isCurrentUser, !isCurrentUser, true)
}
readonly property bool isCurrentUser: root.profileStore.pubkey === root.publicKey readonly property bool isCurrentUser: root.profileStore.pubkey === root.publicKey
readonly property string userDisplayName: contactDetails.displayName readonly property string userDisplayName: contactDetails.displayName
@ -81,7 +84,7 @@ Pane {
readonly property int contactRequestState: contactDetails.contactRequestState readonly property int contactRequestState: contactDetails.contactRequestState
readonly property int outgoingVerificationStatus: contactDetails.verificationStatus readonly property int outgoingVerificationStatus: contactDetails.outgoingVerificationStatus
readonly property int incomingVerificationStatus: contactDetails.incomingVerificationStatus readonly property int incomingVerificationStatus: contactDetails.incomingVerificationStatus
readonly property bool isVerificationRequestSent: readonly property bool isVerificationRequestSent:
@ -93,46 +96,10 @@ Pane {
readonly property bool isTrusted: outgoingVerificationStatus === Constants.verificationStatus.trusted || readonly property bool isTrusted: outgoingVerificationStatus === Constants.verificationStatus.trusted ||
incomingVerificationStatus === Constants.verificationStatus.trusted incomingVerificationStatus === Constants.verificationStatus.trusted
readonly property bool isLocallyTrusted: contactDetails.trustStatus === Constants.trustStatus.trusted readonly property bool isLocallyTrusted: contactDetails.trustStatus === Constants.trustStatus.trusted
readonly property string linkToProfile: root.contactsStore.getLinkToProfile(root.publicKey) readonly property string linkToProfile: root.contactsStore.getLinkToProfile(root.publicKey)
readonly property var conns: Connections {
target: root.contactsStore.myContactsModel ?? null
function onItemChanged(pubKey) {
if (pubKey === root.publicKey)
d.reload()
}
}
// FIXME: use myContactsModel for identity verification
readonly property var conns2: Connections {
target: root.contactsStore.receivedContactRequestsModel ?? null
function onItemChanged(pubKey) {
if (pubKey === root.publicKey)
d.reload()
}
}
readonly property var conns3: Connections {
target: root.contactsStore.sentContactRequestsModel ?? null
function onItemChanged(pubKey) {
if (pubKey === root.publicKey)
d.reload()
}
}
}
function reload() {
d.reload()
}
onDirtyChanged: {
if (!dirty)
d.reload()
} }
Component { Component {
@ -168,8 +135,7 @@ Pane {
objectName: "profileDialog_reviewContactRequestButton" objectName: "profileDialog_reviewContactRequestButton"
size: StatusButton.Size.Small size: StatusButton.Size.Small
text: qsTr("Review contact request") text: qsTr("Review contact request")
onClicked: Global.openReviewContactRequestPopup(root.publicKey, d.contactDetails, onClicked: Global.openReviewContactRequestPopup(root.publicKey, contactDetails, null)
popup => popup.closed.connect(d.reload))
} }
} }
@ -179,8 +145,7 @@ Pane {
objectName: "profileDialog_sendContactRequestButton" objectName: "profileDialog_sendContactRequestButton"
size: StatusButton.Size.Small size: StatusButton.Size.Small
text: qsTr("Send contact request") text: qsTr("Send contact request")
onClicked: Global.openContactRequestPopup(root.publicKey, d.contactDetails, onClicked: Global.openContactRequestPopup(root.publicKey, contactDetails, null)
popup => popup.accepted.connect(d.reload))
} }
} }
@ -190,7 +155,7 @@ Pane {
size: StatusButton.Size.Small size: StatusButton.Size.Small
type: StatusBaseButton.Type.Danger type: StatusBaseButton.Type.Danger
text: qsTr("Block user") text: qsTr("Block user")
onClicked: Global.blockContactRequested(root.publicKey, d.contactDetails) onClicked: Global.blockContactRequested(root.publicKey, contactDetails)
} }
} }
@ -199,7 +164,7 @@ Pane {
StatusButton { StatusButton {
size: StatusButton.Size.Small size: StatusButton.Size.Small
text: qsTr("Unblock user") text: qsTr("Unblock user")
onClicked: Global.unblockContactRequested(root.publicKey, d.contactDetails) onClicked: Global.unblockContactRequested(root.publicKey, contactDetails)
} }
} }
@ -229,8 +194,7 @@ Pane {
text: qsTr("Reply to ID verification request") text: qsTr("Reply to ID verification request")
objectName: "respondToIDRequest_StatusItem" objectName: "respondToIDRequest_StatusItem"
icon.name: "checkmark-circle" icon.name: "checkmark-circle"
onClicked: Global.openIncomingIDRequestPopup(root.publicKey, d.contactDetails, onClicked: Global.openIncomingIDRequestPopup(root.publicKey, contactDetails, null)
popup => popup.closed.connect(d.reload))
} }
} }
@ -241,8 +205,7 @@ Pane {
text: qsTr("Request ID verification") text: qsTr("Request ID verification")
objectName: "requestIDVerification_StatusItem" objectName: "requestIDVerification_StatusItem"
icon.name: "checkmark-circle" icon.name: "checkmark-circle"
onClicked: Global.openSendIDRequestPopup(root.publicKey, d.contactDetails, onClicked: Global.openSendIDRequestPopup(root.publicKey, contactDetails, null)
popup => popup.accepted.connect(d.reload))
} }
} }
@ -253,8 +216,7 @@ Pane {
text: d.incomingVerificationStatus !== Constants.verificationStatus.verified ? qsTr("ID verification pending") text: d.incomingVerificationStatus !== Constants.verificationStatus.verified ? qsTr("ID verification pending")
: qsTr("Review ID verification reply") : qsTr("Review ID verification reply")
icon.name: d.incomingVerificationStatus !== Constants.verificationStatus.verified ? "history" : "checkmark-circle" icon.name: d.incomingVerificationStatus !== Constants.verificationStatus.verified ? "history" : "checkmark-circle"
onClicked: Global.openOutgoingIDRequestPopup(root.publicKey, d.contactDetails, onClicked: Global.openOutgoingIDRequestPopup(root.publicKey, contactDetails, null)
popup => popup.closed.connect(d.reload))
} }
} }
@ -302,14 +264,14 @@ Pane {
: d.mainDisplayName : d.mainDisplayName
pubkey: root.publicKey pubkey: root.publicKey
image: root.dirty ? root.dirtyValues.profileLargeImage image: root.dirty ? root.dirtyValues.profileLargeImage
: Utils.addTimestampToURL(d.contactDetails.largeImage) : Utils.addTimestampToURL(contactDetails.largeImage)
interactive: false interactive: false
imageWidth: 90 imageWidth: 90
imageHeight: imageWidth imageHeight: imageWidth
ensVerified: d.contactDetails.ensVerified ensVerified: contactDetails.ensVerified
Binding on onlineStatus { Binding on onlineStatus {
value: d.contactDetails.onlineStatus value: contactDetails.onlineStatus
when: !d.isCurrentUser when: !d.isCurrentUser
} }
} }
@ -400,24 +362,22 @@ Pane {
SendContactRequestMenuItem { SendContactRequestMenuItem {
enabled: !d.isContact && !d.isBlocked && d.contactRequestState !== Constants.ContactRequestState.Sent && enabled: !d.isContact && !d.isBlocked && d.contactRequestState !== Constants.ContactRequestState.Sent &&
d.contactDetails.trustStatus === Constants.trustStatus.untrustworthy // we have an action button otherwise contactDetails.trustStatus === Constants.trustStatus.untrustworthy // we have an action button otherwise
onTriggered: { onTriggered: {
Global.openContactRequestPopup(root.publicKey, d.contactDetails, null) Global.openContactRequestPopup(root.publicKey, contactDetails, null)
} }
} }
StatusAction { StatusAction {
text: qsTr("Mark as ID verified") text: qsTr("Mark as ID verified")
icon.name: "checkmark-circle" icon.name: "checkmark-circle"
enabled: root.idVerificationFlowsEnabled && d.isContact && !d.isBlocked && !(d.isTrusted || d.isLocallyTrusted) enabled: root.idVerificationFlowsEnabled && d.isContact && !d.isBlocked && !(d.isTrusted || d.isLocallyTrusted)
onTriggered: Global.openMarkAsIDVerifiedPopup(root.publicKey, d.contactDetails, onTriggered: Global.openMarkAsIDVerifiedPopup(root.publicKey, contactDetails, null)
popup => popup.accepted.connect(d.reload))
} }
StatusAction { StatusAction {
text: d.userNickName ? qsTr("Edit nickname") : qsTr("Add nickname") text: d.userNickName ? qsTr("Edit nickname") : qsTr("Add nickname")
icon.name: "edit_pencil" icon.name: "edit_pencil"
onTriggered: { onTriggered: {
Global.openNicknamePopupRequested(root.publicKey, d.contactDetails, Global.openNicknamePopupRequested(root.publicKey, contactDetails, null)
popup => popup.closed.connect(d.reload))
} }
} }
StatusAction { StatusAction {
@ -441,23 +401,22 @@ Pane {
icon.name: "delete" icon.name: "delete"
type: StatusAction.Type.Danger type: StatusAction.Type.Danger
enabled: root.idVerificationFlowsEnabled && d.isContact && (d.isTrusted || d.isLocallyTrusted) enabled: root.idVerificationFlowsEnabled && d.isContact && (d.isTrusted || d.isLocallyTrusted)
onTriggered: Global.openRemoveIDVerificationDialog(root.publicKey, d.contactDetails, onTriggered: Global.openRemoveIDVerificationDialog(root.publicKey, contactDetails, null)
popup => popup.accepted.connect(d.reload))
} }
StatusAction { StatusAction {
text: qsTr("Remove nickname") text: qsTr("Remove nickname")
icon.name: "delete" icon.name: "delete"
type: StatusAction.Type.Danger type: StatusAction.Type.Danger
enabled: !d.isCurrentUser && !!d.contactDetails.localNickname enabled: !d.isCurrentUser && !!contactDetails.localNickname
onTriggered: root.contactsStore.changeContactNickname(root.publicKey, "", d.optionalDisplayName, true) onTriggered: root.contactsStore.changeContactNickname(root.publicKey, "", d.optionalDisplayName, true)
} }
StatusAction { StatusAction {
text: qsTr("Mark as untrusted") text: qsTr("Mark as untrusted")
icon.name: "warning" icon.name: "warning"
type: StatusAction.Type.Danger type: StatusAction.Type.Danger
enabled: d.contactDetails.trustStatus !== Constants.trustStatus.untrustworthy && !d.isBlocked enabled: contactDetails.trustStatus !== Constants.trustStatus.untrustworthy && !d.isBlocked
onTriggered: { onTriggered: {
Global.markAsUntrustedRequested(root.publicKey, d.contactDetails) Global.markAsUntrustedRequested(root.publicKey, contactDetails)
} }
} }
StatusAction { StatusAction {
@ -471,10 +430,9 @@ Pane {
text: qsTr("Remove untrusted mark") text: qsTr("Remove untrusted mark")
icon.name: "warning" icon.name: "warning"
type: StatusAction.Type.Danger type: StatusAction.Type.Danger
enabled: d.contactDetails.trustStatus === Constants.trustStatus.untrustworthy && !d.isBlocked enabled: contactDetails.trustStatus === Constants.trustStatus.untrustworthy && !d.isBlocked
onTriggered: { onTriggered: {
root.contactsStore.removeTrustStatus(root.publicKey) root.contactsStore.removeTrustStatus(root.publicKey)
d.reload()
} }
} }
StatusAction { StatusAction {
@ -483,7 +441,7 @@ Pane {
type: StatusAction.Type.Danger type: StatusAction.Type.Danger
enabled: d.isContact && !d.isBlocked && d.contactRequestState !== Constants.ContactRequestState.Sent enabled: d.isContact && !d.isBlocked && d.contactRequestState !== Constants.ContactRequestState.Sent
onTriggered: { onTriggered: {
Global.removeContactRequested(root.publicKey, d.contactDetails) Global.removeContactRequested(root.publicKey, contactDetails)
} }
} }
StatusAction { StatusAction {
@ -492,7 +450,7 @@ Pane {
type: StatusAction.Type.Danger type: StatusAction.Type.Danger
enabled: !d.isBlocked enabled: !d.isBlocked
onTriggered: { onTriggered: {
Global.blockContactRequested(root.publicKey, d.contactDetails) Global.blockContactRequested(root.publicKey, contactDetails)
} }
} }
} }
@ -524,7 +482,7 @@ Pane {
objectName: "ProfileDialog_userVerificationIcons" objectName: "ProfileDialog_userVerificationIcons"
visible: !d.isCurrentUser visible: !d.isCurrentUser
isContact: d.isContact isContact: d.isContact
trustIndicator: d.contactDetails.trustStatus trustIndicator: contactDetails.trustStatus
isBlocked: d.isBlocked isBlocked: d.isBlocked
tiny: false tiny: false
} }
@ -580,7 +538,7 @@ Pane {
id: bioText id: bioText
width: bioScrollView.availableWidth width: bioScrollView.availableWidth
wrapMode: Text.WrapAtWordBoundaryOrAnywhere wrapMode: Text.WrapAtWordBoundaryOrAnywhere
text: root.dirty ? root.dirtyValues.bio.trim() : d.contactDetails.bio.trim() text: root.dirty ? root.dirtyValues.bio.trim() : contactDetails.bio.trim()
} }
} }
EmojiHash { EmojiHash {