feat(CollectiblesView): Add combobox/popup with custom filtering options

.. to filter by community or collection name

- make the HistoryView own filter button look like the other combos
- fix some cosmetic issues for StatusCombo in small/secondary mode
- fix StatusBaseInput bg color in dark mode (was invisible)
- update CollectiblesViewPage with options to include regular and/or
community collectibles

Fixes #12969
Fixes #12948
This commit is contained in:
Lukáš Tinkl 2023-12-18 11:12:57 +01:00 committed by Lukáš Tinkl
parent 1bd23b027d
commit e2fa5b756a
22 changed files with 626 additions and 118 deletions

View File

@ -12,6 +12,7 @@ import mainui 1.0
import utils 1.0
import shared.views 1.0
import shared.stores 1.0
import Storybook 1.0
import Models 1.0
@ -53,29 +54,11 @@ SplitView {
return supportedAddresses
}
readonly property var currencyStore: QtObject {
property string currentCurrency: "USD"
function getCurrencyAmount(amount, symbol) {
return ({
amount: amount,
symbol: symbol.toUpperCase(),
displayDecimals: 2,
stripTrailingZeroes: false
})
}
function getCurrentCurrencyAmount(amount) {
return ({
amount: amount,
symbol: "USD",
displayDecimals: 2,
stripTrailingZeroes: false
})
}
}
readonly property var currencyStore: CurrenciesStore {}
readonly property var groupedAccountsAssetsModel: GroupedAccountsAssetsModel {}
readonly property var tokensBySymbolModel: TokensBySymbolModel {}
readonly property CommunitiesModel communityModel: CommunitiesModel{}
readonly property CommunitiesModel communityModel: CommunitiesModel {}
// Added this here simply because the network and address filtering wont work in Storybook applied in AssetsView
readonly property SubmodelProxyModel assetsWithFilteredBalances: SubmodelProxyModel {
@ -88,7 +71,7 @@ SplitView {
d.networksChainsCurrentlySelected
d.addressesSelected
return d.networksChainsCurrentlySelected.split(":").includes(chainId+"") &&
(!! d.addressesSelected ? d.addressesSelected.toUpperCase() === account.toUpperCase() : true)
(!! d.addressesSelected ? d.addressesSelected.toUpperCase() === account.toUpperCase() : true)
}
}
}
@ -185,7 +168,7 @@ SplitView {
CheckBox {
id: loadingCheckbox
checked: true
checked: false
text: "loading"
}

View File

@ -23,10 +23,8 @@ SplitView {
ManageCollectiblesModel {
id: collectiblesModel
}
ListModel {
id: emptyModel
includeRegularCollectibles: ctrlIncludeRegularCollectibles.checked
includeCommunityCollectibles: ctrlIncludeCommunityCollectibles.checked
}
Popups {
@ -39,7 +37,7 @@ SplitView {
id: assetsView
SplitView.preferredWidth: 600
SplitView.fillHeight: true
collectiblesModel: ctrlEmptyModel.checked ? emptyModel : collectiblesModel
collectiblesModel: collectiblesModel
filterVisible: ctrlFilterVisible.checked
onCollectibleClicked: logs.logEvent("onCollectibleClicked", ["chainId", "contractAddress", "tokenId", "uid"], arguments)
onSendRequested: logs.logEvent("onSendRequested", ["symbol"], arguments)
@ -63,8 +61,14 @@ SplitView {
checked: true
}
Switch {
id: ctrlEmptyModel
text: "Empty model"
id: ctrlIncludeRegularCollectibles
text: "Regular collectibles"
checked: true
}
Switch {
id: ctrlIncludeCommunityCollectibles
text: "Community collectibles"
checked: true
}
}
}

View File

@ -77,7 +77,7 @@ Item {
const lvRegular = findChild(controlUnderTest, "lvRegularTokens")
verify(!!lvRegular)
const lvRegularCount = lvRegular.count
verify(lvRegularCount === 6)
verify(lvRegularCount === 7)
const delegate0 = findChild(lvRegular, "manageTokensDelegate-0")
verify(!!delegate0)
@ -124,14 +124,14 @@ Item {
verify(!!lvCommunityTokenGroups)
// verify we have 2 community collectible groups
tryCompare(lvCommunityTokenGroups, "count", 2)
tryCompare(lvCommunityTokenGroups, "count", 3)
triggerDelegateMenuAction(lvCommunityTokenGroups, 0, "miHideTokenGroup", true)
verify(controlUnderTest.dirty)
// verify we have one less group
waitForItemPolished(lvCommunityTokenGroups)
tryCompare(lvCommunityTokenGroups, "count", 1)
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
@ -154,8 +154,8 @@ Item {
verify(controlUnderTest.dirty)
// verify we again have 2 community groups, and one less hidden token
tryCompare(lvCommunityTokenGroups, "count", 2)
// verify we again have 3 community groups, and one less hidden token
tryCompare(lvCommunityTokenGroups, "count", 3)
tryCompare(lvHidden, "count", 3)
verify(controlUnderTest.dirty)
@ -164,7 +164,7 @@ Item {
triggerDelegateMenuAction(lvHidden, 0, "miShowTokenGroup")
waitForItemPolished(lvHidden)
tryCompare(lvHidden, "count", 0)
tryCompare(lvCommunityTokenGroups, "count", 2)
tryCompare(lvCommunityTokenGroups, "count", 3)
verify(controlUnderTest.dirty)
}
@ -209,7 +209,7 @@ Item {
const lvCommunityTokenGroups = findChild(loaderCommunityTokens, "lvCommunityTokenGroups")
verify(!!lvCommunityTokenGroups)
waitForItemPolished(lvCommunityTokenGroups)
tryCompare(lvCommunityTokenGroups, "count", 2)
tryCompare(lvCommunityTokenGroups, "count", 3)
const group0 = findChild(lvCommunityTokenGroups, "manageTokensGroupDelegate-0")
const title0 = group0.title
@ -242,7 +242,7 @@ Item {
const lvCommunityTokenGroups = findChild(loaderCommunityTokens, "lvCommunityTokenGroups")
verify(!!lvCommunityTokenGroups)
waitForItemPolished(lvCommunityTokenGroups)
tryCompare(lvCommunityTokenGroups, "count", 2)
tryCompare(lvCommunityTokenGroups, "count", 3)
// get the "Bearz" group at index 1
var bearzGroupTokenDelegate = findChild(lvCommunityTokenGroups, "manageTokensGroupDelegate-1")

View File

@ -4,23 +4,20 @@ import QtQml.Models 2.15
import Models 1.0
ListModel {
function randomizeData() {
// TODO
property bool includeRegularCollectibles: true
onIncludeRegularCollectiblesChanged: fillData()
property bool includeCommunityCollectibles: true
onIncludeCommunityCollectiblesChanged: fillData()
function fillData() {
clear()
if (includeRegularCollectibles)
append(data)
if (includeCommunityCollectibles)
append(communityData)
}
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!",
@ -28,6 +25,7 @@ ListModel {
collectionName: "",
communityId: "",
communityName: "",
communityImage: "",
imageUrl: ModelsData.collectibles.cryptoPunks,
isLoading: false,
backgroundColor: ""
@ -39,6 +37,7 @@ ListModel {
collectionName: "Pepepunks",
communityId: "",
communityName: "",
communityImage: "",
imageUrl: "https://i.seadn.io/s/raw/files/ba2811bb5cd0bed67529d69fa92ef5aa.jpg?auto=format&dpr=1&w=1000",
isLoading: false,
backgroundColor: ""
@ -50,6 +49,7 @@ ListModel {
collectionName: "Kitties",
communityId: "",
communityName: "",
communityImage: "",
imageUrl: ModelsData.collectibles.kitty1Big,
isLoading: true,
backgroundColor: ""
@ -61,6 +61,7 @@ ListModel {
collectionName: "Kitties",
communityId: "",
communityName: "",
communityImage: "",
imageUrl: ModelsData.collectibles.kitty2Big,
isLoading: false,
backgroundColor: ""
@ -72,10 +73,50 @@ ListModel {
collectionName: "Kitties",
communityId: "",
communityName: "",
communityImage: "",
imageUrl: ModelsData.collectibles.kitty3Big,
isLoading: false,
backgroundColor: ""
},
{
uid: "pp21",
name: "pepepunk#21",
collectionUid: "pepepunks",
collectionName: "Pepepunks",
communityId: "",
communityName: "",
communityImage: "",
imageUrl: "https://i.seadn.io/s/raw/files/cfa559bb63e4378f17649c1e3b8f18fe.jpg?auto=format&dpr=1&w=1000",
isLoading: false,
backgroundColor: ""
},
{
uid: "lp#666a",
name: "Lonely Panda #666",
collectionUid: "lpan_collection",
collectionName: "Lonely Panda Collection",
communityId: "",
communityName: "",
communityImage: "",
imageUrl: "",
isLoading: false,
backgroundColor: "pink"
},
]
readonly property var communityData: [
{
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: "691",
name: "KILLABEAR #691",
@ -137,17 +178,20 @@ ListModel {
backgroundColor: ""
},
{
uid: "pp21",
name: "pepepunk#21",
collectionUid: "pepepunks",
collectionName: "Pepepunks",
communityId: "",
communityName: "",
imageUrl: "https://i.seadn.io/s/raw/files/cfa559bb63e4378f17649c1e3b8f18fe.jpg?auto=format&dpr=1&w=1000",
uid: "lb#666",
name: "Lonely Bear #666",
collectionUid: "",
collectionName: "",
communityId: "lbear",
communityName: "Lonely Bearz Community",
communityImage: "",
imageUrl: "",
isLoading: false,
backgroundColor: ""
backgroundColor: "pink"
},
]
Component.onCompleted: append(data)
Component.onCompleted: {
fillData()
}
}

View File

@ -1,6 +1,8 @@
import QtQuick 2.15
QtObject {
id: root
readonly property string currentCurrency: "USD"
property string currentCurrencySymbol: "$"
@ -11,4 +13,22 @@ QtObject {
function getFiatValue(balance, cryptoSymbol, fiatSymbol) {
return balance
}
function getCurrencyAmount(amount, symbol) {
return ({
amount: amount,
symbol: symbol ? symbol.toUpperCase() : root.currentCurrency,
displayDecimals: 2,
stripTrailingZeroes: false
})
}
function getCurrentCurrencyAmount(amount) {
return ({
amount: amount,
symbol: root.currentCurrency,
displayDecimals: 2,
stripTrailingZeroes: false
})
}
}

View File

@ -278,7 +278,7 @@ Item {
Rectangle {
id: background
anchors.fill: parent
color: root.showBackground ? Theme.palette.baseColor2 : "transparent"
color: root.showBackground ? Theme.palette.statusAppNavBar.backgroundColor : "transparent"
radius: 8
clip: true

View File

@ -43,6 +43,8 @@ Item {
implicitWidth: layout.implicitWidth
implicitHeight: layout.implicitHeight
opacity: enabled ? 1 : 0.3
LayoutMirroring.childrenInherit: true
ColumnLayout {
@ -55,7 +57,6 @@ Item {
id: labelItem
Layout.fillWidth: true
visible: !!text
font.pixelSize: 15
color: root.enabled ? Theme.palette.directColor1 : Theme.palette.baseColor1
}
@ -69,7 +70,7 @@ Item {
enabled: root.enabled
font.family: Theme.palette.baseFont.name
font.pixelSize: 14
font.pixelSize: root.size === StatusComboBox.Size.Large ? Theme.secondaryTextFontSize : 13
padding: 16
spacing: 16
@ -112,7 +113,7 @@ Item {
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
text: comboBox.displayText
color: root.enabled ? Theme.palette.directColor1 : Theme.palette.baseColor1
color: root.type === StatusComboBox.Type.Secondary ? Theme.palette.baseColor1 : Theme.palette.directColor1
}
indicator: StatusIcon {
@ -186,7 +187,7 @@ Item {
Layout.fillWidth: true
Layout.topMargin: 11
visible: !!text
font.pixelSize: 12
font.pixelSize: Theme.tertiaryTextFontSize
color: Theme.palette.dangerColor1
horizontalAlignment: TextEdit.AlignRight
wrapMode: Text.WordWrap

View File

@ -27,7 +27,7 @@ QtObject {
//icon bg
property real bgWidth
property real bgHeight
property int bgRadius
property real bgRadius
property color bgColor: "transparent"
property color bgBorderColor: "transparent"
property int bgBorderWidth: 0

View File

@ -434,7 +434,7 @@ void ConcatModel::initRoles()
m_nameRoles.reserve(m_expectedRoles.size() + 1);
for (auto& expectedRoleName : qAsConst(m_expectedRoles))
for (const auto& expectedRoleName : qAsConst(m_expectedRoles))
m_nameRoles.try_emplace(expectedRoleName.toUtf8(), m_nameRoles.size());
for (auto sourceModel : qAsConst(m_sources)) {
@ -455,7 +455,7 @@ void ConcatModel::initRoles()
m_roleNames.reserve(m_nameRoles.size());
for (auto& [name, role] : m_nameRoles)
for (const auto& [name, role] : m_nameRoles)
m_roleNames.insert(role, name);
}
@ -580,7 +580,7 @@ void ConcatModel::connectModelSlots(int index, QAbstractItemModel *model)
emit this->layoutAboutToBeChanged();
});
connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, [this]
connect(model, &QAbstractItemModel::layoutChanged, this, [this]
{
emit this->layoutChanged();
});

View File

@ -6,6 +6,7 @@
ManageTokensController::ManageTokensController(QObject* parent)
: QObject(parent)
, m_regularTokensModel(new ManageTokensModel(this))
, m_regularTokenGroupsModel(new ManageTokensModel(this))
, m_communityTokensModel(new ManageTokensModel(this))
, m_communityTokenGroupsModel(new ManageTokensModel(this))
, m_hiddenTokensModel(new ManageTokensModel(this))
@ -33,6 +34,7 @@ ManageTokensController::ManageTokensController(QObject* parent)
m_communityTokensModel->setCommunityIds(m_communityIds);
m_communityTokensModel->saveCustomSortOrder();
rebuildCommunityTokenGroupsModel();
rebuildRegularTokenGroupsModel();
#ifdef QT_DEBUG
qCInfo(manageTokens) << "!!! ADDING NEW SOURCE DATA TOOK" << t.nsecsElapsed()/1'000'000.f << "ms";
#endif
@ -270,6 +272,8 @@ bool ManageTokensController::lessThan(const QString& lhsSymbol, const QString& r
bool ManageTokensController::filterAcceptsSymbol(const QString& symbol) const
{
if (symbol.isEmpty()) return true;
const auto& [pos, visible, groupId] = m_settingsData.value(symbol, {INT_MAX, true, QString()});
return visible;
}
@ -345,6 +349,9 @@ void ManageTokensController::parseSourceModel()
reloadCommunityIds();
m_communityTokensModel->setCommunityIds(m_communityIds);
// build collections
rebuildRegularTokenGroupsModel();
// (pre)sort
for (auto model: m_allModels) {
model->applySort();
@ -478,6 +485,46 @@ void ManageTokensController::rebuildCommunityTokenGroupsModel()
qCDebug(manageTokens) << "!!! GROUPS MODEL REBUILT WITH GROUPS:" << communityIds;
}
void ManageTokensController::rebuildRegularTokenGroupsModel()
{
QStringList collectionIds;
QList<TokenData> result;
const auto count = m_regularTokensModel->count();
for (auto i = 0; i < count; i++) {
const auto& collectionToken = m_regularTokensModel->itemAt(i);
const auto collectionId = collectionToken.collectionUid;
if (collectionId.isEmpty())
continue;
if (!collectionIds.contains(collectionId)) { // insert into groups
collectionIds.append(collectionId);
TokenData tokenGroup;
tokenGroup.collectionUid = collectionId;
tokenGroup.collectionName = collectionToken.collectionName;
tokenGroup.image = collectionToken.image;
tokenGroup.balance = 1;
result.append(tokenGroup);
} else { // update group's childCount
const auto tokenGroup = std::find_if(result.cbegin(), result.cend(), [collectionId](const auto& item) {
return collectionId == item.collectionUid;
});
if (tokenGroup != result.cend()) {
const auto row = std::distance(result.cbegin(), tokenGroup);
TokenData updTokenGroup = result.takeAt(row);
updTokenGroup.balance = updTokenGroup.balance.toInt() + 1;
result.insert(row, updTokenGroup);
}
}
}
m_regularTokenGroupsModel->clear();
for (const auto& group: result)
m_regularTokenGroupsModel->addItem(group);
qCDebug(manageTokens) << "!!! COLLECTION MODEL REBUILT WITH GROUPS:" << collectionIds;
}
QString ManageTokensController::settingsKey() const
{
return m_settingsKey;

View File

@ -17,10 +17,11 @@ class ManageTokensController : public QObject, public QQmlParserStatus
Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged FINAL)
Q_PROPERTY(QString settingsKey READ settingsKey WRITE setSettingsKey NOTIFY settingsKeyChanged FINAL REQUIRED)
Q_PROPERTY(bool arrangeByCommunity READ arrangeByCommunity WRITE setArrangeByCommunity NOTIFY arrangeByCommunityChanged FINAL) // TODO persist in settings
// TODO arrangeByCollection for collectibles
// output properties
Q_PROPERTY(QAbstractItemModel* regularTokensModel READ regularTokensModel CONSTANT FINAL)
// TODO regularTokenGroupsModel for grouped (collections of) collectibles?
Q_PROPERTY(QAbstractItemModel* regularTokenGroupsModel READ regularTokenGroupsModel CONSTANT FINAL)
Q_PROPERTY(QAbstractItemModel* communityTokensModel READ communityTokensModel CONSTANT FINAL)
Q_PROPERTY(QAbstractItemModel* communityTokenGroupsModel READ communityTokenGroupsModel CONSTANT FINAL)
Q_PROPERTY(QAbstractItemModel* hiddenTokensModel READ hiddenTokensModel CONSTANT FINAL)
@ -68,6 +69,9 @@ private:
ManageTokensModel* m_regularTokensModel{nullptr};
QAbstractItemModel* regularTokensModel() const { return m_regularTokensModel; }
ManageTokensModel* m_regularTokenGroupsModel{nullptr};
QAbstractItemModel* regularTokenGroupsModel() const { return m_regularTokenGroupsModel; }
ManageTokensModel* m_communityTokensModel{nullptr};
QAbstractItemModel* communityTokensModel() const { return m_communityTokensModel; }
@ -86,8 +90,9 @@ private:
QStringList m_communityIds;
void reloadCommunityIds();
void rebuildCommunityTokenGroupsModel();
void rebuildRegularTokenGroupsModel();
const std::array<ManageTokensModel*, 4> m_allModels {m_regularTokensModel, m_communityTokensModel, m_communityTokenGroupsModel, m_hiddenTokensModel};
const std::array<ManageTokensModel*, 5> m_allModels {m_regularTokensModel, m_regularTokenGroupsModel, m_communityTokensModel, m_communityTokenGroupsModel, m_hiddenTokensModel};
QString m_settingsKey;
QString settingsKey() const;

View File

@ -174,7 +174,8 @@ void ManageTokensModel::saveCustomSortOrder()
}
m_data[i] = newToken;
}
emit dataChanged(index(0, 0), index(count - 1, 0), {TokenDataRoles::CustomSortOrderNoRole});
if (count > 0)
emit dataChanged(index(0, 0), index(count - 1, 0), {TokenDataRoles::CustomSortOrderNoRole});
}
void ManageTokensModel::applySort()

View File

@ -1598,8 +1598,10 @@ private slots:
QSignalSpy layoutChangedSpy(&model, &ConcatModel::layoutChanged);
emit sourceModel1.model()->layoutAboutToBeChanged();
emit sourceModel1.model()->layoutChanged();
QCOMPARE(layoutAboutToBeChangedSpy.count(), 1);
QCOMPARE(layoutChangedSpy.count(), 0);
emit sourceModel1.model()->layoutChanged();
QCOMPARE(layoutAboutToBeChangedSpy.count(), 1);
QCOMPARE(layoutChangedSpy.count(), 1);
}

View File

@ -0,0 +1,369 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import utils 1.0
import shared.controls 1.0
import SortFilterProxyModel 0.2
ComboBox {
id: root
required property var regularTokensModel // "uncategorized" collectibles (not grouped)
required property var regularTokenGroupsModel // collection groups
required property var communityTokenGroupsModel // community groups
property bool hasCommunityGroups
property alias selectedFilterGroupIds: d.selectedFilterGroupIds
readonly property bool hasEnabledFilters: d.selectedFilterGroupIds.length
function clearFilter() {
d.selectedFilterGroupIds = []
}
enabled: d.searchTextLowerCase || d.combinedProxyModel.count || d.uncategorizedModel.count
opacity: enabled ? 1 : 0.3
displayText: qsTr("Collection")
horizontalPadding: 12
verticalPadding: Style.current.halfPadding
spacing: Style.current.halfPadding
font.family: Theme.palette.baseFont.name
font.pixelSize: Style.current.additionalTextSize
QtObject {
id: d
readonly property int defaultDelegateHeight: 34
readonly property string searchTextLowerCase: searchBox.input.text.toLowerCase()
readonly property var combinedModel: ConcatModel {
sources: [
SourceModel {
model: root.communityTokenGroupsModel
markerRoleValue: "community"
},
SourceModel {
model: root.regularTokenGroupsModel
markerRoleValue: "collection"
}
]
markerRoleName: "sourceGroup"
onRowsRemoved: root.clearFilter() // different underlying model -> uncheck all groups
}
readonly property var combinedProxyModel: SortFilterProxyModel {
sourceModel: d.combinedModel
proxyRoles: [
JoinRole {
name: "groupName"
roleNames: ["collectionName", "communityName"]
separator: ""
},
JoinRole {
name: "groupId"
roleNames: ["collectionUid", "communityId"]
separator: ""
}
]
filters: [
ExpressionFilter {
enabled: d.searchTextLowerCase !== ""
expression: {
d.searchTextLowerCase // ensure expression is reevaluated when searchString changes
return model.groupName.toLowerCase().includes(d.searchTextLowerCase) || model.groupId.toLowerCase().includes(d.searchTextLowerCase)
}
}
]
}
readonly property var uncategorizedModel: SortFilterProxyModel { // regular collectibles with no collection
sourceModel: root.regularTokensModel
filters: ValueFilter {
roleName: "collectionUid"
value: ""
}
onCountChanged: if (!count) d.removeFilter("") // different underlying model -> uncheck
}
property var selectedFilterGroupIds: []
readonly property bool allVisuallyChecked: selectedFilterGroupIds.length === 0
function addFilter(groupId) {
if (d.selectedFilterGroupIds.includes(groupId))
return
const newFilters = d.selectedFilterGroupIds.concat(groupId)
d.selectedFilterGroupIds = newFilters
}
function removeFilter(groupId) {
const newFilters = d.selectedFilterGroupIds.filter((filter) => filter !== groupId)
d.selectedFilterGroupIds = newFilters
}
function removeGroupFilters() {
const newFilters = d.selectedFilterGroupIds.filter((filter) => filter === "")
d.selectedFilterGroupIds = newFilters
}
}
background: Rectangle {
border.width: 1
border.color: Theme.palette.directColor7
radius: Style.current.radius
color: root.down ? Theme.palette.baseColor2 : "transparent"
HoverHandler {
cursorShape: root.enabled ? Qt.PointingHandCursor : undefined
}
}
contentItem: RowLayout {
spacing: -6 // badge has an implicit border :/
StatusBaseText {
leftPadding: root.horizontalPadding
rightPadding: root.horizontalPadding
font.pixelSize: root.font.pixelSize
font.weight: Font.Medium
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
text: root.displayText
color: Theme.palette.baseColor1
}
StatusBadge {
Layout.preferredHeight: 16
Layout.preferredWidth: 16
Layout.rightMargin: Style.current.halfPadding
value: d.selectedFilterGroupIds.length
visible: root.hasEnabledFilters
}
}
indicator: StatusIcon {
x: root.mirrored ? root.horizontalPadding : root.width - width - root.horizontalPadding
y: root.topPadding + (root.availableHeight - height) / 2
width: 16
height: width
icon: "chevron-down"
color: Theme.palette.baseColor1
}
popup: Popup {
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
y: root.height + 4
implicitWidth: 290
implicitHeight: Math.min(contentHeight+margins, 380)
margins: Style.current.halfPadding
padding: 0
bottomPadding: Style.current.halfPadding
background: Rectangle {
color: Theme.palette.statusSelect.menuItemBackgroundColor
radius: Style.current.radius
layer.enabled: true
layer.effect: DropShadow {
horizontalOffset: 0
verticalOffset: 4
radius: 12
samples: 25
spread: 0.2
color: Theme.palette.dropShadow
}
}
contentItem: ColumnLayout {
spacing: 0
SearchBox {
id: searchBox
Layout.fillWidth: true
Layout.topMargin: 12
Layout.leftMargin: Style.current.halfPadding
Layout.rightMargin: Style.current.halfPadding
Layout.bottomMargin: 12
minimumHeight: d.defaultDelegateHeight
maximumHeight: d.defaultDelegateHeight
input.edit.font.pixelSize: root.font.pixelSize
input.placeholder.font.pixelSize: root.font.pixelSize
input.asset.width: 16
input.asset.height: 16
topPadding: 0
bottomPadding: 0
placeholderText: qsTr("Collection, community, name or #")
}
StatusListView {
Layout.fillWidth: true
Layout.fillHeight: true
implicitWidth: contentWidth
implicitHeight: contentHeight
model: d.combinedProxyModel
delegate: Item { // NB anything but AbstractButton to prevent auto-closing of the popup
width: ListView.view.width
implicitHeight: customMenuDelegate.implicitHeight
CustomItemDelegate {
id: customMenuDelegate
width: parent.width
}
}
section.property: "sourceGroup"
section.delegate: ColumnLayout {
id: sectionDelegate
width: ListView.view.width
height: d.defaultDelegateHeight
spacing: 0
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Theme.palette.statusListItem.backgroundColor
StatusBaseText {
anchors.fill: parent
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
verticalAlignment: Text.AlignVCenter
text: section === "community" ? qsTr("Community minted") : root.hasCommunityGroups ? qsTr("Other")
: qsTr("Collections")
color: Theme.palette.baseColor1
font.pixelSize: Style.current.tertiaryTextFontSize
elide: Text.ElideRight
}
}
// floating divider
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 4
color: sectionDelegate.y <= sectionDelegate.ListView.view.contentY && sectionDelegate.y !== 0 ? Theme.palette.directColor8
: Theme.palette.statusListItem.backgroundColor
}
}
section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
}
StatusMenuSeparator {
Layout.fillWidth: true
visible: d.uncategorizedModel.count
}
CustomItemDelegate {
Layout.fillWidth: true
icon.name: "image"
text: qsTr("No collection")
count: d.uncategorizedModel.count
groupId: ""
visible: count
}
}
}
component CustomItemDelegate: CheckDelegate {
id: menuDelegate
property int count: model.enabledNetworkBalance
readonly property bool isCommunityGroup: !!model && !!model.communityId
property string groupId: model.groupId
readonly property string groupImage: !!model ? model.communityImage || model.imageUrl : ""
highlighted: hovered
leftPadding: Style.current.padding
rightPadding: 44
verticalPadding: 4
spacing: root.spacing
font: root.font
text: model.groupName
icon.source: groupImage
icon.name: isCommunityGroup ? "group" : "gallery"
checked: d.selectedFilterGroupIds.includes(menuDelegate.groupId)
onToggled: checked ? d.addFilter(menuDelegate.groupId) : d.removeFilter(menuDelegate.groupId)
background: Rectangle {
color: menuDelegate.highlighted ? Theme.palette.statusMenu.hoverBackgroundColor : "transparent"
HoverHandler {
cursorShape: menuDelegate.enabled ? Qt.PointingHandCursor : undefined
}
}
indicator: Rectangle {
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
implicitWidth: 18
implicitHeight: implicitWidth
radius: 2
color: menuDelegate.down || menuDelegate.checkState !== Qt.Checked
? Theme.palette.directColor8
: Theme.palette.primaryColor1
StatusIcon {
icon: "checkbox"
width: 11
height: 8
anchors.centerIn: parent
anchors.horizontalCenterOffset: 1
color: d.allVisuallyChecked ? Theme.palette.baseColor1 : Theme.palette.white
visible: menuDelegate.down || menuDelegate.checkState !== Qt.Unchecked || d.allVisuallyChecked
}
}
contentItem: RowLayout {
spacing: root.spacing
Loader {
Layout.preferredWidth: 32
Layout.preferredHeight: 32
sourceComponent: !!menuDelegate.groupImage ? roundImage : roundIcon
Component {
id: roundImage
StatusRoundedImage {
image.source: menuDelegate.icon.source
radius: menuDelegate.isCommunityGroup ? width/2 : 6
showLoadingIndicator: true
image.fillMode: Image.PreserveAspectCrop
}
}
Component {
id: roundIcon
StatusRoundIcon {
asset.bgRadius: menuDelegate.isCommunityGroup ? width/2 : 6
asset.bgWidth: 16
asset.bgHeight: 16
asset.bgColor: Theme.palette.primaryColor3
asset.width: 16
asset.height: 16
asset.name: menuDelegate.icon.name
asset.color: Theme.palette.primaryColor1
}
}
}
StatusBaseText {
Layout.fillWidth: true
text: menuDelegate.text
elide: Text.ElideRight
font.pixelSize: menuDelegate.font.pixelSize
font.weight: menuDelegate.checked ? Font.Medium : Font.Normal
}
Item { Layout.fillWidth: true }
StatusBaseText {
font.pixelSize: Style.current.tertiaryTextFontSize
color: Theme.palette.baseColor1
text: menuDelegate.count
}
}
}
}

View File

@ -5,6 +5,7 @@ StatusTxProgressBar 1.0 StatusTxProgressBar.qml
StatusDateRangePicker 1.0 StatusDateRangePicker.qml
ActivityFilterTagItem 1.0 ActivityFilterTagItem.qml
SortOrderComboBox 1.0 SortOrderComboBox.qml
FilterComboBox 1.0 FilterComboBox.qml
ManageTokenMenuButton 1.0 ManageTokenMenuButton.qml
ManageTokensCommunityTag 1.0 ManageTokensCommunityTag.qml
ManageTokensDelegate 1.0 ManageTokensDelegate.qml

View File

@ -31,16 +31,12 @@ Column {
spacing: 8
StatusRoundButton {
id: filterButton
width: 32
height: 32
icon.name: "filter"
border.width: 1
border.color: Theme.palette.directColor8
type: StatusRoundButton.Type.Tertiary
icon.color: Theme.palette.primaryColor1
onClicked: {
StatusComboBox {
height: 34
size: StatusComboBox.Size.Small
type: StatusComboBox.Type.Secondary
control.displayText: qsTr("Filter")
control.popup.onOpened: {
activityFilterStore.updateStartTimestamp()
activityFilterMenu.popup(x, y + height + 4)
}

View File

@ -64,6 +64,7 @@ ColumnLayout {
readonly property var controller: ManageTokensController {
settingsKey: "WalletCollectibles"
sourceModel: d.renamedModel
}
function hideAllCommunityTokens(communityId) {
@ -87,6 +88,16 @@ ColumnLayout {
d.controller.settingsDirty
return d.controller.filterAcceptsSymbol(model.symbol) && (customFilter.isCommunity ? !!model.communityId : !model.communityId)
}
},
ExpressionFilter {
enabled: customFilter.isCommunity && cmbFilter.hasEnabledFilters
expression: cmbFilter.selectedFilterGroupIds.includes(model.communityId) ||
(!model.communityId && cmbFilter.selectedFilterGroupIds.includes(""))
},
ExpressionFilter {
enabled: !customFilter.isCommunity && cmbFilter.hasEnabledFilters
expression: cmbFilter.selectedFilterGroupIds.includes(model.collectionUid) ||
(!model.collectionUid && cmbFilter.selectedFilterGroupIds.includes(""))
}
]
sorters: [
@ -109,13 +120,14 @@ ColumnLayout {
category: "CollectiblesViewSortSettings"
property alias currentSortField: cmbTokenOrder.currentIndex
property alias currentSortOrder: cmbTokenOrder.currentSortOrder
property alias selectedFilterGroupIds: cmbFilter.selectedFilterGroupIds
}
ColumnLayout {
Layout.fillWidth: true
Layout.preferredHeight: root.filterVisible && (d.hasCollectibles || d.hasCommunityCollectibles) ? implicitHeight : 0
Layout.preferredHeight: root.filterVisible ? implicitHeight : 0
spacing: 20
opacity: Layout.preferredHeight < implicitHeight ? 0 : 1
opacity: root.filterVisible ? 1 : 0
Behavior on Layout.preferredHeight { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
@ -128,6 +140,22 @@ ColumnLayout {
Layout.fillWidth: true
spacing: Style.current.halfPadding
FilterComboBox {
id: cmbFilter
regularTokensModel: d.controller.regularTokensModel
regularTokenGroupsModel: d.controller.regularTokenGroupsModel
communityTokenGroupsModel: d.controller.communityTokenGroupsModel
hasCommunityGroups: d.hasCommunityCollectibles
}
Rectangle {
Layout.preferredHeight: 34
Layout.preferredWidth: 1
Layout.leftMargin: 12
Layout.rightMargin: 12
color: Theme.palette.baseColor2
}
StatusBaseText {
color: Theme.palette.baseColor1
font.pixelSize: Style.current.additionalTextSize
@ -150,6 +178,15 @@ ColumnLayout {
root.manageTokensRequested()
}
}
Item { Layout.fillWidth: true }
StatusLinkText {
visible: cmbFilter.hasEnabledFilters
normalColor: Theme.palette.primaryColor1
text: qsTr("Clear filter")
onClicked: cmbFilter.clearFilter()
}
}
StatusDialogDivider {
@ -159,6 +196,7 @@ ColumnLayout {
ShapeRectangle {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
visible: !d.hasCollectibles && !d.hasCommunityCollectibles
text: qsTr("Collectibles will appear here")
}
@ -185,7 +223,7 @@ ColumnLayout {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
Layout.bottomMargin: Style.current.halfPadding
visible: d.hasCommunityCollectibles
visible: d.hasCollectibles && d.hasCommunityCollectibles
}
RowLayout {

View File

@ -189,6 +189,7 @@ Item {
overview: RootStore.overview
showAllAccounts: root.showAllAccounts
sendModal: root.sendModal
filterVisible: filterButton.checked
onLaunchTransactionDetail: function (entry, entryIndex) {
transactionDetailView.transactionIndex = entryIndex
transactionDetailView.transaction = entry

View File

@ -110,7 +110,7 @@ StatusListItem {
font.pixelSize: 13
loading: modelData && modelData.marketDetailsLoading
text: modelData && modelData.marketDetails && modelData.marketDetails.changePct24hour !== undefined ? "%1 %2%".arg(root.upDownTriangle).arg(LocaleUtils.numberToLocaleString(modelData.marketDetails.changePct24hour, 2))
: "---"
: "---"
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
@ -124,7 +124,7 @@ StatusListItem {
customColor: root.textColor
font.pixelSize: 13
loading: modelData && modelData.marketDetailsLoading
text: modelData && modelData.marketDetails ? LocaleUtils.currencyAmountToLocaleString(modelData.marketDetails.currencyPrice) : ""
text: modelData && modelData.marketDetails ? LocaleUtils.currencyAmountToLocaleString(modelData.marketDetails.currencyPrice) : ""
}
}
ManageTokensCommunityTag {

View File

@ -126,6 +126,11 @@ ColumnLayout {
expression: model.marketDetails.currencyPrice.amount
expectedRoles: ["marketDetails"]
},
FastExpressionRole {
name: "changePct24hour"
expression: model.marketDetails.changePct24hour
expectedRoles: ["marketDetails"]
},
FastExpressionRole {
name: "isCommunityAsset"
expression: !!model.communityId
@ -170,7 +175,7 @@ ColumnLayout {
Layout.fillWidth: true
Layout.preferredHeight: root.filterVisible ? implicitHeight : 0
spacing: 20
opacity: Layout.preferredHeight < implicitHeight ? 0 : 1
opacity: root.filterVisible ? 1 : 0
Behavior on Layout.preferredHeight { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
@ -214,38 +219,29 @@ ColumnLayout {
}
}
StatusScrollView {
StatusListView {
id: assetsListView
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Style.current.padding
leftPadding: 0
verticalPadding: 0
contentWidth: availableWidth
StatusListView {
id: assetsListView
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
objectName: "assetViewStatusListView"
model: root.areAssetsLoading ? d.loadingItemsCount : d.customSFPM
delegate: delegateLoader
section {
property: "isCommunityAsset"
delegate: Loader {
width: ListView.view.width
required property string section
sourceComponent: section === "true" ? sectionDelegate: null
}
Layout.preferredHeight: contentHeight
Layout.fillHeight: true
objectName: "assetViewStatusListView"
model: root.areAssetsLoading ? d.loadingItemsCount : d.customSFPM
delegate: delegateLoader
section {
property: "isCommunityAsset"
delegate: Loader {
width: ListView.view.width
required property string section
sourceComponent: section === "true" ? sectionDelegate : null
}
}
}
Component {
id: sectionDelegate
ColumnLayout {
width: ListView.view.width
width: parent.width
spacing: 0
StatusDialogDivider {
@ -282,7 +278,7 @@ ColumnLayout {
property var modelData: model
property int delegateIndex: index
width: ListView.view.width
sourceComponent: root.areAssetsLoading ? loadingTokenDelegate: tokenDelegate
sourceComponent: root.areAssetsLoading ? loadingTokenDelegate : tokenDelegate
}
}
@ -297,7 +293,7 @@ ColumnLayout {
id: tokenDelegate
TokenDelegate {
objectName: "AssetView_TokenListItem_" + (!!modelData ? modelData.symbol : "")
readonly property string balance: !!modelData && !!modelData.currentBalance? "%1".arg(modelData.currentBalance) : "" // Needed for the tests
readonly property string balance: !!modelData && !!modelData.currentBalance ? "%1".arg(modelData.currentBalance) : "" // Needed for the tests
errorTooltipText_1: !!modelData && !!networkConnectionStore ? networkConnectionStore.getBlockchainNetworkDownTextForToken(modelData.balances) : ""
errorTooltipText_2: !!networkConnectionStore ? networkConnectionStore.getMarketNetworkDownText() : ""
subTitle: {

View File

@ -30,6 +30,7 @@ ColumnLayout {
property var overview
property bool showAllAccounts: false
property var sendModal
property bool filterVisible
signal launchTransactionDetail(var transaction, int entryIndex)
@ -43,7 +44,6 @@ ColumnLayout {
if (!visible)
return
filterPanelLoader.active = true
if (RootStore.transactionActivityStatus.isFilterDirty) {
WalletStores.RootStore.currentActivityFiltersStore.applyAllFilters()
}
@ -101,11 +101,11 @@ ColumnLayout {
Loader {
id: filterPanelLoader
active: false
active: root.filterVisible && (d.isInitialLoading || transactionListRoot.count > 0 || WalletStores.RootStore.currentActivityFiltersStore.filtersSet)
visible: active
asynchronous: true
Layout.fillWidth: true
sourceComponent: ActivityFilterPanel {
visible: d.isInitialLoading || transactionListRoot.count > 0 || WalletStores.RootStore.currentActivityFiltersStore.filtersSet
activityFilterStore: WalletStores.RootStore.currentActivityFiltersStore
store: WalletStores.RootStore
hideNoResults: newTransactions.visible

View File

@ -239,7 +239,7 @@ StatusMenu {
StatusAction {
id: blockMenuItem
objectName: blockUser_StatusItem
objectName: "blockUser_StatusItem"
text: qsTr("Block User")
icon.name: "cancel"
type: StatusAction.Type.Danger