feat: Create the hidden tab (to host both assets and collectibles)
- add new Hidden tab/section to Settings/Wallet/Manage tokens page and storybook - extract the controller(s) to ManageTokensView.qml to share them across the tabs - updated the grouped UI delegate - adjust the tests Fixes #13201 Fixes #13188
This commit is contained in:
parent
2abfe0fa0c
commit
57b3b254b8
|
@ -3,6 +3,7 @@ import QtQuick.Layouts 1.15
|
|||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Models 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
|
@ -30,7 +31,6 @@ SplitView {
|
|||
ManageAssetsPanel {
|
||||
id: showcasePanel
|
||||
width: 500
|
||||
baseModel: ctrlEmptyModel.checked ? null : walletAssetStore.groupedAccountAssetsModel
|
||||
getCurrencyAmount: function (balance, symbol) {
|
||||
return ({
|
||||
amount: balance,
|
||||
|
@ -47,6 +47,10 @@ SplitView {
|
|||
stripTrailingZeroes: false
|
||||
})
|
||||
}
|
||||
controller: ManageTokensController {
|
||||
sourceModel: ctrlEmptyModel.checked ? null : assetsModel
|
||||
settingsKey: "WalletAssets"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import QtQuick.Layouts 1.15
|
|||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ 0.1
|
||||
import StatusQ.Models 0.1
|
||||
import StatusQ.Core 0.1
|
||||
|
||||
import AppLayouts.Wallet.panels 1.0
|
||||
|
@ -23,16 +24,28 @@ SplitView {
|
|||
id: collectiblesModel
|
||||
}
|
||||
|
||||
RolesRenamingModel {
|
||||
id: renamedModel
|
||||
sourceModel: ctrlEmptyModel.checked ? null : collectiblesModel
|
||||
mapping: [
|
||||
RoleRename {
|
||||
from: "uid"
|
||||
to: "symbol"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
StatusScrollView { // wrapped in a ScrollView on purpose; to simulate SettingsContentBase.qml
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
|
||||
ManageCollectiblesPanel {
|
||||
id: showcasePanel
|
||||
width: 500
|
||||
baseModel: ctrlEmptyModel.checked ? null : collectiblesModel
|
||||
controller: ManageTokensController {
|
||||
sourceModel: renamedModel
|
||||
settingsKey: "WalletCollectibles"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Models 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
|
||||
}
|
||||
|
||||
ManageCollectiblesModel {
|
||||
id: collectiblesModel
|
||||
}
|
||||
|
||||
RolesRenamingModel {
|
||||
id: renamedModel
|
||||
sourceModel: collectiblesModel
|
||||
mapping: [
|
||||
RoleRename {
|
||||
from: "uid"
|
||||
to: "symbol"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
ManageTokensController {
|
||||
id: assetsController
|
||||
sourceModel: ctrlEmptyModel.checked ? null : assetsModel
|
||||
settingsKey: "WalletAssets"
|
||||
}
|
||||
|
||||
ManageTokensController {
|
||||
id: collectiblesController
|
||||
sourceModel: ctrlEmptyModel.checked ? null : renamedModel
|
||||
settingsKey: "WalletCollectibles"
|
||||
}
|
||||
|
||||
StatusScrollView { // wrapped in a ScrollView on purpose; to simulate SettingsContentBase.qml
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
|
||||
ManageHiddenPanel {
|
||||
id: showcasePanel
|
||||
width: 500
|
||||
assetsController: assetsController
|
||||
collectiblesController: collectiblesController
|
||||
}
|
||||
}
|
||||
|
||||
LogsAndControlsPanel {
|
||||
id: logsAndControlsPanel
|
||||
|
||||
SplitView.minimumWidth: 150
|
||||
SplitView.preferredWidth: 250
|
||||
|
||||
logsView.logText: logs.logText
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: "Has saved settings: %1".arg(showcasePanel.hasSettings ? "true" : "false")
|
||||
}
|
||||
|
||||
Button {
|
||||
enabled: showcasePanel.hasSettings
|
||||
text: "Clear settings"
|
||||
onClicked: showcasePanel.clearSettings()
|
||||
}
|
||||
|
||||
Switch {
|
||||
id: ctrlEmptyModel
|
||||
text: "Empty model"
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Hidden community groups:"
|
||||
}
|
||||
Label {
|
||||
text: assetsController.hiddenCommunityGroups.concat(collectiblesController.hiddenCommunityGroups).join()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// category: Panels
|
||||
|
||||
// https://www.figma.com/file/eM26pyHZUeAwMLviaS1KJn/%E2%9A%99%EF%B8%8F-Wallet-Settings%3A-Manage-Tokens?type=design&node-id=12-126364&mode=design&t=ZqKtOXpYtpReg4oL-0
|
||||
// https://www.figma.com/file/eM26pyHZUeAwMLviaS1KJn/%E2%9A%99%EF%B8%8F-Wallet-Settings%3A-Manage-Tokens?type=design&node-id=40-127902&mode=design&t=ZqKtOXpYtpReg4oL-0
|
||||
// https://www.figma.com/file/eM26pyHZUeAwMLviaS1KJn/%E2%9A%99%EF%B8%8F-Wallet-Settings%3A-Manage-Tokens?type=design&node-id=577-130046&mode=design&t=ZqKtOXpYtpReg4oL-0
|
||||
// https://www.figma.com/file/eM26pyHZUeAwMLviaS1KJn/%E2%9A%99%EF%B8%8F-Wallet-Settings%3A-Manage-Tokens?type=design&node-id=577-151896&mode=design&t=ZqKtOXpYtpReg4oL-0
|
|
@ -2,6 +2,9 @@ import QtQuick 2.15
|
|||
import QtQuick.Controls 2.15
|
||||
import QtTest 1.15
|
||||
|
||||
import StatusQ 0.1
|
||||
import StatusQ.Models 0.1
|
||||
|
||||
import AppLayouts.Wallet.panels 1.0
|
||||
|
||||
import Storybook 1.0
|
||||
|
@ -17,12 +20,32 @@ Item {
|
|||
id: collectiblesModel
|
||||
}
|
||||
|
||||
RolesRenamingModel {
|
||||
id: renamedModel
|
||||
sourceModel: collectiblesModel
|
||||
mapping: [
|
||||
RoleRename {
|
||||
from: "uid"
|
||||
to: "symbol"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Component {
|
||||
id: componentUnderTest
|
||||
ManageCollectiblesPanel {
|
||||
id: showcasePanel
|
||||
width: 500
|
||||
baseModel: collectiblesModel
|
||||
controller: ManageTokensController {
|
||||
sourceModel: renamedModel
|
||||
settingsKey: "WalletCollectibles"
|
||||
onTokenHidden: (symbol, name) => Global.displayToastMessage(
|
||||
qsTr("%1 was successfully hidden.").arg(name), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage(
|
||||
qsTr("%1 community collectibles successfully hidden").arg(communityName), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,10 +102,6 @@ Item {
|
|||
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
|
||||
|
@ -96,27 +115,8 @@ Item {
|
|||
// verify the signal to show the notification toast got fired
|
||||
tryCompare(notificationSpy, "count", 1)
|
||||
|
||||
// verify we now have +1 hidden and -1 regular tokens after the "hide" operation
|
||||
waitForItemPolished(lvHidden)
|
||||
tryCompare(lvHidden, "count", 1)
|
||||
// verify we now have -1 regular tokens after the "hide" operation
|
||||
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)
|
||||
}
|
||||
|
||||
function test_showHideCommunityGroup() {
|
||||
|
@ -141,33 +141,6 @@ Item {
|
|||
// verify we have one less group
|
||||
waitForItemPolished(lvCommunityTokenGroups)
|
||||
tryCompare(lvCommunityTokenGroups, "count", 2)
|
||||
const lvHidden = findChild(controlUnderTest, "lvHiddenTokens")
|
||||
verify(!!lvHidden)
|
||||
tryCompare(lvHidden, "count", 4) // we've just hidden 4 collectibles coming from this group
|
||||
|
||||
// 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 we again have 3 community groups, and one less hidden token
|
||||
tryCompare(lvCommunityTokenGroups, "count", 3)
|
||||
tryCompare(lvHidden, "count", 3)
|
||||
|
||||
// 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", 3)
|
||||
}
|
||||
|
||||
function test_dnd() {
|
||||
|
@ -264,15 +237,8 @@ Item {
|
|||
// verify the signal to show the notification toast got fired
|
||||
tryCompare(notificationSpy, "count", 1)
|
||||
|
||||
// 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)
|
||||
waitForItemPolished(lvCommunityTokenGroups)
|
||||
triggerDelegateMenuAction(lvCommunityTokenGroups, 1, "miMoveUp", true)
|
||||
verify(controlUnderTest.dirty)
|
||||
bearzGroupTokenDelegate = findChild(lvCommunityTokenGroups, "manageTokensGroupDelegate-0")
|
||||
|
|
|
@ -380,7 +380,6 @@ 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
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ ManageTokensController::ManageTokensController(QObject* parent)
|
|||
, m_communityTokensModel(new ManageTokensModel(this))
|
||||
, m_communityTokenGroupsModel(new ManageTokensModel(this))
|
||||
, m_hiddenTokensModel(new ManageTokensModel(this))
|
||||
, m_hiddenCommunityTokenGroupsModel(new ManageTokensModel(this))
|
||||
{
|
||||
for (auto model : m_allModels) {
|
||||
connect(model, &ManageTokensModel::dirtyChanged, this, &ManageTokensController::dirtyChanged);
|
||||
|
@ -34,6 +35,7 @@ ManageTokensController::ManageTokensController(QObject* parent)
|
|||
m_communityTokensModel->setCommunityIds(m_communityIds);
|
||||
m_communityTokensModel->saveCustomSortOrder();
|
||||
rebuildCommunityTokenGroupsModel();
|
||||
rebuildHiddenCommunityTokenGroupsModel();
|
||||
rebuildRegularTokenGroupsModel();
|
||||
#ifdef QT_DEBUG
|
||||
qCDebug(manageTokens) << "!!! ADDING NEW SOURCE DATA TOOK" << t.nsecsElapsed()/1'000'000.f << "ms";
|
||||
|
@ -59,16 +61,16 @@ ManageTokensController::ManageTokensController(QObject* parent)
|
|||
});
|
||||
}
|
||||
|
||||
void ManageTokensController::showHideRegularToken(int row, bool flag)
|
||||
void ManageTokensController::showHideRegularToken(const QString& symbol, bool flag)
|
||||
{
|
||||
if (flag) { // show
|
||||
auto hiddenItem = m_hiddenTokensModel->takeItem(row);
|
||||
auto hiddenItem = m_hiddenTokensModel->takeItem(symbol);
|
||||
if (hiddenItem) {
|
||||
m_regularTokensModel->addItem(*hiddenItem);
|
||||
emit tokenShown(hiddenItem->symbol, hiddenItem->name);
|
||||
}
|
||||
} else { // hide
|
||||
auto shownItem = m_regularTokensModel->takeItem(row);
|
||||
auto shownItem = m_regularTokensModel->takeItem(symbol);
|
||||
if (shownItem) {
|
||||
m_hiddenTokensModel->addItem(*shownItem, false /*prepend*/);
|
||||
emit tokenHidden(shownItem->symbol, shownItem->name);
|
||||
|
@ -76,10 +78,10 @@ void ManageTokensController::showHideRegularToken(int row, bool flag)
|
|||
}
|
||||
}
|
||||
|
||||
void ManageTokensController::showHideCommunityToken(int row, bool flag)
|
||||
void ManageTokensController::showHideCommunityToken(const QString& symbol, bool flag)
|
||||
{
|
||||
if (flag) { // show
|
||||
auto hiddenItem = m_hiddenTokensModel->takeItem(row);
|
||||
auto hiddenItem = m_hiddenTokensModel->takeItem(symbol);
|
||||
if (hiddenItem) {
|
||||
m_communityTokensModel->addItem(*hiddenItem);
|
||||
if (!m_communityIds.contains(hiddenItem->communityId))
|
||||
|
@ -87,7 +89,7 @@ void ManageTokensController::showHideCommunityToken(int row, bool flag)
|
|||
emit tokenShown(hiddenItem->symbol, hiddenItem->name);
|
||||
}
|
||||
} else { // hide
|
||||
auto shownItem = m_communityTokensModel->takeItem(row);
|
||||
auto shownItem = m_communityTokensModel->takeItem(symbol);
|
||||
if (shownItem) {
|
||||
m_hiddenTokensModel->addItem(*shownItem, false /*prepend*/);
|
||||
if (!m_communityTokensModel->hasCommunityIdToken(shownItem->communityId))
|
||||
|
@ -98,6 +100,7 @@ void ManageTokensController::showHideCommunityToken(int row, bool flag)
|
|||
m_communityTokensModel->setCommunityIds(m_communityIds);
|
||||
m_communityTokensModel->saveCustomSortOrder();
|
||||
rebuildCommunityTokenGroupsModel();
|
||||
rebuildHiddenCommunityTokenGroupsModel();
|
||||
}
|
||||
|
||||
void ManageTokensController::showHideGroup(const QString& groupId, bool flag)
|
||||
|
@ -111,6 +114,9 @@ void ManageTokensController::showHideGroup(const QString& groupId, bool flag)
|
|||
emit communityTokenGroupShown(tokens.constFirst().communityName);
|
||||
}
|
||||
m_communityIds.append(groupId);
|
||||
if (m_hiddenCommunityGroups.remove(groupId)) {
|
||||
emit hiddenCommunityGroupsChanged();
|
||||
}
|
||||
} else { // hide
|
||||
const auto tokens = m_communityTokensModel->takeAllItems(groupId);
|
||||
if (!tokens.isEmpty()) {
|
||||
|
@ -120,10 +126,15 @@ void ManageTokensController::showHideGroup(const QString& groupId, bool flag)
|
|||
emit communityTokenGroupHidden(tokens.constFirst().communityName);
|
||||
}
|
||||
m_communityIds.removeAll(groupId);
|
||||
if (!m_hiddenCommunityGroups.contains(groupId)) {
|
||||
m_hiddenCommunityGroups.insert(groupId);
|
||||
emit hiddenCommunityGroupsChanged();
|
||||
}
|
||||
}
|
||||
m_communityTokensModel->setCommunityIds(m_communityIds);
|
||||
m_communityTokensModel->saveCustomSortOrder();
|
||||
rebuildCommunityTokenGroupsModel();
|
||||
rebuildHiddenCommunityTokenGroupsModel();
|
||||
}
|
||||
|
||||
void ManageTokensController::saveSettings(bool reuseCurrent)
|
||||
|
@ -147,6 +158,7 @@ void ManageTokensController::saveSettings(bool reuseCurrent)
|
|||
|
||||
// save to QSettings
|
||||
m_settings.beginGroup(settingsGroupName());
|
||||
|
||||
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++) {
|
||||
|
@ -158,6 +170,10 @@ void ManageTokensController::saveSettings(bool reuseCurrent)
|
|||
m_settings.setValue(QStringLiteral("groupId"), groupId);
|
||||
}
|
||||
m_settings.endArray();
|
||||
|
||||
// hidden community groups
|
||||
m_settings.setValue(QStringLiteral("HiddenCommunityGroups"), hiddenCommunityGroups());
|
||||
|
||||
m_settings.endGroup();
|
||||
m_settings.sync();
|
||||
|
||||
|
@ -204,6 +220,14 @@ void ManageTokensController::loadSettings()
|
|||
m_settingsData.insert(symbol, {pos, visible, groupId});
|
||||
}
|
||||
m_settings.endArray();
|
||||
|
||||
// hidden community groups
|
||||
const auto groups = m_settings.value(QStringLiteral("HiddenCommunityGroups")).toStringList();
|
||||
if (!groups.isEmpty()) {
|
||||
m_hiddenCommunityGroups = {groups.constBegin(), groups.constEnd()};
|
||||
emit hiddenCommunityGroupsChanged();
|
||||
}
|
||||
|
||||
m_settings.endGroup();
|
||||
setSettingsDirty(false);
|
||||
}
|
||||
|
@ -215,6 +239,11 @@ void ManageTokensController::setSettingsDirty(bool dirty)
|
|||
emit settingsDirtyChanged(m_settingsDirty);
|
||||
}
|
||||
|
||||
QStringList ManageTokensController::hiddenCommunityGroups() const
|
||||
{
|
||||
return {m_hiddenCommunityGroups.constBegin(), m_hiddenCommunityGroups.constEnd()};
|
||||
}
|
||||
|
||||
void ManageTokensController::revert()
|
||||
{
|
||||
parseSourceModel();
|
||||
|
@ -344,6 +373,7 @@ void ManageTokensController::parseSourceModel()
|
|||
for (auto model: m_allModels)
|
||||
model->clear();
|
||||
m_communityIds.clear();
|
||||
m_hiddenCommunityGroups.clear();
|
||||
|
||||
// load settings
|
||||
loadSettings();
|
||||
|
@ -357,6 +387,7 @@ void ManageTokensController::parseSourceModel()
|
|||
|
||||
// build community groups model
|
||||
rebuildCommunityTokenGroupsModel();
|
||||
rebuildHiddenCommunityTokenGroupsModel();
|
||||
reloadCommunityIds();
|
||||
m_communityTokensModel->setCommunityIds(m_communityIds);
|
||||
|
||||
|
@ -485,20 +516,58 @@ void ManageTokensController::rebuildCommunityTokenGroupsModel()
|
|||
});
|
||||
if (tokenGroup != result.cend()) {
|
||||
const auto row = std::distance(result.cbegin(), tokenGroup);
|
||||
TokenData updTokenGroup = result.takeAt(row);
|
||||
TokenData& updTokenGroup = result[row];
|
||||
updTokenGroup.balance = updTokenGroup.balance.toInt() + 1;
|
||||
result.insert(row, updTokenGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_communityTokenGroupsModel->clear();
|
||||
for (const auto& group: result)
|
||||
for (const auto& group: std::as_const(result))
|
||||
m_communityTokenGroupsModel->addItem(group);
|
||||
|
||||
qCDebug(manageTokens) << "!!! GROUPS MODEL REBUILT WITH GROUPS:" << communityIds;
|
||||
}
|
||||
|
||||
void ManageTokensController::rebuildHiddenCommunityTokenGroupsModel()
|
||||
{
|
||||
QStringList communityIds;
|
||||
QList<TokenData> result;
|
||||
|
||||
const auto count = m_hiddenTokensModel->count();
|
||||
for (auto i = 0; i < count; i++) {
|
||||
const auto& communityToken = m_hiddenTokensModel->itemAt(i);
|
||||
const auto communityId = communityToken.communityId;
|
||||
if (communityId.isEmpty())
|
||||
continue;
|
||||
if (!communityIds.contains(communityId) && m_hiddenCommunityGroups.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[row];
|
||||
updTokenGroup.balance = updTokenGroup.balance.toInt() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_hiddenCommunityTokenGroupsModel->clear();
|
||||
for (const auto& group: std::as_const(result))
|
||||
m_hiddenCommunityTokenGroupsModel->addItem(group);
|
||||
|
||||
qCDebug(manageTokens) << "!!! HIDDEN GROUPS MODEL REBUILT WITH GROUPS:" << communityIds;
|
||||
}
|
||||
|
||||
void ManageTokensController::rebuildRegularTokenGroupsModel()
|
||||
{
|
||||
QStringList collectionIds;
|
||||
|
@ -525,15 +594,14 @@ void ManageTokensController::rebuildRegularTokenGroupsModel()
|
|||
});
|
||||
if (tokenGroup != result.cend()) {
|
||||
const auto row = std::distance(result.cbegin(), tokenGroup);
|
||||
TokenData updTokenGroup = result.takeAt(row);
|
||||
TokenData& updTokenGroup = result[row];
|
||||
updTokenGroup.balance = updTokenGroup.balance.toInt() + 1;
|
||||
result.insert(row, updTokenGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_regularTokenGroupsModel->clear();
|
||||
for (const auto& group: result)
|
||||
for (const auto& group: std::as_const(result))
|
||||
m_regularTokenGroupsModel->addItem(group);
|
||||
|
||||
qCDebug(manageTokens) << "!!! COLLECTION MODEL REBUILT WITH GROUPS:" << collectionIds;
|
||||
|
|
|
@ -25,6 +25,11 @@ class ManageTokensController : public QObject, public QQmlParserStatus
|
|||
Q_PROPERTY(QAbstractItemModel* communityTokensModel READ communityTokensModel CONSTANT FINAL)
|
||||
Q_PROPERTY(QAbstractItemModel* communityTokenGroupsModel READ communityTokenGroupsModel CONSTANT FINAL)
|
||||
Q_PROPERTY(QAbstractItemModel* hiddenTokensModel READ hiddenTokensModel CONSTANT FINAL)
|
||||
|
||||
// TODO track hidden collection groups
|
||||
Q_PROPERTY(QStringList hiddenCommunityGroups READ hiddenCommunityGroups NOTIFY hiddenCommunityGroupsChanged FINAL)
|
||||
Q_PROPERTY(QAbstractItemModel* hiddenCommunityTokenGroupsModel READ hiddenCommunityTokenGroupsModel CONSTANT FINAL)
|
||||
|
||||
Q_PROPERTY(bool dirty READ dirty NOTIFY dirtyChanged FINAL)
|
||||
Q_PROPERTY(bool hasSettings READ hasSettings NOTIFY settingsDirtyChanged FINAL)
|
||||
Q_PROPERTY(bool settingsDirty READ settingsDirty NOTIFY settingsDirtyChanged FINAL)
|
||||
|
@ -32,8 +37,8 @@ class ManageTokensController : public QObject, public QQmlParserStatus
|
|||
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 showHideRegularToken(const QString& symbol, bool flag);
|
||||
Q_INVOKABLE void showHideCommunityToken(const QString& symbol, bool flag);
|
||||
Q_INVOKABLE void showHideGroup(const QString& groupId, bool flag);
|
||||
|
||||
Q_INVOKABLE void loadSettings();
|
||||
|
@ -43,6 +48,7 @@ public:
|
|||
|
||||
Q_INVOKABLE void settingsHideToken(const QString& symbol);
|
||||
Q_INVOKABLE void settingsHideCommunityTokens(const QString& communityId, const QStringList& symbols);
|
||||
// TODO settingHideCollectionTokens
|
||||
|
||||
Q_INVOKABLE bool lessThan(const QString& lhsSymbol, const QString& rhsSymbol) const;
|
||||
Q_INVOKABLE bool filterAcceptsSymbol(const QString& symbol) const;
|
||||
|
@ -64,6 +70,8 @@ signals:
|
|||
void communityTokenGroupShown(const QString& communityName);
|
||||
// TODO collectionTokenGroupHidden(const QString& collectionName);
|
||||
|
||||
void hiddenCommunityGroupsChanged();
|
||||
|
||||
private:
|
||||
QAbstractItemModel* m_sourceModel{nullptr};
|
||||
QAbstractItemModel* sourceModel() const { return m_sourceModel; }
|
||||
|
@ -87,6 +95,9 @@ private:
|
|||
ManageTokensModel* m_hiddenTokensModel{nullptr};
|
||||
QAbstractItemModel* hiddenTokensModel() const { return m_hiddenTokensModel; }
|
||||
|
||||
ManageTokensModel* m_hiddenCommunityTokenGroupsModel{nullptr};
|
||||
QAbstractItemModel* hiddenCommunityTokenGroupsModel() const { return m_hiddenCommunityTokenGroupsModel; }
|
||||
|
||||
bool dirty() const;
|
||||
|
||||
bool m_arrangeByCommunity{false};
|
||||
|
@ -96,9 +107,11 @@ private:
|
|||
QStringList m_communityIds;
|
||||
void reloadCommunityIds();
|
||||
void rebuildCommunityTokenGroupsModel();
|
||||
void rebuildHiddenCommunityTokenGroupsModel();
|
||||
void rebuildRegularTokenGroupsModel();
|
||||
|
||||
const std::array<ManageTokensModel*, 5> m_allModels {m_regularTokensModel, m_regularTokenGroupsModel, m_communityTokensModel, m_communityTokenGroupsModel, m_hiddenTokensModel};
|
||||
const std::array<ManageTokensModel*, 6> m_allModels {m_regularTokensModel, m_regularTokenGroupsModel, m_communityTokensModel, m_communityTokenGroupsModel, m_hiddenTokensModel,
|
||||
m_hiddenCommunityTokenGroupsModel};
|
||||
|
||||
QString m_settingsKey;
|
||||
QString settingsKey() const;
|
||||
|
@ -113,4 +126,8 @@ private:
|
|||
void setSettingsDirty(bool dirty);
|
||||
|
||||
bool m_modelConnectionsInitialized{false};
|
||||
|
||||
// explicitely mass-hidden community asset/collectible groups
|
||||
QSet<QString> m_hiddenCommunityGroups;
|
||||
QStringList hiddenCommunityGroups() const;
|
||||
};
|
||||
|
|
|
@ -38,8 +38,13 @@ void ManageTokensModel::addItem(const TokenData& item, bool append)
|
|||
endInsertRows();
|
||||
}
|
||||
|
||||
std::optional<TokenData> ManageTokensModel::takeItem(int row)
|
||||
std::optional<TokenData> ManageTokensModel::takeItem(const QString& symbol)
|
||||
{
|
||||
const auto token = std::find_if(m_data.cbegin(), m_data.cend(), [symbol](const auto& item) {
|
||||
return symbol == item.symbol;
|
||||
});
|
||||
const auto row = std::distance(m_data.cbegin(), token);
|
||||
|
||||
if (row < 0 || row >= rowCount())
|
||||
return {};
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ public:
|
|||
Q_INVOKABLE void moveItem(int fromRow, int toRow);
|
||||
|
||||
void addItem(const TokenData& item, bool append = true);
|
||||
std::optional<TokenData> takeItem(int row);
|
||||
std::optional<TokenData> takeItem(const QString& symbol);
|
||||
QList<TokenData> takeAllItems(const QString& communityId);
|
||||
void clear();
|
||||
|
||||
|
@ -85,6 +85,7 @@ public:
|
|||
|
||||
int count() const { return rowCount(); }
|
||||
const TokenData& itemAt(int row) const { return m_data.at(row); }
|
||||
TokenData& itemAt(int row) { return m_data[row]; }
|
||||
|
||||
void setCommunityIds(const QStringList& ids) { m_communityIds = ids; };
|
||||
bool hasCommunityIdToken(const QString& communityId) const;
|
||||
|
|
|
@ -2,9 +2,11 @@ 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.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Models 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
|
||||
|
@ -32,7 +34,7 @@ ColumnLayout {
|
|||
readonly property bool dirty: {
|
||||
if (!loader.item)
|
||||
return false
|
||||
if (tabBar.currentIndex > d.collectiblesTabIndex)
|
||||
if (tabBar.currentIndex > d.hiddenTabIndex)
|
||||
return false
|
||||
// FIXME take advanced settings into account here too (#13178)
|
||||
if (tabBar.currentIndex === d.collectiblesTabIndex && baseWalletCollectiblesModel.isFetching)
|
||||
|
@ -41,14 +43,14 @@ ColumnLayout {
|
|||
}
|
||||
|
||||
function saveChanges() {
|
||||
if (tabBar.currentIndex > d.collectiblesTabIndex)
|
||||
if (tabBar.currentIndex > d.hiddenTabIndex)
|
||||
return
|
||||
// FIXME save advanced settings (#13178)
|
||||
loader.item.saveSettings()
|
||||
}
|
||||
|
||||
function resetChanges() {
|
||||
if (tabBar.currentIndex > d.collectiblesTabIndex)
|
||||
if (tabBar.currentIndex > d.hiddenTabIndex)
|
||||
return
|
||||
loader.item.revert()
|
||||
}
|
||||
|
@ -58,7 +60,52 @@ ColumnLayout {
|
|||
|
||||
readonly property int assetsTabIndex: 0
|
||||
readonly property int collectiblesTabIndex: 1
|
||||
readonly property int advancedTabIndex: 2
|
||||
readonly property int hiddenTabIndex: 2
|
||||
readonly property int advancedTabIndex: 3
|
||||
|
||||
// assets
|
||||
readonly property var assetsController: ManageTokensController {
|
||||
sourceModel: root.baseWalletAssetsModel
|
||||
settingsKey: "WalletAssets"
|
||||
onTokenHidden: (symbol, name) => Global.displayToastMessage(
|
||||
qsTr("%1 (%2) was successfully hidden").arg(name).arg(symbol), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage(
|
||||
qsTr("%1 community assets successfully hidden").arg(communityName), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
onTokenShown: (symbol, name) => Global.displayToastMessage(qsTr("%1 is now visible").arg(name), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
onCommunityTokenGroupShown: (communityName) => Global.displayToastMessage(
|
||||
qsTr("%1 community assets are now visible").arg(communityName), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
}
|
||||
|
||||
// collectibles
|
||||
readonly property var renamedCollectiblesModel: RolesRenamingModel {
|
||||
sourceModel: root.baseWalletCollectiblesModel
|
||||
mapping: [
|
||||
RoleRename {
|
||||
from: "uid"
|
||||
to: "symbol"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
readonly property var collectiblesController: ManageTokensController {
|
||||
sourceModel: d.renamedCollectiblesModel
|
||||
settingsKey: "WalletCollectibles"
|
||||
onTokenHidden: (symbol, name) => Global.displayToastMessage(
|
||||
qsTr("%1 was successfully hidden").arg(name), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage(
|
||||
qsTr("%1 community collectibles successfully hidden").arg(communityName), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
onTokenShown: (symbol, name) => Global.displayToastMessage(qsTr("%1 is now visible").arg(name), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
onCommunityTokenGroupShown: (communityName) => Global.displayToastMessage(
|
||||
qsTr("%1 community collectibles are now visible").arg(communityName), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
}
|
||||
|
||||
function checkLoadMoreCollectibles() {
|
||||
if (tabBar.currentIndex !== collectiblesTabIndex)
|
||||
|
@ -91,12 +138,14 @@ ColumnLayout {
|
|||
width: implicitWidth
|
||||
text: qsTr("Assets")
|
||||
}
|
||||
|
||||
StatusTabButton {
|
||||
width: implicitWidth
|
||||
text: qsTr("Collectibles")
|
||||
}
|
||||
|
||||
StatusTabButton {
|
||||
width: implicitWidth
|
||||
text: qsTr("Hidden")
|
||||
}
|
||||
StatusTabButton {
|
||||
width: implicitWidth
|
||||
text: qsTr("Advanced")
|
||||
|
@ -116,6 +165,8 @@ ColumnLayout {
|
|||
return tokensPanel
|
||||
case d.collectiblesTabIndex:
|
||||
return collectiblesPanel
|
||||
case d.hiddenTabIndex:
|
||||
return hiddenPanel
|
||||
case d.advancedTabIndex:
|
||||
return advancedTab
|
||||
}
|
||||
|
@ -125,24 +176,32 @@ ColumnLayout {
|
|||
Component {
|
||||
id: tokensPanel
|
||||
ManageAssetsPanel {
|
||||
baseModel: root.baseWalletAssetsModel
|
||||
getCurrencyAmount: function (balance, symbol) {
|
||||
return root.getCurrencyAmount(balance, symbol)
|
||||
}
|
||||
getCurrentCurrencyAmount: function (balance) {
|
||||
return root.getCurrentCurrencyAmount(balance)
|
||||
}
|
||||
controller: d.assetsController
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: collectiblesPanel
|
||||
ManageCollectiblesPanel {
|
||||
baseModel: root.baseWalletCollectiblesModel
|
||||
controller: d.collectiblesController
|
||||
Component.onCompleted: d.checkLoadMoreCollectibles()
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: hiddenPanel
|
||||
ManageHiddenPanel {
|
||||
assetsController: d.assetsController
|
||||
collectiblesController: d.collectiblesController
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: advancedTab
|
||||
ColumnLayout {
|
||||
|
|
|
@ -23,7 +23,7 @@ StatusFlatButton {
|
|||
readonly property bool menuVisible: menuLoader.active
|
||||
|
||||
signal moveRequested(int from, int to)
|
||||
signal showHideRequested(int index, bool flag)
|
||||
signal showHideRequested(string symbol, bool flag)
|
||||
signal showHideGroupRequested(string groupId, bool flag)
|
||||
|
||||
icon.name: "more"
|
||||
|
@ -82,14 +82,14 @@ StatusFlatButton {
|
|||
type: StatusAction.Type.Danger
|
||||
icon.name: "hide"
|
||||
text: root.isCollectible ? qsTr("Hide collectible") : qsTr("Hide asset")
|
||||
onTriggered: root.showHideRequested(root.currentIndex, false)
|
||||
onTriggered: root.showHideRequested(model.symbol, false)
|
||||
}
|
||||
StatusAction {
|
||||
objectName: "miShowToken"
|
||||
enabled: root.inHidden
|
||||
enabled: root.inHidden && !root.isGroup
|
||||
icon.name: "show"
|
||||
text: root.isCollectible ? qsTr("Show collectible") : qsTr("Show asset")
|
||||
onTriggered: root.showHideRequested(root.currentIndex, true)
|
||||
onTriggered: root.showHideRequested(model.symbol, true)
|
||||
}
|
||||
|
||||
// (hide) community tokens
|
||||
|
@ -104,7 +104,7 @@ StatusFlatButton {
|
|||
objectName: "miHideCommunityToken"
|
||||
text: root.isCollectible ? qsTr("This collectible") : qsTr("This asset")
|
||||
onTriggered: {
|
||||
root.showHideRequested(root.currentIndex, false)
|
||||
root.showHideRequested(model.symbol, false)
|
||||
communitySubmenu.dismiss()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ Control {
|
|||
Rectangle {
|
||||
border.width: 1
|
||||
border.color: Theme.palette.baseColor2
|
||||
color: Theme.palette.baseColor4
|
||||
color: enabled ? Theme.palette.baseColor4 : Theme.palette.baseColor3
|
||||
radius: 20
|
||||
}
|
||||
}
|
||||
|
@ -43,8 +43,10 @@ Control {
|
|||
visible: !root.loading
|
||||
StatusSmartIdenticon {
|
||||
id: identicon
|
||||
Layout.maximumWidth: visible ? 16 : 0
|
||||
Layout.maximumHeight: visible ? 16 : 0
|
||||
Layout.preferredWidth: visible ? 16 : 0
|
||||
Layout.preferredHeight: visible ? 16 : 0
|
||||
asset.width: 16
|
||||
asset.height: 16
|
||||
asset.isImage: true
|
||||
visible: !!asset.source
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ 0.1
|
||||
|
@ -24,9 +25,8 @@ DropArea {
|
|||
property alias topInset: delegate.topInset
|
||||
property alias bottomInset: delegate.bottomInset
|
||||
property bool isGrouped
|
||||
property bool isHidden
|
||||
property bool isHidden // inside the "Hidden" section
|
||||
property int count
|
||||
|
||||
property bool isCollectible
|
||||
|
||||
readonly property alias title: delegate.title
|
||||
|
@ -47,7 +47,7 @@ DropArea {
|
|||
PropertyAction { target: root; property: "ListView.delayRemove"; value: false }
|
||||
}
|
||||
|
||||
width: ListView.view.width
|
||||
width: ListView.view ? ListView.view.width : 0
|
||||
height: visible ? delegate.height : 0
|
||||
|
||||
onEntered: function(drag) {
|
||||
|
@ -87,6 +87,7 @@ DropArea {
|
|||
|
||||
actions: [
|
||||
ManageTokensCommunityTag {
|
||||
Layout.maximumWidth: delegate.width *.4
|
||||
visible: !!model.communityId && !root.isGrouped
|
||||
text: model.communityName
|
||||
asset.name: model && !!model.communityImage ? model.communityImage : ""
|
||||
|
@ -101,11 +102,11 @@ DropArea {
|
|||
isCommunityAsset: !!model.communityId
|
||||
isCollectible: root.isCollectible
|
||||
onMoveRequested: (from, to) => root.ListView.view.model.moveItem(from, to)
|
||||
onShowHideRequested: function(index, flag) {
|
||||
onShowHideRequested: function(symbol, flag) {
|
||||
if (isCommunityAsset)
|
||||
root.controller.showHideCommunityToken(index, flag)
|
||||
root.controller.showHideCommunityToken(symbol, flag)
|
||||
else
|
||||
root.controller.showHideRegularToken(index, flag)
|
||||
root.controller.showHideRegularToken(symbol, flag)
|
||||
root.controller.saveSettings()
|
||||
}
|
||||
onShowHideGroupRequested: function(groupId, flag) {
|
||||
|
|
|
@ -21,6 +21,7 @@ DropArea {
|
|||
property var dragParent
|
||||
property alias dragEnabled: groupedCommunityTokenDelegate.dragEnabled
|
||||
property bool isCollectible
|
||||
property bool isHidden // inside the "Hidden" section
|
||||
|
||||
readonly property string communityId: model.communityId
|
||||
readonly property int childCount: model.enabledNetworkBalance // NB using "balance" as "count" in m_communityTokenGroupsModel
|
||||
|
@ -36,8 +37,8 @@ DropArea {
|
|||
}
|
||||
|
||||
keys: ["x-status-draggable-community-group-item"]
|
||||
width: ListView.view.width
|
||||
height: groupedCommunityTokenDelegate.implicitHeight
|
||||
width: ListView.view ? ListView.view.width : 0
|
||||
height: visible ? groupedCommunityTokenDelegate.implicitHeight : 0
|
||||
|
||||
onEntered: function(drag) {
|
||||
const from = drag.source.visualIndex
|
||||
|
@ -52,10 +53,7 @@ DropArea {
|
|||
id: groupedCommunityTokenDelegate
|
||||
width: parent.width
|
||||
height: dragActive ? implicitHeight : parent.height
|
||||
leftPadding: Style.current.halfPadding
|
||||
rightPadding: Style.current.halfPadding
|
||||
bottomPadding: Style.current.halfPadding
|
||||
topPadding: 22
|
||||
horizontalPadding: root.isHidden ? 0 : Style.current.halfPadding
|
||||
draggable: true
|
||||
spacing: 12
|
||||
bgColor: Theme.palette.baseColor4
|
||||
|
@ -68,13 +66,12 @@ DropArea {
|
|||
Drag.hotSpot.y: root.height/2
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
spacing: 12
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 12
|
||||
Layout.rightMargin: 12
|
||||
Layout.bottomMargin: 14
|
||||
spacing: groupedCommunityTokenDelegate.spacing
|
||||
|
||||
StatusIcon {
|
||||
|
@ -100,25 +97,25 @@ DropArea {
|
|||
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 }
|
||||
|
||||
ManageTokensCommunityTag {
|
||||
text: root.childCount
|
||||
asset.name: root.isCollectible ? "image" : "token"
|
||||
asset.isImage: false
|
||||
asset.color: Theme.palette.baseColor1
|
||||
enabled: false
|
||||
}
|
||||
|
||||
ManageTokenMenuButton {
|
||||
objectName: "btnManageTokenMenu-%1".arg(currentIndex)
|
||||
currentIndex: visualIndex
|
||||
count: root.controller.communityTokenGroupsModel.count
|
||||
count: root.controller.communityTokenGroupsModel.count // FIXME collection
|
||||
isGroup: true
|
||||
isCollectible: root.isCollectible
|
||||
groupId: model.communityId
|
||||
onMoveRequested: (from, to) => root.controller.communityTokenGroupsModel.moveItem(from, to) // TODO collection
|
||||
inHidden: root.isHidden
|
||||
onMoveRequested: (from, to) => root.controller.communityTokenGroupsModel.moveItem(from, to) // FIXME collection
|
||||
onShowHideGroupRequested: function(groupId, flag) {
|
||||
root.controller.showHideGroup(groupId, flag)
|
||||
root.controller.saveSettings()
|
||||
|
@ -132,7 +129,7 @@ DropArea {
|
|||
Layout.preferredHeight: contentHeight
|
||||
model: root.controller.communityTokensModel
|
||||
interactive: false
|
||||
visible: root.communityGroupsExpanded
|
||||
visible: root.communityGroupsExpanded && !root.isHidden
|
||||
|
||||
displaced: Transition {
|
||||
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
|
||||
|
|
|
@ -16,10 +16,10 @@ import AppLayouts.Wallet.controls 1.0
|
|||
Control {
|
||||
id: root
|
||||
|
||||
required property var baseModel
|
||||
required property var controller
|
||||
|
||||
readonly property bool dirty: d.controller.dirty
|
||||
readonly property bool hasSettings: d.controller.hasSettings
|
||||
readonly property bool dirty: root.controller.dirty
|
||||
readonly property bool hasSettings: root.controller.hasSettings
|
||||
|
||||
property var getCurrencyAmount: function (balance, symbol) {}
|
||||
property var getCurrentCurrencyAmount: function(balance){}
|
||||
|
@ -27,33 +27,27 @@ Control {
|
|||
background: null
|
||||
|
||||
function saveSettings() {
|
||||
d.controller.saveSettings();
|
||||
root.controller.saveSettings();
|
||||
}
|
||||
|
||||
function revert() {
|
||||
d.controller.revert();
|
||||
root.controller.revert();
|
||||
}
|
||||
|
||||
function clearSettings() {
|
||||
d.controller.clearSettings();
|
||||
root.controller.clearSettings();
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property bool communityGroupsExpanded: true
|
||||
}
|
||||
|
||||
readonly property var controller: ManageTokensController {
|
||||
sourceModel: root.baseModel
|
||||
arrangeByCommunity: switchArrangeByCommunity.checked
|
||||
settingsKey: "WalletAssets"
|
||||
onTokenHidden: (symbol, name) => Global.displayToastMessage(
|
||||
qsTr("%1 (%2) was successfully hidden.").arg(name).arg(symbol), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage(
|
||||
qsTr("%1 community assets successfully hidden").arg(communityName), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
}
|
||||
Binding {
|
||||
target: controller
|
||||
property: "arrangeByCommunity"
|
||||
value: switchArrangeByCommunity.checked
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
|
@ -61,7 +55,7 @@ Control {
|
|||
|
||||
StatusListView {
|
||||
Layout.fillWidth: true
|
||||
model: d.controller.regularTokensModel
|
||||
model: root.controller.regularTokensModel
|
||||
implicitHeight: contentHeight
|
||||
interactive: false
|
||||
|
||||
|
@ -70,9 +64,9 @@ Control {
|
|||
}
|
||||
|
||||
delegate: ManageTokensDelegate {
|
||||
controller: d.controller
|
||||
controller: root.controller
|
||||
dragParent: root
|
||||
count: d.controller.regularTokensModel.count
|
||||
count: root.controller.regularTokensModel.count
|
||||
dragEnabled: count > 1
|
||||
keys: ["x-status-draggable-token-item"]
|
||||
getCurrencyAmount: function (balance, symbol) {
|
||||
|
@ -88,7 +82,7 @@ Control {
|
|||
id: communityTokensHeader
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.current.padding
|
||||
visible: d.controller.communityTokensModel.count
|
||||
visible: root.controller.communityTokensModel.count
|
||||
StatusBaseText {
|
||||
color: Theme.palette.baseColor1
|
||||
text: qsTr("Community")
|
||||
|
@ -120,49 +114,16 @@ Control {
|
|||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
active: d.controller.communityTokensModel.count
|
||||
active: root.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 {
|
||||
Layout.fillWidth: true
|
||||
model: d.controller.hiddenTokensModel
|
||||
implicitHeight: contentHeight
|
||||
interactive: false
|
||||
|
||||
displaced: Transition {
|
||||
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
|
||||
}
|
||||
|
||||
delegate: ManageTokensDelegate {
|
||||
controller: d.controller
|
||||
dragParent: root
|
||||
dragEnabled: false
|
||||
keys: ["x-status-draggable-none"]
|
||||
isHidden: true
|
||||
getCurrencyAmount: function (balance, symbol) {
|
||||
return root.getCurrencyAmount(balance, symbol)
|
||||
}
|
||||
getCurrentCurrencyAmount: function (balance) {
|
||||
return root.getCurrentCurrencyAmount(balance)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: cmpCommunityTokens
|
||||
StatusListView {
|
||||
model: d.controller.communityTokensModel
|
||||
model: root.controller.communityTokensModel
|
||||
implicitHeight: contentHeight
|
||||
interactive: false
|
||||
|
||||
|
@ -171,9 +132,9 @@ Control {
|
|||
}
|
||||
|
||||
delegate: ManageTokensDelegate {
|
||||
controller: d.controller
|
||||
controller: root.controller
|
||||
dragParent: root
|
||||
count: d.controller.communityTokensModel.count
|
||||
count: root.controller.communityTokensModel.count
|
||||
dragEnabled: count > 1
|
||||
keys: ["x-status-draggable-community-token-item"]
|
||||
getCurrencyAmount: function (balance, symbol) {
|
||||
|
@ -189,7 +150,7 @@ Control {
|
|||
Component {
|
||||
id: cmpCommunityTokenGroups
|
||||
StatusListView {
|
||||
model: d.controller.communityTokenGroupsModel
|
||||
model: root.controller.communityTokenGroupsModel
|
||||
implicitHeight: contentHeight
|
||||
interactive: false
|
||||
spacing: Style.current.halfPadding
|
||||
|
@ -199,9 +160,9 @@ Control {
|
|||
}
|
||||
|
||||
delegate: ManageTokensGroupDelegate {
|
||||
controller: d.controller
|
||||
controller: root.controller
|
||||
dragParent: root
|
||||
dragEnabled: d.controller.communityTokenGroupsModel.count > 1
|
||||
dragEnabled: root.controller.communityTokenGroupsModel.count > 1
|
||||
communityGroupsExpanded: d.communityGroupsExpanded
|
||||
getCurrencyAmount: function (balance, symbol) {
|
||||
return root.getCurrencyAmount(balance, symbol)
|
||||
|
|
|
@ -18,53 +18,36 @@ import AppLayouts.Wallet.controls 1.0
|
|||
Control {
|
||||
id: root
|
||||
|
||||
required property var baseModel
|
||||
required property var controller
|
||||
|
||||
readonly property bool dirty: d.controller.dirty
|
||||
readonly property bool hasSettings: d.controller.hasSettings
|
||||
readonly property bool dirty: root.controller.dirty
|
||||
readonly property bool hasSettings: root.controller.hasSettings
|
||||
|
||||
background: null
|
||||
|
||||
function saveSettings() {
|
||||
d.controller.saveSettings();
|
||||
root.controller.saveSettings();
|
||||
}
|
||||
|
||||
function revert() {
|
||||
d.controller.revert();
|
||||
root.controller.revert();
|
||||
}
|
||||
|
||||
function clearSettings() {
|
||||
d.controller.clearSettings();
|
||||
root.controller.clearSettings();
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property bool collectionGroupsExpanded: true
|
||||
//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"
|
||||
onTokenHidden: (symbol, name) => Global.displayToastMessage(
|
||||
qsTr("%1 was successfully hidden.").arg(name), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage(
|
||||
qsTr("%1 community collectibles successfully hidden").arg(communityName), "", "checkmark-circle",
|
||||
false, Constants.ephemeralNotificationType.success, "")
|
||||
}
|
||||
Binding {
|
||||
target: controller
|
||||
property: "arrangeByCommunity"
|
||||
value: switchArrangeByCommunity.checked
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
|
@ -73,7 +56,7 @@ Control {
|
|||
ShapeRectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 2
|
||||
visible: !d.controller.regularTokensModel.count
|
||||
visible: !root.controller.regularTokensModel.count
|
||||
text: qsTr("You’ll be able to manage the display of your collectibles here")
|
||||
}
|
||||
|
||||
|
@ -85,7 +68,7 @@ Control {
|
|||
// id: switchArrangeByCollection
|
||||
// textColor: Theme.palette.baseColor1
|
||||
// text: qsTr("Arrange by collection")
|
||||
// visible: d.controller.regularTokensModel.count
|
||||
// visible: root.controller.regularTokensModel.count
|
||||
// }
|
||||
|
||||
// StatusModalDivider {
|
||||
|
@ -106,7 +89,7 @@ Control {
|
|||
StatusListView {
|
||||
objectName: "lvRegularTokens"
|
||||
Layout.fillWidth: true
|
||||
model: d.controller.regularTokensModel
|
||||
model: root.controller.regularTokensModel
|
||||
implicitHeight: contentHeight
|
||||
interactive: false
|
||||
|
||||
|
@ -116,9 +99,9 @@ Control {
|
|||
|
||||
delegate: ManageTokensDelegate {
|
||||
isCollectible: true
|
||||
controller: d.controller
|
||||
controller: root.controller
|
||||
dragParent: root
|
||||
count: d.controller.regularTokensModel.count
|
||||
count: root.controller.regularTokensModel.count
|
||||
dragEnabled: count > 1
|
||||
keys: ["x-status-draggable-token-item"]
|
||||
}
|
||||
|
@ -128,7 +111,7 @@ Control {
|
|||
id: communityTokensHeader
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.current.padding
|
||||
visible: d.controller.communityTokensModel.count
|
||||
visible: root.controller.communityTokensModel.count
|
||||
StatusBaseText {
|
||||
color: Theme.palette.baseColor1
|
||||
text: qsTr("Community")
|
||||
|
@ -163,46 +146,17 @@ Control {
|
|||
Loader {
|
||||
objectName: "loaderCommunityTokens"
|
||||
Layout.fillWidth: true
|
||||
active: d.controller.communityTokensModel.count
|
||||
active: root.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
|
||||
model: root.controller.communityTokensModel
|
||||
implicitHeight: contentHeight
|
||||
interactive: false
|
||||
|
||||
|
@ -212,9 +166,9 @@ Control {
|
|||
|
||||
delegate: ManageTokensDelegate {
|
||||
isCollectible: true
|
||||
controller: d.controller
|
||||
controller: root.controller
|
||||
dragParent: root
|
||||
count: d.controller.communityTokensModel.count
|
||||
count: root.controller.communityTokensModel.count
|
||||
dragEnabled: count > 1
|
||||
keys: ["x-status-draggable-community-token-item"]
|
||||
}
|
||||
|
@ -225,7 +179,7 @@ Control {
|
|||
id: cmpCommunityTokenGroups
|
||||
StatusListView {
|
||||
objectName: "lvCommunityTokenGroups"
|
||||
model: d.controller.communityTokenGroupsModel
|
||||
model: root.controller.communityTokenGroupsModel
|
||||
implicitHeight: contentHeight
|
||||
interactive: false
|
||||
spacing: Style.current.halfPadding
|
||||
|
@ -236,9 +190,9 @@ Control {
|
|||
|
||||
delegate: ManageTokensGroupDelegate {
|
||||
isCollectible: true
|
||||
controller: d.controller
|
||||
controller: root.controller
|
||||
dragParent: root
|
||||
dragEnabled: d.controller.communityTokenGroupsModel.count > 1
|
||||
dragEnabled: root.controller.communityTokenGroupsModel.count > 1
|
||||
communityGroupsExpanded: d.communityGroupsExpanded
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
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.Models 0.1
|
||||
|
||||
import utils 1.0
|
||||
import shared.controls 1.0
|
||||
|
||||
import AppLayouts.Wallet.controls 1.0
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
Control {
|
||||
id: root
|
||||
|
||||
required property var assetsController
|
||||
required property var collectiblesController
|
||||
|
||||
readonly property bool dirty: false // never dirty, the "show xxx" actions are immediate
|
||||
readonly property bool hasSettings: root.assetsController.hasSettings || root.collectiblesController.hasSettings
|
||||
|
||||
background: null
|
||||
|
||||
function clearSettings() {
|
||||
root.assetsController.clearSettings();
|
||||
root.collectiblesController.clearSettings();
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property bool assetsExpanded: true
|
||||
property bool collectiblesExpanded: true
|
||||
|
||||
readonly property int assetsCount: root.assetsController.hiddenTokensModel.count + root.assetsController.hiddenCommunityTokenGroupsModel.count
|
||||
readonly property int collectiblesCount: root.collectiblesController.hiddenTokensModel.count + root.collectiblesController.hiddenCommunityTokenGroupsModel.count // + TODO collection groups
|
||||
|
||||
readonly property var filteredHiddenAssets: SortFilterProxyModel {
|
||||
sourceModel: root.assetsController.hiddenTokensModel
|
||||
filters: FastExpressionFilter {
|
||||
expression: {
|
||||
root.assetsController.hiddenCommunityGroups
|
||||
return !root.assetsController.hiddenCommunityGroups.includes(model.communityId)
|
||||
}
|
||||
expectedRoles: ["communityId"]
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var filteredHiddenCollectibles: SortFilterProxyModel {
|
||||
sourceModel: root.collectiblesController.hiddenTokensModel
|
||||
filters: FastExpressionFilter {
|
||||
expression: {
|
||||
root.collectiblesController.hiddenCommunityGroups
|
||||
return !root.collectiblesController.hiddenCommunityGroups.includes(model.communityId)
|
||||
}
|
||||
expectedRoles: ["communityId"]
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var combinedModel: ConcatModel {
|
||||
sources: [
|
||||
SourceModel { // single hidden assets (not belonging to a group)
|
||||
model: d.filteredHiddenAssets
|
||||
markerRoleValue: "asset"
|
||||
},
|
||||
SourceModel { // community asset groups
|
||||
model: root.assetsController.hiddenCommunityTokenGroupsModel
|
||||
markerRoleValue: "assetGroup"
|
||||
},
|
||||
SourceModel { // single hidden collectibles (not belonging to a group)
|
||||
model: d.filteredHiddenCollectibles
|
||||
markerRoleValue: "collectible"
|
||||
},
|
||||
SourceModel { // community collectible groups
|
||||
model: root.collectiblesController.hiddenCommunityTokenGroupsModel
|
||||
markerRoleValue: "collectibleGroup"
|
||||
}
|
||||
]
|
||||
|
||||
markerRoleName: "tokenType"
|
||||
}
|
||||
|
||||
readonly property var sfpm: SortFilterProxyModel {
|
||||
sourceModel: d.combinedModel
|
||||
proxyRoles: [
|
||||
FastExpressionRole {
|
||||
name: "isCollectible"
|
||||
expression: model.tokenType.startsWith("collectible")
|
||||
expectedRoles: "tokenType"
|
||||
},
|
||||
FastExpressionRole {
|
||||
name: "isGroup"
|
||||
expression: model.tokenType.endsWith("Group")
|
||||
expectedRoles: "tokenType"
|
||||
}
|
||||
// TODO collection
|
||||
]
|
||||
// TODO sort by recency/timestamp (newest first)
|
||||
}
|
||||
}
|
||||
|
||||
component SectionDelegate: Rectangle {
|
||||
id: sectionDelegate
|
||||
height: 64
|
||||
color: Theme.palette.statusListItem.backgroundColor
|
||||
|
||||
property bool isCollectible
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
StatusFlatButton {
|
||||
size: StatusBaseButton.Size.Small
|
||||
icon.name: checked ? "chevron-down" : "next"
|
||||
checked: sectionDelegate.isCollectible ? d.collectiblesExpanded : d.assetsExpanded
|
||||
textColor: Theme.palette.baseColor1
|
||||
textHoverColor: Theme.palette.directColor1
|
||||
onToggled: {
|
||||
if (sectionDelegate.isCollectible)
|
||||
d.collectiblesExpanded = !d.collectiblesExpanded
|
||||
else
|
||||
d.assetsExpanded = !d.assetsExpanded
|
||||
}
|
||||
}
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
text: sectionDelegate.isCollectible ? qsTr("Collectibles") : qsTr("Assets")
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component Placeholder: ShapeRectangle {
|
||||
property bool isCollectible
|
||||
text: isCollectible ? qsTr("Your hidden collectibles will appear here") : qsTr("Your hidden assets will appear here")
|
||||
}
|
||||
|
||||
Component {
|
||||
id: tokenDelegate
|
||||
ManageTokensDelegate {
|
||||
isCollectible: model.isCollectible
|
||||
controller: isCollectible ? root.collectiblesController : root.assetsController
|
||||
dragParent: null
|
||||
dragEnabled: false
|
||||
keys: ["x-status-draggable-none"]
|
||||
isHidden: true
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: tokenGroupDelegate
|
||||
ManageTokensGroupDelegate {
|
||||
isCollectible: model.isCollectible
|
||||
controller: isCollectible ? root.collectiblesController : root.assetsController
|
||||
dragParent: null
|
||||
dragEnabled: false
|
||||
keys: ["x-status-draggable-none"]
|
||||
isHidden: true
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 2 // subtle spacing for the dashed placeholders to be fully visible
|
||||
|
||||
ColumnLayout { // no assets placeholder
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
visible: !d.assetsCount
|
||||
SectionDelegate {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Placeholder {
|
||||
Layout.fillWidth: true
|
||||
visible: d.assetsExpanded
|
||||
}
|
||||
}
|
||||
|
||||
StatusListView {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: contentHeight
|
||||
Layout.fillHeight: true
|
||||
model: d.sfpm
|
||||
|
||||
displaced: Transition {
|
||||
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
|
||||
}
|
||||
|
||||
delegate: Loader {
|
||||
required property var model
|
||||
required property int index
|
||||
|
||||
width: ListView.view.width
|
||||
height: visible ? 76 : 0
|
||||
sourceComponent: model.isGroup ? tokenGroupDelegate : tokenDelegate
|
||||
visible: (!model.isCollectible && d.assetsExpanded) || (model.isCollectible && d.collectiblesExpanded)
|
||||
}
|
||||
|
||||
section.property: "isCollectible"
|
||||
section.delegate: SectionDelegate {
|
||||
width: ListView.view.width
|
||||
isCollectible: section == "true"
|
||||
}
|
||||
section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
|
||||
}
|
||||
|
||||
ColumnLayout { // no collectibles placeholder
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
visible: !d.collectiblesCount
|
||||
SectionDelegate {
|
||||
Layout.fillWidth: true
|
||||
isCollectible: true
|
||||
}
|
||||
Placeholder {
|
||||
Layout.fillWidth: true
|
||||
isCollectible: true
|
||||
visible: d.collectiblesExpanded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,3 +4,4 @@ WalletNftPreview 1.0 WalletNftPreview.qml
|
|||
ActivityFilterPanel 1.0 ActivityFilterPanel.qml
|
||||
ManageAssetsPanel 1.0 ManageAssetsPanel.qml
|
||||
ManageCollectiblesPanel 1.0 ManageCollectiblesPanel.qml
|
||||
ManageHiddenPanel 1.0 ManageHiddenPanel.qml
|
||||
|
|
Loading…
Reference in New Issue