mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-27 14:01:19 +00:00
fix(@desktop/wallet): fix scrolling transactions list reset to top when
new ones are fetched. Fixes #9617
This commit is contained in:
parent
172c849c54
commit
89055e1b08
@ -43,12 +43,10 @@ QtObject:
|
|||||||
type
|
type
|
||||||
Model* = ref object of QAbstractListModel
|
Model* = ref object of QAbstractListModel
|
||||||
items: seq[Item]
|
items: seq[Item]
|
||||||
itemsWithDateHeaders: seq[Item]
|
|
||||||
hasMore: bool
|
hasMore: bool
|
||||||
|
|
||||||
proc delete(self: Model) =
|
proc delete(self: Model) =
|
||||||
self.items = @[]
|
self.items = @[]
|
||||||
self.itemsWithDateHeaders = @[]
|
|
||||||
self.QAbstractListModel.delete
|
self.QAbstractListModel.delete
|
||||||
|
|
||||||
proc setup(self: Model) =
|
proc setup(self: Model) =
|
||||||
@ -60,20 +58,20 @@ QtObject:
|
|||||||
result.hasMore = true
|
result.hasMore = true
|
||||||
|
|
||||||
proc `$`*(self: Model): string =
|
proc `$`*(self: Model): string =
|
||||||
for i in 0 ..< self.itemsWithDateHeaders.len:
|
for i in 0 ..< self.items.len:
|
||||||
result &= fmt"""[{i}]:({$self.itemsWithDateHeaders[i]})"""
|
result &= fmt"""[{i}]:({$self.items[i]})"""
|
||||||
|
|
||||||
proc countChanged(self: Model) {.signal.}
|
proc countChanged(self: Model) {.signal.}
|
||||||
|
|
||||||
proc getCount*(self: Model): int {.slot.} =
|
proc getCount*(self: Model): int {.slot.} =
|
||||||
self.itemsWithDateHeaders.len
|
self.items.len
|
||||||
|
|
||||||
QtProperty[int] count:
|
QtProperty[int] count:
|
||||||
read = getCount
|
read = getCount
|
||||||
notify = countChanged
|
notify = countChanged
|
||||||
|
|
||||||
method rowCount(self: Model, index: QModelIndex = nil): int =
|
method rowCount(self: Model, index: QModelIndex = nil): int =
|
||||||
return self.itemsWithDateHeaders.len
|
return self.items.len
|
||||||
|
|
||||||
method roleNames(self: Model): Table[int, string] =
|
method roleNames(self: Model): Table[int, string] =
|
||||||
{
|
{
|
||||||
@ -114,10 +112,10 @@ QtObject:
|
|||||||
if (not index.isValid):
|
if (not index.isValid):
|
||||||
return
|
return
|
||||||
|
|
||||||
if (index.row < 0 or index.row >= self.itemsWithDateHeaders.len):
|
if (index.row < 0 or index.row >= self.items.len):
|
||||||
return
|
return
|
||||||
|
|
||||||
let item = self.itemsWithDateHeaders[index.row]
|
let item = self.items[index.row]
|
||||||
let enumRole = role.ModelRole
|
let enumRole = role.ModelRole
|
||||||
|
|
||||||
case enumRole:
|
case enumRole:
|
||||||
@ -186,14 +184,14 @@ QtObject:
|
|||||||
|
|
||||||
proc setItems*(self: Model, items: seq[Item]) =
|
proc setItems*(self: Model, items: seq[Item]) =
|
||||||
self.beginResetModel()
|
self.beginResetModel()
|
||||||
self.itemsWithDateHeaders = items
|
self.items = items
|
||||||
self.endResetModel()
|
self.endResetModel()
|
||||||
self.countChanged()
|
self.countChanged()
|
||||||
|
|
||||||
proc getLastTxBlockNumber*(self: Model): string {.slot.} =
|
proc getLastTxBlockNumber*(self: Model): string {.slot.} =
|
||||||
if (self.itemsWithDateHeaders.len == 0):
|
if (self.items.len == 0):
|
||||||
return "0x0"
|
return "0x0"
|
||||||
return self.itemsWithDateHeaders[^1].getBlockNumber()
|
return self.items[^1].getBlockNumber()
|
||||||
|
|
||||||
proc hasMoreChanged*(self: Model) {.signal.}
|
proc hasMoreChanged*(self: Model) {.signal.}
|
||||||
|
|
||||||
@ -219,43 +217,49 @@ QtObject:
|
|||||||
result = cmp(x.getNonce(), y.getNonce())
|
result = cmp(x.getNonce(), y.getNonce())
|
||||||
|
|
||||||
proc addNewTransactions*(self: Model, transactions: seq[Item], wasFetchMore: bool) =
|
proc addNewTransactions*(self: Model, transactions: seq[Item], wasFetchMore: bool) =
|
||||||
let existingTxIds = self.items.map(tx => tx.getId())
|
if transactions.len == 0:
|
||||||
let hasNewTxs = transactions.len > 0 and transactions.any(tx => not existingTxIds.contains(tx.getId()))
|
return
|
||||||
|
|
||||||
if hasNewTxs or not wasFetchMore:
|
var txs = transactions
|
||||||
var allTxs = self.items.concat(transactions)
|
|
||||||
allTxs.sort(cmpTransactions, SortOrder.Descending)
|
|
||||||
eth_service_utils.deduplicate(allTxs, tx => tx.getTxHash())
|
|
||||||
|
|
||||||
# add day headers to the transaction list
|
# Reset the model if empty
|
||||||
var itemsWithDateHeaders: seq[Item] = @[]
|
if self.items.len == 0:
|
||||||
var tempTimeStamp: Time
|
eth_service_utils.deduplicate(txs, tx => tx.getTxHash())
|
||||||
for tx in allTxs:
|
self.setItems(txs)
|
||||||
let durationInDays = (tempTimeStamp.toTimeInterval() - fromUnix(tx.getTimestamp()).toTimeInterval()).days
|
return
|
||||||
if(durationInDays != 0):
|
|
||||||
itemsWithDateHeaders.add(initTimestampItem(tx.getTimestamp()))
|
|
||||||
itemsWithDateHeaders.add(tx)
|
|
||||||
tempTimeStamp = fromUnix(tx.getTimestamp())
|
|
||||||
|
|
||||||
self.items = allTxs
|
# Concatenate existing and new, filter out duplicates
|
||||||
self.setItems(itemsWithDateHeaders)
|
let parentModelIndex = newQModelIndex()
|
||||||
self.setHasMore(true)
|
defer: parentModelIndex.delete
|
||||||
|
|
||||||
|
var newItems = concat(self.items, txs)
|
||||||
|
eth_service_utils.deduplicate(newItems, tx => tx.getTxHash())
|
||||||
|
|
||||||
|
# Though we claim that we insert rows to preserve listview indices without
|
||||||
|
# model reset, we actually reset the list, but since old items order is not changed
|
||||||
|
# by deduplicate() call, the order is preserved and only new items are added
|
||||||
|
# to the end. For model it looks like we inserted.
|
||||||
|
# Unsorted, sorting is done on UI side
|
||||||
|
self.beginInsertRows(parentModelIndex, self.items.len, newItems.len - 1)
|
||||||
|
self.items = newItems
|
||||||
|
self.endInsertRows()
|
||||||
|
|
||||||
|
self.countChanged()
|
||||||
|
|
||||||
proc addPageSizeBuffer*(self: Model, pageSize: int) =
|
proc addPageSizeBuffer*(self: Model, pageSize: int) =
|
||||||
if pageSize > 0:
|
if pageSize > 0:
|
||||||
var itemsWithDateHeaders: seq[Item] = @[]
|
self.beginInsertRows(newQModelIndex(), self.items.len, self.items.len + pageSize - 1)
|
||||||
itemsWithDateHeaders.add(initTimestampItem(0))
|
|
||||||
for i in 0 ..< pageSize:
|
for i in 0 ..< pageSize:
|
||||||
self.beginInsertRows(newQModelIndex(), self.itemsWithDateHeaders.len, self.itemsWithDateHeaders.len)
|
self.items.add(initLoadingItem())
|
||||||
self.itemsWithDateHeaders.add(initLoadingItem())
|
|
||||||
self.endInsertRows()
|
self.endInsertRows()
|
||||||
self.countChanged()
|
self.countChanged()
|
||||||
|
|
||||||
proc removePageSizeBuffer*(self: Model) =
|
proc removePageSizeBuffer*(self: Model) =
|
||||||
for i in 0 ..< self.itemsWithDateHeaders.len:
|
var removed = false
|
||||||
if self.itemsWithDateHeaders[i].getLoadingTransaction():
|
for i in 0 ..< self.items.len:
|
||||||
self.beginRemoveRows(newQModelIndex(), i, self.itemsWithDateHeaders.len-1)
|
if self.items[i].getLoadingTransaction():
|
||||||
self.itemsWithDateHeaders.delete(i, self.itemsWithDateHeaders.len-1)
|
self.beginRemoveRows(newQModelIndex(), i, self.items.len-1)
|
||||||
|
self.items.delete(i, self.items.len-1)
|
||||||
self.endRemoveRows()
|
self.endRemoveRows()
|
||||||
self.countChanged()
|
self.countChanged()
|
||||||
return
|
return
|
||||||
|
@ -7,6 +7,8 @@ import StatusQ.Components 0.1
|
|||||||
import StatusQ.Controls 0.1
|
import StatusQ.Controls 0.1
|
||||||
import StatusQ.Core.Theme 0.1
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
import utils 1.0
|
import utils 1.0
|
||||||
|
|
||||||
import "../panels"
|
import "../panels"
|
||||||
@ -69,32 +71,88 @@ ColumnLayout {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
model: RootStore.historyTransactions
|
property string firstSection
|
||||||
|
|
||||||
|
model: SortFilterProxyModel {
|
||||||
|
id: txModel
|
||||||
|
|
||||||
|
sourceModel: RootStore.historyTransactions
|
||||||
|
|
||||||
|
// LocaleUtils is not accessable from inside expression, but local function works
|
||||||
|
property var formatDate: (ms) => LocaleUtils.formatDate(ms, Locale.ShortFormat)
|
||||||
|
sorters: RoleSorter {
|
||||||
|
roleName: "timestamp"
|
||||||
|
sortOrder: Qt.DescendingOrder
|
||||||
|
}
|
||||||
|
proxyRoles: ExpressionRole {
|
||||||
|
name: "date"
|
||||||
|
expression: {
|
||||||
|
return timestamp > 0 ? txModel.formatDate(timestamp * 1000) : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delegate: Loader {
|
delegate: Loader {
|
||||||
width: ListView.view.width
|
width: ListView.view.width
|
||||||
sourceComponent: isTimeStamp ? dateHeader : transactionDelegate
|
sourceComponent: transactionDelegate
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
item.modelData = model
|
item.modelData = model
|
||||||
|
|
||||||
|
if (index == 0)
|
||||||
|
ListView.view.firstSection = date
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
footer: footerComp
|
footer: footerComp
|
||||||
|
|
||||||
|
displaced: Transition {
|
||||||
|
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: {
|
||||||
|
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
|
||||||
|
|
||||||
|
onMovingVerticallyChanged: {
|
||||||
|
if (!userScrolled) {
|
||||||
|
userScrolled = true
|
||||||
|
currentIndex = Qt.binding(() => lastVisibleIndex >= 0 ? lastVisibleIndex : (count - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
lastVisibleIndex = indexAt(lastVisibleItemPos.x, lastVisibleItemPos.y)
|
||||||
|
}
|
||||||
|
|
||||||
ScrollBar.vertical: StatusScrollBar {}
|
ScrollBar.vertical: StatusScrollBar {}
|
||||||
|
|
||||||
onAtYEndChanged: if(atYEnd && RootStore.historyTransactions.hasMore) fetchHistory()
|
section.property: "date"
|
||||||
}
|
section.delegate: Item {
|
||||||
|
width: ListView.view.width
|
||||||
|
height: ListView.view.firstSection === section || section.length > 0 ? 40 : 0
|
||||||
|
visible: height > 0
|
||||||
|
|
||||||
Component {
|
required property string section
|
||||||
id: dateHeader
|
|
||||||
StatusListItem {
|
StatusBaseText {
|
||||||
property var modelData
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
height: 40
|
text: parent.section
|
||||||
title: modelData !== undefined && !!modelData ? LocaleUtils.formatDate(modelData.timestamp * 1000, Locale.ShortFormat) : ""
|
color: Theme.palette.baseColor1
|
||||||
statusListItemTitle.color: Theme.palette.baseColor1
|
font.pixelSize: 15
|
||||||
color: Theme.palette.statusListItem.backgroundColor
|
|
||||||
sensor.enabled: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onAtYEndChanged: if (atYEnd && RootStore.historyTransactions.hasMore) fetchHistory()
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: transactionDelegate
|
id: transactionDelegate
|
||||||
|
Loading…
x
Reference in New Issue
Block a user