feat(@desktop/wallet): Add loading state for activity feed (#11326)
closes #11072
This commit is contained in:
parent
d0f3b6d652
commit
bade10c5e0
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue