fix(@desktop/profile): profile social links

- added to sync mechanism
- added to backup mechanism
- UI updated according to the newest changes

Closes: #10390
This commit is contained in:
Sale Djenic 2023-06-06 13:59:50 +02:00 committed by saledjenic
parent 274fc98839
commit ae492fe631
20 changed files with 320 additions and 137 deletions

View File

@ -2,6 +2,7 @@ import json, chronicles
import base
import ../../../../app_service/common/social_links
import ../../../../app_service/service/message/dto/[message, pinned_message_update, reaction, removed_message]
import ../../../../app_service/service/chat/dto/[chat]
import ../../../../app_service/service/bookmarks/dto/[bookmark]
@ -30,6 +31,7 @@ type MessageSignal* = ref object of Signal
removedChats*: seq[string]
currentStatus*: seq[StatusUpdateDto]
settings*: seq[SettingsFieldDto]
socialLinksInfo*: SocialLinksInfo
clearedHistories*: seq[ClearedHistoryDto]
verificationRequests*: seq[VerificationRequest]
savedAddresses*: seq[SavedAddressDto]
@ -53,102 +55,110 @@ proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal =
signal.messages = @[]
signal.contacts = @[]
if event["event"]{"contacts"} != nil:
for jsonContact in event["event"]["contacts"]:
if not event.contains("event"):
return signal
let e = event["event"]
if e.contains("contacts"):
for jsonContact in e["contacts"]:
signal.contacts.add(jsonContact.toContactsDto())
if event["event"]{"messages"} != nil:
for jsonMsg in event["event"]["messages"]:
if e.contains("messages"):
for jsonMsg in e["messages"]:
var message = jsonMsg.toMessageDto()
signal.messages.add(message)
info "received", signal="messages.new", messageID=message.id
if event["event"]{"chats"} != nil:
for jsonChat in event["event"]["chats"]:
if e.contains("chats"):
for jsonChat in e["chats"]:
var chat = jsonChat.toChatDto()
signal.chats.add(chat)
if event["event"]{"clearedHistories"} != nil:
for jsonClearedHistory in event["event"]{"clearedHistories"}:
if e.contains("clearedHistories"):
for jsonClearedHistory in e["clearedHistories"]:
var clearedHistoryDto = jsonClearedHistory.toClearedHistoryDto()
signal.clearedHistories.add(clearedHistoryDto)
if event["event"]{"statusUpdates"} != nil:
for jsonStatusUpdate in event["event"]["statusUpdates"]:
if e.contains("statusUpdates"):
for jsonStatusUpdate in e["statusUpdates"]:
var statusUpdate = jsonStatusUpdate.toStatusUpdateDto()
signal.statusUpdates.add(statusUpdate)
if event["event"]{"currentStatus"} != nil:
var currentStatus = event["event"]["currentStatus"].toStatusUpdateDto()
if e.contains("currentStatus"):
var currentStatus = e["currentStatus"].toStatusUpdateDto()
signal.currentStatus.add(currentStatus)
if event["event"]{"bookmarks"} != nil:
for jsonBookmark in event["event"]["bookmarks"]:
if e.contains("bookmarks"):
for jsonBookmark in e["bookmarks"]:
var bookmark = jsonBookmark.toBookmarkDto()
signal.bookmarks.add(bookmark)
if event["event"]{"installations"} != nil:
for jsonDevice in event["event"]["installations"]:
if e.contains("installations"):
for jsonDevice in e["installations"]:
signal.installations.add(jsonDevice.toInstallationDto())
if event["event"]{"emojiReactions"} != nil:
for jsonReaction in event["event"]["emojiReactions"]:
if e.contains("emojiReactions"):
for jsonReaction in e["emojiReactions"]:
signal.emojiReactions.add(jsonReaction.toReactionDto())
if event["event"]{"communities"} != nil:
for jsonCommunity in event["event"]["communities"]:
if e.contains("communities"):
for jsonCommunity in e["communities"]:
signal.communities.add(jsonCommunity.toCommunityDto())
if event["event"]{"communitiesSettings"} != nil:
for jsonCommunitySettings in event["event"]["communitiesSettings"]:
if e.contains("communitiesSettings"):
for jsonCommunitySettings in e["communitiesSettings"]:
signal.communitiesSettings.add(jsonCommunitySettings.toCommunitySettingsDto())
if event["event"]{"requestsToJoinCommunity"} != nil:
for jsonCommunity in event["event"]["requestsToJoinCommunity"]:
if e.contains("requestsToJoinCommunity"):
for jsonCommunity in e["requestsToJoinCommunity"]:
signal.membershipRequests.add(jsonCommunity.toCommunityMembershipRequestDto())
if event["event"]{"removedMessages"} != nil:
for jsonRemovedMessage in event["event"]["removedMessages"]:
if e.contains("removedMessages"):
for jsonRemovedMessage in e["removedMessages"]:
signal.deletedMessages.add(jsonRemovedMessage.toRemovedMessageDto())
if event["event"]{"removedChats"} != nil:
for removedChatID in event["event"]["removedChats"]:
if e.contains("removedChats"):
for removedChatID in e["removedChats"]:
signal.removedChats.add(removedChatID.getStr())
if event["event"]{"activityCenterNotifications"} != nil:
for jsonNotification in event["event"]["activityCenterNotifications"]:
if e.contains("activityCenterNotifications"):
for jsonNotification in e["activityCenterNotifications"]:
signal.activityCenterNotifications.add(jsonNotification.toActivityCenterNotificationDto())
if event["event"]{"pinMessages"} != nil:
for jsonPinnedMessage in event["event"]["pinMessages"]:
if e.contains("pinMessages"):
for jsonPinnedMessage in e["pinMessages"]:
signal.pinnedMessages.add(jsonPinnedMessage.toPinnedMessageUpdateDto())
if event["event"]{"settings"} != nil:
for jsonSettingsField in event["event"]["settings"]:
if e.contains("settings"):
for jsonSettingsField in e["settings"]:
signal.settings.add(jsonSettingsField.toSettingsFieldDto())
if event["event"]{"verificationRequests"} != nil:
for jsonVerificationRequest in event["event"]["verificationRequests"]:
if e.contains("socialLinksInfo"):
signal.socialLinksInfo = toSocialLinksInfo(e["socialLinksInfo"])
if e.contains("verificationRequests"):
for jsonVerificationRequest in e["verificationRequests"]:
signal.verificationRequests.add(jsonVerificationRequest.toVerificationRequest())
if event["event"]{"savedAddresses"} != nil:
for jsonSavedAddress in event["event"]["savedAddresses"]:
if e.contains("savedAddresses"):
for jsonSavedAddress in e["savedAddresses"]:
signal.savedAddresses.add(jsonSavedAddress.toSavedAddressDto())
if event["event"]{"keypairs"} != nil:
for jsonKc in event["event"]["keypairs"]:
if e.contains("keypairs"):
for jsonKc in e["keypairs"]:
signal.keypairs.add(jsonKc.toKeypairDto())
if event["event"]{"keycards"} != nil:
for jsonKc in event["event"]["keycards"]:
if e.contains("keycards"):
for jsonKc in e["keycards"]:
signal.keycards.add(jsonKc.toKeycardDto())
if event["event"]{"keycardActions"} != nil:
for jsonKc in event["event"]["keycardActions"]:
if e.contains("keycardActions"):
for jsonKc in e["keycardActions"]:
signal.keycardActions.add(jsonKc.toKeycardActionDto())
if event["event"]{"accounts"} != nil:
for jsonAcc in event["event"]["accounts"]:
if e.contains("accounts"):
for jsonAcc in e["accounts"]:
signal.walletAccounts.add(jsonAcc.toWalletAccountDto())
result = signal

