feat: Manage tokens panel UI component & model
- implements the UI component to manage regular & community tokens (drag'n'drop or context menu to reorder, show/hide, group by community) - implements a custom C++ QAIM model which acts as a fake proxy model for the above QML panel (internally it does all the sorting/grouping/hiding and preserves the custom sort order) - adds and corrects support for cascading submenus in StatusAction, and StatusMenu[Item] - adds support for mirrored (horizontally flipped) StatusSwitch - adds a new SortOrderComboBox.qml (this was being used in the first Figma version, can be ignored now, will be used by the main wallet view later) - some minor fixes and cleanups in the used components Iterates #12377 Closes #12587
This commit is contained in:
parent
d73c51d380
commit
7183621369
|
@ -123,6 +123,7 @@ add_test(NAME QmlTests COMMAND QmlTests -platform offscreen)
|
|||
|
||||
list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/../ui/app")
|
||||
list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/../ui/imports")
|
||||
list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/../ui/StatusQ/src")
|
||||
list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/src")
|
||||
list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/pages")
|
||||
list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/stubs")
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
|
||||
import AppLayouts.Wallet.panels 1.0
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import Storybook 1.0
|
||||
import Models 1.0
|
||||
|
||||
SplitView {
|
||||
id: root
|
||||
|
||||
Logs { id: logs }
|
||||
|
||||
orientation: Qt.Horizontal
|
||||
|
||||
ManageTokensModel {
|
||||
id: assetsModel
|
||||
}
|
||||
|
||||
StatusScrollView { // wrapped in a ScrollView on purpose; to simulate SettingsContentBase.qml
|
||||
SplitView.fillWidth: true
|
||||
SplitView.preferredHeight: 500
|
||||
ManageTokensPanel {
|
||||
id: showcasePanel
|
||||
width: 500
|
||||
baseModel: ctrlEmptyModel.checked ? null : assetsModel
|
||||
}
|
||||
}
|
||||
|
||||
LogsAndControlsPanel {
|
||||
id: logsAndControlsPanel
|
||||
|
||||
SplitView.minimumWidth: 150
|
||||
SplitView.preferredWidth: 250
|
||||
|
||||
logsView.logText: logs.logText
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: "Dirty: %1".arg(showcasePanel.dirty ? "true" : "false")
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Save"
|
||||
onClicked: showcasePanel.saveSettings()
|
||||
}
|
||||
|
||||
Button {
|
||||
enabled: showcasePanel.dirty
|
||||
text: "Revert"
|
||||
onClicked: showcasePanel.revert()
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Random data"
|
||||
onClicked: {
|
||||
assetsModel.clear()
|
||||
assetsModel.randomizeData()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Clear settings"
|
||||
onClicked: showcasePanel.clearSettings()
|
||||
}
|
||||
|
||||
Switch {
|
||||
id: ctrlEmptyModel
|
||||
text: "Empty model"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// category: Panels
|
||||
|
||||
// https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?type=design&node-id=18139-95033&mode=design&t=nqFScWLfusXBNQA5-0
|
||||
// https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?type=design&node-id=17674-273051&mode=design&t=nqFScWLfusXBNQA5-0
|
||||
// https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?type=design&node-id=17636-249780&mode=design&t=nqFScWLfusXBNQA5-0
|
||||
// https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?type=design&node-id=17674-276833&mode=design&t=nqFScWLfusXBNQA5-0
|
||||
// https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?type=design&node-id=17675-283206&mode=design&t=nqFScWLfusXBNQA5-0
|
|
@ -0,0 +1,237 @@
|
|||
import QtQuick 2.15
|
||||
import QtQml.Models 2.15
|
||||
|
||||
import Models 1.0
|
||||
|
||||
ListModel {
|
||||
function randomizeData() {
|
||||
var data = []
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const communityId = i % 2 == 0 ? "" : "communityId%1".arg(Math.round(i))
|
||||
const enabledNetworkBalance = !!communityId ? Math.round(i)
|
||||
: {
|
||||
amount: 1,
|
||||
symbol: "ZRX"
|
||||
}
|
||||
var obj = {
|
||||
name: "Item %1".arg(i),
|
||||
symbol: "SYM %1".arg(i),
|
||||
enabledNetworkBalance: enabledNetworkBalance,
|
||||
enabledNetworkCurrencyBalance: {
|
||||
amount: 10.37,
|
||||
symbol: "EUR",
|
||||
displayDecimals: 2
|
||||
},
|
||||
communityId: communityId,
|
||||
communityName: "COM %1".arg(i),
|
||||
communityImage: ""
|
||||
}
|
||||
data.push(obj)
|
||||
}
|
||||
append(data)
|
||||
}
|
||||
|
||||
readonly property var data: [
|
||||
{
|
||||
name: "0x",
|
||||
symbol: "ZRX",
|
||||
enabledNetworkBalance: {
|
||||
amount: 1,
|
||||
symbol: "ZRX"
|
||||
},
|
||||
enabledNetworkCurrencyBalance: {
|
||||
amount: 10.37,
|
||||
symbol: "EUR",
|
||||
displayDecimals: 2
|
||||
},
|
||||
communityId: "ddls",
|
||||
communityName: "Doodles",
|
||||
communityImage: ModelsData.collectibles.doodles // FIXME backend
|
||||
},
|
||||
{
|
||||
name: "Omg",
|
||||
symbol: "OMG",
|
||||
enabledNetworkBalance: {
|
||||
amount: 2,
|
||||
symbol: "OMG"
|
||||
},
|
||||
enabledNetworkCurrencyBalance: {
|
||||
amount: 13.37,
|
||||
symbol: "EUR",
|
||||
displayDecimals: 2
|
||||
},
|
||||
communityId: "sox",
|
||||
communityName: "Socks",
|
||||
communityImage: ModelsData.icons.socks
|
||||
},
|
||||
{
|
||||
name: "Decentraland",
|
||||
symbol: "MANA",
|
||||
enabledNetworkBalance: {
|
||||
amount: 301,
|
||||
symbol: "MANA"
|
||||
},
|
||||
enabledNetworkCurrencyBalance: {
|
||||
amount: 75.256,
|
||||
symbol: "EUR",
|
||||
displayDecimals: 2
|
||||
},
|
||||
changePct24hour: -2.1,
|
||||
communityId: "",
|
||||
communityName: "",
|
||||
communityImage: ""
|
||||
},
|
||||
{
|
||||
name: "Ave Maria",
|
||||
symbol: "AAVE",
|
||||
enabledNetworkBalance: {
|
||||
amount: 23.3,
|
||||
symbol: "AAVE",
|
||||
displayDecimals: 2
|
||||
},
|
||||
enabledNetworkCurrencyBalance: {
|
||||
amount: 2.335,
|
||||
symbol: "EUR",
|
||||
displayDecimals: 2
|
||||
},
|
||||
changePct24hour: 4.56,
|
||||
communityId: "",
|
||||
communityName: "",
|
||||
communityImage: ""
|
||||
},
|
||||
{
|
||||
name: "Polymorphism",
|
||||
symbol: "POLY",
|
||||
enabledNetworkBalance: {
|
||||
amount: 3590,
|
||||
symbol: "POLY"
|
||||
},
|
||||
enabledNetworkCurrencyBalance: {
|
||||
amount: 2.7,
|
||||
symbol: "EUR",
|
||||
displayDecimals: 2
|
||||
},
|
||||
changePct24hour: -11.6789,
|
||||
communityId: "",
|
||||
communityName: "",
|
||||
communityImage: ""
|
||||
},
|
||||
{
|
||||
name: "Dai",
|
||||
symbol: "DAI",
|
||||
enabledNetworkBalance: {
|
||||
amount: 634.22,
|
||||
symbol: "DAI",
|
||||
displayDecimals: 2
|
||||
},
|
||||
enabledNetworkCurrencyBalance: {
|
||||
amount: 594.72,
|
||||
symbol: "EUR",
|
||||
displayDecimals: 2
|
||||
},
|
||||
changePct24hour: 0,
|
||||
communityId: "",
|
||||
communityName: "",
|
||||
communityImage: ""
|
||||
},
|
||||
{
|
||||
name: "Makers' choice",
|
||||
symbol: "MKR",
|
||||
enabledNetworkBalance: {
|
||||
amount: 1.3,
|
||||
symbol: "MKR",
|
||||
displayDecimals: 2
|
||||
},
|
||||
enabledNetworkCurrencyBalance: {
|
||||
amount: 100.37,
|
||||
symbol: "EUR",
|
||||
displayDecimals: 2
|
||||
},
|
||||
changePct24hour: -1,
|
||||
communityId: "",
|
||||
communityName: "",
|
||||
communityImage: ""
|
||||
},
|
||||
{
|
||||
name: "Ethereum",
|
||||
symbol: "ETH",
|
||||
enabledNetworkBalance: {
|
||||
amount: 0.12345,
|
||||
symbol: "ETH",
|
||||
displayDecimals: 8,
|
||||
stripTrailingZeroes: true
|
||||
},
|
||||
enabledNetworkCurrencyBalance: {
|
||||
amount: 182.72,
|
||||
symbol: "EUR",
|
||||
displayDecimals: 2
|
||||
},
|
||||
changePct24hour: -3.51,
|
||||
communityId: "",
|
||||
communityName: "",
|
||||
communityImage: ""
|
||||
},
|
||||
{
|
||||
name: "GetOuttaHere",
|
||||
symbol: "InvisibleSYM",
|
||||
enabledNetworkBalance: {},
|
||||
enabledNetworkCurrencyBalance: {},
|
||||
changePct24hour: NaN,
|
||||
communityId: "",
|
||||
communityName: "",
|
||||
communityImage: ""
|
||||
},
|
||||
{
|
||||
enabledNetworkBalance: ({
|
||||
displayDecimals: true,
|
||||
stripTrailingZeroes: true,
|
||||
amount: 324343.3,
|
||||
symbol: "SNT"
|
||||
}),
|
||||
enabledNetworkCurrencyBalance: ({
|
||||
displayDecimals: 4,
|
||||
stripTrailingZeroes: true,
|
||||
amount: 2.333321323400,
|
||||
symbol: "EUR"
|
||||
}),
|
||||
symbol: "SNT",
|
||||
name: "Status",
|
||||
communityId: "",
|
||||
communityName: "",
|
||||
communityImage: ""
|
||||
},
|
||||
{
|
||||
name: "Meth",
|
||||
symbol: "MET",
|
||||
enabledNetworkBalance: {
|
||||
amount: 666,
|
||||
symbol: "MET"
|
||||
},
|
||||
enabledNetworkCurrencyBalance: {
|
||||
amount: 1000.37,
|
||||
symbol: "EUR",
|
||||
displayDecimals: 2
|
||||
},
|
||||
communityId: "ddls",
|
||||
communityName: "Doodles",
|
||||
communityImage: ModelsData.collectibles.doodles
|
||||
},
|
||||
{
|
||||
name: "Ast",
|
||||
symbol: "AST",
|
||||
enabledNetworkBalance: {
|
||||
amount: 1,
|
||||
symbol: "AST"
|
||||
},
|
||||
enabledNetworkCurrencyBalance: {
|
||||
amount: 0.374,
|
||||
symbol: "EUR",
|
||||
displayDecimals: 2
|
||||
},
|
||||
communityId: "ast",
|
||||
communityName: "Astafarians",
|
||||
communityImage: ModelsData.icons.dribble
|
||||
}
|
||||
]
|
||||
Component.onCompleted: append(data)
|
||||
}
|
|
@ -9,6 +9,7 @@ FlatTokensModel 1.0 FlatTokensModel.qml
|
|||
IconModel 1.0 IconModel.qml
|
||||
LinkPreviewModel 1.0 LinkPreviewModel.qml
|
||||
MintedTokensModel 1.0 MintedTokensModel.qml
|
||||
ManageTokensModel 1.0 ManageTokensModel.qml
|
||||
RecipientModel 1.0 RecipientModel.qml
|
||||
SourceOfTokensModel 1.0 SourceOfTokensModel.qml
|
||||
TokenHoldersModel 1.0 TokenHoldersModel.qml
|
||||
|
|
|
@ -109,6 +109,12 @@ add_library(StatusQ SHARED
|
|||
src/statuswindow.cpp
|
||||
src/stringutilsinternal.cpp
|
||||
src/submodelproxymodel.cpp
|
||||
|
||||
# wallet
|
||||
src/wallet/managetokenscontroller.cpp
|
||||
src/wallet/managetokenscontroller.h
|
||||
src/wallet/managetokensmodel.cpp
|
||||
src/wallet/managetokensmodel.h
|
||||
)
|
||||
|
||||
set_target_properties(StatusQ PROPERTIES
|
||||
|
|
|
@ -146,9 +146,14 @@ ItemDelegate {
|
|||
property int visualIndex
|
||||
/*!
|
||||
\qmlproperty bool StatusDraggableListItem::draggable
|
||||
This property holds whether this item can be dragged (and whether the drag handle is displayed)
|
||||
This property holds whether the drag handle is displayed
|
||||
*/
|
||||
property bool draggable
|
||||
/*!
|
||||
\qmlproperty bool StatusDraggableListItem::dragEnabled
|
||||
This property holds whether this item can be dragged (and whether the drag handle is displayed)
|
||||
*/
|
||||
property bool dragEnabled: draggable
|
||||
/*!
|
||||
\qmlproperty bool StatusDraggableListItem::customizable
|
||||
This property holds whether this item can be customized
|
||||
|
@ -200,6 +205,13 @@ ItemDelegate {
|
|||
*/
|
||||
property color bgColor: "transparent"
|
||||
|
||||
/*!
|
||||
\qmlproperty color StatusDraggableListItem::assetBgColor
|
||||
This property holds icon/image background color, if any
|
||||
Defaults to "transparent" (ie no background)
|
||||
*/
|
||||
property color assetBgColor: "transparent"
|
||||
|
||||
Drag.dragType: Drag.Automatic
|
||||
Drag.hotSpot.x: dragHandler.mouseX
|
||||
Drag.hotSpot.y: dragHandler.mouseY
|
||||
|
@ -209,7 +221,7 @@ ItemDelegate {
|
|||
\qmlproperty readonly bool StatusDraggableListItem::dragActive
|
||||
This property holds whether a drag is currently in progress
|
||||
*/
|
||||
readonly property bool dragActive: draggable && dragHandler.drag.active
|
||||
readonly property bool dragActive: dragHandler.drag.active
|
||||
onDragActiveChanged: {
|
||||
if (dragActive)
|
||||
Drag.start()
|
||||
|
@ -234,19 +246,25 @@ ItemDelegate {
|
|||
]
|
||||
|
||||
background: Rectangle {
|
||||
color: root.dragActive && !root.customizable ? Theme.palette.indirectColor2 : "transparent"
|
||||
color: root.dragActive && !root.customizable ? Theme.palette.alphaColor(Theme.palette.baseColor2, 0.7) : root.bgColor
|
||||
border.width: root.customizable ? 0 : 1
|
||||
border.color: Theme.palette.baseColor2
|
||||
radius: customizable ? 0 : 8
|
||||
radius: root.customizable ? 0 : 8
|
||||
|
||||
MouseArea {
|
||||
id: dragHandler
|
||||
anchors.fill: parent
|
||||
drag.target: root.draggable ? root : null
|
||||
drag.target: root.dragEnabled ? root : null
|
||||
drag.axis: root.dragAxis
|
||||
preventStealing: true // otherwise DND is broken inside a Flickable/ScrollView
|
||||
hoverEnabled: true
|
||||
cursorShape: root.dragActive ? Qt.ClosedHandCursor : Qt.OpenHandCursor
|
||||
cursorShape: {
|
||||
if (!root.enabled)
|
||||
return undefined
|
||||
if (root.dragEnabled)
|
||||
return root.dragActive ? Qt.ClosedHandCursor : Qt.OpenHandCursor
|
||||
return Qt.PointingHandCursor
|
||||
}
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: {
|
||||
root.clicked(mouse);
|
||||
|
@ -255,8 +273,8 @@ ItemDelegate {
|
|||
}
|
||||
|
||||
// inset to simulate spacing
|
||||
topInset: 6
|
||||
bottomInset: 6
|
||||
topInset: 4
|
||||
bottomInset: 4
|
||||
|
||||
horizontalPadding: 12
|
||||
verticalPadding: 16
|
||||
|
@ -273,10 +291,10 @@ ItemDelegate {
|
|||
Layout.preferredHeight: 20
|
||||
icon: "justify"
|
||||
visible: root.draggable && !root.customizable
|
||||
color: root.dragEnabled ? Theme.palette.baseColor1 : Theme.palette.baseColor2
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.leftMargin: root.spacing/2
|
||||
asynchronous: true
|
||||
active: !!root.icon.name || !!root.icon.source
|
||||
visible: active
|
||||
|
@ -293,7 +311,7 @@ ItemDelegate {
|
|||
visible: text
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
font.weight: root.highlighted ? Font.Medium : Font.Normal
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Row {
|
||||
|
@ -302,6 +320,8 @@ ItemDelegate {
|
|||
spacing: 8
|
||||
|
||||
StatusBaseText {
|
||||
width: Math.min(parent.width - (secondaryTitleIconLoader.item ? parent.spacing + secondaryTitleIconLoader.item.width : 0),
|
||||
implicitWidth)
|
||||
text: root.secondaryTitle
|
||||
color: Theme.palette.baseColor1
|
||||
elide: Text.ElideRight
|
||||
|
@ -309,6 +329,8 @@ ItemDelegate {
|
|||
}
|
||||
|
||||
Loader {
|
||||
id: secondaryTitleIconLoader
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
asynchronous: true
|
||||
active: !!root.secondaryTitleIcon
|
||||
visible: active
|
||||
|
@ -349,13 +371,10 @@ ItemDelegate {
|
|||
id: imageComponent
|
||||
StatusRoundedImage {
|
||||
radius: root.bgRadius
|
||||
color: root.bgColor
|
||||
color: root.assetBgColor
|
||||
width: root.icon.width
|
||||
height: root.icon.height
|
||||
image.source: root.icon.source
|
||||
image.sourceSize: Qt.size(width, height)
|
||||
image.smooth: false
|
||||
image.mipmap: true
|
||||
showLoadingIndicator: true
|
||||
image.fillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
|
|
|
@ -74,7 +74,6 @@ CheckBox {
|
|||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
color: Theme.palette.directColor1
|
||||
lineHeight: 1.2
|
||||
leftPadding: root.leftSide? (!!root.text ? root.indicator.width + root.spacing
|
||||
: root.indicator.width) : 0
|
||||
|
|
|
@ -18,6 +18,9 @@ ItemDelegate {
|
|||
icon.width: 16
|
||||
icon.height: 16
|
||||
|
||||
font.family: Theme.palette.baseFont.name
|
||||
font.pixelSize: 15
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: root.spacing
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import StatusQ.Components 0.1
|
|||
Switch {
|
||||
id: root
|
||||
|
||||
property color textColor: Theme.palette.directColor1
|
||||
|
||||
background: MouseArea {
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
acceptedButtons: Qt.NoButton
|
||||
|
@ -18,8 +20,9 @@ Switch {
|
|||
|
||||
implicitWidth: 52
|
||||
implicitHeight: 28
|
||||
x: root.leftPadding
|
||||
y: parent.height / 2 - height / 2
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.leftPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
@ -71,8 +74,9 @@ Switch {
|
|||
contentItem: StatusBaseText {
|
||||
text: root.text
|
||||
opacity: enabled ? 1.0 : 0.3
|
||||
color: root.textColor
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: !!root.text ? root.indicator.width + root.spacing
|
||||
: root.indicator.width
|
||||
leftPadding: root.mirrored ? 0 : !!root.text ? root.indicator.width + root.spacing : root.indicator.width
|
||||
rightPadding: root.mirrored ? !!root.text ? root.indicator.width + root.spacing : root.indicator.width : 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ Action {
|
|||
imgIsIdenticon: false
|
||||
color: root.icon.color
|
||||
name: root.icon.name
|
||||
hoverColor: Theme.palette.statusMenu.hoverBackgroundColor
|
||||
}
|
||||
|
||||
property StatusFontSettings fontSettings: StatusFontSettings {}
|
||||
|
|
|
@ -36,13 +36,23 @@ Menu {
|
|||
property real maxImplicitWidth: 640
|
||||
readonly property color defaultIconColor: Theme.palette.primaryColor1
|
||||
|
||||
property int type: StatusAction.Type.Normal
|
||||
|
||||
property StatusAssetSettings assetSettings: StatusAssetSettings {
|
||||
width: 18
|
||||
height: 18
|
||||
rotation: 0
|
||||
isLetterIdenticon: false
|
||||
isImage: false
|
||||
color: root.defaultIconColor
|
||||
color: {
|
||||
if (!root.enabled)
|
||||
return Theme.palette.baseColor1
|
||||
if (root.type === StatusAction.Type.Danger)
|
||||
return Theme.palette.dangerColor1
|
||||
if (root.type === StatusAction.Type.Success)
|
||||
return Theme.palette.successColor1
|
||||
return Theme.palette.primaryColor1
|
||||
}
|
||||
}
|
||||
|
||||
property StatusFontSettings fontSettings: StatusFontSettings {}
|
||||
|
@ -57,8 +67,6 @@ Menu {
|
|||
property var openHandler
|
||||
property var closeHandler
|
||||
|
||||
signal menuItemClicked(int menuIndex)
|
||||
|
||||
function checkIfEmpty() {
|
||||
for (let i = 0; i < root.contentItem.count; ++i) {
|
||||
const menuItem = root.contentItem.itemAtIndex(i)
|
||||
|
@ -98,6 +106,7 @@ Menu {
|
|||
visible: root.hideDisabledItems ? enabled : true
|
||||
height: visible ? implicitHeight : 0
|
||||
onImplicitWidthChanged: {
|
||||
if (visible)
|
||||
d.maxDelegateImplWidth = Math.max(d.maxDelegateImplWidth, implicitWidth)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,10 @@ MenuItem {
|
|||
readonly property bool subMenuOpened: isSubMenu && root.subMenu.opened
|
||||
readonly property bool hasAction: !!root.action
|
||||
readonly property bool isStatusAction: d.hasAction && (root.action instanceof StatusAction)
|
||||
readonly property bool isStatusDangerAction: d.isStatusAction && root.action.type === StatusAction.Type.Danger
|
||||
readonly property bool isStatusSuccessAction: d.isStatusAction && root.action.type === StatusAction.Type.Success
|
||||
readonly property bool isStatusDangerAction: (d.isStatusAction && root.action.type === StatusAction.Type.Danger) ||
|
||||
(d.isStatusSubMenu && root.subMenu.type === StatusAction.Type.Danger)
|
||||
readonly property bool isStatusSuccessAction: (d.isStatusAction && root.action.type === StatusAction.Type.Success) ||
|
||||
(d.isStatusSubMenu && root.subMenu.type === StatusAction.Type.Success)
|
||||
|
||||
readonly property StatusAssetSettings originalAssetSettings: d.isStatusSubMenu && root.subMenu.assetSettings
|
||||
? root.subMenu.assetSettings
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQuick.Layouts 1.12
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
StatusAction {
|
||||
id: root
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.0964 9.05604L10.6 13.1437V2.5H9.39996V13.1436L4.90361 9.05604L4.09641 9.94396L9.59641 14.944L10 15.3109L10.4036 14.944L15.9036 9.94396L15.0964 9.05604ZM3 17.6H17V16.4H3V17.6Z" fill="#09101C"/>
|
||||
</svg>
|
After Width: | Height: | Size: 349 B |
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="arrow-top.svg"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="38.25"
|
||||
inkscape:cx="10"
|
||||
inkscape:cy="10"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 4.9036,11.04396 9.4,6.9563 V 17.6 h 1.20004 V 6.9564 l 4.49635,4.08756 0.8072,-0.88792 -5.5,-5.00004 L 10,4.7891 9.5964,5.156 l -5.5,5.00004 z M 17,2.5 H 3 v 1.2 h 14 z"
|
||||
fill="#09101c"
|
||||
id="path1" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -15,6 +15,9 @@
|
|||
#include "StatusQ/submodelproxymodel.h"
|
||||
|
||||
|
||||
#include "wallet/managetokenscontroller.h"
|
||||
#include "wallet/managetokensmodel.h"
|
||||
|
||||
class StatusQPlugin : public QQmlExtensionPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -28,6 +31,9 @@ public:
|
|||
qmlRegisterType<StatusSyntaxHighlighter>("StatusQ", 0, 1, "StatusSyntaxHighlighter");
|
||||
qmlRegisterType<RXValidator>("StatusQ", 0, 1, "RXValidator");
|
||||
|
||||
qmlRegisterType<ManageTokensController>("StatusQ.Models", 0, 1, "ManageTokensController");
|
||||
qmlRegisterType<ManageTokensModel>("StatusQ.Models", 0, 1, "ManageTokensModel");
|
||||
|
||||
qmlRegisterType<LeftJoinModel>("StatusQ", 0, 1, "LeftJoinModel");
|
||||
qmlRegisterType<SubmodelProxyModel>("StatusQ", 0, 1, "SubmodelProxyModel");
|
||||
qmlRegisterType<RoleRename>("StatusQ", 0, 1, "RoleRename");
|
||||
|
|
|
@ -0,0 +1,393 @@
|
|||
#include "managetokenscontroller.h"
|
||||
|
||||
#include <QElapsedTimer>
|
||||
|
||||
ManageTokensController::ManageTokensController(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_regularTokensModel(new ManageTokensModel(this))
|
||||
, m_communityTokensModel(new ManageTokensModel(this))
|
||||
, m_communityTokenGroupsModel(new ManageTokensModel(this))
|
||||
, m_hiddenTokensModel(new ManageTokensModel(this))
|
||||
{
|
||||
for (auto model : m_allModels) {
|
||||
connect(model, &ManageTokensModel::dirtyChanged, this, &ManageTokensController::dirtyChanged);
|
||||
}
|
||||
|
||||
connect(this, &ManageTokensController::sourceModelChanged, this, [this]() {
|
||||
if (!m_sourceModel) {
|
||||
m_modelConnectionsInitialized = false;
|
||||
return;
|
||||
}
|
||||
if (m_modelConnectionsInitialized)
|
||||
return;
|
||||
connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &parent, int first, int last) {
|
||||
#ifdef QT_DEBUG
|
||||
QElapsedTimer t;
|
||||
t.start();
|
||||
qCInfo(manageTokens) << "!!! ADDING" << last-first+1 << "NEW TOKENS";
|
||||
#endif
|
||||
for (int i = first; i <= last; i++)
|
||||
addItem(i);
|
||||
reloadCommunityIds();
|
||||
m_communityTokensModel->setCommunityIds(m_communityIds);
|
||||
m_communityTokensModel->saveCustomSortOrder();
|
||||
rebuildCommunityTokenGroupsModel();
|
||||
#ifdef QT_DEBUG
|
||||
qCInfo(manageTokens) << "!!! ADDING NEW SOURCE DATA TOOK" << t.nsecsElapsed()/1'000'000.f << "ms";
|
||||
#endif
|
||||
});
|
||||
connect(m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &ManageTokensController::parseSourceModel);
|
||||
connect(m_sourceModel, &QAbstractItemModel::dataChanged, this, &ManageTokensController::parseSourceModel); // NB at this point we don't know in which submodel the item is
|
||||
connect(m_communityTokensModel, &ManageTokensModel::rowsMoved, this, [this]() {
|
||||
if (!m_arrangeByCommunity)
|
||||
rebuildCommunityTokenGroupsModel();
|
||||
reloadCommunityIds();
|
||||
m_communityTokensModel->setCommunityIds(m_communityIds);
|
||||
m_communityTokensModel->saveCustomSortOrder();
|
||||
});
|
||||
connect(m_communityTokenGroupsModel, &ManageTokensModel::rowsMoved, this, [this](const QModelIndex &parent, int start, int end, const QModelIndex &destination, int toRow) {
|
||||
qCDebug(manageTokens) << "!!! GROUP MOVED FROM" << start << "TO" << toRow;
|
||||
// FIXME swap toRow<->start instead of reloadCommunityIds()?
|
||||
reloadCommunityIds();
|
||||
m_communityTokensModel->setCommunityIds(m_communityIds);
|
||||
m_communityTokensModel->saveCustomSortOrder();
|
||||
});
|
||||
m_modelConnectionsInitialized = true;
|
||||
});
|
||||
}
|
||||
|
||||
void ManageTokensController::showHideRegularToken(int row, bool flag)
|
||||
{
|
||||
if (flag) { // show
|
||||
auto hiddenItem = m_hiddenTokensModel->takeItem(row);
|
||||
if (hiddenItem)
|
||||
m_regularTokensModel->addItem(*hiddenItem);
|
||||
} else { // hide
|
||||
auto shownItem = m_regularTokensModel->takeItem(row);
|
||||
if (shownItem)
|
||||
m_hiddenTokensModel->addItem(*shownItem, false /*prepend*/);
|
||||
}
|
||||
}
|
||||
|
||||
void ManageTokensController::showHideCommunityToken(int row, bool flag)
|
||||
{
|
||||
if (flag) { // show
|
||||
auto hiddenItem = m_hiddenTokensModel->takeItem(row);
|
||||
if (hiddenItem) {
|
||||
m_communityTokensModel->addItem(*hiddenItem);
|
||||
if (!m_communityIds.contains(hiddenItem->communityId))
|
||||
m_communityIds.append(hiddenItem->communityId);
|
||||
}
|
||||
} else { // hide
|
||||
auto shownItem = m_communityTokensModel->takeItem(row);
|
||||
if (shownItem) {
|
||||
m_hiddenTokensModel->addItem(*shownItem, false /*prepend*/);
|
||||
if (!m_communityTokensModel->hasCommunityIdToken(shownItem->communityId))
|
||||
m_communityIds.removeAll(shownItem->communityId);
|
||||
}
|
||||
}
|
||||
m_communityTokensModel->setCommunityIds(m_communityIds);
|
||||
m_communityTokensModel->saveCustomSortOrder();
|
||||
rebuildCommunityTokenGroupsModel();
|
||||
}
|
||||
|
||||
void ManageTokensController::showHideGroup(const QString& groupId, bool flag)
|
||||
{
|
||||
if (flag) { // show
|
||||
const auto tokens = m_hiddenTokensModel->takeAllItems(groupId);
|
||||
for (const auto& token: tokens) {
|
||||
m_communityTokensModel->addItem(token);
|
||||
}
|
||||
m_communityIds.append(groupId);
|
||||
} else { // hide
|
||||
const auto tokens = m_communityTokensModel->takeAllItems(groupId);
|
||||
for (const auto& token: tokens) {
|
||||
m_hiddenTokensModel->addItem(token, false /*prepend*/);
|
||||
}
|
||||
m_communityIds.removeAll(groupId);
|
||||
}
|
||||
m_communityTokensModel->setCommunityIds(m_communityIds);
|
||||
m_communityTokensModel->saveCustomSortOrder();
|
||||
rebuildCommunityTokenGroupsModel();
|
||||
}
|
||||
|
||||
void ManageTokensController::saveSettings()
|
||||
{
|
||||
Q_ASSERT(!m_settingsKey.isEmpty());
|
||||
|
||||
// gather the data to save
|
||||
SerializedTokenData result;
|
||||
for (auto model: {m_regularTokensModel, m_communityTokensModel})
|
||||
result.insert(model->save());
|
||||
result.insert(m_hiddenTokensModel->save(false));
|
||||
|
||||
// save to QSettings
|
||||
m_settings.beginGroup(QStringLiteral("ManageTokens-%1").arg(m_settingsKey));
|
||||
m_settings.beginWriteArray(m_settingsKey);
|
||||
SerializedTokenData::const_key_value_iterator it = result.constKeyValueBegin();
|
||||
for (auto i = 0; it != result.constKeyValueEnd() && i < result.size(); it++, i++) {
|
||||
m_settings.setArrayIndex(i);
|
||||
const auto tuple = it->second;
|
||||
m_settings.setValue(QStringLiteral("symbol"), it->first);
|
||||
m_settings.setValue(QStringLiteral("pos"), std::get<0>(tuple));
|
||||
m_settings.setValue(QStringLiteral("visible"), std::get<1>(tuple));
|
||||
m_settings.setValue(QStringLiteral("groupId"), std::get<2>(tuple));
|
||||
}
|
||||
m_settings.endArray();
|
||||
m_settings.endGroup();
|
||||
m_settings.sync();
|
||||
|
||||
// unset dirty
|
||||
for (auto model: m_allModels)
|
||||
model->setDirty(false);
|
||||
}
|
||||
|
||||
void ManageTokensController::clearSettings()
|
||||
{
|
||||
Q_ASSERT(!m_settingsKey.isEmpty());
|
||||
|
||||
// clear the relevant QSettings group
|
||||
m_settings.beginGroup(QStringLiteral("ManageTokens-%1").arg(m_settingsKey));
|
||||
m_settings.remove(QString());
|
||||
m_settings.endGroup();
|
||||
m_settings.sync();
|
||||
}
|
||||
|
||||
void ManageTokensController::loadSettings()
|
||||
{
|
||||
Q_ASSERT(!m_settingsKey.isEmpty());
|
||||
|
||||
m_settingsData.clear();
|
||||
|
||||
// load from QSettings
|
||||
m_settings.beginGroup(QStringLiteral("ManageTokens-%1").arg(m_settingsKey));
|
||||
const auto size = m_settings.beginReadArray(m_settingsKey);
|
||||
for (auto i = 0; i < size; i++) {
|
||||
m_settings.setArrayIndex(i);
|
||||
const auto symbol = m_settings.value(QStringLiteral("symbol")).toString();
|
||||
if (symbol.isEmpty()) {
|
||||
qCWarning(manageTokens) << Q_FUNC_INFO << "Missing symbol while reading tokens settings";
|
||||
continue;
|
||||
}
|
||||
const auto pos = m_settings.value(QStringLiteral("pos"), -1).toInt();
|
||||
const auto visible = m_settings.value(QStringLiteral("visible"), true).toBool();
|
||||
const auto groupId = m_settings.value(QStringLiteral("groupId")).toString();
|
||||
m_settingsData.insert(symbol, {pos, visible, groupId});
|
||||
}
|
||||
m_settings.endArray();
|
||||
m_settings.endGroup();
|
||||
}
|
||||
|
||||
void ManageTokensController::revert()
|
||||
{
|
||||
loadSettings();
|
||||
parseSourceModel();
|
||||
}
|
||||
|
||||
void ManageTokensController::classBegin()
|
||||
{
|
||||
// empty on purpose
|
||||
}
|
||||
|
||||
void ManageTokensController::componentComplete()
|
||||
{
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
void ManageTokensController::setSourceModel(QAbstractItemModel* newSourceModel)
|
||||
{
|
||||
if(m_sourceModel == newSourceModel) return;
|
||||
|
||||
if(!newSourceModel) {
|
||||
disconnect(sourceModel());
|
||||
// clear all the models
|
||||
for (auto model: m_allModels)
|
||||
model->clear();
|
||||
m_communityIds.clear();
|
||||
m_sourceModel = newSourceModel;
|
||||
emit sourceModelChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
m_sourceModel = newSourceModel;
|
||||
|
||||
connect(m_sourceModel, &QAbstractItemModel::modelReset, this, &ManageTokensController::parseSourceModel);
|
||||
|
||||
if (m_sourceModel && m_sourceModel->roleNames().isEmpty()) { // workaround for when a model has no roles and roles are added when the model is populated (ListModel)
|
||||
// QTBUG-57971
|
||||
connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &ManageTokensController::parseSourceModel);
|
||||
return;
|
||||
} else {
|
||||
parseSourceModel();
|
||||
}
|
||||
}
|
||||
|
||||
void ManageTokensController::parseSourceModel()
|
||||
{
|
||||
if (!m_sourceModel)
|
||||
return;
|
||||
|
||||
disconnect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &ManageTokensController::parseSourceModel);
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
QElapsedTimer t;
|
||||
t.start();
|
||||
#endif
|
||||
|
||||
// clear all the models
|
||||
for (auto model: m_allModels)
|
||||
model->clear();
|
||||
m_communityIds.clear();
|
||||
|
||||
// read and transform the original data
|
||||
const auto newSize = m_sourceModel->rowCount();
|
||||
qCInfo(manageTokens) << "!!! PARSING" << newSize << "TOKENS";
|
||||
for (auto i = 0; i < newSize; i++) {
|
||||
addItem(i);
|
||||
}
|
||||
|
||||
// build community groups model
|
||||
rebuildCommunityTokenGroupsModel();
|
||||
reloadCommunityIds();
|
||||
m_communityTokensModel->setCommunityIds(m_communityIds);
|
||||
|
||||
// (pre)sort
|
||||
for (auto model: m_allModels) {
|
||||
model->applySort();
|
||||
model->saveCustomSortOrder();
|
||||
model->setDirty(false);
|
||||
}
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
qCInfo(manageTokens) << "!!! PARSING SOURCE DATA TOOK" << t.nsecsElapsed()/1'000'000.f << "ms";
|
||||
#endif
|
||||
|
||||
emit sourceModelChanged();
|
||||
}
|
||||
|
||||
void ManageTokensController::addItem(int index)
|
||||
{
|
||||
const auto sourceRoleNames = m_sourceModel->roleNames();
|
||||
|
||||
const auto dataForIndex = [&](const QModelIndex &idx, const QByteArray& rolename) -> QVariant {
|
||||
const auto key = sourceRoleNames.key(rolename, -1);
|
||||
if (key == -1)
|
||||
return {};
|
||||
return idx.data(key);
|
||||
};
|
||||
|
||||
const auto srcIndex = m_sourceModel->index(index, 0);
|
||||
const auto symbol = dataForIndex(srcIndex, kSymbolRoleName).toString();
|
||||
const auto communityId = dataForIndex(srcIndex, kCommunityIdRoleName).toString();
|
||||
const auto communityName = dataForIndex(srcIndex, kCommunityNameRoleName).toString();
|
||||
const auto visible = m_settingsData.contains(symbol) ? std::get<1>(m_settingsData.value(symbol)) : true;
|
||||
|
||||
TokenData token;
|
||||
token.symbol = symbol;
|
||||
token.name = dataForIndex(srcIndex, kNameRoleName).toString();
|
||||
token.image = dataForIndex(srcIndex, kTokenImageRoleName).toString();
|
||||
token.communityId = communityId;
|
||||
token.communityName = !communityName.isEmpty() ? communityName : communityId;
|
||||
token.communityImage = dataForIndex(srcIndex, kCommunityImageRoleName).toString();
|
||||
token.collectionUid = dataForIndex(srcIndex, kCollectionUidRoleName).toString();
|
||||
token.collectionName = dataForIndex(srcIndex, kCollectionNameRoleName).toString();
|
||||
token.balance = dataForIndex(srcIndex, kEnabledNetworkBalanceRoleName);
|
||||
token.currencyBalance = dataForIndex(srcIndex, kEnabledNetworkCurrencyBalanceRoleName);
|
||||
|
||||
token.customSortOrderNo = m_settingsData.contains(symbol) ? std::get<0>(m_settingsData.value(symbol))
|
||||
: (visible ? INT_MAX : 0); // append/prepend
|
||||
|
||||
if (!visible)
|
||||
m_hiddenTokensModel->addItem(token, /*append*/ false);
|
||||
else if (!communityId.isEmpty())
|
||||
m_communityTokensModel->addItem(token);
|
||||
else
|
||||
m_regularTokensModel->addItem(token);
|
||||
}
|
||||
|
||||
bool ManageTokensController::dirty() const
|
||||
{
|
||||
return std::any_of(m_allModels.cbegin(), m_allModels.cend(), [](auto model) {
|
||||
return model->dirty();
|
||||
});
|
||||
}
|
||||
|
||||
bool ManageTokensController::arrangeByCommunity() const
|
||||
{
|
||||
return m_arrangeByCommunity;
|
||||
}
|
||||
|
||||
void ManageTokensController::setArrangeByCommunity(bool newArrangeByCommunity)
|
||||
{
|
||||
if(m_arrangeByCommunity == newArrangeByCommunity) return;
|
||||
m_arrangeByCommunity = newArrangeByCommunity;
|
||||
if (!m_arrangeByCommunity)
|
||||
m_communityTokensModel->applySort();
|
||||
else
|
||||
rebuildCommunityTokenGroupsModel();
|
||||
emit arrangeByCommunityChanged();
|
||||
}
|
||||
|
||||
void ManageTokensController::reloadCommunityIds()
|
||||
{
|
||||
m_communityIds.clear();
|
||||
auto model = m_arrangeByCommunity ? m_communityTokenGroupsModel : m_communityTokensModel;
|
||||
const auto count = model->count();
|
||||
for (int i = 0; i < count; i++) {
|
||||
const auto& token = model->itemAt(i);
|
||||
if (!m_communityIds.contains(token.communityId))
|
||||
m_communityIds.append(token.communityId);
|
||||
}
|
||||
qCDebug(manageTokens) << "!!! FOUND UNIQUE COMMUNITY GROUP IDs:" << m_communityIds;
|
||||
}
|
||||
|
||||
void ManageTokensController::rebuildCommunityTokenGroupsModel()
|
||||
{
|
||||
QStringList communityIds;
|
||||
QList<TokenData> result;
|
||||
|
||||
const auto count = m_communityTokensModel->count();
|
||||
for (auto i = 0; i < count; i++) {
|
||||
const auto& communityToken = m_communityTokensModel->itemAt(i);
|
||||
const auto communityId = communityToken.communityId;
|
||||
if (!communityIds.contains(communityId)) { // insert into groups
|
||||
communityIds.append(communityId);
|
||||
|
||||
TokenData tokenGroup;
|
||||
tokenGroup.communityId = communityId;
|
||||
tokenGroup.communityName = communityToken.communityName;
|
||||
tokenGroup.communityImage = communityToken.communityImage;
|
||||
tokenGroup.balance = 1;
|
||||
result.append(tokenGroup);
|
||||
} else { // update group's childCount
|
||||
const auto tokenGroup = std::find_if(result.cbegin(), result.cend(), [communityId](const auto& item) {
|
||||
return communityId == item.communityId;
|
||||
});
|
||||
if (tokenGroup != result.cend()) {
|
||||
const auto row = std::distance(result.cbegin(), tokenGroup);
|
||||
TokenData updTokenGroup = result.takeAt(row);
|
||||
updTokenGroup.balance = updTokenGroup.balance.toInt() + 1;
|
||||
result.insert(row, updTokenGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_communityTokenGroupsModel->clear();
|
||||
for (const auto& group: result)
|
||||
m_communityTokenGroupsModel->addItem(group);
|
||||
|
||||
qCDebug(manageTokens) << "!!! GROUPS MODEL REBUILT WITH GROUPS:" << communityIds;
|
||||
}
|
||||
|
||||
QString ManageTokensController::settingsKey() const
|
||||
{
|
||||
return m_settingsKey;
|
||||
}
|
||||
|
||||
void ManageTokensController::setSettingsKey(const QString& newSettingsKey)
|
||||
{
|
||||
if (m_settingsKey == newSettingsKey)
|
||||
return;
|
||||
m_settingsKey = newSettingsKey;
|
||||
emit settingsKeyChanged();
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
#include <QObject>
|
||||
#include <QQmlParserStatus>
|
||||
#include <QSettings>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "managetokensmodel.h"
|
||||
|
||||
class QAbstractItemModel;
|
||||
|
||||
class ManageTokensController : public QObject, public QQmlParserStatus
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QQmlParserStatus)
|
||||
|
||||
// input properties
|
||||
Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged FINAL REQUIRED)
|
||||
Q_PROPERTY(QString settingsKey READ settingsKey WRITE setSettingsKey NOTIFY settingsKeyChanged FINAL REQUIRED)
|
||||
Q_PROPERTY(bool arrangeByCommunity READ arrangeByCommunity WRITE setArrangeByCommunity NOTIFY arrangeByCommunityChanged FINAL)
|
||||
|
||||
// output properties
|
||||
Q_PROPERTY(QAbstractItemModel* regularTokensModel READ regularTokensModel CONSTANT FINAL)
|
||||
// TODO regularTokenGroupsModel for grouped (collections of) collectibles?
|
||||
Q_PROPERTY(QAbstractItemModel* communityTokensModel READ communityTokensModel CONSTANT FINAL)
|
||||
Q_PROPERTY(QAbstractItemModel* communityTokenGroupsModel READ communityTokenGroupsModel CONSTANT FINAL)
|
||||
Q_PROPERTY(QAbstractItemModel* hiddenTokensModel READ hiddenTokensModel CONSTANT FINAL)
|
||||
Q_PROPERTY(bool dirty READ dirty NOTIFY dirtyChanged FINAL)
|
||||
|
||||
public:
|
||||
explicit ManageTokensController(QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void showHideRegularToken(int row, bool flag);
|
||||
Q_INVOKABLE void showHideCommunityToken(int row, bool flag);
|
||||
Q_INVOKABLE void showHideGroup(const QString& groupId, bool flag);
|
||||
|
||||
Q_INVOKABLE void saveSettings();
|
||||
Q_INVOKABLE void clearSettings();
|
||||
Q_INVOKABLE void revert();
|
||||
|
||||
// TODO: to be used by SFPM on the main wallet page as an "expressionRole"
|
||||
// bool lessThan(lhsSymbol, rhsSymbol) const;
|
||||
// bool filterAcceptsRow(index or symbol?) const;
|
||||
|
||||
protected:
|
||||
void classBegin() override;
|
||||
void componentComplete() override;
|
||||
|
||||
signals:
|
||||
void sourceModelChanged();
|
||||
void dirtyChanged();
|
||||
void arrangeByCommunityChanged();
|
||||
void settingsKeyChanged();
|
||||
|
||||
private:
|
||||
QAbstractItemModel* m_sourceModel{nullptr};
|
||||
QAbstractItemModel* sourceModel() const { return m_sourceModel; }
|
||||
void setSourceModel(QAbstractItemModel* newSourceModel);
|
||||
void parseSourceModel();
|
||||
|
||||
void addItem(int index);
|
||||
|
||||
ManageTokensModel* m_regularTokensModel{nullptr};
|
||||
QAbstractItemModel* regularTokensModel() const { return m_regularTokensModel; };
|
||||
|
||||
ManageTokensModel* m_communityTokensModel{nullptr};
|
||||
QAbstractItemModel* communityTokensModel() const { return m_communityTokensModel; };
|
||||
|
||||
ManageTokensModel* m_communityTokenGroupsModel{nullptr};
|
||||
QAbstractItemModel* communityTokenGroupsModel() const { return m_communityTokenGroupsModel; };
|
||||
|
||||
ManageTokensModel* m_hiddenTokensModel{nullptr};
|
||||
QAbstractItemModel* hiddenTokensModel() const { return m_hiddenTokensModel; };
|
||||
|
||||
bool dirty() const;
|
||||
|
||||
bool m_arrangeByCommunity{false};
|
||||
bool arrangeByCommunity() const;
|
||||
void setArrangeByCommunity(bool newArrangeByCommunity);
|
||||
|
||||
QStringList m_communityIds;
|
||||
void reloadCommunityIds();
|
||||
void rebuildCommunityTokenGroupsModel();
|
||||
|
||||
const std::array<ManageTokensModel*, 4> m_allModels {m_regularTokensModel, m_communityTokensModel, m_communityTokenGroupsModel, m_hiddenTokensModel};
|
||||
|
||||
QString m_settingsKey;
|
||||
QString settingsKey() const;
|
||||
void setSettingsKey(const QString& newSettingsKey);
|
||||
QSettings m_settings;
|
||||
void loadSettings();
|
||||
SerializedTokenData m_settingsData; // symbol -> {sortOrder, visible, groupId}
|
||||
|
||||
bool m_modelConnectionsInitialized{false};
|
||||
};
|
|
@ -0,0 +1,195 @@
|
|||
#include "managetokensmodel.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
Q_LOGGING_CATEGORY(manageTokens, "status.models.manageTokens", QtInfoMsg)
|
||||
|
||||
ManageTokensModel::ManageTokensModel(QObject* parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connect(this, &QAbstractItemModel::rowsInserted, this, &ManageTokensModel::countChanged);
|
||||
connect(this, &QAbstractItemModel::rowsRemoved, this, &ManageTokensModel::countChanged);
|
||||
connect(this, &QAbstractItemModel::modelReset, this, &ManageTokensModel::countChanged);
|
||||
connect(this, &QAbstractItemModel::layoutChanged, this, &ManageTokensModel::countChanged);
|
||||
}
|
||||
|
||||
void ManageTokensModel::moveItem(int fromRow, int toRow)
|
||||
{
|
||||
qCDebug(manageTokens) << Q_FUNC_INFO << "from" << fromRow << "to" << toRow;
|
||||
|
||||
if (toRow < 0 || toRow >= rowCount() || fromRow < 0 || fromRow >= rowCount())
|
||||
return;
|
||||
|
||||
auto destRow = toRow;
|
||||
if (toRow > fromRow)
|
||||
destRow++;
|
||||
|
||||
beginMoveRows({}, fromRow, fromRow, {}, destRow);
|
||||
m_data.move(fromRow, toRow);
|
||||
endMoveRows();
|
||||
setDirty(true);
|
||||
}
|
||||
|
||||
void ManageTokensModel::addItem(const TokenData& item, bool append)
|
||||
{
|
||||
const auto destRow = append ? rowCount() : 0;
|
||||
beginInsertRows({}, destRow, destRow);
|
||||
append ? m_data.append(item) : m_data.prepend(item);
|
||||
endInsertRows();
|
||||
setDirty(true);
|
||||
}
|
||||
|
||||
std::optional<TokenData> ManageTokensModel::takeItem(int row)
|
||||
{
|
||||
if (row < 0 || row >= rowCount())
|
||||
return {};
|
||||
|
||||
beginRemoveRows({}, row, row);
|
||||
auto res = m_data.takeAt(row);
|
||||
endRemoveRows();
|
||||
setDirty(true);
|
||||
return res;
|
||||
}
|
||||
|
||||
QList<TokenData> ManageTokensModel::takeAllItems(const QString& communityId)
|
||||
{
|
||||
QList<TokenData> result;
|
||||
QList<int> indexesToRemove;
|
||||
|
||||
for (int i = 0; i < m_data.count(); i++) {
|
||||
const auto &token = m_data.at(i);
|
||||
if (token.communityId == communityId) {
|
||||
result.append(token);
|
||||
indexesToRemove.append(i);
|
||||
}
|
||||
}
|
||||
|
||||
QList<int>::reverse_iterator its;
|
||||
for(its = indexesToRemove.rbegin(); its != indexesToRemove.rend(); ++its) {
|
||||
const auto row = *its;
|
||||
beginRemoveRows({}, row, row);
|
||||
m_data.removeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
setDirty(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ManageTokensModel::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
m_data.clear();
|
||||
endResetModel();
|
||||
setDirty(false);
|
||||
}
|
||||
|
||||
SerializedTokenData ManageTokensModel::save(bool isVisible)
|
||||
{
|
||||
saveCustomSortOrder();
|
||||
const auto size = count();
|
||||
SerializedTokenData result;
|
||||
for (int i = 0; i < size; i++) {
|
||||
const auto& token = itemAt(i);
|
||||
const auto groupId = !token.communityId.isEmpty() ? token.communityId : token.collectionUid;
|
||||
result.insert(token.symbol, {i, isVisible, groupId});
|
||||
}
|
||||
setDirty(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
int ManageTokensModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return m_data.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ManageTokensModel::roleNames() const
|
||||
{
|
||||
static const QHash<int, QByteArray> roles {
|
||||
{SymbolRole, kSymbolRoleName},
|
||||
{NameRole, kNameRoleName},
|
||||
{CommunityIdRole, kCommunityIdRoleName},
|
||||
{CommunityNameRole, kCommunityNameRoleName},
|
||||
{CommunityImageRole, kCommunityImageRoleName},
|
||||
{CollectionUidRole, kCollectionUidRoleName},
|
||||
{CollectionNameRole, kCollectionNameRoleName},
|
||||
{BalanceRole, kEnabledNetworkBalanceRoleName},
|
||||
{CurrencyBalanceRole, kEnabledNetworkCurrencyBalanceRoleName},
|
||||
{CustomSortOrderNoRole, kCustomSortOrderNoRoleName},
|
||||
{TokenImageRole, kTokenImageRoleName},
|
||||
};
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
QVariant ManageTokensModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::ParentIsInvalid))
|
||||
return {};
|
||||
|
||||
const auto& token = m_data.at(index.row());
|
||||
|
||||
switch(static_cast<TokenDataRoles>(role))
|
||||
{
|
||||
case SymbolRole: return token.symbol;
|
||||
case NameRole: return token.name;
|
||||
case CommunityIdRole: return token.communityId;
|
||||
case CommunityNameRole: return token.communityName;
|
||||
case CommunityImageRole: return token.communityImage;
|
||||
case CollectionUidRole: return token.collectionUid;
|
||||
case CollectionNameRole: return token.collectionName;
|
||||
case BalanceRole: return token.balance;
|
||||
case CurrencyBalanceRole: return token.currencyBalance;
|
||||
case CustomSortOrderNoRole: return token.customSortOrderNo;
|
||||
case TokenImageRole: return token.image;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ManageTokensModel::dirty() const
|
||||
{
|
||||
return m_dirty;
|
||||
}
|
||||
|
||||
void ManageTokensModel::setDirty(bool flag)
|
||||
{
|
||||
if (m_dirty == flag) return;
|
||||
m_dirty = flag;
|
||||
emit dirtyChanged();
|
||||
}
|
||||
|
||||
void ManageTokensModel::saveCustomSortOrder()
|
||||
{
|
||||
const auto count = rowCount();
|
||||
for (auto i = 0; i < count; i++) {
|
||||
TokenData newToken{m_data.at(i)};
|
||||
if (newToken.communityId.isEmpty()) {
|
||||
newToken.customSortOrderNo = i;
|
||||
} else {
|
||||
const auto communityIdx = m_communityIds.indexOf(newToken.communityId) + 1;
|
||||
newToken.customSortOrderNo = i + (communityIdx * 100'000);
|
||||
}
|
||||
m_data[i] = newToken;
|
||||
}
|
||||
emit dataChanged(index(0, 0), index(count - 1, 0), {TokenDataRoles::CustomSortOrderNoRole});
|
||||
}
|
||||
|
||||
void ManageTokensModel::applySort()
|
||||
{
|
||||
emit layoutAboutToBeChanged({}, QAbstractItemModel::VerticalSortHint);
|
||||
|
||||
// clazy:exclude=clazy-detaching-member
|
||||
std::stable_sort(m_data.begin(), m_data.end(), [this](const TokenData& lhs, const TokenData& rhs) {
|
||||
return lhs.customSortOrderNo < rhs.customSortOrderNo;
|
||||
});
|
||||
|
||||
emit layoutChanged({}, QAbstractItemModel::VerticalSortHint);
|
||||
}
|
||||
|
||||
bool ManageTokensModel::hasCommunityIdToken(const QString& communityId) const
|
||||
{
|
||||
return std::any_of(m_data.cbegin(), m_data.constEnd(), [communityId](const auto& token) {
|
||||
return token.communityId == communityId;
|
||||
});
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include <optional>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(manageTokens)
|
||||
|
||||
namespace
|
||||
{
|
||||
const auto kSymbolRoleName = QByteArrayLiteral("symbol");
|
||||
const auto kNameRoleName = QByteArrayLiteral("name");
|
||||
const auto kCommunityIdRoleName = QByteArrayLiteral("communityId");
|
||||
const auto kCommunityNameRoleName = QByteArrayLiteral("communityName");
|
||||
const auto kCommunityImageRoleName = QByteArrayLiteral("communityImage");
|
||||
const auto kCollectionUidRoleName = QByteArrayLiteral("collectionUid");
|
||||
const auto kCollectionNameRoleName = QByteArrayLiteral("collectionName");
|
||||
const auto kEnabledNetworkBalanceRoleName = QByteArrayLiteral("enabledNetworkBalance");
|
||||
const auto kEnabledNetworkCurrencyBalanceRoleName = QByteArrayLiteral("enabledNetworkCurrencyBalance");
|
||||
const auto kCustomSortOrderNoRoleName = QByteArrayLiteral("customSortOrderNo");
|
||||
const auto kTokenImageRoleName = QByteArrayLiteral("imageUrl");
|
||||
} // namespace
|
||||
|
||||
struct TokenData {
|
||||
QString symbol, name, communityId, communityName, communityImage, collectionUid, collectionName, image;
|
||||
QVariant balance, currencyBalance;
|
||||
int customSortOrderNo{-1};
|
||||
};
|
||||
|
||||
// symbol -> {sortOrder, visible, groupId}
|
||||
using SerializedTokenData = QHash<QString, std::tuple<int, bool, QString>>;
|
||||
|
||||
class ManageTokensModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int count READ count NOTIFY countChanged FINAL)
|
||||
Q_PROPERTY(bool dirty READ dirty NOTIFY dirtyChanged FINAL)
|
||||
|
||||
public:
|
||||
enum TokenDataRoles {
|
||||
SymbolRole = Qt::UserRole + 1,
|
||||
NameRole,
|
||||
CommunityIdRole,
|
||||
CommunityNameRole,
|
||||
CommunityImageRole,
|
||||
CollectionUidRole,
|
||||
CollectionNameRole,
|
||||
BalanceRole,
|
||||
CurrencyBalanceRole,
|
||||
CustomSortOrderNoRole,
|
||||
TokenImageRole,
|
||||
};
|
||||
Q_ENUM(TokenDataRoles)
|
||||
|
||||
explicit ManageTokensModel(QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void moveItem(int fromRow, int toRow);
|
||||
|
||||
void addItem(const TokenData& item, bool append = true);
|
||||
std::optional<TokenData> takeItem(int row);
|
||||
QList<TokenData> takeAllItems(const QString& communityId);
|
||||
void clear();
|
||||
|
||||
SerializedTokenData save(bool isVisible = true);
|
||||
|
||||
bool dirty() const;
|
||||
void setDirty(bool flag);
|
||||
|
||||
void saveCustomSortOrder();
|
||||
void applySort();
|
||||
|
||||
int count() const { return rowCount(); }
|
||||
const TokenData& itemAt(int row) const { return m_data.at(row); }
|
||||
|
||||
void setCommunityIds(const QStringList& ids) { m_communityIds = ids; };
|
||||
bool hasCommunityIdToken(const QString& communityId) const;
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
signals:
|
||||
void countChanged();
|
||||
void dirtyChanged();
|
||||
|
||||
private:
|
||||
QStringList m_communityIds;
|
||||
|
||||
bool m_dirty{false};
|
||||
|
||||
QList<TokenData> m_data;
|
||||
};
|
|
@ -9,5 +9,5 @@ ShowcaseDelegate {
|
|||
|
||||
icon.source: hasImage ? showcaseObj.imageUrl : ""
|
||||
bgRadius: Style.current.radius
|
||||
bgColor: !!showcaseObj && !!showcaseObj.backgroundColor ? showcaseObj.backgroundColor : "transparent"
|
||||
assetBgColor: !!showcaseObj && !!showcaseObj.backgroundColor ? showcaseObj.backgroundColor : "transparent"
|
||||
}
|
||||
|
|
|
@ -90,7 +90,6 @@ StatusDraggableListItem {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
|
||||
StatusFlatButton {
|
||||
id: root
|
||||
|
||||
property int currentIndex
|
||||
property int count
|
||||
|
||||
property bool inHidden
|
||||
property bool isGroup
|
||||
property string groupId
|
||||
property bool isCommunityAsset
|
||||
|
||||
readonly property bool hideEnabled: model.symbol !== "ETH"
|
||||
readonly property bool menuVisible: menuLoader.active
|
||||
|
||||
signal moveRequested(int from, int to)
|
||||
signal showHideRequested(int index, bool flag)
|
||||
signal showHideGroupRequested(string groupId, bool flag)
|
||||
|
||||
icon.name: "more"
|
||||
horizontalPadding: 4
|
||||
verticalPadding: 4
|
||||
textColor: hovered || highlighted ? Theme.palette.directColor1 : Theme.palette.baseColor1
|
||||
highlighted: menuLoader.item && menuLoader.item.opened
|
||||
|
||||
onClicked: {
|
||||
menuLoader.active = true
|
||||
menuLoader.item.popup(width - menuLoader.item.width, height)
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: menuLoader
|
||||
active: false
|
||||
sourceComponent: StatusMenu {
|
||||
onClosed: menuLoader.active = false
|
||||
|
||||
StatusAction {
|
||||
enabled: !root.inHidden && root.currentIndex !== 0
|
||||
icon.name: "arrow-top"
|
||||
text: qsTr("Move to top")
|
||||
onTriggered: root.moveRequested(root.currentIndex, 0)
|
||||
}
|
||||
StatusAction {
|
||||
enabled: !root.inHidden && root.currentIndex !== 0
|
||||
icon.name: "arrow-up"
|
||||
text: qsTr("Move up")
|
||||
onTriggered: root.moveRequested(root.currentIndex, root.currentIndex - 1)
|
||||
}
|
||||
StatusAction {
|
||||
enabled: !root.inHidden && root.currentIndex < root.count - 1
|
||||
icon.name: "arrow-down"
|
||||
text: qsTr("Move down")
|
||||
onTriggered: root.moveRequested(root.currentIndex, root.currentIndex + 1)
|
||||
}
|
||||
StatusAction {
|
||||
enabled: !root.inHidden && root.currentIndex < root.count - 1
|
||||
icon.name: "arrow-bottom"
|
||||
text: qsTr("Move to bottom")
|
||||
onTriggered: root.moveRequested(root.currentIndex, root.count - 1)
|
||||
}
|
||||
|
||||
StatusMenuSeparator { enabled: !root.inHidden && root.hideEnabled }
|
||||
|
||||
// any token
|
||||
StatusAction {
|
||||
enabled: !root.inHidden && root.hideEnabled && !root.isGroup && !root.isCommunityAsset
|
||||
type: StatusAction.Type.Danger
|
||||
icon.name: "hide"
|
||||
text: qsTr("Hide asset")
|
||||
onTriggered: root.showHideRequested(root.currentIndex, false)
|
||||
}
|
||||
StatusAction {
|
||||
enabled: root.inHidden
|
||||
icon.name: "show"
|
||||
text: qsTr("Show asset")
|
||||
onTriggered: root.showHideRequested(root.currentIndex, true)
|
||||
}
|
||||
|
||||
// (hide) community tokens
|
||||
StatusMenu {
|
||||
id: communitySubmenu
|
||||
enabled: !root.inHidden && root.isCommunityAsset
|
||||
title: qsTr("Hide")
|
||||
assetSettings.name: "hide"
|
||||
type: StatusAction.Type.Danger
|
||||
|
||||
StatusAction {
|
||||
text: qsTr("This asset")
|
||||
onTriggered: {
|
||||
root.showHideRequested(root.currentIndex, false)
|
||||
communitySubmenu.dismiss()
|
||||
}
|
||||
}
|
||||
StatusAction {
|
||||
text: qsTr("All assets from this community")
|
||||
onTriggered: {
|
||||
root.showHideGroupRequested(root.groupId, false)
|
||||
communitySubmenu.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// token group
|
||||
StatusAction {
|
||||
enabled: !root.inHidden && root.isGroup
|
||||
type: StatusAction.Type.Danger
|
||||
icon.name: "hide"
|
||||
text: qsTr("Hide all assets from this community")
|
||||
onTriggered: root.showHideGroupRequested(root.groupId, false)
|
||||
}
|
||||
StatusAction {
|
||||
enabled: root.inHidden && root.groupId
|
||||
icon.name: "show"
|
||||
text: qsTr("Show all assets from this community")
|
||||
onTriggered: root.showHideGroupRequested(root.groupId, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
ComboBox {
|
||||
id: root
|
||||
|
||||
property int sortOrder: Qt.DescendingOrder
|
||||
readonly property string currentSortRoleName: d.currentSortRoleName
|
||||
|
||||
model: d.predefinedSortModel
|
||||
textRole: "text"
|
||||
valueRole: "value"
|
||||
displayText: !d.isCustomSortOrder ? "%1 %2".arg(currentText).arg(sortOrder === Qt.DescendingOrder ? "↓" : "↑")
|
||||
: currentText
|
||||
|
||||
Component.onCompleted: currentIndex = indexOfValue(SortOrderComboBox.TokenOrderCustom)
|
||||
|
||||
enum TokenOrder {
|
||||
TokenOrderNone = 0,
|
||||
TokenOrderCustom,
|
||||
TokenOrderValue,
|
||||
TokenOrderBalance,
|
||||
TokenOrder1WChange,
|
||||
TokenOrderAlpha
|
||||
}
|
||||
|
||||
horizontalPadding: 12
|
||||
verticalPadding: 8
|
||||
spacing: 8
|
||||
|
||||
font.family: Theme.palette.baseFont.name
|
||||
font.pixelSize: Style.current.additionalTextSize
|
||||
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property int defaultDelegateHeight: 34
|
||||
|
||||
// // models
|
||||
// readonly property SortFilterProxyModel tokensModel: SortFilterProxyModel {
|
||||
// sourceModel: root.baseModel
|
||||
// proxyRoles: [
|
||||
// ExpressionRole {
|
||||
// name: "currentBalance"
|
||||
// expression: model.enabledNetworkBalance.amount
|
||||
// },
|
||||
// ExpressionRole {
|
||||
// name: "currentCurrencyBalance"
|
||||
// expression: model.enabledNetworkCurrencyBalance.amount
|
||||
// }
|
||||
// ]
|
||||
// sorters: RoleSorter {
|
||||
// roleName: cmbTokenOrder.currentSortRoleName
|
||||
// sortOrder: cmbTokenOrder.sortOrder
|
||||
// enabled: !d.isCustomSortOrder
|
||||
// }
|
||||
// filters: ValueFilter {
|
||||
// roleName: "visibleForNetworkWithPositiveBalance"
|
||||
// value: true
|
||||
// }
|
||||
// }
|
||||
|
||||
readonly property var predefinedSortModel: [
|
||||
{ value: SortOrderComboBox.TokenOrderValue, text: qsTr("Token value"), icon: "token-sale", sortRoleName: "currentCurrencyBalance" }, // custom SFPM ExpressionRole
|
||||
{ value: SortOrderComboBox.TokenOrderBalance, text: qsTr("Token balance"), icon: "wallet", sortRoleName: "currentBalance" }, // custom SFPM ExpressionRole
|
||||
{ value: SortOrderComboBox.TokenOrder1WChange, text: qsTr("1W change"), icon: "time", sortRoleName: "changePct24hour" }, // FIXME changePct1Week role missing in backend!!!
|
||||
{ value: SortOrderComboBox.TokenOrderAlpha, text: qsTr("Alphabetic"), icon: "bold", sortRoleName: "name" },
|
||||
{ value: SortOrderComboBox.TokenOrderNone, text: "---", icon: "", sortRoleName: "" },
|
||||
{ value: SortOrderComboBox.TokenOrderCustom, text: qsTr("Custom order"), icon: "exchange", sortRoleName: "" }
|
||||
]
|
||||
readonly property string currentSortRoleName: root.currentIndex !== -1 ? d.predefinedSortModel[root.currentIndex].sortRoleName : ""
|
||||
readonly property bool isCustomSortOrder: root.currentValue === SortOrderComboBox.TokenOrderCustom
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
border.width: 1
|
||||
border.color: Theme.palette.directColor7
|
||||
radius: 8
|
||||
color: root.down ? Theme.palette.baseColor2 : "transparent"
|
||||
HoverHandler {
|
||||
cursorShape: root.enabled ? Qt.PointingHandCursor : undefined
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: StatusBaseText {
|
||||
leftPadding: root.horizontalPadding
|
||||
rightPadding: root.horizontalPadding
|
||||
font.pixelSize: root.font.pixelSize
|
||||
font.weight: Font.Medium
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
text: root.displayText
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
|
||||
indicator: StatusIcon {
|
||||
x: root.mirrored ? root.horizontalPadding : root.width - width - root.horizontalPadding
|
||||
y: root.topPadding + (root.availableHeight - height) / 2
|
||||
width: 16
|
||||
height: width
|
||||
icon: "chevron-down"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||
y: root.height + 4
|
||||
|
||||
implicitWidth: root.width
|
||||
margins: 8
|
||||
|
||||
padding: 1
|
||||
verticalPadding: 8
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.palette.statusSelect.menuItemBackgroundColor
|
||||
radius: 8
|
||||
border.color: Theme.palette.baseColor2
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 4
|
||||
radius: 12
|
||||
samples: 25
|
||||
spread: 0.2
|
||||
color: Theme.palette.dropShadow
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: d.defaultDelegateHeight
|
||||
text: qsTr("Sort by")
|
||||
font.pixelSize: Style.current.tertiaryTextFontSize
|
||||
leftPadding: Style.current.padding
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
StatusListView {
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: contentWidth
|
||||
implicitHeight: contentHeight
|
||||
|
||||
model: root.popup.visible ? root.delegateModel : null
|
||||
currentIndex: root.highlightedIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: regularMenuComponent
|
||||
RowLayout {
|
||||
spacing: root.spacing
|
||||
|
||||
StatusIcon {
|
||||
visible: !!icon
|
||||
icon: iconName
|
||||
color: root.enabled ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
|
||||
width: 16
|
||||
height: 16
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
text: menuText
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
color: root.enabled ? Theme.palette.directColor1 : Theme.palette.baseColor1
|
||||
font.pixelSize: root.font.pixelSize
|
||||
font.weight: root.currentIndex === menuIndex ? Font.DemiBold : Font.Normal
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Row {
|
||||
visible: !isCustomOrder
|
||||
spacing: 4
|
||||
StatusFlatRoundButton {
|
||||
radius: 6
|
||||
width: 24
|
||||
height: 24
|
||||
icon.name: "arrow-up"
|
||||
icon.width: 18
|
||||
icon.height: 18
|
||||
opacity: root.highlightedIndex === menuIndex || highlighted // not "visible, we want the item to stay put
|
||||
highlighted: root.currentIndex === menuIndex && root.sortOrder === Qt.AscendingOrder
|
||||
onClicked: {
|
||||
if (root.currentIndex !== menuIndex)
|
||||
root.currentIndex = menuIndex
|
||||
root.sortOrder = Qt.AscendingOrder
|
||||
root.popup.close()
|
||||
}
|
||||
}
|
||||
StatusFlatRoundButton {
|
||||
radius: 6
|
||||
width: 24
|
||||
height: 24
|
||||
icon.name: "arrow-down"
|
||||
icon.width: 18
|
||||
icon.height: 18
|
||||
opacity: root.highlightedIndex === menuIndex || highlighted // not "visible, we want the item to stay put
|
||||
highlighted: root.currentIndex === menuIndex && root.sortOrder === Qt.DescendingOrder
|
||||
onClicked: {
|
||||
if (root.currentIndex !== menuIndex)
|
||||
root.currentIndex = menuIndex
|
||||
root.sortOrder = Qt.DescendingOrder
|
||||
root.popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: separatorMenuComponent
|
||||
StatusMenuSeparator {}
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
required property int index
|
||||
required property var modelData
|
||||
readonly property bool isSeparator: text === "---"
|
||||
|
||||
id: menuDelegate
|
||||
width: root.width
|
||||
highlighted: root.highlightedIndex === index
|
||||
enabled: !isSeparator
|
||||
leftPadding: isSeparator ? 0 : 14
|
||||
rightPadding: isSeparator ? 0 : 8
|
||||
verticalPadding: isSeparator ? 2 : 5
|
||||
spacing: root.spacing
|
||||
font: root.font
|
||||
text: root.textRole ? (Array.isArray(root.model) ? modelData[root.textRole] : model[root.textRole])
|
||||
: modelData
|
||||
icon.name: modelData["icon"]
|
||||
icon.color: Theme.palette.primaryColor1
|
||||
background: Rectangle {
|
||||
implicitHeight: parent.isSeparator ? 3 : d.defaultDelegateHeight
|
||||
color: {
|
||||
if (menuDelegate.index === root.currentIndex)
|
||||
return Theme.palette.primaryColor3
|
||||
if (menuDelegate.highlighted)
|
||||
return Theme.palette.statusMenu.hoverBackgroundColor
|
||||
|
||||
return "transparent"
|
||||
}
|
||||
HoverHandler {
|
||||
cursorShape: root.enabled ? Qt.PointingHandCursor : undefined
|
||||
}
|
||||
}
|
||||
contentItem: Loader {
|
||||
readonly property int menuIndex: menuDelegate.index
|
||||
readonly property string menuText: menuDelegate.text
|
||||
readonly property string iconName: menuDelegate.icon.name
|
||||
readonly property bool isCustomOrder: !menuDelegate.modelData["sortRoleName"]
|
||||
sourceComponent: menuDelegate.isSeparator ? separatorMenuComponent : regularMenuComponent
|
||||
}
|
||||
onClicked: root.currentIndex = index
|
||||
}
|
||||
}
|
|
@ -4,3 +4,5 @@ AccountHeaderGradient 1.0 AccountHeaderGradient.qml
|
|||
StatusTxProgressBar 1.0 StatusTxProgressBar.qml
|
||||
StatusDateRangePicker 1.0 StatusDateRangePicker.qml
|
||||
ActivityFilterTagItem 1.0 ActivityFilterTagItem.qml
|
||||
SortOrderComboBox 1.0 SortOrderComboBox.qml
|
||||
ManageTokenMenuButton 1.0 ManageTokenMenuButton.qml
|
||||
|
|
|
@ -0,0 +1,378 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
import StatusQ.Models 0.1
|
||||
|
||||
import utils 1.0
|
||||
import shared.controls 1.0
|
||||
|
||||
import AppLayouts.Wallet.controls 1.0
|
||||
|
||||
Control {
|
||||
id: root
|
||||
|
||||
required property var baseModel
|
||||
|
||||
readonly property bool dirty: d.controller.dirty
|
||||
|
||||
background: null
|
||||
|
||||
function saveSettings() {
|
||||
d.controller.saveSettings();
|
||||
}
|
||||
|
||||
function revert() {
|
||||
d.controller.revert();
|
||||
}
|
||||
|
||||
function clearSettings() {
|
||||
d.controller.clearSettings();
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property bool communityGroupsExpanded: true
|
||||
|
||||
readonly property var controller: ManageTokensController {
|
||||
sourceModel: root.baseModel
|
||||
arrangeByCommunity: switchArrangeByCommunity.checked
|
||||
settingsKey: "WalletAssets"
|
||||
}
|
||||
}
|
||||
|
||||
component CommunityTag: InformationTag {
|
||||
tagPrimaryLabel.font.weight: Font.Medium
|
||||
customBackground: Component {
|
||||
Rectangle {
|
||||
color: Theme.palette.baseColor4
|
||||
radius: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component LocalTokenDelegate: DropArea {
|
||||
id: delegateRoot
|
||||
|
||||
property int visualIndex: index
|
||||
property alias dragEnabled: delegate.dragEnabled
|
||||
property alias bgColor: delegate.bgColor
|
||||
property alias topInset: delegate.topInset
|
||||
property alias bottomInset: delegate.bottomInset
|
||||
property bool isGrouped
|
||||
property bool isHidden
|
||||
property int count
|
||||
|
||||
ListView.onRemove: SequentialAnimation {
|
||||
PropertyAction { target: delegateRoot; property: "ListView.delayRemove"; value: true }
|
||||
NumberAnimation { target: delegateRoot; property: "scale"; to: 0; easing.type: Easing.InOutQuad }
|
||||
PropertyAction { target: delegateRoot; property: "ListView.delayRemove"; value: false }
|
||||
}
|
||||
|
||||
width: ListView.view.width
|
||||
height: visible ? delegate.height : 0
|
||||
|
||||
onEntered: function(drag) {
|
||||
var from = drag.source.visualIndex
|
||||
var to = delegate.visualIndex
|
||||
if (to === from)
|
||||
return
|
||||
//console.warn("!!! DROP from/to", from, to)
|
||||
ListView.view.model.moveItem(from, to)
|
||||
drag.accept()
|
||||
}
|
||||
|
||||
StatusDraggableListItem {
|
||||
id: delegate
|
||||
|
||||
visualIndex: index
|
||||
dragParent: root
|
||||
Drag.keys: delegateRoot.keys
|
||||
draggable: true
|
||||
|
||||
width: delegateRoot.width
|
||||
title: model.name// + " (%1 -> %2)".arg(index).arg(model.customSortOrderNo)
|
||||
secondaryTitle: hovered || menuBtn.menuVisible ? "%1 <b>·</b> %2".arg(LocaleUtils.currencyAmountToLocaleString(model.enabledNetworkBalance))
|
||||
.arg(LocaleUtils.currencyAmountToLocaleString(model.enabledNetworkCurrencyBalance))
|
||||
: LocaleUtils.currencyAmountToLocaleString(model.enabledNetworkBalance)
|
||||
hasImage: true
|
||||
icon.source: model.imageUrl || Constants.tokenIcon(model.symbol)
|
||||
icon.width: 32
|
||||
icon.height: 32
|
||||
spacing: 12
|
||||
|
||||
actions: [
|
||||
CommunityTag {
|
||||
tagPrimaryLabel.text: model.communityName
|
||||
visible: !!model.communityId && !delegateRoot.isGrouped
|
||||
image.source: model.communityImage
|
||||
},
|
||||
ManageTokenMenuButton {
|
||||
id: menuBtn
|
||||
currentIndex: visualIndex
|
||||
count: delegateRoot.count
|
||||
inHidden: delegateRoot.isHidden
|
||||
groupId: model.communityId
|
||||
isCommunityAsset: !!model.communityId
|
||||
onMoveRequested: (from, to) => isCommunityAsset ? d.controller.communityTokensModel.moveItem(from, to)
|
||||
: d.controller.regularTokensModel.moveItem(from, to)
|
||||
onShowHideRequested: (index, flag) => isCommunityAsset ? d.controller.showHideCommunityToken(index, flag)
|
||||
: d.controller.showHideRegularToken(index, flag)
|
||||
onShowHideGroupRequested: (groupId, flag) => d.controller.showHideGroup(groupId, flag)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
component LocalTokenGroupDelegate: DropArea {
|
||||
id: communityDelegateRoot
|
||||
|
||||
property int visualIndex: index
|
||||
readonly property string communityId: model.communityId
|
||||
readonly property int childCount: model.enabledNetworkBalance // NB using "balance" as "count" in m_communityTokenGroupsModel
|
||||
|
||||
ListView.onRemove: SequentialAnimation {
|
||||
PropertyAction { target: communityDelegateRoot; property: "ListView.delayRemove"; value: true }
|
||||
NumberAnimation { target: communityDelegateRoot; property: "scale"; to: 0; easing.type: Easing.InOutQuad }
|
||||
PropertyAction { target: communityDelegateRoot; property: "ListView.delayRemove"; value: false }
|
||||
}
|
||||
|
||||
keys: ["x-status-draggable-community-group-item"]
|
||||
visible: childCount
|
||||
width: ListView.view.width
|
||||
height: visible ? groupedCommunityTokenDelegate.implicitHeight : 0
|
||||
|
||||
onEntered: function(drag) {
|
||||
var from = drag.source.visualIndex
|
||||
var to = groupedCommunityTokenDelegate.visualIndex
|
||||
if (to === from)
|
||||
return
|
||||
//console.warn("!!! DROP GROUP from/to", from, to)
|
||||
ListView.view.model.moveItem(from, to)
|
||||
drag.accept()
|
||||
}
|
||||
|
||||
StatusDraggableListItem {
|
||||
id: groupedCommunityTokenDelegate
|
||||
width: parent.width
|
||||
height: dragActive ? implicitHeight : parent.height
|
||||
leftPadding: Style.current.halfPadding
|
||||
rightPadding: Style.current.halfPadding
|
||||
bottomPadding: Style.current.halfPadding
|
||||
topPadding: 22
|
||||
draggable: true
|
||||
spacing: 12
|
||||
bgColor: Theme.palette.baseColor4
|
||||
|
||||
visualIndex: index
|
||||
dragParent: root
|
||||
Drag.keys: communityDelegateRoot.keys
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 12
|
||||
Layout.rightMargin: 12
|
||||
Layout.bottomMargin: 14
|
||||
spacing: groupedCommunityTokenDelegate.spacing
|
||||
|
||||
StatusIcon {
|
||||
Layout.preferredWidth: 20
|
||||
Layout.preferredHeight: 20
|
||||
icon: "justify"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
|
||||
StatusRoundedImage {
|
||||
radius: groupedCommunityTokenDelegate.bgRadius
|
||||
Layout.preferredWidth: 32
|
||||
Layout.preferredHeight: 32
|
||||
image.source: model.communityImage
|
||||
showLoadingIndicator: true
|
||||
image.fillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
text: model.communityName// + "(%1 -> %2)".arg(index).arg(model.customSortOrderNo)
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.leftMargin: -parent.spacing/2
|
||||
text: "<b>·</b> %1".arg(qsTr("%n asset(s)", "", communityDelegateRoot.childCount))
|
||||
elide: Text.ElideRight
|
||||
color: Theme.palette.baseColor1
|
||||
maximumLineCount: 1
|
||||
visible: !d.communityGroupsExpanded
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
ManageTokenMenuButton {
|
||||
currentIndex: visualIndex
|
||||
count: d.controller.communityTokenGroupsModel.count
|
||||
isGroup: true
|
||||
groupId: model.communityId
|
||||
onMoveRequested: (from, to) => d.controller.communityTokenGroupsModel.moveItem(from, to)
|
||||
onShowHideGroupRequested: (groupId, flag) => d.controller.showHideGroup(groupId, flag)
|
||||
}
|
||||
}
|
||||
|
||||
StatusListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: contentHeight
|
||||
model: d.controller.communityTokensModel
|
||||
interactive: false
|
||||
visible: d.communityGroupsExpanded
|
||||
|
||||
displaced: Transition {
|
||||
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
|
||||
}
|
||||
|
||||
delegate: LocalTokenDelegate {
|
||||
isGrouped: true
|
||||
count: communityDelegateRoot.childCount
|
||||
dragEnabled: count > 1
|
||||
keys: ["x-status-draggable-community-token-item-%1".arg(model.communityId)]
|
||||
bgColor: Theme.palette.indirectColor4
|
||||
topInset: 2 // tighter "spacing"
|
||||
bottomInset: 2
|
||||
visible: communityDelegateRoot.communityId === model.communityId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: Style.current.padding
|
||||
|
||||
StatusListView {
|
||||
Layout.fillWidth: true
|
||||
model: d.controller.regularTokensModel
|
||||
implicitHeight: contentHeight
|
||||
interactive: false
|
||||
|
||||
displaced: Transition {
|
||||
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
|
||||
}
|
||||
|
||||
delegate: LocalTokenDelegate {
|
||||
count: d.controller.regularTokensModel.count
|
||||
dragEnabled: count > 1
|
||||
keys: ["x-status-draggable-token-item"]
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: communityTokensHeader
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.current.padding
|
||||
visible: d.controller.communityTokensModel.count
|
||||
StatusBaseText {
|
||||
color: Theme.palette.baseColor1
|
||||
text: qsTr("Community")// + " -> %1".arg(switchArrangeByCommunity.checked ? d.controller.communityTokenGroupsModel.count : d.controller.communityTokensModel.count)
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
StatusSwitch {
|
||||
LayoutMirroring.enabled: true
|
||||
LayoutMirroring.childrenInherit: true
|
||||
id: switchArrangeByCommunity
|
||||
textColor: Theme.palette.baseColor1
|
||||
text: qsTr("Arrange by community")
|
||||
}
|
||||
}
|
||||
|
||||
StatusModalDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: -Style.current.halfPadding
|
||||
visible: communityTokensHeader.visible && switchArrangeByCommunity.checked
|
||||
}
|
||||
|
||||
StatusLinkText {
|
||||
Layout.alignment: Qt.AlignTrailing
|
||||
visible: communityTokensHeader.visible && switchArrangeByCommunity.checked
|
||||
text: d.communityGroupsExpanded ? qsTr("Collapse all") : qsTr("Expand all")
|
||||
normalColor: linkColor
|
||||
font.weight: Font.Normal
|
||||
onClicked: d.communityGroupsExpanded = !d.communityGroupsExpanded
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
active: d.controller.communityTokensModel.count
|
||||
visible: active
|
||||
sourceComponent: switchArrangeByCommunity.checked ? cmpCommunityTokenGroups : cmpCommunityTokens
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.current.padding
|
||||
color: Theme.palette.baseColor1
|
||||
text: qsTr("Hidden")// + " -> %1".arg(d.controller.hiddenTokensModel.count)
|
||||
visible: d.controller.hiddenTokensModel.count
|
||||
}
|
||||
|
||||
StatusListView {
|
||||
Layout.fillWidth: true
|
||||
model: d.controller.hiddenTokensModel
|
||||
implicitHeight: contentHeight
|
||||
interactive: false
|
||||
|
||||
displaced: Transition {
|
||||
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
|
||||
}
|
||||
|
||||
delegate: LocalTokenDelegate {
|
||||
dragEnabled: false
|
||||
keys: ["x-status-draggable-none"]
|
||||
isHidden: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: cmpCommunityTokens
|
||||
StatusListView {
|
||||
model: d.controller.communityTokensModel
|
||||
implicitHeight: contentHeight
|
||||
interactive: false
|
||||
|
||||
displaced: Transition {
|
||||
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
|
||||
}
|
||||
|
||||
delegate: LocalTokenDelegate {
|
||||
count: d.controller.communityTokensModel.count
|
||||
dragEnabled: count > 1
|
||||
keys: ["x-status-draggable-community-token-item"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: cmpCommunityTokenGroups
|
||||
StatusListView {
|
||||
model: d.controller.communityTokenGroupsModel
|
||||
implicitHeight: contentHeight
|
||||
interactive: false
|
||||
spacing: Style.current.halfPadding
|
||||
|
||||
displaced: Transition {
|
||||
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
|
||||
}
|
||||
|
||||
delegate: LocalTokenGroupDelegate {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,3 +2,4 @@ WalletHeader 1.0 WalletHeader.qml
|
|||
WalletTxProgressBlock 1.0 WalletTxProgressBlock.qml
|
||||
WalletNftPreview 1.0 WalletNftPreview.qml
|
||||
ActivityFilterPanel 1.0 ActivityFilterPanel.qml
|
||||
ManageTokensPanel 1.0 ManageTokensPanel.qml
|
||||
|
|
|
@ -11,8 +11,8 @@ import utils 1.0
|
|||
Control {
|
||||
id: root
|
||||
|
||||
property alias image : image
|
||||
property alias iconAsset : iconAsset
|
||||
property alias image: image
|
||||
property alias iconAsset: iconAsset
|
||||
property alias tagPrimaryLabel: tagPrimaryLabel
|
||||
property alias tagSecondaryLabel: tagSecondaryLabel
|
||||
property alias middleLabel: middleLabel
|
||||
|
@ -31,63 +31,54 @@ Control {
|
|||
|
||||
QtObject {
|
||||
id: d
|
||||
property var loadingComponent: Component { LoadingComponent {}}
|
||||
property var loadingComponent: Component { LoadingComponent {} }
|
||||
}
|
||||
|
||||
horizontalPadding: Style.current.halfPadding
|
||||
verticalPadding: 5
|
||||
horizontalPadding: 12
|
||||
verticalPadding: 8
|
||||
spacing: 4
|
||||
|
||||
background: Loader {
|
||||
sourceComponent: root.loading ? d.loadingComponent : root.customBackground
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: 4
|
||||
spacing: root.spacing
|
||||
visible: !root.loading
|
||||
// FIXME this could be StatusIcon but it can't load images from an arbitrary URL
|
||||
Image {
|
||||
id: image
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.maximumWidth: visible ? 16 : 0
|
||||
Layout.maximumHeight: visible ? 16 : 0
|
||||
visible: image.source !== ""
|
||||
visible: !!source
|
||||
}
|
||||
StatusIcon {
|
||||
id: iconAsset
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.maximumWidth: visible ? 16 : 0
|
||||
Layout.maximumHeight: visible ? 16 : 0
|
||||
visible: iconAsset.icon !== ""
|
||||
visible: !!icon
|
||||
}
|
||||
StatusBaseText {
|
||||
id: tagPrimaryLabel
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: Style.current.tertiaryTextFontSize
|
||||
font.weight: Font.Normal
|
||||
color: Theme.palette.directColor1
|
||||
visible: text !== ""
|
||||
}
|
||||
StatusBaseText {
|
||||
id: middleLabel
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: Style.current.tertiaryTextFontSize
|
||||
font.weight: Font.Normal
|
||||
color: Theme.palette.baseColor1
|
||||
visible: text !== ""
|
||||
}
|
||||
StatusBaseText {
|
||||
id: tagSecondaryLabel
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.maximumWidth: root.secondarylabelMaxWidth
|
||||
font.pixelSize: Style.current.tertiaryTextFontSize
|
||||
font.weight: Font.Normal
|
||||
color: Theme.palette.baseColor1
|
||||
visible: text !== ""
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
Loader {
|
||||
id: rightComponent
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue