feat: Manage Collectibles panel UI component & model

- implements a QML panel to organize collectibles
- factors out the delegates to separate files to be reusable with the
Assets tab
- adds QML tests to assess the UI functionality (move, show/hide, save/load)
- does not cover the problematic "Arrange by collection" switch (TBD
later)

Fixes: https://github.com/status-im/status-desktop/issues/12379
This commit is contained in:
Lukáš Tinkl 2023-11-09 12:21:56 +01:00 committed by Lukáš Tinkl
parent 48c9e372dc
commit ba30afd202
19 changed files with 1240 additions and 234 deletions

View File

@ -0,0 +1,87 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import StatusQ 0.1
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
ManageCollectiblesModel {
id: collectiblesModel
}
StatusScrollView { // wrapped in a ScrollView on purpose; to simulate SettingsContentBase.qml
SplitView.fillWidth: true
SplitView.fillHeight: true
ManageCollectiblesPanel {
id: showcasePanel
width: 500
baseModel: ctrlEmptyModel.checked ? null : collectiblesModel
}
}
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 {
enabled: false
text: "Random data (TODO)"
onClicked: {
collectiblesModel.clear()
collectiblesModel.randomizeData()
}
}
Button {
text: "Clear settings"
onClicked: showcasePanel.clearSettings()
}
Switch {
id: ctrlEmptyModel
text: "Empty model"
}
}
}
}
// category: Panels
// https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?type=design&node-id=19341-250476&mode=design&t=jR53lJ7aDzVHE4hZ-0
// https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?type=design&node-id=19655-204534&mode=design&t=jR53lJ7aDzVHE4hZ-0
// https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?type=design&node-id=19622-173583&mode=design&t=jR53lJ7aDzVHE4hZ-0
// https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?type=design&node-id=19622-179146&mode=design&t=jR53lJ7aDzVHE4hZ-0

View File

@ -1,5 +1,6 @@
#include <QtQuickTest/quicktest.h>
#include <QQmlEngine>
#include <QtQuickTest>
#include "src/TextUtils.h"
class Setup : public QObject
@ -21,6 +22,8 @@ public slots:
engine->addImportPath(path);
qmlRegisterSingletonType<TextUtils>("TextUtils", 1, 0, "TextUtils", &TextUtils::qmlInstance);
QStandardPaths::setTestModeEnabled(true);
}
};

View File

@ -0,0 +1,374 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtTest 1.15
import AppLayouts.Wallet.panels 1.0
import Storybook 1.0
import Models 1.0
Item {
id: root
width: 600
height: 400
ManageCollectiblesModel {
id: collectiblesModel
}
Component {
id: componentUnderTest
ManageCollectiblesPanel {
id: showcasePanel
width: 500
baseModel: collectiblesModel
}
}
TestCase {
name: "ManageCollectiblesPanel"
when: windowShown
property ManageCollectiblesPanel controlUnderTest: null
function findDelegateIndexWithTitle(listview, title) {
waitForItemPolished(listview)
const count = listview.count
for (let i = 0; i < count; i++) {
const item = listview.itemAtIndex(i)
if (!!item && item.visible && item.title === title)
return i
}
return -1
}
function findDelegateMenuAction(listview, index, actionName, isGroup=false) {
const token = findChild(listview, "manageTokens%2Delegate-%1".arg(index).arg(isGroup ? "Group" : ""))
verify(!!token)
const delegateBtn = findChild(token, "btnManageTokenMenu-%1".arg(index))
verify(!!delegateBtn)
mouseClick(delegateBtn)
const btnMenuLoader = findChild(delegateBtn, "manageTokensContextMenuLoader")
verify(!!btnMenuLoader)
tryCompare(btnMenuLoader, "active", true)
const btnMenu = btnMenuLoader.item
verify(!!btnMenu)
verify(btnMenu.open)
return findChild(btnMenu, actionName)
}
function triggerDelegateMenuAction(listview, index, actionName, isGroup=false) {
const action = findDelegateMenuAction(listview, index, actionName, isGroup)
verify(!!action)
action.trigger()
}
function init() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
}
function test_showHideToken() {
verify(!controlUnderTest.dirty)
const lvHidden = findChild(controlUnderTest, "lvHiddenTokens")
verify(!!lvHidden)
verify(lvHidden.count === 0)
const lvRegular = findChild(controlUnderTest, "lvRegularTokens")
verify(!!lvRegular)
const lvRegularCount = lvRegular.count
verify(lvRegularCount === 6)
const delegate0 = findChild(lvRegular, "manageTokensDelegate-0")
verify(!!delegate0)
const title = delegate0.title
triggerDelegateMenuAction(lvRegular, 0, "miHideToken")
verify(controlUnderTest.dirty)
// verify we now have +1 hidden and -1 regular tokens after the "hide" operation
waitForItemPolished(lvHidden)
tryCompare(lvHidden, "count", 1)
tryCompare(lvRegular, "count", lvRegularCount-1)
// verify it's the same item we've just hidden
const hiddenToken = findChild(lvHidden, "manageTokensDelegate-0")
compare(hiddenToken.title, title)
// trigger the "show" action
triggerDelegateMenuAction(lvHidden, 0, "miShowToken")
// verify the counts are back to original
waitForItemPolished(lvHidden)
compare(lvHidden.count, 0)
compare(lvRegular.count, lvRegularCount)
// verify we got appended to the regular list by checking we have the same title of the delegate
const delegateN = findChild(lvRegular, "manageTokensDelegate-%1".arg(lvRegular.count-1))
verify(!!delegateN)
const titleN = delegateN.title
compare(title, titleN)
verify(controlUnderTest.dirty)
}
function test_showHideCommunityGroup() {
verify(!controlUnderTest.dirty)
const loaderCommunityTokens = findChild(controlUnderTest, "loaderCommunityTokens")
verify(!!loaderCommunityTokens)
tryCompare(loaderCommunityTokens, "active", true)
const switchArrangeByCommunity = findChild(controlUnderTest, "switchArrangeByCommunity")
verify(!!switchArrangeByCommunity)
switchArrangeByCommunity.toggle()
const lvCommunityTokenGroups = findChild(loaderCommunityTokens, "lvCommunityTokenGroups")
verify(!!lvCommunityTokenGroups)
// verify we have 2 community collectible groups
tryCompare(lvCommunityTokenGroups, "count", 2)
triggerDelegateMenuAction(lvCommunityTokenGroups, 0, "miHideTokenGroup", true)
verify(controlUnderTest.dirty)
// verify we have one less group
waitForItemPolished(lvCommunityTokenGroups)
tryCompare(lvCommunityTokenGroups, "count", 1)
const lvHidden = findChild(controlUnderTest, "lvHiddenTokens")
verify(!!lvHidden)
tryCompare(lvHidden, "count", 4) // we've just hidden 4 collectibles coming from this group
verify(controlUnderTest.dirty)
// verify hidden items are not draggable
const hiddenToken = findChild(lvHidden, "manageTokensDelegate-0")
verify(!!hiddenToken)
compare(hiddenToken.dragEnabled, false)
const hiddenDraggable = findChild(hiddenToken, "draggableDelegate")
verify(!!hiddenDraggable)
mousePress(hiddenToken)
tryCompare(hiddenDraggable, "dragActive", false)
mouseRelease(hiddenToken)
// now show one of the 4 hidden tokens
waitForItemPolished(lvHidden)
triggerDelegateMenuAction(lvHidden, 0, "miShowToken")
verify(controlUnderTest.dirty)
// verify we again have 2 community groups, and one less hidden token
tryCompare(lvCommunityTokenGroups, "count", 2)
tryCompare(lvHidden, "count", 3)
verify(controlUnderTest.dirty)
// now mass show tokens from this group, verify we have 0 hidden tokens and 2 visible groups
triggerDelegateMenuAction(lvHidden, 0, "miShowTokenGroup")
waitForItemPolished(lvHidden)
tryCompare(lvHidden, "count", 0)
tryCompare(lvCommunityTokenGroups, "count", 2)
verify(controlUnderTest.dirty)
}
function test_dnd() {
verify(!controlUnderTest.dirty)
const lvRegular = findChild(controlUnderTest, "lvRegularTokens")
verify(!!lvRegular)
verify(lvRegular.count !== 0)
const delegate0 = findChild(lvRegular, "manageTokensDelegate-0")
verify(!!delegate0)
const title0 = delegate0.title
verify(!!title0)
const title1 = findChild(lvRegular, "manageTokensDelegate-1").title
verify(!!title1)
// DND one item down (~80px in height)
mouseDrag(delegate0, delegate0.width/2, delegate0.height/2, 0, 80)
// cross compare the titles
tryCompare(findChild(lvRegular, "manageTokensDelegate-0"), "title", title1)
tryCompare(findChild(lvRegular, "manageTokensDelegate-1"), "title", title0)
verify(controlUnderTest.dirty)
}
function test_group_dnd() {
verify(!controlUnderTest.dirty)
const switchArrangeByCommunity = findChild(controlUnderTest, "switchArrangeByCommunity")
verify(!!switchArrangeByCommunity)
mouseClick(switchArrangeByCommunity)
const switchCollapseCommunityGroups = findChild(controlUnderTest, "switchCollapseCommunityGroups")
verify(!!switchCollapseCommunityGroups)
mouseClick(switchCollapseCommunityGroups)
const loaderCommunityTokens = findChild(controlUnderTest, "loaderCommunityTokens")
verify(!!loaderCommunityTokens)
tryCompare(loaderCommunityTokens, "active", true)
const lvCommunityTokenGroups = findChild(loaderCommunityTokens, "lvCommunityTokenGroups")
verify(!!lvCommunityTokenGroups)
waitForItemPolished(lvCommunityTokenGroups)
tryCompare(lvCommunityTokenGroups, "count", 2)
const group0 = findChild(lvCommunityTokenGroups, "manageTokensGroupDelegate-0")
const title0 = group0.title
verify(!!title0)
const title1 = findChild(lvCommunityTokenGroups, "manageTokensGroupDelegate-1").title
verify(!!title1)
verify(title0 !== title1)
// DND one group down (~80px in height)
mouseDrag(group0, group0.width/2, group0.height/2, 0, 80)
// cross compare the titles
tryCompare(findChild(lvCommunityTokenGroups, "manageTokensGroupDelegate-0"), "title", title1)
tryCompare(findChild(lvCommunityTokenGroups, "manageTokensGroupDelegate-1"), "title", title0)
verify(controlUnderTest.dirty)
}
function test_group_move_hide_show_community_token() {
verify(!controlUnderTest.dirty)
const titleToTest = "Bearz"
const switchArrangeByCommunity = findChild(controlUnderTest, "switchArrangeByCommunity")
verify(!!switchArrangeByCommunity)
mouseClick(switchArrangeByCommunity)
const loaderCommunityTokens = findChild(controlUnderTest, "loaderCommunityTokens")
verify(!!loaderCommunityTokens)
tryCompare(loaderCommunityTokens, "active", true)
const lvCommunityTokenGroups = findChild(loaderCommunityTokens, "lvCommunityTokenGroups")
verify(!!lvCommunityTokenGroups)
waitForItemPolished(lvCommunityTokenGroups)
tryCompare(lvCommunityTokenGroups, "count", 2)
// get the "Bearz" group at index 1
var bearzGroupTokenDelegate = findChild(lvCommunityTokenGroups, "manageTokensGroupDelegate-1")
const bearzTitle = bearzGroupTokenDelegate.title
compare(bearzTitle, titleToTest)
verify(!!bearzGroupTokenDelegate)
waitForItemPolished(bearzGroupTokenDelegate)
// get the Bearz child listview
const bearzChildLV = findChild(bearzGroupTokenDelegate, "manageTokensGroupListView")
verify(!!bearzChildLV)
// find the 2385 delegate from the Bearz group and hide it
const bear2385DelegateIdx = findDelegateIndexWithTitle(bearzChildLV, "KILLABEAR #2385")
verify(bear2385DelegateIdx !== -1)
triggerDelegateMenuAction(bearzChildLV, bear2385DelegateIdx, "miHideCommunityToken")
verify(controlUnderTest.dirty)
// verify the hidden section now has 1 item and it's the one we just hid
const lvHidden = findChild(controlUnderTest, "lvHiddenTokens")
verify(!!lvHidden)
waitForItemPolished(lvHidden)
verify(lvHidden.count === 1)
tryCompare(findChild(lvHidden, "manageTokensDelegate-0"), "title", "KILLABEAR #2385")
// now move the Bearz group up so that it's first (ends up at index 0)
waitForItemPolished(controlUnderTest)
triggerDelegateMenuAction(lvCommunityTokenGroups, 1, "miMoveUp", true)
verify(controlUnderTest.dirty)
bearzGroupTokenDelegate = findChild(lvCommunityTokenGroups, "manageTokensGroupDelegate-0")
verify(!!bearzGroupTokenDelegate)
// get one of the other group's (Pandas) tokens and hide it
const pandasGroupTokenDelegate = findChild(lvCommunityTokenGroups, "manageTokensGroupDelegate-1")
verify(!!pandasGroupTokenDelegate)
const pandasChildLV = findChild(pandasGroupTokenDelegate, "manageTokensGroupListView")
verify(!!pandasChildLV)
const panda909DelegateIdx = findDelegateIndexWithTitle(pandasChildLV, "Frenly Panda #909")
triggerDelegateMenuAction(pandasChildLV, panda909DelegateIdx, "miHideCommunityToken")
verify(controlUnderTest.dirty)
// finally verify that the Bearz group is still at top
waitForItemPolished(lvCommunityTokenGroups)
tryCompare(findChild(lvCommunityTokenGroups, "manageTokensGroupDelegate-0"), "title", titleToTest)
}
function test_moveOperations() {
verify(!controlUnderTest.dirty)
const lvRegular = findChild(controlUnderTest, "lvRegularTokens")
verify(!!lvRegular)
verify(lvRegular.count !== 0)
var delegate0 = findChild(lvRegular, "manageTokensDelegate-0")
verify(!!delegate0)
const title = delegate0.title
// verify moveUp and moveToTop is not available for the first item
const moveUpAction = findDelegateMenuAction(lvRegular, 0, "miMoveUp")
tryCompare(moveUpAction, "enabled", false)
const moveTopAction = findDelegateMenuAction(lvRegular, 0, "miMoveToTop")
tryCompare(moveTopAction, "enabled", false)
// trigger move to bottom
triggerDelegateMenuAction(lvRegular, 0, "miMoveToBottom")
waitForItemPolished(lvRegular)
verify(controlUnderTest.dirty)
// verify the previous first and current last are actually the same item
const delegateN = findChild(lvRegular, "manageTokensDelegate-%1".arg(lvRegular.count-1))
verify(!!delegateN)
const titleN = delegateN.title
compare(title, titleN)
// verify move down and to bottom is not available for the last item
const moveDownAction = findDelegateMenuAction(lvRegular, lvRegular.count-1, "miMoveDown")
tryCompare(moveDownAction, "enabled", false)
const moveBottomAction = findDelegateMenuAction(lvRegular, lvRegular.count-1, "miMoveToBottom")
tryCompare(moveBottomAction, "enabled", false)
// trigger move to top and verify we got the same title (item) again
triggerDelegateMenuAction(lvRegular, lvRegular.count-1, "miMoveToTop")
waitForItemPolished(lvRegular)
tryCompare(findChild(lvRegular, "manageTokensDelegate-0"), "title", title)
// trigger move down and verify we got the same title (item) again
triggerDelegateMenuAction(lvRegular, 0, "miMoveDown")
tryCompare(findChild(lvRegular, "manageTokensDelegate-1"), "title", title)
// trigger move up and verify we got the same title (item) again
triggerDelegateMenuAction(lvRegular, 1, "miMoveUp")
tryCompare(findChild(lvRegular, "manageTokensDelegate-0"), "title", title)
}
function test_saveLoad() {
// start with clear settings
controlUnderTest.clearSettings()
controlUnderTest.revert()
verify(!controlUnderTest.dirty)
const titleToTest = "Big Kitty"
const lvRegular = findChild(controlUnderTest, "lvRegularTokens")
verify(!!lvRegular)
const bigKittyIndex = findDelegateIndexWithTitle(lvRegular, titleToTest)
verify(bigKittyIndex !== -1)
const title0 = findChild(lvRegular, "manageTokensDelegate-0").title
verify(!!title0)
verify(title0 !== titleToTest)
// trigger move to top and verify we got the correct title
triggerDelegateMenuAction(lvRegular, bigKittyIndex, "miMoveToTop")
waitForItemPolished(lvRegular)
tryCompare(findChild(lvRegular, "manageTokensDelegate-0"), "title", titleToTest)
// save
verify(controlUnderTest.dirty)
controlUnderTest.saveSettings()
verify(!controlUnderTest.dirty)
// load the settings and check BigKitty is still on top
controlUnderTest.revert()
verify(!controlUnderTest.dirty)
waitForItemPolished(lvRegular)
tryCompare(findChild(lvRegular, "manageTokensDelegate-0"), "title", titleToTest)
}
}
}

View File

@ -0,0 +1,153 @@
import QtQuick 2.15
import QtQml.Models 2.15
import Models 1.0
ListModel {
function randomizeData() {
// TODO
}
readonly property var data: [
{
uid: "fp#9140",
name: "Frenly Panda #9140",
collectionUid: "",
collectionName: "",
communityId: "fpan",
communityName: "Frenly Pandas",
communityImage: "https://pbs.twimg.com/profile_images/1599347398769143808/C6qG3RQv_400x400.jpg",
imageUrl: "https://i.seadn.io/gae/qPfQjj4P1w0xVQXAmQJLmQ4ZtLFAJU6oiH69Lsny82LFbipLAgXhHKrcLBx2U09SmRnzeHY0ygz-3NIb-JegE_hWrZquFeL-qUPXPdw",
isLoading: false,
backgroundColor: "pink"
},
{
uid: "123",
name: "Punx not dead!",
collectionUid: "",
collectionName: "",
communityId: "",
communityName: "",
imageUrl: ModelsData.collectibles.cryptoPunks,
isLoading: false,
backgroundColor: ""
},
{
uid: "pp23",
name: "pepepunk#23",
collectionUid: "pepepunks",
collectionName: "",
communityId: "",
communityName: "",
imageUrl: "https://i.seadn.io/s/raw/files/ba2811bb5cd0bed67529d69fa92ef5aa.jpg?auto=format&dpr=1&w=1000",
isLoading: false,
backgroundColor: ""
},
{
uid: "34545656768",
name: "Kitty 1",
collectionUid: "KT",
collectionName: "Kitties",
communityId: "",
communityName: "",
imageUrl: ModelsData.collectibles.kitty1Big,
isLoading: false,
backgroundColor: ""
},
{
uid: "123456",
name: "Kitty 2",
collectionUid: "KT",
collectionName: "Kitties",
communityId: "",
communityName: "",
imageUrl: ModelsData.collectibles.kitty2Big,
isLoading: false,
backgroundColor: ""
},
{
uid: "12345645459537432",
name: "Big Kitty",
collectionUid: "KT",
collectionName: "Kitties",
communityId: "",
communityName: "",
imageUrl: ModelsData.collectibles.kitty3Big,
isLoading: false,
backgroundColor: ""
},
{
uid: "691",
name: "KILLABEAR #691",
collectionUid: "",
collectionName: "",
communityId: "bbrz",
communityName: "Bearz",
communityImage: "https://i.seadn.io/gcs/files/4a875f997063f4f3772190852c1c44f0.png?w=128&auto=format",
imageUrl: "https://assets.killabears.com/content/killabears/gif/691-e81f892696a8ae700e0dbc62eb072060679a2046d1ef5eb2671bdb1fad1f68e3.gif",
isLoading: true,
backgroundColor: "navy"
},
{
uid: "8876",
name: "KILLABEAR #2385",
collectionUid: "",
collectionName: "",
communityId: "bbrz",
communityName: "Bearz",
communityImage: "https://i.seadn.io/gcs/files/4a875f997063f4f3772190852c1c44f0.png?w=128&auto=format",
imageUrl: "https://assets.killabears.com/content/killabears/transparent-512/2385-86ba13cc6945ed0aea7c32a363a96be2f218898358745ae07b947452cb7e4e79.png",
isLoading: false,
backgroundColor: "pink"
},
{
uid: "fp#3195",
name: "Frenly Panda #3195",
collectionUid: "",
collectionName: "",
communityId: "fpan",
communityName: "Frenly Pandas",
communityImage: "https://pbs.twimg.com/profile_images/1599347398769143808/C6qG3RQv_400x400.jpg",
imageUrl: "https://i.seadn.io/s/raw/files/59ad1f2e3c5eb5d4b62c06e200076514.png",
isLoading: false,
backgroundColor: ""
},
{
uid: "fp#4297",
name: "Frenly Panda #4297",
collectionUid: "",
collectionName: "",
communityId: "fpan",
communityName: "Frenly Pandas",
communityImage: "https://pbs.twimg.com/profile_images/1599347398769143808/C6qG3RQv_400x400.jpg",
imageUrl: "https://i.seadn.io/gae/K4_vmYtXAqU6LTnGDliLtJZc4UPmf9jUlk09_FDbXvSKKyUARyyV9RQEgXdb5bjje5OE9j9ZryC5pzcwBwH7TDOIl8oq7D2tSJ7p",
isLoading: false,
backgroundColor: ""
},
{
uid: "fp#909",
name: "Frenly Panda #909",
collectionUid: "",
collectionName: "",
communityId: "fpan",
communityName: "Frenly Pandas",
communityImage: "https://pbs.twimg.com/profile_images/1599347398769143808/C6qG3RQv_400x400.jpg",
imageUrl: "https://i.seadn.io/gae/cR-Bjmb6DsrywCJMOqEBPkkrMHjbTzeRSAKIvLpd7i8ss6raYZ3-doh8oF2z8bJsnmfC1oR3kllz6UxMfFaYAKdXYzXlhfVsDHo6bg",
isLoading: false,
backgroundColor: ""
},
{
uid: "pp21",
name: "pepepunk#21",
collectionUid: "pepepunks",
collectionName: "",
communityId: "",
communityName: "",
imageUrl: "https://i.seadn.io/s/raw/files/cfa559bb63e4378f17649c1e3b8f18fe.jpg?auto=format&dpr=1&w=1000",
isLoading: false,
backgroundColor: ""
},
]
Component.onCompleted: append(data)
}