View File

@ -24,10 +24,16 @@ proc delete*(self: Controller) =
discard
proc init*(self: Controller) =
self.settingsService.fetchAndStoreSocialLinks()
self.events.on(SIGNAL_BIO_UPDATED) do(e: Args):
let args = SettingsTextValueArgs(e)
self.delegate.onBioChanged(args.value)
self.events.on(SIGNAL_SOCIAL_LINKS_UPDATED) do(e: Args):
let args = SocialLinksArgs(e)
self.delegate.onSocialLinksUpdated(args.socialLinks, args.error)
proc storeIdentityImage*(self: Controller, address: string, image: string, aX: int, aY: int, bX: int, bY: int) =
discard self.profileService.storeIdentityImage(address, image, aX, aY, bX, bY)
@ -38,9 +44,9 @@ proc setDisplayName*(self: Controller, displayName: string) =
self.profileService.setDisplayName(displayName)
proc getSocialLinks*(self: Controller): SocialLinks =
self.settingsService.getSocialLinks()
return self.settingsService.getSocialLinks()
proc setSocialLinks*(self: Controller, links: SocialLinks): bool =
proc setSocialLinks*(self: Controller, links: SocialLinks) =
self.settingsService.setSocialLinks(links)
proc getBio*(self: Controller): string =

