feat(@desktop/wallet): Add loading state for activity feed (#11326)

closes #11072
This commit is contained in:
Cuteivist 2023-06-30 15:50:06 +02:00 committed by GitHub
parent d0f3b6d652
commit bade10c5e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 98 deletions

View File

@ -181,21 +181,26 @@ QtObject:
self.errorCodeChanged()
proc processResponse(self: Controller, response: JsonNode) =
defer: self.setLoadingData(false)
let res = fromJson(response, backend_activity.FilterResponse)
defer: self.setErrorCode(res.errorCode.int)
if res.errorCode != ErrorCodeSuccess:
error "error fetching activity entries: ", res.errorCode
if res.errorCode == ErrorCodeFilterCanceled and self.model.getCount() == 0:
# Only successful initial response can change loading flag
return
if res.errorCode != ErrorCodeSuccess:
self.setLoadingData(false)
error "error fetching activity entries: ", res.errorCode
return
let entries = self.backendToPresentation(res.activities)
self.model.setEntries(entries, res.offset, res.hasMore)
self.setLoadingData(false)
proc updateFilter*(self: Controller) {.slot.} =
self.setLoadingData(true)
self.model.resetModel(@[])
let response = backend_activity.filterActivityAsync(self.addresses, seq[backend_activity.ChainId](self.chainIds), self.currentActivityFilter, 0, FETCH_BATCH_COUNT_DEFAULT)
if response.error != nil:

View File

@ -71,11 +71,14 @@ QtObject:
self.hasMore = hasMore
self.hasMoreChanged()
proc resetModel*(self: Model, newEntries: seq[ActivityEntry]) =
self.beginResetModel()
self.entries = newEntries
self.endResetModel()
proc setEntries*(self: Model, newEntries: seq[ActivityEntry], offset: int, hasMore: bool) =
if offset == 0:
self.beginResetModel()
self.entries = newEntries
self.endResetModel()
self.resetModel(newEntries)
else:
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete

View File

@ -523,6 +523,10 @@ StatusListItem {
// subtitle
subTitle: {
if (root.loading) {
return "dummy text dummy text dummy text dummy text dummy text dummy text"
}
if (!root.isModelDataValid) {
return ""
}
@ -542,7 +546,7 @@ StatusListItem {
return qsTr("%1 to %2 via %3").arg(transactionValue).arg(toAddress).arg(networkName)
}
}
statusListItemSubTitle.maximumLoadingStateWidth: 300
statusListItemSubTitle.maximumLoadingStateWidth: 400
statusListItemSubTitle.customColor: Theme.palette.directColor1
statusListItemSubTitle.font.pixelSize: root.loading ? d.loadingPixelSize : d.subtitlePixelSize
statusListItemTagsRowLayout.anchors.topMargin: 4 // Spacing between title row nad subtitle row

View File

@ -38,6 +38,7 @@ QtObject {
property var history: typeof walletSectionTransactions !== "undefined" ? walletSectionTransactions
: null
property var historyTransactions: Global.appIsReady? walletSection.activityController.model : null
readonly property bool loadingHistoryTransactions: Global.appIsReady && walletSection.activityController.loadingData
property bool isNonArchivalNode: history ? history.isNonArchivalNode
: false
property var marketValueStore: TokenMarketValuesStore{}
@ -175,7 +176,7 @@ QtObject {
function fetchMoreTransactions() {
if (RootStore.historyTransactions.count === 0
|| !RootStore.historyTransactions.hasMore
|| walletSection.activityController.loadingData)
|| loadingHistoryTransactions)
return
walletSection.activityController.loadMoreItems()
}

View File

@ -32,17 +32,9 @@ ColumnLayout {
QtObject {
id: d
property bool isLoading: false
readonly property bool isInitialLoading: RootStore.loadingHistoryTransactions && transactionListRoot.count === 0
property var activityFiltersStore: WalletStores.ActivityFiltersStore{}
}
Connections {
target: RootStore.history
function onLoadingTrxHistoryChanged(isLoading: bool, address: string) {
if (root.overview.mixedcaseAddress.toLowerCase() === address.toLowerCase()) {
d.isLoading = isLoading
}
}
readonly property int loadingSectionWidth: 56
}
StyledText {
@ -59,18 +51,18 @@ ColumnLayout {
id: noTxs
Layout.fillWidth: true
Layout.preferredHeight: 42
visible: !d.isLoading && transactionListRoot.count === 0 && !d.activityFiltersStore.filtersSet
visible: !d.isInitialLoading && !d.activityFiltersStore.filtersSet && transactionListRoot.count === 0
font.pixelSize: Style.current.primaryTextFontSize
text: qsTr("Activity for this account will appear here")
}
ActivityFilterPanel {
id: filterComponent
visible: !d.isLoading && (transactionListRoot.count !== 0 || d.activityFiltersStore.filtersSet)
visible: !noTxs.visible && (!d.isInitialLoading || d.activityFiltersStore.filtersSet)
Layout.fillWidth: true
activityFilterStore: d.activityFiltersStore
store: WalletStores.RootStore
isLoading: d.isLoading
isLoading: d.isInitialLoading
}
Item {
@ -93,8 +85,6 @@ ColumnLayout {
objectName: "walletAccountTransactionList"
anchors.fill: parent
property string firstSection
model: SortFilterProxyModel {
id: txModel
@ -136,57 +126,15 @@ ColumnLayout {
footer: footerComp
displaced: Transition { // TODO Remove animation when ordered fetching of transactions is implemented
NumberAnimation { properties: "y"; duration: 250; easing.type: Easing.Linear; alwaysRunToEnd: true }
}
readonly property point lastVisibleItemPos: Qt.point(0, contentY + height - 1)
property int lastVisibleIndex: indexAt(lastVisibleItemPos.x, lastVisibleItemPos.y)
onCountChanged: {
// Preserve last visible item in view when more items added at the end or
// inbetween
// TODO Remove this logic, when new activity design is implemented
// and items are loaded in order
const lastVisibleItem = itemAtIndex(lastVisibleIndex)
const newItem = itemAt(lastVisibleItemPos.x, lastVisibleItemPos.y)
const lastVisibleItemY = lastVisibleItem ? lastVisibleItem.y : -1
if (newItem) {
if (newItem.y < lastVisibleItemY) { // item inserted higher than last visible
lastVisibleIndex = indexAt(lastVisibleItemPos.x, lastVisibleItemPos.y)
}
}
}
currentIndex: 0
property bool userScrolled: false
// TODO Remove this logic, when new activity design is implemented
// and items are loaded in order
onMovingVerticallyChanged: {
if (!userScrolled) {
userScrolled = true
currentIndex = Qt.binding(() => lastVisibleIndex >= 0 ? lastVisibleIndex : (count - 1))
}
lastVisibleIndex = indexAt(lastVisibleItemPos.x, lastVisibleItemPos.y)
}
ScrollBar.vertical: StatusScrollBar {}
section.property: "date"
topMargin: -20 // Top margin for first section. Section cannot have different sizes
topMargin: d.isInitialLoading ? 0 : -20 // Top margin for first section. Section cannot have different sizes
section.delegate: ColumnLayout {
id: sectionDelegate
readonly property bool isFirstSection: ListView.view.firstSection === section
width: ListView.view.width
// display always first section. Other sections when more items are being fetched must not be visible
// 1 because we don't use empty for loading state
// Additionaly height must be defined so all sections use same height to to glitch sections when updating model
height: isFirstSection || section.length > 1 ? 58 : 0
visible: height > 0 // display always first section. Other sections when more items are being fetched must not be visible
height: 58
spacing: 0
required property string section
@ -197,32 +145,47 @@ ColumnLayout {
implicitHeight: 1
}
StatusTextWithLoadingState {
StatusBaseText {
id: sectionText
Layout.alignment: Qt.AlignBottom
leftPadding: 16
bottomPadding: 8
text: loading ? "dummy" : parent.section // dummy text because loading component height depends on text height, and not visible with height == 0
Binding on width {
when: sectionText.loading
value: 56
restoreMode: Binding.RestoreBindingOrValue
}
customColor: Theme.palette.baseColor1
leftPadding: Style.current.padding
bottomPadding: Style.current.halfPadding
text: parent.section
font.pixelSize: 13
verticalAlignment: Qt.AlignBottom
loading: { // First section must be loading when first item in the list is loading. Other sections are never visible until have date value
if (parent.ListView.view.count > 0) {
const firstItem = parent.ListView.view.itemAtIndex(0)
if (sectionDelegate.isFirstSection && firstItem && firstItem.loading) {
return true
}
}
return false
}
}
}
onAtYEndChanged: if(atYEnd) RootStore.fetchMoreTransactions()
visibleArea.onYPositionChanged: tryFetchMoreTransactions()
Connections {
target: RootStore
function onLoadingHistoryTransactionsChanged() {
// Calling timer instead directly to not cause binding loop
if (!RootStore.loadingHistoryTransactions)
fetchMoreTimer.start()
}
}
function tryFetchMoreTransactions() {
if (!footerItem || !RootStore.historyTransactions.hasMore)
return
const footerYPosition = footerItem.height / contentHeight
if (footerYPosition >= 1.0) {
return
}
// On startup, first loaded ListView will have heightRatio equal 0
if (footerYPosition + visibleArea.yPosition + visibleArea.heightRatio > 1.0) {
RootStore.fetchMoreTransactions()
}
}
Timer {
id: fetchMoreTimer
interval: 1
onTriggered: transactionListRoot.tryFetchMoreTransactions()
}
}
Separator {
@ -315,25 +278,45 @@ ColumnLayout {
launchTransactionDetail(modelData)
}
}
loading: false // TODO handle loading state
Component.onCompleted: {
if (index == 0)
ListView.view.firstSection = date
}
}
}
Component {
id: footerComp
ColumnLayout {
id: footerColumn
readonly property bool allActivityLoaded: !RootStore.historyTransactions.hasMore && transactionListRoot.count !== 0
width: root.width
visible: !RootStore.historyTransactions.hasMore && transactionListRoot.count !== 0
spacing: 12
Separator {
Layout.fillWidth: true
Layout.topMargin: Style.current.halfPadding
visible: d.isInitialLoading
}
StatusTextWithLoadingState {
Layout.alignment: Qt.AlignLeft
Layout.leftMargin: Style.current.padding
text: "01.01.2000"
width: d.loadingSectionWidth
font.pixelSize: 15
loading: visible
visible: d.isInitialLoading
}
Repeater {
model: RootStore.historyTransactions.hasMore || d.isInitialLoading ? 10 : 0
TransactionDelegate {
Layout.fillWidth: true
loading: true
}
}
Separator {
Layout.topMargin: Style.current.bigPadding
Layout.fillWidth: true
visible: !RootStore.historyTransactions.hasMore
visible: footerColumn.allActivityLoaded
}
StatusBaseText {
Layout.fillWidth: true
@ -341,13 +324,13 @@ ColumnLayout {
text: qsTr("You have reached the beginning of the activity for this account")
font.pixelSize: 13
color: Theme.palette.baseColor1
visible: !RootStore.historyTransactions.hasMore
visible: footerColumn.allActivityLoaded
horizontalAlignment: Text.AlignHCenter
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Back to most recent transaction")
visible: !RootStore.historyTransactions.hasMore && transactionListRoot.contentHeight > transactionListRoot.height
visible: footerColumn.allActivityLoaded && transactionListRoot.contentHeight > transactionListRoot.height
onClicked: transactionListRoot.positionViewAtBeginning()
}
}