View File

@ -63,6 +63,7 @@ QtObject {
readonly property string custom: Style.png("collectibles/SNT")
readonly property string doodles: Style.png("collectibles/Doodles")
readonly property string mana: Style.png("collectibles/MANA-token-icon")
readonly property string cryptoPunks: Style.png("collectibles/CryptoPunks")
}
readonly property QtObject networks: QtObject {

View File

@ -9,6 +9,7 @@ FlatTokensModel 1.0 FlatTokensModel.qml
IconModel 1.0 IconModel.qml
LinkPreviewModel 1.0 LinkPreviewModel.qml
MintedTokensModel 1.0 MintedTokensModel.qml
ManageCollectiblesModel 1.0 ManageCollectiblesModel.qml
ManageTokensModel 1.0 ManageTokensModel.qml
RecipientModel 1.0 RecipientModel.qml
SourceOfTokensModel 1.0 SourceOfTokensModel.qml

View File

@ -295,7 +295,6 @@ ItemDelegate {
}
Loader {
asynchronous: true
active: !!root.icon.name || !!root.icon.source
visible: active
sourceComponent: root.hasIcon ? iconComponent : root.hasImage ? imageComponent : letterIdenticonComponent
@ -375,6 +374,7 @@ ItemDelegate {
width: root.icon.width
height: root.icon.height
image.source: root.icon.source
image.sourceSize: Qt.size(width, height)
showLoadingIndicator: true
image.fillMode: Image.PreserveAspectCrop
}

View File

@ -142,6 +142,8 @@
<file>assets/img/icons/arrow-left.svg</file>
<file>assets/img/icons/arrow-right.svg</file>
<file>assets/img/icons/arrow-up.svg</file>
<file>assets/img/icons/arrow-top.svg</file>
<file>assets/img/icons/arrow-bottom.svg</file>
<file>assets/img/icons/arrow.svg</file>
<file>assets/img/icons/audio.svg</file>
<file>assets/img/icons/backspace.svg</file>

View File

@ -51,6 +51,7 @@ ManageTokensController::ManageTokensController(QObject* parent)
reloadCommunityIds();
m_communityTokensModel->setCommunityIds(m_communityIds);
m_communityTokensModel->saveCustomSortOrder();
m_communityTokensModel->applySort();
});
m_modelConnectionsInitialized = true;
});
@ -115,6 +116,9 @@ void ManageTokensController::saveSettings()
{
Q_ASSERT(!m_settingsKey.isEmpty());
if (m_arrangeByCommunity)
m_communityTokensModel->applySort();
// gather the data to save
SerializedTokenData result;
for (auto model: {m_regularTokensModel, m_communityTokensModel})
@ -127,11 +131,11 @@ void ManageTokensController::saveSettings()
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;
const auto& [pos, visible, groupId] = 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.setValue(QStringLiteral("pos"), pos);
m_settings.setValue(QStringLiteral("visible"), visible);
m_settings.setValue(QStringLiteral("groupId"), groupId);
}
m_settings.endArray();
m_settings.endGroup();
@ -169,7 +173,7 @@ void ManageTokensController::loadSettings()
qCWarning(manageTokens) << Q_FUNC_INFO << "Missing symbol while reading tokens settings";
continue;
}
const auto pos = m_settings.value(QStringLiteral("pos"), -1).toInt();
const auto pos = m_settings.value(QStringLiteral("pos"), INT_MAX).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});
@ -180,7 +184,6 @@ void ManageTokensController::loadSettings()
void ManageTokensController::revert()
{
loadSettings();
parseSourceModel();
}
@ -203,6 +206,7 @@ void ManageTokensController::setSourceModel(QAbstractItemModel* newSourceModel)
// clear all the models
for (auto model: m_allModels)
model->clear();
m_settingsData.clear();
m_communityIds.clear();
m_sourceModel = newSourceModel;
emit sourceModelChanged();
@ -239,9 +243,12 @@ void ManageTokensController::parseSourceModel()
model->clear();
m_communityIds.clear();
// load settings
loadSettings();
// read and transform the original data
const auto newSize = m_sourceModel->rowCount();
qCInfo(manageTokens) << "!!! PARSING" << newSize << "TOKENS";
qCDebug(manageTokens) << "!!! PARSING" << newSize << "TOKENS";
for (auto i = 0; i < newSize; i++) {
addItem(i);
}
@ -259,7 +266,7 @@ void ManageTokensController::parseSourceModel()
}
#ifdef QT_DEBUG
qCInfo(manageTokens) << "!!! PARSING SOURCE DATA TOOK" << t.nsecsElapsed()/1'000'000.f << "ms";
qCDebug(manageTokens) << "!!! PARSING SOURCE DATA TOOK" << t.nsecsElapsed()/1'000'000.f << "ms";
#endif
emit sourceModelChanged();
@ -281,16 +288,21 @@ void ManageTokensController::addItem(int index)
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;
const auto bgColor = dataForIndex(srcIndex, kBackgroundColorRoleName).value<QColor>();
const auto collectionUid = dataForIndex(srcIndex, kCollectionUidRoleName).toString();
const auto collectionName = dataForIndex(srcIndex, kCollectionNameRoleName).toString();
TokenData token;
token.symbol = symbol;
token.name = dataForIndex(srcIndex, kNameRoleName).toString();
token.image = dataForIndex(srcIndex, kTokenImageRoleName).toString();
if (bgColor.isValid())
token.backgroundColor = bgColor;
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.collectionUid = collectionUid;
token.collectionName = !collectionName.isEmpty() ? collectionName : collectionUid;
token.balance = dataForIndex(srcIndex, kEnabledNetworkBalanceRoleName);
token.currencyBalance = dataForIndex(srcIndex, kEnabledNetworkCurrencyBalanceRoleName);