View File

@ -1,4 +1,5 @@
import NimQml
import ../../../../../app_service/common/social_links
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -34,7 +35,10 @@ method onBioChanged*(self: AccessInterface, bio: string) {.base.} =
method setDisplayName*(self: AccessInterface, displayName: string) {.base.} =
raise newException(ValueError, "No implementation available")
method saveSocialLinks*(self: AccessInterface): bool {.base.} =
method saveSocialLinks*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onSocialLinksUpdated*(self: AccessInterface, socialLinks: SocialLinks, error: string) {.base.} =
raise newException(ValueError, "No implementation available")
# View Delegate Interface

View File

@ -49,12 +49,12 @@ method isLoaded*(self: Module): bool =
method getModuleAsVariant*(self: Module): QVariant =
return self.viewVariant
proc updateSocialLinks(self: Module, socialLinks: SocialLinks) =
var socialLinkItems = toSocialLinkItems(socialLinks)
self.view.socialLinksSaved(socialLinkItems)
method viewDidLoad*(self: Module) =
var socialLinkItems = toSocialLinkItems(self.controller.getSocialLinks())
self.view.socialLinksModel().setItems(socialLinkItems)
self.view.temporarySocialLinksModel().setItems(socialLinkItems)
self.updateSocialLinks(self.controller.getSocialLinks())
self.moduleLoaded = true
self.delegate.profileModuleDidLoad()
@ -79,6 +79,12 @@ method setBio(self: Module, bio: string) =
method onBioChanged*(self: Module, bio: string) =
self.view.emitBioChangedSignal()
method saveSocialLinks*(self: Module): bool =
let socialLinks = map(self.view.temporarySocialLinksModel.items(), x => SocialLink(text: x.text, url: x.url))
return self.controller.setSocialLinks(socialLinks)
method saveSocialLinks*(self: Module) =
let socialLinks = map(self.view.temporarySocialLinksModel.items(), x => SocialLink(text: x.text, url: x.url, icon: x.icon))
self.controller.setSocialLinks(socialLinks)
method onSocialLinksUpdated*(self: Module, socialLinks: SocialLinks, error: string) =
if error.len > 0:
# maybe we want in future popup or somehow display an error to a user
return
self.updateSocialLinks(socialLinks)

View File

@ -90,6 +90,9 @@ QtObject:
read = areSocialLinksDirty
notify = socialLinksDirtyChanged
proc containsSocialLink*(self: View, text: string, url: string): bool {.slot.} =
return self.temporarySocialLinksModel.containsSocialLink(text, url)
proc createLink(self: View, text: string, url: string, linkType: int, icon: string) {.slot.} =
self.temporarySocialLinksModel.appendItem(initSocialLinkItem(text, url, (LinkType)linkType, icon))
self.temporarySocialLinksJsonChanged()
@ -114,12 +117,14 @@ QtObject:
self.socialLinksDirtyChanged()
self.temporarySocialLinksJsonChanged()
proc saveSocialLinks(self: View, silent: bool = false): bool {.slot.} =
result = self.delegate.saveSocialLinks()
if (result):
self.socialLinksModel.setItems(self.temporarySocialLinksModel.items)
proc socialLinksSaved*(self: View, items: seq[SocialLinkItem]) =
self.socialLinksModel.setItems(items)
self.temporarySocialLinksModel.setItems(items)
self.socialLinksJsonChanged()
self.temporarySocialLinksJsonChanged()
proc saveSocialLinks(self: View, silent: bool = false) {.slot.} =
self.delegate.saveSocialLinks()
if not silent:
self.socialLinksDirtyChanged()

View File

