mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-26 05:26:00 +00:00
feat(Profile): New Social Links workflow
TLDR; - all links visible in settings/popup - persistent order of items - drag'n'drop to reorder - editing/deleting in a new popup - several links of the same type Needs changes in nimqml (to expose QAIM::begin/endMoveRows), in DOtherSide (to expose those to NIM), in status-go (to preserve the links order and fully save them to DB) Fixes #9777
This commit is contained in:
parent
494ab84fe1
commit
1998a6556a
@ -48,18 +48,9 @@ method isLoaded*(self: Module): bool =
|
||||
method getModuleAsVariant*(self: Module): QVariant =
|
||||
return self.viewVariant
|
||||
|
||||
func hasCustomLink(socialLinks: seq[SocialLinkItem]): bool =
|
||||
for socialLinkItem in socialLinks:
|
||||
if socialLinkItem.linkType() == LinkType.Custom:
|
||||
return true
|
||||
|
||||
method viewDidLoad*(self: Module) =
|
||||
var socialLinkItems = toSocialLinkItems(self.controller.getSocialLinks())
|
||||
|
||||
# Add custom link placeholder
|
||||
if not socialLinkItems.hasCustomLink:
|
||||
socialLinkItems.add(initSocialLinkItem("", "", LinkType.Custom))
|
||||
|
||||
self.view.socialLinksModel().setItems(socialLinkItems)
|
||||
self.view.temporarySocialLinksModel().setItems(socialLinkItems)
|
||||
|
||||
|
@ -90,35 +90,38 @@ QtObject:
|
||||
read = areSocialLinksDirty
|
||||
notify = socialLinksDirtyChanged
|
||||
|
||||
proc createCustomLink(self: View, text: string, url: string) {.slot.} =
|
||||
self.temporarySocialLinksModel.appendItem(initSocialLinkItem(text, url, LinkType.Custom))
|
||||
proc createLink(self: View, text: string, url: string, linkType: int, icon: string) {.slot.} =
|
||||
self.temporarySocialLinksModel.appendItem(initSocialLinkItem(text, url, (LinkType)linkType, icon))
|
||||
self.temporarySocialLinksJsonChanged()
|
||||
self.socialLinksDirtyChanged()
|
||||
|
||||
proc removeCustomLink(self: View, uuid: string) {.slot.} =
|
||||
proc removeLink(self: View, uuid: string) {.slot.} =
|
||||
if (self.temporarySocialLinksModel.removeItem(uuid)):
|
||||
self.temporarySocialLinksJsonChanged()
|
||||
self.socialLinksDirtyChanged()
|
||||
|
||||
proc updateLink(self: View, uuid: string, text: string, url: string): bool {.slot.} =
|
||||
proc updateLink(self: View, uuid: string, text: string, url: string) {.slot.} =
|
||||
if (self.temporarySocialLinksModel.updateItem(uuid, text, url)):
|
||||
self.temporarySocialLinksJsonChanged()
|
||||
self.socialLinksDirtyChanged()
|
||||
|
||||
proc resetSocialLinks(self: View): bool {.slot.} =
|
||||
proc moveLink(self: View, fromRow: int, toRow: int) {.slot.} =
|
||||
discard self.temporarySocialLinksModel.moveItem(fromRow, toRow)
|
||||
|
||||
proc resetSocialLinks(self: View) {.slot.} =
|
||||
if (self.areSocialLinksDirty()):
|
||||
self.temporarySocialLinksModel.setItems(self.socialLinksModel.items)
|
||||
self.socialLinksDirtyChanged()
|
||||
self.temporarySocialLinksJsonChanged()
|
||||
|
||||
|
||||
proc saveSocialLinks(self: View): bool {.slot.} =
|
||||
proc saveSocialLinks(self: View, silent: bool = false): bool {.slot.} =
|
||||
result = self.delegate.saveSocialLinks()
|
||||
if (result):
|
||||
self.socialLinksModel.setItems(self.temporarySocialLinksModel.items)
|
||||
self.socialLinksDirtyChanged()
|
||||
self.socialLinksJsonChanged()
|
||||
self.temporarySocialLinksJsonChanged()
|
||||
if not silent:
|
||||
self.socialLinksDirtyChanged()
|
||||
|
||||
proc bioChanged*(self: View) {.signal.}
|
||||
proc getBio(self: View): string {.slot.} =
|
||||
|
@ -45,21 +45,44 @@ QtObject:
|
||||
self.endResetModel()
|
||||
|
||||
proc appendItem*(self: SocialLinksModel, item: SocialLinkItem) =
|
||||
self.beginInsertRows(newQModelIndex(), self.items.len, self.items.len)
|
||||
let parentModelIndex = newQModelIndex()
|
||||
defer: parentModelIndex.delete
|
||||
self.beginInsertRows(parentModelIndex, self.items.len, self.items.len)
|
||||
self.items.add(item)
|
||||
self.endInsertRows()
|
||||
|
||||
proc removeItem*(self: SocialLinksModel, uuid: string): bool =
|
||||
for i in 0 ..< self.items.len:
|
||||
if (self.items[i].uuid == uuid):
|
||||
if (self.items[i].linkType == LinkType.Custom):
|
||||
self.beginRemoveRows(newQModelIndex(), i, i)
|
||||
self.items.delete(i)
|
||||
self.endRemoveRows()
|
||||
return true
|
||||
return false
|
||||
let parentModelIndex = newQModelIndex()
|
||||
defer: parentModelIndex.delete
|
||||
self.beginRemoveRows(parentModelIndex, i, i)
|
||||
self.items.delete(i)
|
||||
self.endRemoveRows()
|
||||
return true
|
||||
return false
|
||||
|
||||
# used only by the temp model to reorder items with DND; compat with ListModel::move(from, to, count)
|
||||
proc moveItem*(self: SocialLinksModel, fromRow: int, toRow: int): bool =
|
||||
if toRow < 0 or toRow > self.items.len - 1:
|
||||
return false
|
||||
|
||||
let sourceIndex = newQModelIndex()
|
||||
defer: sourceIndex.delete
|
||||
let destIndex = newQModelIndex()
|
||||
defer: destIndex.delete
|
||||
|
||||
var destRow = toRow
|
||||
if toRow > fromRow:
|
||||
inc(destRow)
|
||||
|
||||
let currentItem = self.items[fromRow]
|
||||
self.beginMoveRows(sourceIndex, fromRow, fromRow, destIndex, destRow)
|
||||
self.items.delete(fromRow)
|
||||
self.items.insert(@[currentItem], toRow)
|
||||
self.endMoveRows()
|
||||
return true
|
||||
|
||||
proc updateItem*(self: SocialLinksModel, uuid, text, url: string): bool =
|
||||
for i in 0 ..< self.items.len:
|
||||
if (self.items[i].uuid == uuid):
|
||||
@ -75,6 +98,7 @@ QtObject:
|
||||
|
||||
if changedRoles.len > 0:
|
||||
let index = self.createIndex(i, 0, nil)
|
||||
defer: index.delete
|
||||
self.dataChanged(index, index, changedRoles)
|
||||
return true
|
||||
|
||||
|
@ -22,7 +22,7 @@ proc socialLinkTextToIcon(text: string): string =
|
||||
if (text == SOCIAL_LINK_YOUTUBE_ID): return "youtube"
|
||||
if (text == SOCIAL_LINK_DISCORD_ID): return "discord"
|
||||
if (text == SOCIAL_LINK_TELEGRAM_ID): return "telegram"
|
||||
return ""
|
||||
return "link"
|
||||
|
||||
proc toSocialLinks*(jsonObj: JsonNode): SocialLinks =
|
||||
result = map(jsonObj.getElems(),
|
||||
|
@ -153,4 +153,8 @@ ListModel {
|
||||
title: "LanguageCurrencySettings"
|
||||
section: "Settings"
|
||||
}
|
||||
ListElement {
|
||||
title: "ProfileSocialLinksPanel"
|
||||
section: "Panels"
|
||||
}
|
||||
}
|
||||
|
@ -124,5 +124,8 @@
|
||||
],
|
||||
"CommunityMintedTokensView": [
|
||||
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2934%3A479136&t=zs22ORYUVDYpqubQ-1"
|
||||
],
|
||||
"ProfileSocialLinksPanel": [
|
||||
"https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=14588%3A308727&t=cwFGbBHsAGOP0T5R-0"
|
||||
]
|
||||
}
|
||||
|
142
storybook/pages/ProfileSocialLinksPanelPage.qml
Normal file
142
storybook/pages/ProfileSocialLinksPanelPage.qml
Normal file
@ -0,0 +1,142 @@
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Utils 0.1 as CoreUtils
|
||||
|
||||
import mainui 1.0
|
||||
import AppLayouts.Profile.panels 1.0
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import Storybook 1.0
|
||||
|
||||
SplitView {
|
||||
id: root
|
||||
|
||||
Logs { id: logs }
|
||||
|
||||
orientation: Qt.Vertical
|
||||
|
||||
Popups {
|
||||
popupParent: root
|
||||
rootStore: QtObject {}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: linksModel
|
||||
ListElement {
|
||||
uuid: "0001"
|
||||
text: "__github"
|
||||
url: "https://github.com/caybro"
|
||||
linkType: 3 // Constants.socialLinkType.github
|
||||
icon: "github"
|
||||
}
|
||||
ListElement {
|
||||
uuid: "0002"
|
||||
text: "__twitter"
|
||||
url: "https://twitter.com/caybro"
|
||||
linkType: 1 // Constants.socialLinkType.twitter
|
||||
icon: "twitter"
|
||||
}
|
||||
ListElement {
|
||||
uuid: "0003"
|
||||
text: "__personal_site"
|
||||
url: "https://status.im"
|
||||
linkType: 2 // Constants.socialLinkType.personalSite
|
||||
icon: "language"
|
||||
}
|
||||
ListElement {
|
||||
uuid: "0004"
|
||||
text: "__youtube"
|
||||
url: "https://www.youtube.com/@LukasTinkl"
|
||||
linkType: 4 // Constants.socialLinkType.youtube
|
||||
icon: "youtube"
|
||||
}
|
||||
ListElement { // NB: empty on purpose, for testing
|
||||
uuid: ""
|
||||
text: ""
|
||||
url: ""
|
||||
linkType: -1
|
||||
icon: ""
|
||||
}
|
||||
ListElement {
|
||||
uuid: "0005"
|
||||
text: "Figma design very long URL link text that should elide"
|
||||
url: "https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?node-id=1223%3A124882&t=qvYeJ8grsZLyUS0V-0"
|
||||
linkType: 0 // Constants.socialLinkType.custom
|
||||
icon: "link"
|
||||
}
|
||||
ListElement {
|
||||
uuid: "0006"
|
||||
text: "__telegram"
|
||||
url: "https://t.me/ltinkl"
|
||||
linkType: 6 // Constants.socialLinkType.telegram
|
||||
icon: "telegram"
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Global
|
||||
function onOpenLink(link) {
|
||||
logs.logEvent("Global::openLink", ["link"], [link])
|
||||
}
|
||||
}
|
||||
|
||||
StatusScrollView { // wrapped in a ScrollView on purpose; to simulate SettingsContentBase.qml
|
||||
SplitView.fillWidth: true
|
||||
SplitView.preferredHeight: 300
|
||||
ProfileSocialLinksPanel {
|
||||
width: 500
|
||||
profileStore: QtObject {
|
||||
function createLink(text, url, linkType, icon) {
|
||||
logs.logEvent("ProfileStore::createLink", ["text", "url", "linkType", "icon"], arguments)
|
||||
linksModel.append({text, url, linkType, icon})
|
||||
}
|
||||
|
||||
function removeLink(uuid) {
|
||||
logs.logEvent("ProfileStore::removeLink", ["uuid"], arguments)
|
||||
const idx = CoreUtils.ModelUtils.indexOf(linksModel, "uuid", uuid)
|
||||
if (idx === -1)
|
||||
return
|
||||
linksModel.remove(idx, 1)
|
||||
}
|
||||
|
||||
function updateLink(uuid, text, url) {
|
||||
logs.logEvent("ProfileStore::updateLink", ["uuid", "text", "url"], arguments)
|
||||
const idx = CoreUtils.ModelUtils.indexOf(linksModel, "uuid", uuid)
|
||||
if (idx === -1)
|
||||
return
|
||||
if (!!text)
|
||||
linksModel.setProperty(idx, "text", text)
|
||||
if (!!url)
|
||||
linksModel.setProperty(idx, "url", url)
|
||||
}
|
||||
|
||||
function moveLink(fromRow, toRow, count) {
|
||||
logs.logEvent("ProfileStore::moveLink", ["fromRow", "toRow", "count"], arguments)
|
||||
linksModel.move(fromRow, toRow, 1)
|
||||
}
|
||||
|
||||
function resetSocialLinks() {
|
||||
logs.logEvent("ProfileStore::resetSocialLinks")
|
||||
}
|
||||
|
||||
function saveSocialLinks(silent = false) {
|
||||
logs.logEvent("ProfileStore::saveSocialLinks", ["silent"], arguments)
|
||||
}
|
||||
}
|
||||
|
||||
socialLinksModel: linksModel
|
||||
}
|
||||
}
|
||||
|
||||
LogsAndControlsPanel {
|
||||
id: logsAndControlsPanel
|
||||
|
||||
SplitView.minimumHeight: 100
|
||||
SplitView.preferredHeight: 200
|
||||
|
||||
logsView.logText: logs.logText
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ Feature: User Identity
|
||||
And the user signs up with username "tester123" and password "TesTEr16843/!@00"
|
||||
And the user lands on the signed in app
|
||||
|
||||
@mayfail
|
||||
Scenario Outline: The user sets display name, bio and social links
|
||||
Given the user opens app settings screen
|
||||
And the user opens the profile settings
|
||||
|
240
ui/StatusQ/src/StatusQ/Components/StatusDraggableListItem.qml
Normal file
240
ui/StatusQ/src/StatusQ/Components/StatusDraggableListItem.qml
Normal file
@ -0,0 +1,240 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusDraggableListItem
|
||||
\inherits QtQuickControls::ItemDelegate
|
||||
\inqmlmodule StatusQ.Components
|
||||
\since StatusQ.Components 0.1
|
||||
\brief It is a list item with the ability to be dragged and dropped to reorder within a list view. Inherits from \c QtQuickControls::ItemDelegate.
|
||||
|
||||
The \c StatusDraggableListItem is a list item with an icon, title and a subtitle on the left side, and optional actions on the right.
|
||||
|
||||
It displays a drag handle on its left side
|
||||
|
||||
Example of how to use it:
|
||||
|
||||
\qml
|
||||
StatusListView {
|
||||
id: linksView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: root.socialLinksModel
|
||||
|
||||
displaced: Transition {
|
||||
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
|
||||
}
|
||||
|
||||
delegate: DropArea {
|
||||
id: delegateRoot
|
||||
|
||||
property int visualIndex: index
|
||||
|
||||
width: ListView.view.width
|
||||
height: draggableDelegate.height
|
||||
|
||||
keys: ["x-status-draggable-list-item-internal"]
|
||||
|
||||
onEntered: function(drag) {
|
||||
const from = drag.source.visualIndex
|
||||
const to = draggableDelegate.visualIndex
|
||||
if (to === from)
|
||||
return
|
||||
root.profileStore.moveLink(from, to, 1)
|
||||
drag.accept()
|
||||
}
|
||||
|
||||
onDropped: function(drop) {
|
||||
root.profileStore.saveSocialLinks(true)
|
||||
}
|
||||
|
||||
StatusDraggableListItem {
|
||||
id: draggableDelegate
|
||||
|
||||
readonly property string asideText: ProfileUtils.stripSocialLinkPrefix(model.url, model.linkType)
|
||||
|
||||
visible: !!asideText
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
dragParent: linksView
|
||||
visualIndex: delegateRoot.visualIndex
|
||||
draggable: linksView.count > 1
|
||||
title: ProfileUtils.linkTypeToText(model.linkType) || model.text
|
||||
icon.name: model.icon
|
||||
icon.color: ProfileUtils.linkTypeColor(model.linkType)
|
||||
actions: [
|
||||
StatusLinkText {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignRight
|
||||
horizontalAlignment: Text.AlignRight
|
||||
font.pixelSize: Theme.primaryTextFontSize
|
||||
font.weight: Font.Normal
|
||||
text: draggableDelegate.asideText
|
||||
onClicked: Global.openLink(model.url)
|
||||
},
|
||||
StatusFlatRoundButton {
|
||||
icon.name: "edit_pencil"
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
icon.width: 16
|
||||
icon.height: 16
|
||||
type: StatusFlatRoundButton.Type.Tertiary
|
||||
tooltip.text: qsTr("Edit link")
|
||||
onClicked: Global.openPopup(modifySocialLinkModal,
|
||||
{linkType: model.linkType, icon: model.icon, uuid: model.uuid,
|
||||
linkText: model.text, linkUrl: draggableDelegate.asideText})
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
\endqml
|
||||
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
ItemDelegate {
|
||||
id: root
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusDraggableListItem::title
|
||||
This property holds the primary text (title)
|
||||
*/
|
||||
property string title: text
|
||||
/*!
|
||||
\qmlproperty string StatusDraggableListItem::secondaryTitle
|
||||
This property holds the secondary text (title), displayed below primary
|
||||
*/
|
||||
property string secondaryTitle
|
||||
|
||||
/*!
|
||||
\qmlproperty list<Item> StatusDraggableListItem::actions
|
||||
This property holds the optional list of actions, displayed on the right side.
|
||||
The actions are reparented into a RowLayout.
|
||||
*/
|
||||
property list<Item> actions
|
||||
onActionsChanged: {
|
||||
for (let idx in actions) {
|
||||
let action = actions[idx]
|
||||
action.parent = actionsRow
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty Item StatusDraggableListItem::dragParent
|
||||
This property holds the drag parent (the Item that this Item gets reparented to when being dragged)
|
||||
*/
|
||||
property Item dragParent
|
||||
/*!
|
||||
\qmlproperty int StatusDraggableListItem::visualIndex
|
||||
This property holds the persistent visual index of this item's parent (usually a DropArea)
|
||||
*/
|
||||
property int visualIndex
|
||||
/*!
|
||||
\qmlproperty bool StatusDraggableListItem::draggable
|
||||
This property holds whether this item can be dragged (and whether the drag handle is displayed)
|
||||
*/
|
||||
property bool draggable
|
||||
|
||||
Drag.dragType: Drag.Automatic
|
||||
Drag.hotSpot.x: root.width/2
|
||||
Drag.hotSpot.y: root.height/2
|
||||
Drag.keys: ["x-status-draggable-list-item-internal"]
|
||||
|
||||
/*!
|
||||
\qmlproperty readonly bool StatusDraggableListItem::dragActive
|
||||
This property holds whether a drag is currently in progress
|
||||
*/
|
||||
readonly property bool dragActive: draggable && dragHandler.drag.active
|
||||
onDragActiveChanged: {
|
||||
if (dragActive)
|
||||
Drag.start()
|
||||
else
|
||||
Drag.drop()
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
when: root.dragActive
|
||||
ParentChange {
|
||||
target: root
|
||||
parent: root.dragParent
|
||||
}
|
||||
|
||||
AnchorChanges {
|
||||
target: root
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.verticalCenter: undefined
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
border.width: 1
|
||||
border.color: Theme.palette.baseColor2
|
||||
radius: 8
|
||||
|
||||
MouseArea {
|
||||
id: dragHandler
|
||||
anchors.fill: parent
|
||||
drag.target: root
|
||||
drag.axis: Drag.YAxis
|
||||
preventStealing: true // otherwise DND is broken inside a Flickable/ScrollView
|
||||
hoverEnabled: true
|
||||
cursorShape: root.dragActive ? Qt.ClosedHandCursor : Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
|
||||
// inset to simulate spacing
|
||||
topInset: 6
|
||||
bottomInset: 6
|
||||
|
||||
horizontalPadding: 12
|
||||
verticalPadding: 16
|
||||
spacing: 8
|
||||
|
||||
icon.width: 20
|
||||
icon.height: 20
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: root.spacing
|
||||
|
||||
StatusIcon {
|
||||
icon: "justify"
|
||||
visible: root.draggable
|
||||
}
|
||||
|
||||
StatusIcon {
|
||||
Layout.preferredWidth: root.icon.width
|
||||
Layout.preferredHeight: root.icon.height
|
||||
Layout.leftMargin: root.spacing/2
|
||||
icon: root.icon.name
|
||||
color: root.icon.color
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
text: root.title
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
// TODO secondaryTitle
|
||||
|
||||
RowLayout {
|
||||
id: actionsRow
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
spacing: 12
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ StatusContactVerificationIcons 0.1 StatusContactVerificationIcons.qml
|
||||
StatusDateGroupLabel 0.1 StatusDateGroupLabel.qml
|
||||
StatusDescriptionListItem 0.1 StatusDescriptionListItem.qml
|
||||
StatusDotsLoadingIndicator 0.1 StatusDotsLoadingIndicator.qml
|
||||
StatusDraggableListItem 0.1 StatusDraggableListItem.qml
|
||||
StatusLetterIdenticon 0.1 StatusLetterIdenticon.qml
|
||||
StatusListItem 0.1 StatusListItem.qml
|
||||
StatusListSectionHeadline 0.1 StatusListSectionHeadline.qml
|
||||
|
@ -194,11 +194,10 @@ QC.Popup {
|
||||
parent: QC.Overlay.overlay
|
||||
|
||||
width: 480
|
||||
// implicitHeight: headerImpl.implicitHeight + contentItem.implicitHeight + footerImpl.implicitHeight
|
||||
|
||||
padding: 0
|
||||
topPadding: padding + headerImpl.implicitHeight
|
||||
bottomPadding: padding + footerImpl.implicitHeight
|
||||
topPadding: padding + headerImpl.height
|
||||
bottomPadding: padding + footerImpl.height
|
||||
leftPadding: padding
|
||||
rightPadding: padding
|
||||
|
||||
@ -218,6 +217,7 @@ QC.Popup {
|
||||
id: headerImpl
|
||||
anchors.top: parent.top
|
||||
width: visible ? parent.width : 0
|
||||
height: visible ? implicitHeight : 0
|
||||
|
||||
visible: root.showHeader
|
||||
title: header.title
|
||||
@ -246,6 +246,7 @@ QC.Popup {
|
||||
id: footerImpl
|
||||
anchors.bottom: parent.bottom
|
||||
width: visible ? parent.width : 0
|
||||
height: visible ? implicitHeight : 0
|
||||
showFooter: root.showFooter
|
||||
visible: root.showFooter
|
||||
}
|
||||
|
5
ui/StatusQ/src/assets/img/icons/justify.svg
Normal file
5
ui/StatusQ/src/assets/img/icons/justify.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 13.5977L5 13.5977" stroke="#939BA1" stroke-width="1.3" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
<path d="M15 5.59766L5 5.59766" stroke="#939BA1" stroke-width="1.3" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
<path d="M15 9.59766L5 9.59766" stroke="#939BA1" stroke-width="1.3" stroke-linecap="square" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 457 B |
@ -257,7 +257,7 @@ Item {
|
||||
property var categoryItem
|
||||
|
||||
StatusAction {
|
||||
text: categoryItem.muted ? qsTr("Unmute category") : qsTr("Mute category")
|
||||
text: !!categoryItem ? categoryItem.muted ? qsTr("Unmute category") : qsTr("Mute category") : ""
|
||||
icon.name: "notification"
|
||||
onTriggered: {
|
||||
if (categoryItem.muted) {
|
||||
|
@ -1,101 +0,0 @@
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
import shared.controls 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property alias hyperlink: hyperlinkInput.text
|
||||
property alias url: urlInput.text
|
||||
readonly property alias removeButton: removeButton
|
||||
|
||||
readonly property var focusItem: hyperlinkInput.input.edit
|
||||
property var nextFocusItem: null
|
||||
|
||||
implicitHeight: layout.implicitHeight
|
||||
implicitWidth: layout.implicitWidth
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
ColumnLayout {
|
||||
id: leftLayout
|
||||
|
||||
spacing: 26
|
||||
|
||||
StatusInput {
|
||||
id: hyperlinkInput
|
||||
objectName: "hyperlinkInput"
|
||||
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("Hyperlink Text")
|
||||
placeholderText: qsTr("Example: My Myspace Profile")
|
||||
charLimit: 24
|
||||
|
||||
input.tabNavItem: urlInput.input.edit
|
||||
}
|
||||
|
||||
StatusInput {
|
||||
id: urlInput
|
||||
objectName: "urlInput"
|
||||
|
||||
Layout.fillWidth: true
|
||||
label: qsTr("URL")
|
||||
placeholderText: qsTr("Link URL")
|
||||
|
||||
input.tabNavItem: root.nextFocusItem
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 280
|
||||
Layout.fillHeight: true
|
||||
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
x: 64
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
spacing: 10
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("Preview")
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: 15
|
||||
}
|
||||
|
||||
SocialLinkPreview {
|
||||
Layout.preferredHeight: 32
|
||||
text: !!hyperlinkInput.text ? hyperlinkInput.text : qsTr("My Myspace Profile")
|
||||
url: !!urlInput.text ? urlInput.text : urlInput.placeholderText
|
||||
linkType: Constants.socialLinkType.custom
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StatusFlatButton {
|
||||
id: removeButton
|
||||
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
|
||||
icon.name: "delete"
|
||||
text: qsTr("Remove")
|
||||
size: StatusBaseButton.Small
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ import QtQuick 2.14
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
StatusInput {
|
||||
@ -11,19 +10,13 @@ StatusInput {
|
||||
property int linkType
|
||||
property string icon
|
||||
|
||||
leftPadding: 18 // by design
|
||||
leftPadding: Style.current.padding
|
||||
input.clearable: true
|
||||
|
||||
placeholderText: {
|
||||
if (linkType === Constants.socialLinkType.twitter) return qsTr("Twitter Handle")
|
||||
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")
|
||||
return ""
|
||||
}
|
||||
placeholderText: ProfileUtils.linkTypeToDescription(linkType)
|
||||
input.asset {
|
||||
name: root.icon
|
||||
color: ProfileUtils.linkTypeColor(root.linkType)
|
||||
width: 20
|
||||
height: 20
|
||||
}
|
||||
|
@ -1,2 +1,3 @@
|
||||
CommunityDelegate 1.0 CommunityDelegate.qml
|
||||
WalletAccountDelegate 1.0 WalletAccountDelegate.qml
|
||||
StaticSocialLinkInput 1.0 StaticSocialLinkInput.qml
|
||||
|
@ -15,11 +15,6 @@ Item {
|
||||
property alias displayName: displayNameInput
|
||||
property alias bio: bioInput
|
||||
|
||||
property var socialLinksModel
|
||||
|
||||
signal socialLinkChanged(string uuid, string text, string url)
|
||||
signal addSocialLinksClicked
|
||||
|
||||
implicitHeight: layout.implicitHeight
|
||||
implicitWidth: layout.implicitWidth
|
||||
|
||||
@ -59,39 +54,7 @@ Item {
|
||||
maximumHeight: 108
|
||||
input.verticalAlignment: TextEdit.AlignTop
|
||||
|
||||
input.tabNavItem: socialLinksRepeater.count ? socialLinksRepeater.itemAt(0).input.edit : null
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: socialLinksRepeater
|
||||
|
||||
model: root.socialLinksModel
|
||||
delegate: StaticSocialLinkInput {
|
||||
objectName: model.text + "-socialLinkInput"
|
||||
|
||||
Layout.fillWidth: true
|
||||
linkType: model.linkType
|
||||
text: Utils.stripSocialLinkPrefix(model.url, model.linkType)
|
||||
icon: model.icon
|
||||
|
||||
onTextChanged: root.socialLinkChanged(model.uuid, model.text, Utils.addSocialLinkPrefix(text, model.linkType))
|
||||
|
||||
input.tabNavItem: {
|
||||
if (index < socialLinksRepeater.count - 1) {
|
||||
return socialLinksRepeater.itemAt(index + 1).input.edit
|
||||
}
|
||||
return addMoreSocialLinksButton
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusIconTextButton {
|
||||
id: addMoreSocialLinksButton
|
||||
|
||||
objectName: "addMoreSocialLinksButton"
|
||||
Layout.topMargin: -8 // by design
|
||||
text: qsTr("Add more social links")
|
||||
onClicked: root.addSocialLinksClicked()
|
||||
input.tabNavItem: displayNameInput.input.edit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
160
ui/app/AppLayouts/Profile/panels/ProfileSocialLinksPanel.qml
Normal file
160
ui/app/AppLayouts/Profile/panels/ProfileSocialLinksPanel.qml
Normal file
@ -0,0 +1,160 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Shapes 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import AppLayouts.Profile.popups 1.0
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
Control {
|
||||
id: root
|
||||
|
||||
property var profileStore
|
||||
property var socialLinksModel
|
||||
|
||||
background: null
|
||||
|
||||
implicitHeight: layout.implicitHeight + linksView.contentHeight
|
||||
|
||||
Component {
|
||||
id: addSocialLinkModalComponent
|
||||
AddSocialLinkModal {
|
||||
onAddLinkRequested: root.profileStore.createLink(linkText, linkUrl, linkType, linkIcon)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: modifySocialLinkModal
|
||||
ModifySocialLinkModal {
|
||||
onUpdateLinkRequested: root.profileStore.updateLink(uuid, linkText, linkUrl)
|
||||
onRemoveLinkRequested: root.profileStore.removeLink(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: layout
|
||||
spacing: 12
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.current.halfPadding
|
||||
StatusBaseText {
|
||||
text: qsTr("Links")
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
StatusLinkText {
|
||||
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 !== ""
|
||||
}
|
||||
}
|
||||
|
||||
// empty placeholder when no links; dashed rounded rectangle
|
||||
ShapeRectangle {
|
||||
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")
|
||||
}
|
||||
|
||||
StatusListView {
|
||||
id: linksView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: root.socialLinksModel
|
||||
|
||||
displaced: Transition {
|
||||
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
|
||||
}
|
||||
|
||||
delegate: DropArea {
|
||||
id: delegateRoot
|
||||
|
||||
property int visualIndex: index
|
||||
|
||||
width: ListView.view.width
|
||||
height: draggableDelegate.height
|
||||
|
||||
keys: ["x-status-draggable-list-item-internal"]
|
||||
|
||||
onEntered: function(drag) {
|
||||
const from = drag.source.visualIndex
|
||||
const to = draggableDelegate.visualIndex
|
||||
if (to === from)
|
||||
return
|
||||
root.profileStore.moveLink(from, to, 1)
|
||||
drag.accept()
|
||||
}
|
||||
|
||||
onDropped: function(drop) {
|
||||
root.profileStore.saveSocialLinks(true /*silent*/)
|
||||
}
|
||||
|
||||
StatusDraggableListItem {
|
||||
id: draggableDelegate
|
||||
|
||||
readonly property string asideText: ProfileUtils.stripSocialLinkPrefix(model.url, model.linkType)
|
||||
|
||||
visible: !!asideText
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
dragParent: linksView
|
||||
visualIndex: delegateRoot.visualIndex
|
||||
draggable: linksView.count > 1
|
||||
title: ProfileUtils.linkTypeToText(model.linkType) || model.text
|
||||
icon.name: model.icon
|
||||
icon.color: ProfileUtils.linkTypeColor(model.linkType)
|
||||
actions: [
|
||||
StatusLinkText {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignRight
|
||||
horizontalAlignment: Text.AlignRight
|
||||
font.pixelSize: Theme.primaryTextFontSize
|
||||
font.weight: Font.Normal
|
||||
text: draggableDelegate.asideText
|
||||
onClicked: Global.openLink(model.url)
|
||||
},
|
||||
StatusFlatRoundButton {
|
||||
icon.name: "edit_pencil"
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
icon.width: 16
|
||||
icon.height: 16
|
||||
type: StatusFlatRoundButton.Type.Tertiary
|
||||
tooltip.text: qsTr("Edit link")
|
||||
onClicked: Global.openPopup(modifySocialLinkModal,
|
||||
{linkType: model.linkType, icon: model.icon, uuid: model.uuid,
|
||||
linkText: model.text, linkUrl: draggableDelegate.asideText})
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
108
ui/app/AppLayouts/Profile/panels/ShapeRectangle.qml
Normal file
108
ui/app/AppLayouts/Profile/panels/ShapeRectangle.qml
Normal file
@ -0,0 +1,108 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Shapes 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
/*!
|
||||
\qmltype ShapeRectangle
|
||||
\inherits Shape
|
||||
\brief Rectangle-like component with the ability to further customize the outline/border style using a Shape/ShapePath;
|
||||
with optional text in the middle
|
||||
|
||||
Example of how to use it:
|
||||
|
||||
\qml
|
||||
ShapeRectangle {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: parent.width - 4 // the rectangular path is rendered outside
|
||||
Layout.preferredHeight: 44
|
||||
text: qsTr("Your links will appear here")
|
||||
}
|
||||
\endqml
|
||||
|
||||
\sa Shape
|
||||
\sa ShapePath
|
||||
*/
|
||||
Shape {
|
||||
id: root
|
||||
|
||||
property string text
|
||||
|
||||
readonly property int radius: Style.current.radius
|
||||
readonly property alias path: path
|
||||
|
||||
asynchronous: true
|
||||
|
||||
// design values; Shape doesn't have an implicit size
|
||||
implicitWidth: 448
|
||||
implicitHeight: 44
|
||||
|
||||
StatusBaseText {
|
||||
anchors.centerIn: parent
|
||||
color: Theme.palette.baseColor1
|
||||
text: root.text
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
id: path
|
||||
fillColor: "transparent"
|
||||
strokeColor: Theme.palette.baseColor2
|
||||
strokeWidth: 1
|
||||
strokeStyle: ShapePath.DashLine
|
||||
dashPattern: [4, 4]
|
||||
|
||||
startX: root.radius
|
||||
startY: 0
|
||||
PathLine {
|
||||
x: root.width - root.radius
|
||||
y: 0
|
||||
}
|
||||
PathCubic {
|
||||
control1X: root.width
|
||||
control2X: root.width
|
||||
control1Y: 0
|
||||
control2Y: 0
|
||||
x: root.width
|
||||
y: root.radius
|
||||
}
|
||||
PathLine {
|
||||
x: root.width
|
||||
y: root.height - root.radius
|
||||
}
|
||||
PathCubic {
|
||||
control1X: root.width
|
||||
control2X: root.width
|
||||
control1Y: root.height
|
||||
control2Y: root.height
|
||||
x: root.width - root.radius
|
||||
y: root.height
|
||||
}
|
||||
PathLine {
|
||||
x: root.radius
|
||||
y: root.height
|
||||
}
|
||||
PathCubic {
|
||||
control1X: 0
|
||||
control2X: 0
|
||||
control1Y: root.height
|
||||
control2Y: root.height
|
||||
x: 0
|
||||
y: root.height - root.radius
|
||||
}
|
||||
PathLine {
|
||||
x: 0
|
||||
y: root.radius
|
||||
}
|
||||
PathCubic {
|
||||
control1X: 0
|
||||
control2X: 0
|
||||
control1Y: 0
|
||||
control2Y: 0
|
||||
x: root.radius
|
||||
y: 0
|
||||
}
|
||||
}
|
||||
}
|
1
ui/app/AppLayouts/Profile/panels/qmldir
Normal file
1
ui/app/AppLayouts/Profile/panels/qmldir
Normal file
@ -0,0 +1 @@
|
||||
ProfileSocialLinksPanel 1.0 ProfileSocialLinksPanel.qml
|
127
ui/app/AppLayouts/Profile/popups/AddSocialLinkModal.qml
Normal file
127
ui/app/AppLayouts/Profile/popups/AddSocialLinkModal.qml
Normal file
@ -0,0 +1,127 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
|
||||
import AppLayouts.Profile.controls 1.0
|
||||
|
||||
StatusStackModal {
|
||||
id: root
|
||||
|
||||
signal addLinkRequested(string linkText, string linkUrl, int linkType, string linkIcon)
|
||||
|
||||
implicitWidth: 480 // design
|
||||
implicitHeight: 512 // design
|
||||
anchors.centerIn: parent
|
||||
padding: currentIndex === 0 ? 0 : Style.current.padding
|
||||
|
||||
header.title: qsTr("Add more links")
|
||||
rightButtons: [finishButton]
|
||||
finishButton: StatusButton {
|
||||
text: qsTr("Add")
|
||||
enabled: !!linkTarget.text
|
||||
onClicked: {
|
||||
root.addLinkRequested(d.selectedLinkTypeText || customTitle.text, // text for custom link, otherwise the link typeId
|
||||
ProfileUtils.addSocialLinkPrefix(linkTarget.text, d.selectedLinkType),
|
||||
d.selectedLinkType, d.selectedIcon)
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
showFooter: currentIndex > 0
|
||||
|
||||
onClosed: destroy()
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property int selectedLinkIndex: -1
|
||||
readonly property int selectedLinkType: d.selectedLinkIndex !== -1 ? staticLinkTypesModel.get(d.selectedLinkIndex).type : 0
|
||||
readonly property string selectedLinkTypeText: d.selectedLinkIndex !== -1 ? staticLinkTypesModel.get(d.selectedLinkIndex).text : ""
|
||||
readonly property string selectedIcon: d.selectedLinkIndex !== -1 ? staticLinkTypesModel.get(d.selectedLinkIndex).icon : ""
|
||||
|
||||
readonly property var staticLinkTypesModel: ListModel {
|
||||
readonly property var data: [
|
||||
{ type: Constants.socialLinkType.twitter, icon: "twitter", text: "__twitter" },
|
||||
{ type: Constants.socialLinkType.personalSite, icon: "language", text: "__personal_site" },
|
||||
{ type: Constants.socialLinkType.github, icon: "github", text: "__github" },
|
||||
{ type: Constants.socialLinkType.youtube, icon: "youtube", text: "__youtube" },
|
||||
{ type: Constants.socialLinkType.discord, icon: "discord", text: "__discord" },
|
||||
{ type: Constants.socialLinkType.telegram, icon: "telegram", text: "__telegram" },
|
||||
{ type: Constants.socialLinkType.custom, icon: "link", text: "" }
|
||||
]
|
||||
Component.onCompleted: append(data)
|
||||
}
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
//StatusAnimatedStack doesn't handle well items' visibility,
|
||||
//keeping this solution for now until #8024 is fixed
|
||||
if (currentIndex === 1) {
|
||||
customTitle.input.edit.clear()
|
||||
linkTarget.input.edit.clear()
|
||||
if (d.selectedLinkType === Constants.socialLinkType.custom)
|
||||
customTitle.input.edit.forceActiveFocus()
|
||||
else
|
||||
linkTarget.input.edit.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
stackItems: [
|
||||
StatusListView {
|
||||
width: root.availableWidth
|
||||
height: root.availableHeight
|
||||
model: d.staticLinkTypesModel
|
||||
delegate: StatusListItem {
|
||||
width: ListView.view.width
|
||||
title: ProfileUtils.linkTypeToText(model.type) || qsTr("Custom link")
|
||||
asset.name: model.icon
|
||||
asset.color: ProfileUtils.linkTypeColor(model.type)
|
||||
onClicked: {
|
||||
d.selectedLinkIndex = index
|
||||
root.currentIndex++
|
||||
}
|
||||
components: [
|
||||
StatusIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
icon: "next"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
ColumnLayout {
|
||||
width: root.availableWidth
|
||||
spacing: Style.current.halfPadding
|
||||
|
||||
StaticSocialLinkInput {
|
||||
id: customTitle
|
||||
Layout.fillWidth: true
|
||||
visible: d.selectedLinkType === Constants.socialLinkType.custom
|
||||
placeholderText: ""
|
||||
label: qsTr("Add a title")
|
||||
linkType: Constants.socialLinkType.custom
|
||||
icon: "language"
|
||||
charLimit: Constants.maxSocialLinkTextLength
|
||||
input.tabNavItem: linkTarget.input.edit
|
||||
}
|
||||
|
||||
StaticSocialLinkInput {
|
||||
id: linkTarget
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: customTitle.visible ? Style.current.padding : 0
|
||||
placeholderText: ""
|
||||
label: ProfileUtils.linkTypeToDescription(linkType) || qsTr("Add your link")
|
||||
linkType: d.selectedLinkType
|
||||
icon: d.selectedIcon
|
||||
input.tabNavItem: customTitle.input.edit
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
99
ui/app/AppLayouts/Profile/popups/ModifySocialLinkModal.qml
Normal file
99
ui/app/AppLayouts/Profile/popups/ModifySocialLinkModal.qml
Normal file
@ -0,0 +1,99 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQml.Models 2.15
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
|
||||
import AppLayouts.Profile.controls 1.0
|
||||
|
||||
StatusDialog {
|
||||
id: root
|
||||
|
||||
property int linkType: -1
|
||||
property string icon
|
||||
|
||||
property string uuid
|
||||
property string linkText
|
||||
property string linkUrl
|
||||
|
||||
signal updateLinkRequested(string uuid, string linkText, string linkUrl)
|
||||
signal removeLinkRequested(string uuid)
|
||||
|
||||
implicitWidth: 480 // design
|
||||
|
||||
title: ProfileUtils.linkTypeToText(linkType) || qsTr("Modify Custom Link")
|
||||
|
||||
footer: StatusDialogFooter {
|
||||
leftButtons: ObjectModel {
|
||||
StatusButton {
|
||||
type: StatusButton.Danger
|
||||
text: qsTr("Delete")
|
||||
onClicked: {
|
||||
root.removeLinkRequested(root.uuid)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
root.updateLinkRequested(root.uuid, customTitle.text, ProfileUtils.addSocialLinkPrefix(linkTarget.text, root.linkType))
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onAboutToShow: {
|
||||
if (linkType === Constants.socialLinkType.custom)
|
||||
customTitle.input.edit.forceActiveFocus()
|
||||
else
|
||||
linkTarget.input.edit.forceActiveFocus()
|
||||
}
|
||||
|
||||
onClosed: destroy()
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
width: root.availableWidth
|
||||
spacing: Style.current.halfPadding
|
||||
|
||||
StaticSocialLinkInput {
|
||||
id: customTitle
|
||||
Layout.fillWidth: true
|
||||
visible: root.linkType === Constants.socialLinkType.custom
|
||||
placeholderText: ""
|
||||
label: qsTr("Change title")
|
||||
linkType: Constants.socialLinkType.custom
|
||||
icon: "language"
|
||||
text: root.linkText
|
||||
charLimit: Constants.maxSocialLinkTextLength
|
||||
input.tabNavItem: linkTarget.input.edit
|
||||
}
|
||||
|
||||
StaticSocialLinkInput {
|
||||
id: linkTarget
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: customTitle.visible ? Style.current.padding : 0
|
||||
placeholderText: ""
|
||||
label: ProfileUtils.linkTypeToDescription(linkType) || qsTr("Modify your link")
|
||||
linkType: root.linkType
|
||||
icon: root.icon
|
||||
text: root.linkUrl
|
||||
input.tabNavItem: customTitle.input.edit
|
||||
}
|
||||
}
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
|
||||
import "../stores"
|
||||
import "../controls"
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
StatusDialog {
|
||||
id: root
|
||||
objectName: "socialLinksModal"
|
||||
|
||||
property ProfileStore profileStore
|
||||
|
||||
width: 640
|
||||
topPadding: 24
|
||||
bottomPadding: 24
|
||||
leftPadding: 34
|
||||
rightPadding: 34
|
||||
|
||||
title: qsTr("Social Links")
|
||||
footer: null
|
||||
|
||||
onOpened: {
|
||||
staticLinksRepeater.model = staticSocialLinks
|
||||
customLinksRepeater.model = customSocialLinks
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
// ensure text input values are reevaluated
|
||||
staticLinksRepeater.model = null
|
||||
customLinksRepeater.model = null
|
||||
}
|
||||
|
||||
SortFilterProxyModel {
|
||||
id: staticSocialLinks
|
||||
|
||||
sourceModel: root.profileStore.temporarySocialLinksModel
|
||||
filters: ValueFilter {
|
||||
roleName: "linkType"
|
||||
value: Constants.socialLinkType.custom
|
||||
inverted: true
|
||||
}
|
||||
sorters: RoleSorter {
|
||||
roleName: "linkType"
|
||||
}
|
||||
}
|
||||
|
||||
SortFilterProxyModel {
|
||||
id: customSocialLinks
|
||||
|
||||
sourceModel: root.profileStore.temporarySocialLinksModel
|
||||
filters: ValueFilter {
|
||||
roleName: "linkType"
|
||||
value: Constants.socialLinkType.custom
|
||||
}
|
||||
}
|
||||
|
||||
StatusScrollView {
|
||||
id: scrollView
|
||||
|
||||
implicitHeight: contentHeight + topPadding + bottomPadding
|
||||
anchors.fill: parent
|
||||
padding: 0
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
|
||||
spacing: 24 // by design
|
||||
|
||||
Repeater {
|
||||
id: staticLinksRepeater
|
||||
delegate: StaticSocialLinkInput {
|
||||
objectName: model.text + "-socialLinkInput"
|
||||
|
||||
Layout.fillWidth: true
|
||||
linkType: model.linkType
|
||||
text: Utils.stripSocialLinkPrefix(model.url, model.linkType)
|
||||
icon: model.icon
|
||||
|
||||
input.tabNavItem: {
|
||||
if (index < staticLinksRepeater.count - 1) {
|
||||
return staticLinksRepeater.itemAt(index + 1).input.edit
|
||||
}
|
||||
return customLinksRepeater.count ? customLinksRepeater.itemAt(0).focusItem : null
|
||||
}
|
||||
|
||||
onTextChanged: root.profileStore.updateLink(model.uuid, model.text, Utils.addSocialLinkPrefix(text, linkType))
|
||||
|
||||
Component.onCompleted: if (index === 0) {
|
||||
input.edit.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
text: qsTr("Custom links")
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: 15
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: customLinksLayout
|
||||
|
||||
spacing: 40
|
||||
|
||||
Layout.topMargin: -4 // by design
|
||||
|
||||
Repeater {
|
||||
id: customLinksRepeater
|
||||
delegate: CustomSocialLinkInput {
|
||||
objectName: "customSocialLinkInput"
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
hyperlink: model.text
|
||||
url: model.url
|
||||
|
||||
removeButton.visible: index > 0
|
||||
removeButton.onClicked: root.profileStore.removeCustomLink(model.uuid)
|
||||
|
||||
nextFocusItem: {
|
||||
if (index < customLinksRepeater.count - 1) {
|
||||
return customLinksRepeater.itemAt(index + 1).focusItem
|
||||
}
|
||||
return addAnotherCustomLinkButton
|
||||
}
|
||||
|
||||
onHyperlinkChanged: root.profileStore.updateLink(model.uuid, hyperlink, url)
|
||||
onUrlChanged: root.profileStore.updateLink(model.uuid, hyperlink, url)
|
||||
|
||||
Rectangle {
|
||||
y: -customLinksLayout.spacing / 2
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.palette.baseColor2
|
||||
visible: index > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusIconTextButton {
|
||||
id: addAnotherCustomLinkButton
|
||||
text: qsTr("Add another custom link")
|
||||
onClicked: {
|
||||
root.profileStore.createCustomLink("", "")
|
||||
scrollView.contentY = scrollView.contentHeight - scrollView.availableHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,2 +1,4 @@
|
||||
BackupSeedModal 1.0 BackupSeedModal.qml
|
||||
SetupSyncingPopup 1.0 SetupSyncingPopup.qml
|
||||
AddSocialLinkModal 1.0 AddSocialLinkModal.qml
|
||||
ModifySocialLinkModal 1.0 ModifySocialLinkModal.qml
|
||||
|
@ -22,6 +22,7 @@ QtObject {
|
||||
readonly property string socialLinksJson: profileModule.socialLinksJson
|
||||
readonly property var socialLinksModel: profileModule.socialLinksModel
|
||||
readonly property var temporarySocialLinksModel: profileModule.temporarySocialLinksModel // for editing purposes
|
||||
readonly property var temporarySocialLinksJson: profileModule.temporarySocialLinksJson
|
||||
readonly property bool socialLinksDirty: profileModule.socialLinksDirty
|
||||
|
||||
onUserDeclinedBackupBannerChanged: {
|
||||
@ -52,24 +53,28 @@ QtObject {
|
||||
root.profileModule.setDisplayName(displayName)
|
||||
}
|
||||
|
||||
function createCustomLink(text, url) {
|
||||
root.profileModule.createCustomLink(text, url)
|
||||
function createLink(text, url, linkType, icon) {
|
||||
root.profileModule.createLink(text, url, linkType, icon)
|
||||
}
|
||||
|
||||
function removeCustomLink(uuid) {
|
||||
root.profileModule.removeCustomLink(uuid)
|
||||
function removeLink(uuid) {
|
||||
root.profileModule.removeLink(uuid)
|
||||
}
|
||||
|
||||
function updateLink(uuid, text, url) {
|
||||
root.profileModule.updateLink(uuid, text, url)
|
||||
}
|
||||
|
||||
function moveLink(fromRow, toRow, count) {
|
||||
root.profileModule.moveLink(fromRow, toRow)
|
||||
}
|
||||
|
||||
function resetSocialLinks() {
|
||||
root.profileModule.resetSocialLinks()
|
||||
}
|
||||
|
||||
function saveSocialLinks() {
|
||||
root.profileModule.saveSocialLinks()
|
||||
function saveSocialLinks(silent = false) {
|
||||
root.profileModule.saveSocialLinks(silent)
|
||||
}
|
||||
|
||||
function setBio(bio) {
|
||||
|
@ -18,11 +18,11 @@ import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 20
|
||||
|
||||
property PrivacyStore privacyStore
|
||||
property ProfileStore profileStore
|
||||
property WalletStore walletStore
|
||||
@ -48,7 +48,6 @@ ColumnLayout {
|
||||
descriptionPanel.displayName.text = Qt.binding(() => { return profileStore.displayName })
|
||||
descriptionPanel.bio.text = Qt.binding(() => { return profileStore.bio })
|
||||
profileStore.resetSocialLinks()
|
||||
descriptionPanel.reevaluateSocialLinkInputs()
|
||||
biometricsSwitch.checked = Qt.binding(() => { return biometricsSwitch.currentStoredValue })
|
||||
profileHeader.icon = Qt.binding(() => { return profileStore.profileLargeImage })
|
||||
}
|
||||
@ -93,8 +92,6 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.current.padding
|
||||
Layout.rightMargin: Style.current.padding
|
||||
Layout.topMargin: 20
|
||||
Layout.bottomMargin: 20
|
||||
|
||||
store: root.profileStore
|
||||
|
||||
@ -109,33 +106,11 @@ ColumnLayout {
|
||||
editImageButtonVisible: true
|
||||
}
|
||||
|
||||
SortFilterProxyModel {
|
||||
id: staticSocialLinksSubsetModel
|
||||
|
||||
function filterPredicate(linkType) {
|
||||
return linkType === Constants.socialLinkType.twitter ||
|
||||
linkType === Constants.socialLinkType.personalSite
|
||||
}
|
||||
|
||||
sourceModel: profileStore.temporarySocialLinksModel
|
||||
filters: ExpressionFilter {
|
||||
expression: staticSocialLinksSubsetModel.filterPredicate(model.linkType)
|
||||
}
|
||||
sorters: RoleSorter {
|
||||
roleName: "linkType"
|
||||
}
|
||||
}
|
||||
|
||||
ProfileDescriptionPanel {
|
||||
id: descriptionPanel
|
||||
|
||||
readonly property bool isEnsName: profileStore.preferredName
|
||||
|
||||
function reevaluateSocialLinkInputs() {
|
||||
socialLinksModel = null
|
||||
socialLinksModel = staticSocialLinksSubsetModel
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
displayName.focus: !isEnsName
|
||||
@ -144,17 +119,22 @@ ColumnLayout {
|
||||
displayName.validationMode: StatusInput.ValidationMode.Always
|
||||
displayName.validators: isEnsName ? [] : Constants.validators.displayName
|
||||
bio.text: profileStore.bio
|
||||
socialLinksModel: staticSocialLinksSubsetModel
|
||||
|
||||
onSocialLinkChanged: profileStore.updateLink(uuid, text, url)
|
||||
onAddSocialLinksClicked: socialLinksModal.open()
|
||||
}
|
||||
|
||||
SocialLinksModal {
|
||||
id: socialLinksModal
|
||||
profileStore: root.profileStore
|
||||
Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
onClosed: descriptionPanel.reevaluateSocialLinkInputs()
|
||||
ProfileSocialLinksPanel {
|
||||
id: socialLinksPanel
|
||||
|
||||
Layout.fillWidth: true
|
||||
profileStore: root.profileStore
|
||||
socialLinksModel: root.profileStore.temporarySocialLinksModel
|
||||
}
|
||||
|
||||
Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StatusListItem {
|
||||
@ -171,6 +151,11 @@ ColumnLayout {
|
||||
onClicked: biometricsSwitch.toggle()
|
||||
}
|
||||
|
||||
Separator {
|
||||
Layout.fillWidth: true
|
||||
visible: Qt.platform.os === Constants.mac
|
||||
}
|
||||
|
||||
StatusTabBar {
|
||||
id: showcaseTabBar
|
||||
Layout.fillWidth: true
|
||||
|
@ -23,16 +23,6 @@ Control {
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
function textToType(text) {
|
||||
if (text === "__twitter") return Constants.socialLinkType.twitter
|
||||
if (text === "__personal_site") return Constants.socialLinkType.personalSite
|
||||
if (text === "__github") return Constants.socialLinkType.github
|
||||
if (text === "__youtube") return Constants.socialLinkType.youtube
|
||||
if (text === "__discord") return Constants.socialLinkType.discord
|
||||
if (text === "__telegram") return Constants.socialLinkType.telegram
|
||||
return Constants.socialLinkType.custom
|
||||
}
|
||||
|
||||
// Unfortunately, nim can't expose temporary QObjects thorugh slots
|
||||
// The only way to expose static models on demand is through json strings (see getContactDetailsAsJson)
|
||||
// Model is built here manually, which I know is completely wrong..
|
||||
@ -46,9 +36,9 @@ Control {
|
||||
for (let i=0; i<links.length; i++) {
|
||||
let obj = links[i]
|
||||
const url = obj.url
|
||||
const type = textToType(obj.text)
|
||||
const type = ProfileUtils.linkTextToType(obj.text)
|
||||
socialLinksModel.append({
|
||||
"text": Utils.stripSocialLinkPrefix(url, type),
|
||||
"text": type === Constants.socialLinkType.custom ? obj.text : ProfileUtils.stripSocialLinkPrefix(url, type),
|
||||
"url": url,
|
||||
"linkType": type,
|
||||
"icon": obj.icon
|
||||
@ -68,21 +58,10 @@ Control {
|
||||
SortFilterProxyModel {
|
||||
id: sortedSocialLinksModel
|
||||
|
||||
function customsLastPredicate(linkTypeLeft, linkTypeRight) {
|
||||
if (linkTypeLeft === Constants.socialLinkType.custom) return false
|
||||
if (linkTypeRight === Constants.socialLinkType.custom) return true
|
||||
return linkTypeLeft < linkTypeRight
|
||||
}
|
||||
|
||||
sourceModel: socialLinksModel
|
||||
filters: ExpressionFilter {
|
||||
expression: model.text !== "" && model.url !== ""
|
||||
}
|
||||
sorters: [
|
||||
ExpressionSorter {
|
||||
expression: sortedSocialLinksModel.customsLastPredicate(modelLeft.linkType, modelRight.linkType)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
|
@ -585,8 +585,8 @@ Pane {
|
||||
Layout.leftMargin: column.anchors.leftMargin + Style.current.halfPadding
|
||||
Layout.rightMargin: column.anchors.rightMargin + Style.current.halfPadding
|
||||
bio: root.dirty ? root.dirtyValues.bio : d.contactDetails.bio
|
||||
userSocialLinksJson: root.dirty ? root.profileStore.temporarySocialLinksJson
|
||||
: d.contactDetails.socialLinks
|
||||
userSocialLinksJson: root.readOnly ? root.profileStore.temporarySocialLinksJson
|
||||
: d.contactDetails.socialLinks
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
@ -610,7 +610,7 @@ Pane {
|
||||
StatusBaseInput {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 56
|
||||
leftPadding: 14
|
||||
leftPadding: Style.current.padding
|
||||
rightPadding: Style.current.halfPadding
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
@ -644,7 +644,7 @@ Pane {
|
||||
StatusBaseInput {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 56
|
||||
leftPadding: 14
|
||||
leftPadding: Style.current.padding
|
||||
rightPadding: Style.current.halfPadding
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
@ -577,6 +577,14 @@ QtObject {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property QtObject regularExpressions: QtObject {
|
||||
readonly property var alphanumericalExpanded: /^$|^[a-zA-Z0-9\-_ ]+$/
|
||||
}
|
||||
|
||||
readonly property QtObject errorMessages: QtObject {
|
||||
readonly property string alphanumericalExpandedRegExp: qsTr("Only letters, numbers, underscores, whitespaces and hyphens allowed")
|
||||
}
|
||||
|
||||
readonly property QtObject socialLinkType: QtObject {
|
||||
readonly property int custom: 0
|
||||
readonly property int twitter: 1
|
||||
@ -587,6 +595,8 @@ QtObject {
|
||||
readonly property int telegram: 6
|
||||
}
|
||||
|
||||
readonly property int maxSocialLinkTextLength: 24
|
||||
|
||||
readonly property QtObject localPairingEventType: QtObject {
|
||||
readonly property int eventUnknown: -1
|
||||
readonly property int eventConnectionError: 0
|
||||
@ -613,14 +623,6 @@ QtObject {
|
||||
Finished = 4
|
||||
}
|
||||
|
||||
readonly property QtObject regularExpressions: QtObject {
|
||||
readonly property var alphanumericalExpanded: /^$|^[a-zA-Z0-9\-_ ]+$/
|
||||
}
|
||||
|
||||
readonly property QtObject errorMessages: QtObject {
|
||||
readonly property string alphanumericalExpandedRegExp: qsTr("Only letters, numbers, underscores, whitespaces and hyphens allowed")
|
||||
}
|
||||
|
||||
readonly property var socialLinkPrefixesByType: [ // NB order must match the "socialLinkType" enum above
|
||||
"",
|
||||
"https://twitter.com/",
|
||||
|
@ -2,9 +2,62 @@ pragma Singleton
|
||||
|
||||
import QtQml 2.14
|
||||
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
QtObject {
|
||||
function displayName(nickName, ensName, displayName, aliasName)
|
||||
{
|
||||
return nickName || ensName || displayName || aliasName
|
||||
}
|
||||
|
||||
// social links utils
|
||||
function addSocialLinkPrefix(link, type) {
|
||||
const prefix = Constants.socialLinkPrefixesByType[type]
|
||||
if (link.startsWith(prefix))
|
||||
return link
|
||||
return prefix + link
|
||||
}
|
||||
|
||||
function stripSocialLinkPrefix(link, type) {
|
||||
return link.replace(Constants.socialLinkPrefixesByType[type], "")
|
||||
}
|
||||
|
||||
function linkTypeToText(linkType) {
|
||||
if (linkType === Constants.socialLinkType.twitter) return qsTr("Twitter")
|
||||
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")
|
||||
return "" // "custom" link type allows for user defined text
|
||||
}
|
||||
|
||||
function linkTypeColor(linkType) {
|
||||
if (linkType === Constants.socialLinkType.twitter) return "#03A9F4"
|
||||
if (linkType === Constants.socialLinkType.github) return "#000000"
|
||||
if (linkType === Constants.socialLinkType.youtube) return "#FF3000"
|
||||
if (linkType === Constants.socialLinkType.discord) return "#7289DA"
|
||||
if (linkType === Constants.socialLinkType.telegram) return "#0088CC"
|
||||
return Theme.palette.primaryColor1
|
||||
}
|
||||
|
||||
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.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")
|
||||
return ""
|
||||
}
|
||||
|
||||
function linkTextToType(text) {
|
||||
if (text === "__twitter") return Constants.socialLinkType.twitter
|
||||
if (text === "__personal_site") return Constants.socialLinkType.personalSite
|
||||
if (text === "__github") return Constants.socialLinkType.github
|
||||
if (text === "__youtube") return Constants.socialLinkType.youtube
|
||||
if (text === "__discord") return Constants.socialLinkType.discord
|
||||
if (text === "__telegram") return Constants.socialLinkType.telegram
|
||||
return Constants.socialLinkType.custom
|
||||
}
|
||||
}
|
||||
|
@ -572,17 +572,6 @@ QtObject {
|
||||
return JSON.stringify({imagePath: String(imgPath).replace("file://", ""), cropRect: cropRect})
|
||||
}
|
||||
|
||||
function addSocialLinkPrefix(link, type) {
|
||||
const prefix = Constants.socialLinkPrefixesByType[type]
|
||||
if (link.startsWith(prefix))
|
||||
return link
|
||||
return prefix + link
|
||||
}
|
||||
|
||||
function stripSocialLinkPrefix(link, type) {
|
||||
return link.replace(Constants.socialLinkPrefixesByType[type], "")
|
||||
}
|
||||
|
||||
// handle translations for section names coming from app_sections_config.nim
|
||||
function translatedSectionName(sectionType, fallback) {
|
||||
switch(sectionType) {
|
||||
|
@ -666,6 +666,10 @@ DOS_API void DOS_CALL dos_qabstractitemmodel_beginRemoveRows(DosQAbstractItemMod
|
||||
/// \param vptr The QAbstractItemModel
|
||||
DOS_API void DOS_CALL dos_qabstractitemmodel_endRemoveRows(DosQAbstractItemModel *vptr);
|
||||
|
||||
DOS_API void DOS_CALL dos_qabstractitemmodel_beginMoveRows(DosQAbstractItemModel *vptr, DosQModelIndex *sourceParent, int sourceFirst, int sourceLast,
|
||||
DosQModelIndex *destinationParent, int destinationChild);
|
||||
DOS_API void DOS_CALL dos_qabstractitemmodel_endMoveRows(DosQAbstractItemModel *vptr);
|
||||
|
||||
/// \brief Calls the QAbstractItemModel::beginInsertColumns() function
|
||||
/// \param vptr The QAbstractItemModel
|
||||
/// \param parent The parent QModelIndex
|
||||
|
@ -61,6 +61,13 @@ public:
|
||||
/// @see QAbstractItemModel::endRemoveRows
|
||||
virtual void publicEndRemoveRows() = 0;
|
||||
|
||||
/// @see QAbstractItemModel::beginMoveRows
|
||||
virtual void publicBeginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast,
|
||||
const QModelIndex &destinationParent, int destinationChild) = 0;
|
||||
|
||||
/// @see QAbstractItemModel::endMoveRows
|
||||
virtual void publicEndMoveRows() = 0;
|
||||
|
||||
/// @see QAbstractItemModel::beginInsertColumns
|
||||
virtual void publicBeginInsertColumns(const QModelIndex &index, int first, int last) = 0;
|
||||
|
||||
|
@ -92,6 +92,13 @@ public:
|
||||
/// Expose endInsertRows
|
||||
void publicEndRemoveRows() override;
|
||||
|
||||
/// Expose beginMoveRows
|
||||
void publicBeginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast,
|
||||
const QModelIndex &destinationParent, int destinationChild) override;
|
||||
|
||||
/// Expose endMoveRows
|
||||
void publicEndMoveRows() override;
|
||||
|
||||
/// Expose beginInsertColumns
|
||||
void publicBeginInsertColumns(const QModelIndex &index, int first, int last) override;
|
||||
|
||||
|
17
vendor/DOtherSide/lib/src/DOtherSide.cpp
vendored
17
vendor/DOtherSide/lib/src/DOtherSide.cpp
vendored
@ -1219,6 +1219,23 @@ void dos_qabstractitemmodel_endRemoveRows(::DosQAbstractItemModel *vptr)
|
||||
model->publicEndRemoveRows();
|
||||
}
|
||||
|
||||
void dos_qabstractitemmodel_beginMoveRows(DosQAbstractItemModel* vptr, ::DosQModelIndex* sourceParent, int sourceFirst, int sourceLast,
|
||||
DosQModelIndex* destinationParent, int destinationChild)
|
||||
{
|
||||
auto object = static_cast<QObject *>(vptr);
|
||||
auto model = dynamic_cast<DOS::DosIQAbstractItemModelImpl *>(object);
|
||||
auto sourceIndex = static_cast<QModelIndex *>(sourceParent);
|
||||
auto destIndex = static_cast<QModelIndex *>(destinationParent);
|
||||
model->publicBeginMoveRows(*sourceIndex, sourceFirst, sourceLast, *destIndex, destinationChild);
|
||||
}
|
||||
|
||||
void dos_qabstractitemmodel_endMoveRows(DosQAbstractItemModel* vptr)
|
||||
{
|
||||
auto object = static_cast<QObject *>(vptr);
|
||||
auto model = dynamic_cast<DOS::DosIQAbstractItemModelImpl *>(object);
|
||||
model->publicEndMoveRows();
|
||||
}
|
||||
|
||||
void dos_qabstractitemmodel_beginInsertColumns(::DosQAbstractItemModel *vptr, ::DosQModelIndex *parentIndex, int first, int last)
|
||||
{
|
||||
auto object = static_cast<QObject *>(vptr);
|
||||
|
@ -190,6 +190,18 @@ void DosQAbstractGenericModel<T>::publicEndRemoveRows()
|
||||
T::endRemoveRows();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void DosQAbstractGenericModel<T>::publicBeginMoveRows(const QModelIndex& sourceParent, int sourceFirst, int sourceLast, const QModelIndex& destinationParent, int destinationChild)
|
||||
{
|
||||
T::beginMoveRows(sourceParent, sourceFirst, sourceLast, destinationParent, destinationChild);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void DosQAbstractGenericModel<T>::publicEndMoveRows()
|
||||
{
|
||||
T::endMoveRows();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void DosQAbstractGenericModel<T>::publicBeginResetModel()
|
||||
{
|
||||
|
2
vendor/nimqml
vendored
2
vendor/nimqml
vendored
@ -1 +1 @@
|
||||
Subproject commit e8d27eee2730c1e33913d46078f0e8aa6520b434
|
||||
Subproject commit 103a186576660d1d1b5ff7f87ba2b83e908b03a1
|
2
vendor/status-go
vendored
2
vendor/status-go
vendored
@ -1 +1 @@
|
||||
Subproject commit bac7eb08ca233a4ab9177ac16fa4fe0c2b68f79e
|
||||
Subproject commit 0eef19f80e1980f737894b7e258e2853c023a35a
|
Loading…
x
Reference in New Issue
Block a user