View File

@ -117,6 +117,7 @@ QHash<int, QByteArray> ManageTokensModel::roleNames() const
{CurrencyBalanceRole, kEnabledNetworkCurrencyBalanceRoleName},
{CustomSortOrderNoRole, kCustomSortOrderNoRoleName},
{TokenImageRole, kTokenImageRoleName},
{TokenBackgroundColorRole, kBackgroundColorRoleName},
};
return roles;
@ -142,6 +143,7 @@ QVariant ManageTokensModel::data(const QModelIndex& index, int role) const
case CurrencyBalanceRole: return token.currencyBalance;
case CustomSortOrderNoRole: return token.customSortOrderNo;
case TokenImageRole: return token.image;
case TokenBackgroundColorRole: return token.backgroundColor;
}
return {};

View File

@ -1,9 +1,11 @@
#pragma once
#include <QAbstractListModel>
#include <QColor>
#include <QLoggingCategory>
#include <optional>
#include <tuple>
Q_DECLARE_LOGGING_CATEGORY(manageTokens)
@ -16,16 +18,18 @@ 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 kEnabledNetworkBalanceRoleName = QByteArrayLiteral("enabledNetworkBalance"); // TODO add an extra (separate role) for group->childCount
const auto kEnabledNetworkCurrencyBalanceRoleName = QByteArrayLiteral("enabledNetworkCurrencyBalance");
const auto kCustomSortOrderNoRoleName = QByteArrayLiteral("customSortOrderNo");
const auto kTokenImageRoleName = QByteArrayLiteral("imageUrl");
const auto kBackgroundColorRoleName = QByteArrayLiteral("backgroundColor");
} // namespace
struct TokenData {
QString symbol, name, communityId, communityName, communityImage, collectionUid, collectionName, image;
QColor backgroundColor{Qt::transparent};
QVariant balance, currencyBalance;
int customSortOrderNo{-1};
int customSortOrderNo{INT_MAX};
};
// symbol -> {sortOrder, visible, groupId}
@ -50,6 +54,7 @@ public:
CurrencyBalanceRole,
CustomSortOrderNoRole,
TokenImageRole,
TokenBackgroundColorRole,
};
Q_ENUM(TokenDataRoles)