@ -1,4 +1,4 @@
import NimQml, tables, sequtils, sugar
import NimQml, tables, strutils, sequtils, sugar
import ../../../app_service/common/social_links
@ -39,10 +39,21 @@ QtObject:
new(result, delete)
result.setup
proc countChanged(self: SocialLinksModel) {.signal.}
proc getCount(self: SocialLinksModel): int {.slot.} =
self.items.len
QtProperty[int] count:
read = getCount
notify = countChanged
proc containsSocialLink*(self: SocialLinksModel, text, url: string): bool =
return self.items.any(item => cmpIgnoreCase(item.text, text) == 0 and cmpIgnoreCase(item.url, url) == 0)
proc setItems*(self: SocialLinksModel, items: seq[SocialLinkItem]) =
self.beginResetModel()
self.items = items
self.endResetModel()
self.countChanged()
proc appendItem*(self: SocialLinksModel, item: SocialLinkItem) =
let parentModelIndex = newQModelIndex()
@ -50,6 +61,7 @@ QtObject:
self.beginInsertRows(parentModelIndex, self.items.len, self.items.len)
self.items.add(item)
self.endInsertRows()
self.countChanged()
proc removeItem*(self: SocialLinksModel, uuid: string): bool =
for i in 0 ..< self.items.len:
@ -59,6 +71,7 @@ QtObject:
self.beginRemoveRows(parentModelIndex, i, i)
self.items.delete(i)
self.endRemoveRows()
self.countChanged()
return true
return false

View File

@ -336,7 +336,6 @@ method checkFetchingStatusAndProceedWithAppLoading*[T](self: Module[T]) =
method onFetchingFromWakuMessageReceived*[T](self: Module[T], backedUpMsgClock: uint64, section: string,
totalMessages: int, receivedMessageAtPosition: int) =
echo "onFetchingFromWakuMessageReceived: ", backedUpMsgClock, " section: ", section, " tm: ", totalMessages, " recAtPos: ", receivedMessageAtPosition
self.view.fetchingDataModel().checkLastKnownClockAndReinitModel(backedUpMsgClock, listOfEntitiesWeExpectToBeSynced)
if self.view.fetchingDataModel().allMessagesLoaded():
return

View File

@ -1,5 +1,7 @@
import json, sequtils, sugar
include json_utils
const SOCIAL_LINK_TWITTER_ID* = "__twitter"
const SOCIAL_LINK_PERSONAL_SITE_ID* = "__personal_site"
const SOCIAL_LINK_GITHUB_ID* = "__github"
@ -15,6 +17,10 @@ type
SocialLinks* = seq[SocialLink]
SocialLinksInfo* = object
links*: seq[SocialLink]
removed*: bool
proc socialLinkTextToIcon(text: string): string =
if (text == SOCIAL_LINK_TWITTER_ID): return "twitter"
if (text == SOCIAL_LINK_PERSONAL_SITE_ID): return "language"
@ -30,7 +36,12 @@ proc toSocialLinks*(jsonObj: JsonNode): SocialLinks =
url: node["url"].getStr(),
icon: socialLinkTextToIcon(node["text"].getStr()))
)
return
proc toSocialLinksInfo*(jsonObj: JsonNode): SocialLinksInfo =
discard jsonObj.getProp("removed", result.removed)
var linksObj: JsonNode
if jsonObj.getProp("links", linksObj):
result.links = toSocialLinks(linksObj)
proc toJsonNode*(links: SocialLinks): JsonNode =
%*links

View File

@ -4,6 +4,7 @@ import json
import ../../visual_identity/dto
include ../../../common/json_utils
import ../../../common/social_links
type
Image* = object
@ -28,6 +29,7 @@ type AccountDto* = object
type WakuBackedUpProfileDto* = object
displayName*: string
images*: seq[Image]
socialLinks*: SocialLinks
proc isValid*(self: AccountDto): bool =
result = self.name.len > 0 and self.keyUid.len > 0
@ -69,7 +71,10 @@ proc toWakuBackedUpProfileDto*(jsonObj: JsonNode): WakuBackedUpProfileDto =
result = WakuBackedUpProfileDto()
discard jsonObj.getProp("displayName", result.displayName)
var imagesObj: JsonNode
if(jsonObj.getProp("images", imagesObj) and imagesObj.kind == JArray):
for imgObj in imagesObj:
var obj: JsonNode
if(jsonObj.getProp("images", obj) and obj.kind == JArray):
for imgObj in obj:
result.images.add(toImage(imgObj))
if(jsonObj.getProp("socialLinks", obj) and obj.kind == JArray):
result.socialLinks = toSocialLinks(obj)

View File

@ -25,6 +25,7 @@ const SIGNAL_CURRENCY_UPDATED* = "currencyUpdated"
const SIGNAL_DISPLAY_NAME_UPDATED* = "displayNameUpdated"
const SIGNAL_BIO_UPDATED* = "bioUpdated"
const SIGNAL_MNEMONIC_REMOVED* = "mnemonicRemoved"
const SIGNAL_SOCIAL_LINKS_UPDATED* = "socialLinksUpdated"
const SIGNAL_CURRENT_USER_STATUS_UPDATED* = "currentUserStatusUpdated"
logScope:
@ -38,6 +39,13 @@ type
statusType*: StatusType
text*: string
SocialLinksArgs* = ref object of Args
socialLinks*: SocialLinks
error*: string
SettingProfilePictureArgs* = ref object of Args
value*: int
QtObject:
type Service* = ref object of QObject
events: EventEmitter
@ -47,7 +55,7 @@ QtObject:
notifExemptionsCache: Table[string, NotificationsExemptions]
# Forward declaration
proc fetchSocialLinks*(self: Service): SocialLinks
proc storeSocialLinksAndNotify(self: Service, data: SocialLinksArgs)
proc initNotificationSettings*(self: Service)
proc getNotifSettingAllowNotifications*(self: Service): bool
proc getNotifSettingOneToOneChats*(self: Service): string
@ -76,7 +84,6 @@ QtObject:
let response = status_settings.getSettings()
self.settings = response.result.toSettingsDto()
self.initNotificationSettings()
self.socialLinks = self.fetchSocialLinks()
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
@ -103,6 +110,10 @@ QtObject:
self.settings.mnemonic = ""
self.events.emit(SIGNAL_MNEMONIC_REMOVED, Args())
if receivedData.socialLinksInfo.links.len > 0 or
receivedData.socialLinksInfo.removed:
self.storeSocialLinksAndNotify(SocialLinksArgs(socialLinks: receivedData.socialLinksInfo.links))
self.initialized = true
proc initNotificationSettings(self: Service) =
@ -920,32 +931,38 @@ QtObject:
proc getSocialLinks*(self: Service): SocialLinks =
return self.socialLinks
proc fetchSocialLinks*(self: Service): SocialLinks =
proc storeSocialLinksAndNotify(self: Service, data: SocialLinksArgs) =
self.socialLinks = data.socialLinks
self.events.emit(SIGNAL_SOCIAL_LINKS_UPDATED, data)
proc fetchAndStoreSocialLinks*(self: Service) =
var data = SocialLinksArgs()
try:
let response = status_settings.getSocialLinks()
if(not response.error.isNil):
data.error = response.error.message
error "error getting social links", errDescription = response.error.message
result = toSocialLinks(response.result)
data.socialLinks = toSocialLinks(response.result)
except Exception as e:
data.error = e.msg
error "error getting social links", errDesription = e.msg
self.storeSocialLinksAndNotify(data)
proc setSocialLinks*(self: Service, links: SocialLinks): bool =
result = false
proc setSocialLinks*(self: Service, links: SocialLinks) =
var data = SocialLinksArgs()
let isValid = all(links, proc (link: SocialLink): bool = common_utils.validateLink(link.url))
if (not isValid):
error "error saving social links"
return result
if not isValid:
data.error = "invalid link provided"
error "validation error", errDescription=data.error
return
try:
let response = status_settings.setSocialLinks(%*links)
if(not response.error.isNil):
error "error setting social links", errDescription = response.error.message
self.socialLinks = self.fetchSocialLinks()
result = true
let response = status_settings.addOrReplaceSocialLinks(%*links)
if not response.error.isNil:
data.error = response.error.message
error "error saving social links", errDescription=data.error
return
data.socialLinks = links
except Exception as e:
error "error setting social links", errDesription = e.msg
data.error = e.msg
error "error saving social links", errDescription=data.error
self.storeSocialLinksAndNotify(data)

View File