View File

@ -17,6 +17,7 @@ StatusFlatButton {
property bool isGroup
property string groupId
property bool isCommunityAsset
property bool isCollectible
readonly property bool hideEnabled: model.symbol !== "ETH"
readonly property bool menuVisible: menuLoader.active
@ -38,29 +39,34 @@ StatusFlatButton {
Loader {
id: menuLoader
objectName: "manageTokensContextMenuLoader"
active: false
sourceComponent: StatusMenu {
onClosed: menuLoader.active = false
StatusAction {
objectName: "miMoveToTop"
enabled: !root.inHidden && root.currentIndex !== 0
icon.name: "arrow-top"
text: qsTr("Move to top")
onTriggered: root.moveRequested(root.currentIndex, 0)
}
StatusAction {
objectName: "miMoveUp"
enabled: !root.inHidden && root.currentIndex !== 0
icon.name: "arrow-up"
text: qsTr("Move up")
onTriggered: root.moveRequested(root.currentIndex, root.currentIndex - 1)
}
StatusAction {
objectName: "miMoveDown"
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 {
objectName: "miMoveToBottom"
enabled: !root.inHidden && root.currentIndex < root.count - 1
icon.name: "arrow-bottom"
text: qsTr("Move to bottom")
@ -71,16 +77,18 @@ StatusFlatButton {
// any token
StatusAction {
objectName: "miHideToken"
enabled: !root.inHidden && root.hideEnabled && !root.isGroup && !root.isCommunityAsset
type: StatusAction.Type.Danger
icon.name: "hide"
text: qsTr("Hide asset")
text: root.isCollectible ? qsTr("Hide collectible") : qsTr("Hide asset")
onTriggered: root.showHideRequested(root.currentIndex, false)
}
StatusAction {
objectName: "miShowToken"
enabled: root.inHidden
icon.name: "show"
text: qsTr("Show asset")
text: root.isCollectible ? qsTr("Show collectible") : qsTr("Show asset")
onTriggered: root.showHideRequested(root.currentIndex, true)
}
@ -93,14 +101,16 @@ StatusFlatButton {
type: StatusAction.Type.Danger
StatusAction {
text: qsTr("This asset")
objectName: "miHideCommunityToken"
text: root.isCollectible ? qsTr("This collectible") : qsTr("This asset")
onTriggered: {
root.showHideRequested(root.currentIndex, false)
communitySubmenu.dismiss()
}
}
StatusAction {
text: qsTr("All assets from this community")
objectName: "miHideAllCommunityTokens"
text: root.isCollectible ? qsTr("All collectibles from this community") : qsTr("All assets from this community")
onTriggered: {
root.showHideGroupRequested(root.groupId, false)
communitySubmenu.dismiss()
@ -110,16 +120,18 @@ StatusFlatButton {
// token group
StatusAction {
objectName: "miHideTokenGroup"
enabled: !root.inHidden && root.isGroup
type: StatusAction.Type.Danger
icon.name: "hide"
text: qsTr("Hide all assets from this community")
text: root.isCollectible ? qsTr("Hide all collectibles from this community") : qsTr("Hide all assets from this community")
onTriggered: root.showHideGroupRequested(root.groupId, false)
}
StatusAction {
objectName: "miShowTokenGroup"
enabled: root.inHidden && root.groupId
icon.name: "show"
text: qsTr("Show all assets from this community")
text: root.isCollectible ? qsTr("Show all collectibles from this community") : qsTr("Show all assets from this community")
onTriggered: root.showHideGroupRequested(root.groupId, true)
}
}

View File

@ -0,0 +1,55 @@
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
import StatusQ.Components 0.1
import utils 1.0
Control {
id: root
property string text
property string imageSrc
property bool loading
property Component customBackground: Component {
Rectangle {
border.width: 1
border.color: Theme.palette.baseColor2
color: Theme.palette.baseColor4
radius: 20
}
}
QtObject {
id: d
property var loadingComponent: Component { LoadingComponent {} }
}
horizontalPadding: 12
verticalPadding: Style.current.halfPadding
spacing: 4
background: Loader {
sourceComponent: root.loading ? d.loadingComponent : root.customBackground
}
contentItem: RowLayout {
spacing: root.spacing
visible: !root.loading
StatusRoundedImage {
Layout.maximumWidth: visible ? 16 : 0
Layout.maximumHeight: visible ? 16 : 0
image.source: root.imageSrc
visible: !!image.source
}
StatusBaseText {
font.pixelSize: Style.current.tertiaryTextFontSize
font.weight: Font.Medium
text: root.text
}
}
}

View File

@ -0,0 +1,102 @@
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 utils 1.0
DropArea {
id: root
objectName: "manageTokensDelegate-%1".arg(index)
// expected roles: symbol, name, communityId, communityName, communityImage, collectionName, imageUrl
property var controller
property int visualIndex: index
property alias dragParent: delegate.dragParent
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
property bool isCollectible
readonly property alias title: delegate.title
readonly property var priv: QtObject {
id: priv
readonly property int iconSize: root.isCollectible ? 44 : 32
readonly property int bgRadius: root.isCollectible ? Style.current.radius : iconSize/2
}
ListView.onRemove: SequentialAnimation {
PropertyAction { target: root; property: "ListView.delayRemove"; value: true }
NumberAnimation { target: root; property: "scale"; to: 0; easing.type: Easing.InOutQuad }
PropertyAction { target: root; property: "ListView.delayRemove"; value: false }
}
width: ListView.view.width
height: visible ? delegate.height : 0
onEntered: function(drag) {
const from = drag.source.visualIndex
const to = delegate.visualIndex
if (to === from)
return
ListView.view.model.moveItem(from, to)
drag.accept()
}
StatusDraggableListItem {
id: delegate
objectName: "draggableDelegate"
visualIndex: index
Drag.keys: root.keys
Drag.hotSpot.x: root.width/2
Drag.hotSpot.y: root.height/2
draggable: true
width: root.width
title: model.name
secondaryTitle: root.isCollectible ? (!!model.communityId ? qsTr("Community minted") : model.collectionName || model.symbol) :
hovered || menuBtn.menuVisible ? "%1 • %2".arg(LocaleUtils.currencyAmountToLocaleString(model.enabledNetworkBalance))
.arg(LocaleUtils.currencyAmountToLocaleString(model.enabledNetworkCurrencyBalance))
: LocaleUtils.currencyAmountToLocaleString(model.enabledNetworkBalance)
bgRadius: priv.bgRadius
hasImage: true
icon.source: root.isCollectible ? model.imageUrl : Constants.tokenIcon(model.symbol) // TODO unify via backend model for both assets and collectibles
icon.width: priv.iconSize
icon.height: priv.iconSize
spacing: 12
assetBgColor: model.backgroundColor
actions: [
ManageTokensCommunityTag {
visible: !!model.communityId && !root.isGrouped
text: model.communityName
imageSrc: model.communityImage
},
ManageTokenMenuButton {
id: menuBtn
objectName: "btnManageTokenMenu-%1".arg(currentIndex)
currentIndex: root.visualIndex
count: root.count
inHidden: root.isHidden
groupId: model.communityId
isCommunityAsset: !!model.communityId
isCollectible: root.isCollectible
onMoveRequested: (from, to) => root.ListView.view.model.moveItem(from, to)
onShowHideRequested: (index, flag) => isCommunityAsset ? root.controller.showHideCommunityToken(index, flag)
: root.controller.showHideRegularToken(index, flag)
onShowHideGroupRequested: (groupId, flag) => root.controller.showHideGroup(groupId, flag)
}
]
}
}

View File

@ -0,0 +1,151 @@
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 utils 1.0
DropArea {
id: root
objectName: "manageTokensGroupDelegate-%1".arg(index)
// expected roles: communityId, communityName, communityImage
property int visualIndex: index
property var controller
property bool communityGroupsExpanded
property var dragParent
property alias dragEnabled: groupedCommunityTokenDelegate.dragEnabled
property bool isCollectible
readonly property string communityId: model.communityId
readonly property int childCount: model.enabledNetworkBalance // NB using "balance" as "count" in m_communityTokenGroupsModel
readonly property alias title: groupedCommunityTokenDelegate.title
ListView.onRemove: SequentialAnimation {
PropertyAction { target: root; property: "ListView.delayRemove"; value: true }
NumberAnimation { target: root; property: "scale"; to: 0; easing.type: Easing.InOutQuad }
PropertyAction { target: root; property: "ListView.delayRemove"; value: false }
}
keys: ["x-status-draggable-community-group-item"]
width: ListView.view.width
height: groupedCommunityTokenDelegate.implicitHeight
onEntered: function(drag) {
const from = drag.source.visualIndex
const to = groupedCommunityTokenDelegate.visualIndex
if (to === from)
return
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
title: model.communityName
visualIndex: index
dragParent: root.dragParent
Drag.keys: root.keys
Drag.hotSpot.x: root.width/2
Drag.hotSpot.y: root.height/2
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: root.dragEnabled ? Theme.palette.baseColor1 : Theme.palette.baseColor2
}
StatusRoundedImage {
//radius: groupedCommunityTokenDelegate.bgRadius // TODO different for a collection
Layout.preferredWidth: root.isCollectible ? 44 : 32
Layout.preferredHeight: root.isCollectible ? 44 : 32
image.source: model.communityImage
showLoadingIndicator: true
image.fillMode: Image.PreserveAspectCrop
}
StatusBaseText {
text: groupedCommunityTokenDelegate.title
elide: Text.ElideRight
maximumLineCount: 1
font.weight: Font.Medium
}
StatusBaseText {
Layout.leftMargin: -parent.spacing/2
text: "• %1".arg(root.isCollectible ? qsTr("%n collectible(s)", "", root.childCount) : qsTr("%n asset(s)", "", root.childCount))
elide: Text.ElideRight
color: Theme.palette.baseColor1
maximumLineCount: 1
visible: !root.communityGroupsExpanded
}
Item { Layout.fillWidth: true }
ManageTokenMenuButton {
objectName: "btnManageTokenMenu-%1".arg(currentIndex)
currentIndex: visualIndex
count: root.controller.communityTokenGroupsModel.count
isGroup: true
isCollectible: root.isCollectible
groupId: model.communityId
onMoveRequested: (from, to) => root.controller.communityTokenGroupsModel.moveItem(from, to) // TODO collection
onShowHideGroupRequested: (groupId, flag) => root.controller.showHideGroup(groupId, flag)
}
}
StatusListView {
objectName: "manageTokensGroupListView"
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
model: root.controller.communityTokensModel
interactive: false
visible: root.communityGroupsExpanded
displaced: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
delegate: ManageTokensDelegate {
controller: root.controller
dragParent: root.dragParent
isGrouped: true
count: root.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: root.communityId === model.communityId
isCollectible: root.isCollectible
}
}
}
}
}

View File

@ -6,3 +6,6 @@ StatusDateRangePicker 1.0 StatusDateRangePicker.qml
ActivityFilterTagItem 1.0 ActivityFilterTagItem.qml
SortOrderComboBox 1.0 SortOrderComboBox.qml
ManageTokenMenuButton 1.0 ManageTokenMenuButton.qml
ManageTokensCommunityTag 1.0 ManageTokensCommunityTag.qml
ManageTokensDelegate 1.0 ManageTokensDelegate.qml
ManageTokensGroupDelegate 1.0 ManageTokensGroupDelegate.qml

View File

@ -0,0 +1,239 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
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 collectionGroupsExpanded: true
property bool communityGroupsExpanded: true
readonly property var renamedModel: RolesRenamingModel {
sourceModel: root.baseModel
mapping: [
RoleRename {
from: "uid"
to: "symbol"
}
]
}
readonly property var controller: ManageTokensController {
sourceModel: d.renamedModel
arrangeByCommunity: switchArrangeByCommunity.checked
settingsKey: "WalletCollectibles"
}
}
contentItem: ColumnLayout {
spacing: Style.current.padding
ShapeRectangle {
Layout.fillWidth: true
Layout.margins: 2
visible: !d.controller.regularTokensModel.count
text: qsTr("Youll be able to manage the display of your collectibles here")
}
// TODO https://github.com/status-im/status-desktop/issues/12703
// StatusSwitch {
// Layout.alignment: Qt.AlignTrailing
// LayoutMirroring.enabled: true
// LayoutMirroring.childrenInherit: true
// id: switchArrangeByCollection
// textColor: Theme.palette.baseColor1
// text: qsTr("Arrange by collection (TODO)")
// visible: d.controller.regularTokensModel.count
// }
// StatusModalDivider {
// Layout.fillWidth: true
// Layout.topMargin: -Style.current.halfPadding
// visible: switchArrangeByCollection.visible && switchArrangeByCollection.checked
// }
// StatusLinkText {
// Layout.alignment: Qt.AlignTrailing
// visible: switchArrangeByCollection.visible && switchArrangeByCollection.checked
// text: d.collectionGroupsExpanded ? qsTr("Collapse all") : qsTr("Expand all")
// normalColor: linkColor
// font.weight: Font.Normal
// onClicked: d.collectionGroupsExpanded = !d.collectionGroupsExpanded
// }
StatusListView {
objectName: "lvRegularTokens"
Layout.fillWidth: true
model: d.controller.regularTokensModel
implicitHeight: contentHeight
interactive: false
displaced: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
delegate: ManageTokensDelegate {
isCollectible: true
controller: d.controller
dragParent: root
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")
}
Item { Layout.fillWidth: true }
StatusSwitch {
objectName: "switchArrangeByCommunity"
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 {
objectName: "switchCollapseCommunityGroups"
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 {
objectName: "loaderCommunityTokens"
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")
visible: d.controller.hiddenTokensModel.count
}
StatusListView {
objectName: "lvHiddenTokens"
Layout.fillWidth: true
model: d.controller.hiddenTokensModel
implicitHeight: contentHeight
interactive: false
displaced: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
delegate: ManageTokensDelegate {
isCollectible: true
controller: d.controller
dragParent: root
dragEnabled: false
keys: ["x-status-draggable-none"]
isHidden: true
}
}
}
Component {
id: cmpCommunityTokens
StatusListView {
objectName: "lvCommunityTokens"
model: d.controller.communityTokensModel
implicitHeight: contentHeight
interactive: false
displaced: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
delegate: ManageTokensDelegate {
isCollectible: true
controller: d.controller
dragParent: root
count: d.controller.communityTokensModel.count
dragEnabled: count > 1
keys: ["x-status-draggable-community-token-item"]
}
}
}
Component {
id: cmpCommunityTokenGroups
StatusListView {
objectName: "lvCommunityTokenGroups"
model: d.controller.communityTokenGroupsModel
implicitHeight: contentHeight
interactive: false
spacing: Style.current.halfPadding
displaced: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
delegate: ManageTokensGroupDelegate {
isCollectible: true
controller: d.controller
dragParent: root
dragEnabled: d.controller.communityTokenGroupsModel.count > 1
communityGroupsExpanded: d.communityGroupsExpanded
}
}
}
}

View File

@ -10,7 +10,6 @@ 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
@ -47,213 +46,6 @@ Control {
}
}
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
@ -267,7 +59,9 @@ Control {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
delegate: LocalTokenDelegate {
delegate: ManageTokensDelegate {
controller: d.controller
dragParent: root
count: d.controller.regularTokensModel.count
dragEnabled: count > 1
keys: ["x-status-draggable-token-item"]
@ -281,7 +75,7 @@ Control {
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)
text: qsTr("Community")
}
Item { Layout.fillWidth: true }
StatusSwitch {
@ -319,7 +113,7 @@ Control {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
color: Theme.palette.baseColor1
text: qsTr("Hidden")// + " -> %1".arg(d.controller.hiddenTokensModel.count)
text: qsTr("Hidden")
visible: d.controller.hiddenTokensModel.count
}
@ -333,7 +127,9 @@ Control {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
delegate: LocalTokenDelegate {
delegate: ManageTokensDelegate {
controller: d.controller
dragParent: root
dragEnabled: false
keys: ["x-status-draggable-none"]
isHidden: true
@ -352,7 +148,9 @@ Control {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
delegate: LocalTokenDelegate {
delegate: ManageTokensDelegate {
controller: d.controller
dragParent: root
count: d.controller.communityTokensModel.count
dragEnabled: count > 1
keys: ["x-status-draggable-community-token-item"]
@ -372,7 +170,12 @@ Control {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
delegate: LocalTokenGroupDelegate {}
delegate: ManageTokensGroupDelegate {
controller: d.controller
dragParent: root
dragEnabled: d.controller.communityTokenGroupsModel.count > 1
communityGroupsExpanded: d.communityGroupsExpanded
}
}
}
}

View File

@ -3,3 +3,4 @@ WalletTxProgressBlock 1.0 WalletTxProgressBlock.qml
WalletNftPreview 1.0 WalletNftPreview.qml
ActivityFilterPanel 1.0 ActivityFilterPanel.qml
ManageTokensPanel 1.0 ManageTokensPanel.qml
ManageCollectiblesPanel 1.0 ManageCollectiblesPanel.qml