@ -96,8 +96,8 @@ proc setExemptions*(id: string, muteAllMessages: bool, personalMentions: string,
proc deleteExemptions*(id: string): RpcResponse[JsonNode] {.raises: [Exception].} =
return core.callPrivateRPC("settings_deleteExemptions", %* [id])
proc setSocialLinks*(value: JsonNode): RpcResponse[JsonNode] {.raises: [Exception].} =
return core.callPrivateRPC("settings_setSocialLinks", %* [value])
proc addOrReplaceSocialLinks*(value: JsonNode): RpcResponse[JsonNode] {.raises: [Exception].} =
return core.callPrivateRPC("settings_addOrReplaceSocialLinks", %* [value])
proc getSocialLinks*(): RpcResponse[JsonNode] {.raises: [Exception].} =
return core.callPrivateRPC("settings_getSocialLinks")

View File

@ -440,11 +440,11 @@ class ProfileSettingsView(BaseElement):
verify_equals(8, len(table)) # Expecting 8 as social media link fields to verify
links = {
'Twitter': [table[0][0]],
'Personal Site': [table[1][0]],
'Personal site': [table[1][0]],
'Github': [table[2][0]],
'YouTube': [table[3][0]],
'Discord': [table[4][0]],
'Telegram': [table[5][0]],
'YouTube channel': [table[3][0]],
'Discord handle': [table[4][0]],
'Telegram handle': [table[5][0]],
'Custom link': [table[6][0], table[7][0]],
}
@ -481,11 +481,11 @@ class ProfileSettingsView(BaseElement):
links = self.social_links
compare_text(links['Twitter'], twitter)
compare_text(links['Personal Site'], personal_site)
compare_text(links['Personal site'], personal_site)
compare_text(links['Github'], github)
compare_text(links['YouTube'], youtube)
compare_text(links['Discord'], discord)
compare_text(links['Telegram'], telegram)
compare_text(links['YouTube channel'], youtube)
compare_text(links['Discord handle'], discord)
compare_text(links['Telegram handle'], telegram)
compare_text(links[custom_link_text], custom_link)
def verify_social_no_links(self):

View File

@ -26,6 +26,7 @@ Control {
Component {
id: addSocialLinkModalComponent
AddSocialLinkModal {
containsSocialLink: root.profileStore.containsSocialLink
onAddLinkRequested: root.profileStore.createLink(linkText, linkUrl, linkType, linkIcon)
}
}
@ -33,6 +34,7 @@ Control {
Component {
id: modifySocialLinkModal
ModifySocialLinkModal {
containsSocialLink: root.profileStore.containsSocialLink
onUpdateLinkRequested: root.profileStore.updateLink(uuid, linkText, linkUrl)
onRemoveLinkRequested: root.profileStore.removeLink(uuid)
}
@ -50,31 +52,31 @@ Control {
color: Theme.palette.baseColor1
}
Item { Layout.fillWidth: true }
StatusLinkText {
objectName: "addMoreSocialLinks"
text: qsTr(" Add more links")
color: Theme.palette.primaryColor1
font.pixelSize: Theme.tertiaryTextFontSize
font.weight: Font.Normal
onClicked: Global.openPopup(addSocialLinkModalComponent)
}
}
SortFilterProxyModel {
id: filteredSocialLinksModel
sourceModel: root.socialLinksModel
filters: ExpressionFilter {
expression: model.text !== "" && model.url !== ""
StatusBaseText {
text: qsTr("%1/%2").arg(root.profileStore.temporarySocialLinksModel.count).arg(Constants.maxNumOfSocialLinks)
color: Theme.palette.baseColor1
}
}
// empty placeholder when no links; dashed rounded rectangle
ShapeRectangle {
readonly property bool maxReached: root.profileStore.temporarySocialLinksModel.count === Constants.maxNumOfSocialLinks
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: parent.width - 4 // the rectangular path is rendered outside
Layout.preferredHeight: 44
visible: !filteredSocialLinksModel.count
text: qsTr("Your links will appear here")
text: maxReached? qsTr("Link limit of %1 reached").arg(Constants.maxNumOfSocialLinks) : ""
StatusLinkText {
objectName: "addMoreSocialLinks"
anchors.centerIn: parent
visible: !parent.maxReached
text: qsTr(" Add a link")
color: Theme.palette.primaryColor1
font.pixelSize: Theme.tertiaryTextFontSize
font.weight: Font.Normal
onClicked: Global.openPopup(addSocialLinkModalComponent)
}
}
StatusListView {

View File

@ -7,6 +7,7 @@ import utils 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
@ -15,6 +16,7 @@ import AppLayouts.Profile.controls 1.0
StatusStackModal {
id: root
property var containsSocialLink: function (text, url) {return false}
signal addLinkRequested(string linkText, string linkUrl, int linkType, string linkIcon)
implicitWidth: 480 // design
@ -26,7 +28,7 @@ StatusStackModal {
rightButtons: [finishButton]
finishButton: StatusButton {
text: qsTr("Add")
enabled: !!linkTarget.text
enabled: linkTarget.valid && (!customTitle.visible || customTitle.valid)
onClicked: {
root.addLinkRequested(d.selectedLinkTypeText || customTitle.text, // text for custom link, otherwise the link typeId
ProfileUtils.addSocialLinkPrefix(linkTarget.text, d.selectedLinkType),
@ -84,6 +86,8 @@ StatusStackModal {
asset.name: model.icon
asset.color: ProfileUtils.linkTypeColor(model.type)
onClicked: {
customTitle.reset()
linkTarget.reset()
d.selectedLinkIndex = index
root.currentIndex++
}
@ -110,6 +114,28 @@ StatusStackModal {
icon: "language"
charLimit: Constants.maxSocialLinkTextLength
input.tabNavItem: linkTarget.input.edit
validators: [
StatusValidator {
name: "text-validation"
validate: (value) => {
return value.trim() !== ""
}
errorMessage: qsTr("Invalid title")
},
StatusValidator {
name: "check-social-link-existence"
validate: (value) => {
return !root.containsSocialLink(value,
ProfileUtils.addSocialLinkPrefix(linkTarget.text, d.selectedLinkType))
}
errorMessage: d.selectedLinkType === Constants.socialLinkType.custom?
qsTr("Name and link combination already added") :
qsTr("Link already added")
}
]
onValidChanged: {linkTarget.validate(true)}
onTextChanged: {linkTarget.validate(true)}
}
StaticSocialLinkInput {
@ -121,6 +147,29 @@ StatusStackModal {
linkType: d.selectedLinkType
icon: d.selectedIcon
input.tabNavItem: customTitle.input.edit
validators: [
StatusValidator {
name: "link-validation"
validate: (value) => {
return value.trim() !== "" && Utils.validLink(ProfileUtils.addSocialLinkPrefix(value, d.selectedLinkType))
}
errorMessage: qsTr("Invalid %1").arg(ProfileUtils.linkTypeToDescription(linkTarget.linkType).toLowerCase() || qsTr("link"))
},
StatusValidator {
name: "check-social-link-existence"
validate: (value) => {
return !root.containsSocialLink(d.selectedLinkTypeText || customTitle.text,
ProfileUtils.addSocialLinkPrefix(value, d.selectedLinkType))
}
errorMessage: d.selectedLinkType === Constants.socialLinkType.custom?
qsTr("Name and link combination already added") :
qsTr("Link already added")
}
]
onValidChanged: {customTitle.validate(true)}
onTextChanged: {customTitle.validate(true)}
}
}
]

View File

@ -8,6 +8,7 @@ import utils 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Components 0.1
import StatusQ.Popups.Dialog 0.1
@ -16,6 +17,7 @@ import AppLayouts.Profile.controls 1.0
StatusDialog {
id: root
property var containsSocialLink: function (text, url) {return false}
property int linkType: -1
property string icon
@ -44,12 +46,7 @@ StatusDialog {
rightButtons: ObjectModel {
StatusButton {
text: qsTr("Update")
enabled: {
if (!customTitle.visible)
return linkTarget.text && linkTarget.text !== linkUrl
return linkTarget.text && (linkTarget.text !== linkUrl || (customTitle.text && customTitle.text !== root.linkText))
}
enabled: linkTarget.valid && (!customTitle.visible || customTitle.valid)
onClicked: {
root.updateLinkRequested(root.uuid, customTitle.text, ProfileUtils.addSocialLinkPrefix(linkTarget.text, root.linkType))
root.close()
@ -82,6 +79,29 @@ StatusDialog {
text: root.linkText
charLimit: Constants.maxSocialLinkTextLength
input.tabNavItem: linkTarget.input.edit
validators: [
StatusValidator {
name: "text-validation"
validate: (value) => {
return value.trim() !== ""
}
errorMessage: qsTr("Invalid title")
},
StatusValidator {
name: "check-social-link-existence"
validate: (value) => {
return !root.containsSocialLink(value,
ProfileUtils.addSocialLinkPrefix(linkTarget.text, root.linkType))
}
errorMessage: root.linkType === Constants.socialLinkType.custom?
qsTr("Name and link combination already added") :
qsTr("Link already added")
}
]
onValidChanged: {linkTarget.validate(true)}
onTextChanged: {linkTarget.validate(true)}
}
StaticSocialLinkInput {
@ -94,6 +114,29 @@ StatusDialog {
icon: root.icon
text: root.linkUrl
input.tabNavItem: customTitle.input.edit
validators: [
StatusValidator {
name: "link-validation"
validate: (value) => {
return value.trim() !== "" && Utils.validLink(ProfileUtils.addSocialLinkPrefix(value, root.linkType))
}
errorMessage: qsTr("Invalid %1").arg(ProfileUtils.linkTypeToDescription(linkTarget.linkType).toLowerCase() || qsTr("link"))
},
StatusValidator {
name: "check-social-link-existence"
validate: (value) => {
return !root.containsSocialLink(customTitle.text,
ProfileUtils.addSocialLinkPrefix(value, root.linkType))
}
errorMessage: root.linkType === Constants.socialLinkType.custom?
qsTr("Name and link combination already added") :
qsTr("Link already added")
}
]
onValidChanged: {customTitle.validate(true)}
onTextChanged: {customTitle.validate(true)}
}
}
}

View File

@ -55,6 +55,10 @@ QtObject {
root.profileModule.setDisplayName(displayName)
}
function containsSocialLink(text, url) {
return root.profileModule.containsSocialLink(text, url)
}
function createLink(text, url, linkType, icon) {
root.profileModule.createLink(text, url, linkType, icon)
}

View File

@ -617,6 +617,7 @@ QtObject {
readonly property int telegram: 6
}
readonly property int maxNumOfSocialLinks: 20
readonly property int maxSocialLinkTextLength: 24
readonly property QtObject localPairingEventType: QtObject {

View File

@ -24,11 +24,11 @@ QtObject {
function linkTypeToText(linkType) {
if (linkType === Constants.socialLinkType.twitter) return qsTr("Twitter")
if (linkType === Constants.socialLinkType.personalSite) return qsTr("Personal Site")
if (linkType === Constants.socialLinkType.personalSite) return qsTr("Personal site")
if (linkType === Constants.socialLinkType.github) return qsTr("Github")
if (linkType === Constants.socialLinkType.youtube) return qsTr("YouTube")
if (linkType === Constants.socialLinkType.discord) return qsTr("Discord")
if (linkType === Constants.socialLinkType.telegram) return qsTr("Telegram")
if (linkType === Constants.socialLinkType.youtube) return qsTr("YouTube channel")
if (linkType === Constants.socialLinkType.discord) return qsTr("Discord handle")
if (linkType === Constants.socialLinkType.telegram) return qsTr("Telegram handle")
return "" // "custom" link type allows for user defined text
}
@ -42,12 +42,12 @@ QtObject {
}
function linkTypeToDescription(linkType) {
if (linkType === Constants.socialLinkType.twitter) return qsTr("Twitter Handle")
if (linkType === Constants.socialLinkType.personalSite) return qsTr("Personal Site")
if (linkType === Constants.socialLinkType.twitter) return qsTr("Twitter username")
if (linkType === Constants.socialLinkType.personalSite) return qsTr("Personal site")
if (linkType === Constants.socialLinkType.github) return qsTr("Github")
if (linkType === Constants.socialLinkType.youtube) return qsTr("YouTube Channel")
if (linkType === Constants.socialLinkType.discord) return qsTr("Discord Handle")
if (linkType === Constants.socialLinkType.telegram) return qsTr("Telegram Handle")
if (linkType === Constants.socialLinkType.youtube) return qsTr("YouTube channel")
if (linkType === Constants.socialLinkType.discord) return qsTr("Discord handle")
if (linkType === Constants.socialLinkType.telegram) return qsTr("Telegram handle")
return ""
}

View File

@ -97,6 +97,14 @@ QtObject {
return Style.current.accountColors[colorIndex]
}
function validLink(link) {
if (link.length === 0) {
return false
}
var regex = /[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/
return regex.test(link)
}
function getLinkStyle(link, hoveredLink, textColor) {
return `<style type="text/css">` +
`a {` +

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit bf29188b2d5e88dde54549927f41b9c2808bf225
Subproject commit 874a0656db7dfe1a7e0b7e7008c6bef6ac801a40