feat(wallet): make filter API async
Bump status-go to include the async API changes Handle the wallet-activity-filtering-done event Propagate the has more flag Extend the debugging UX Update #10994
This commit is contained in:
parent
c21c7cf705
commit
43c7258328
|
@ -7,6 +7,9 @@ import entry
|
||||||
import ../transactions/item
|
import ../transactions/item
|
||||||
import ../transactions/module as transactions_module
|
import ../transactions/module as transactions_module
|
||||||
|
|
||||||
|
import app/core/eventemitter
|
||||||
|
import app/core/signals/types
|
||||||
|
|
||||||
import backend/activity as backend_activity
|
import backend/activity as backend_activity
|
||||||
import backend/backend as backend
|
import backend/backend as backend
|
||||||
import backend/transactions
|
import backend/transactions
|
||||||
|
@ -17,12 +20,20 @@ proc toRef*[T](obj: T): ref T =
|
||||||
new(result)
|
new(result)
|
||||||
result[] = obj
|
result[] = obj
|
||||||
|
|
||||||
|
const FETCH_BATCH_COUNT_DEFAULT = 10
|
||||||
|
|
||||||
QtObject:
|
QtObject:
|
||||||
type
|
type
|
||||||
Controller* = ref object of QObject
|
Controller* = ref object of QObject
|
||||||
model: Model
|
model: Model
|
||||||
transactionsModule: transactions_module.AccessInterface
|
transactionsModule: transactions_module.AccessInterface
|
||||||
currentActivityFilter: backend_activity.ActivityFilter
|
currentActivityFilter: backend_activity.ActivityFilter
|
||||||
|
|
||||||
|
events: EventEmitter
|
||||||
|
|
||||||
|
loadingData: bool
|
||||||
|
errorCode: backend_activity.ErrorCode
|
||||||
|
|
||||||
# TODO remove chains and addresses to use the app one
|
# TODO remove chains and addresses to use the app one
|
||||||
addresses: seq[string]
|
addresses: seq[string]
|
||||||
chainIds: seq[int]
|
chainIds: seq[int]
|
||||||
|
@ -33,27 +44,20 @@ QtObject:
|
||||||
proc delete*(self: Controller) =
|
proc delete*(self: Controller) =
|
||||||
self.QObject.delete
|
self.QObject.delete
|
||||||
|
|
||||||
proc newController*(transactionsModule: transactions_module.AccessInterface): Controller =
|
|
||||||
new(result, delete)
|
|
||||||
result.model = newModel()
|
|
||||||
result.transactionsModule = transactionsModule
|
|
||||||
result.currentActivityFilter = backend_activity.getIncludeAllActivityFilter()
|
|
||||||
result.setup()
|
|
||||||
|
|
||||||
proc getModel*(self: Controller): QVariant {.slot.} =
|
proc getModel*(self: Controller): QVariant {.slot.} =
|
||||||
return newQVariant(self.model)
|
return newQVariant(self.model)
|
||||||
|
|
||||||
QtProperty[QVariant] model:
|
QtProperty[QVariant] model:
|
||||||
read = getModel
|
read = getModel
|
||||||
|
|
||||||
# TODO: move it to service, make it async and lazy load details for transactions
|
|
||||||
proc backendToPresentation(self: Controller, backendEntities: seq[backend_activity.ActivityEntry]): seq[entry.ActivityEntry] =
|
proc backendToPresentation(self: Controller, backendEntities: seq[backend_activity.ActivityEntry]): seq[entry.ActivityEntry] =
|
||||||
var multiTransactionsIds: seq[int] = @[]
|
var multiTransactionsIds: seq[int] = @[]
|
||||||
var transactionIdentities: seq[backend.TransactionIdentity] = @[]
|
var transactionIdentities: seq[backend.TransactionIdentity] = @[]
|
||||||
var pendingTransactionIdentities: seq[backend.TransactionIdentity] = @[]
|
var pendingTransactionIdentities: seq[backend.TransactionIdentity] = @[]
|
||||||
|
|
||||||
# Extract metadata required to fetch details
|
# Extract metadata required to fetch details
|
||||||
# TODO: temporary here to show the working API. Will be done as required on a detail request from UI
|
# TODO: temporary here to show the working API. Details for each entry will be done as required
|
||||||
|
# on a detail request from UI after metadata is extended to include the required info
|
||||||
for backendEntry in backendEntities:
|
for backendEntry in backendEntities:
|
||||||
case backendEntry.payloadType:
|
case backendEntry.payloadType:
|
||||||
of MultiTransaction:
|
of MultiTransaction:
|
||||||
|
@ -122,25 +126,38 @@ QtObject:
|
||||||
error "failed to find pending transaction with identity: ", identity
|
error "failed to find pending transaction with identity: ", identity
|
||||||
ptIndex += 1
|
ptIndex += 1
|
||||||
|
|
||||||
proc refreshData(self: Controller) =
|
proc loadingDataChanged*(self: Controller) {.signal.}
|
||||||
|
|
||||||
# result type is RpcResponse
|
proc setLoadingData(self: Controller, loadingData: bool) =
|
||||||
let response = backend_activity.getActivityEntries(self.addresses, self.chainIds, self.currentActivityFilter, 0, 10)
|
self.loadingData = loadingData
|
||||||
# RPC returns null for result in case of empty array
|
self.loadingDataChanged()
|
||||||
if response.error != nil or (response.result.kind != JArray and response.result.kind != JNull):
|
|
||||||
|
proc errorCodeChanged*(self: Controller) {.signal.}
|
||||||
|
|
||||||
|
proc setErrorCode(self: Controller, errorCode: int) =
|
||||||
|
self.errorCode = backend_activity.ErrorCode(errorCode)
|
||||||
|
self.errorCodeChanged()
|
||||||
|
|
||||||
|
proc refreshData(self: Controller) =
|
||||||
|
let response = backend_activity.filterActivityAsync(self.addresses, self.chainIds, self.currentActivityFilter, 0, FETCH_BATCH_COUNT_DEFAULT)
|
||||||
|
if response.error != nil:
|
||||||
error "error fetching activity entries: ", response.error
|
error "error fetching activity entries: ", response.error
|
||||||
return
|
return
|
||||||
|
|
||||||
if response.result.kind == JNull:
|
self.setLoadingData(true)
|
||||||
self.model.setEntries(@[])
|
|
||||||
|
proc processResponse(self: Controller, response: JsonNode) =
|
||||||
|
let res = fromJson(response, backend_activity.FilterResponse)
|
||||||
|
|
||||||
|
defer: self.setErrorCode(res.errorCode.int)
|
||||||
|
|
||||||
|
if res.errorCode != ErrorCodeSuccess:
|
||||||
|
error "error fetching activity entries: ", res.errorCode
|
||||||
return
|
return
|
||||||
|
|
||||||
var backendEntities = newSeq[backend_activity.ActivityEntry](response.result.len)
|
let entries = self.backendToPresentation(res.activities)
|
||||||
for i in 0 ..< response.result.len:
|
self.model.setEntries(entries, res.thereMightBeMore)
|
||||||
backendEntities[i] = fromJson(response.result[i], backend_activity.ActivityEntry)
|
self.setLoadingData(false)
|
||||||
|
|
||||||
let entries = self.backendToPresentation(backendEntities)
|
|
||||||
self.model.setEntries(entries)
|
|
||||||
|
|
||||||
proc updateFilter*(self: Controller) {.slot.} =
|
proc updateFilter*(self: Controller) {.slot.} =
|
||||||
self.refreshData()
|
self.refreshData()
|
||||||
|
@ -160,6 +177,31 @@ QtObject:
|
||||||
|
|
||||||
self.currentActivityFilter.types = types
|
self.currentActivityFilter.types = types
|
||||||
|
|
||||||
|
proc newController*(transactionsModule: transactions_module.AccessInterface, events: EventEmitter): Controller =
|
||||||
|
new(result, delete)
|
||||||
|
result.model = newModel()
|
||||||
|
result.transactionsModule = transactionsModule
|
||||||
|
result.currentActivityFilter = backend_activity.getIncludeAllActivityFilter()
|
||||||
|
result.events = events
|
||||||
|
result.setup()
|
||||||
|
|
||||||
|
let controller = result
|
||||||
|
|
||||||
|
proc handleEvent(e: Args) =
|
||||||
|
var data = WalletSignal(e)
|
||||||
|
case data.eventType:
|
||||||
|
of backend_activity.eventActivityFilteringDone:
|
||||||
|
let responseJson = parseJson(data.message)
|
||||||
|
if responseJson.kind != JObject:
|
||||||
|
error "unexpected json type", responseJson.kind
|
||||||
|
return
|
||||||
|
|
||||||
|
controller.processResponse(responseJson)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
result.events.on(SignalType.Wallet.event, handleEvent)
|
||||||
|
|
||||||
proc setFilterStatus*(self: Controller, statusesArrayJsonString: string) {.slot.} =
|
proc setFilterStatus*(self: Controller, statusesArrayJsonString: string) {.slot.} =
|
||||||
let statusesJson = parseJson(statusesArrayJsonString)
|
let statusesJson = parseJson(statusesArrayJsonString)
|
||||||
if statusesJson.kind != JArray:
|
if statusesJson.kind != JArray:
|
||||||
|
@ -221,3 +263,17 @@ QtObject:
|
||||||
chainIds[i] = chainIdsJson[i].getInt()
|
chainIds[i] = chainIdsJson[i].getInt()
|
||||||
|
|
||||||
self.chainIds = chainIds
|
self.chainIds = chainIds
|
||||||
|
|
||||||
|
proc getLoadingData*(self: Controller): bool {.slot.} =
|
||||||
|
return self.loadingData
|
||||||
|
|
||||||
|
QtProperty[bool] loadingData:
|
||||||
|
read = getLoadingData
|
||||||
|
notify = loadingDataChanged
|
||||||
|
|
||||||
|
proc getErrorCode*(self: Controller): int {.slot.} =
|
||||||
|
return self.errorCode.int
|
||||||
|
|
||||||
|
QtProperty[int] errorCode:
|
||||||
|
read = getErrorCode
|
||||||
|
notify = errorCodeChanged
|
|
@ -65,26 +65,24 @@ QtObject:
|
||||||
of ModelRole.ActivityEntryRole:
|
of ModelRole.ActivityEntryRole:
|
||||||
result = newQVariant(entry)
|
result = newQVariant(entry)
|
||||||
|
|
||||||
proc setEntries*(self: Model, entries: seq[ActivityEntry]) =
|
proc hasMoreChanged*(self: Model) {.signal.}
|
||||||
|
|
||||||
|
proc setHasMore(self: Model, hasMore: bool) {.slot.} =
|
||||||
|
self.hasMore = hasMore
|
||||||
|
self.hasMoreChanged()
|
||||||
|
|
||||||
|
proc setEntries*(self: Model, entries: seq[ActivityEntry], hasMore: bool) =
|
||||||
self.beginResetModel()
|
self.beginResetModel()
|
||||||
self.entries = entries
|
self.entries = entries
|
||||||
self.endResetModel()
|
self.endResetModel()
|
||||||
self.countChanged()
|
self.countChanged()
|
||||||
|
self.setHasMore(hasMore)
|
||||||
# TODO: update data
|
|
||||||
|
|
||||||
# TODO: fetch more
|
# TODO: fetch more
|
||||||
|
|
||||||
proc hasMoreChanged*(self: Model) {.signal.}
|
|
||||||
|
|
||||||
proc getHasMore*(self: Model): bool {.slot.} =
|
proc getHasMore*(self: Model): bool {.slot.} =
|
||||||
return self.hasMore
|
return self.hasMore
|
||||||
|
|
||||||
proc setHasMore*(self: Model, hasMore: bool) {.slot.} =
|
|
||||||
self.hasMore = hasMore
|
|
||||||
self.hasMoreChanged()
|
|
||||||
|
|
||||||
QtProperty[bool] hasMore:
|
QtProperty[bool] hasMore:
|
||||||
read = getHasMore
|
read = getHasMore
|
||||||
write = setHasMore
|
|
||||||
notify = hasMoreChanged
|
notify = hasMoreChanged
|
||||||
|
|
|
@ -101,7 +101,7 @@ proc newModule*(
|
||||||
result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService)
|
result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService)
|
||||||
result.filter = initFilter(result.controller)
|
result.filter = initFilter(result.controller)
|
||||||
|
|
||||||
result.activityController = activityc.newController(result.transactionsModule)
|
result.activityController = activityc.newController(result.transactionsModule, events)
|
||||||
result.view = newView(result, result.activityController)
|
result.view = newView(result, result.activityController)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,9 @@ export response_type
|
||||||
# see status-go/services/wallet/activity/filter.go NoLimitTimestampForPeriod
|
# see status-go/services/wallet/activity/filter.go NoLimitTimestampForPeriod
|
||||||
const noLimitTimestampForPeriod = 0
|
const noLimitTimestampForPeriod = 0
|
||||||
|
|
||||||
|
# Declared in services/wallet/activity/service.go
|
||||||
|
const eventActivityFilteringDone*: string = "wallet-activity-filtering-done"
|
||||||
|
|
||||||
# TODO: consider using common status-go types via protobuf
|
# TODO: consider using common status-go types via protobuf
|
||||||
# TODO: consider using flags instead of list of enums
|
# TODO: consider using flags instead of list of enums
|
||||||
type
|
type
|
||||||
|
@ -146,6 +149,17 @@ type
|
||||||
activityStatus*: ActivityStatus
|
activityStatus*: ActivityStatus
|
||||||
tokenType*: TokenType
|
tokenType*: TokenType
|
||||||
|
|
||||||
|
# Mirrors services/wallet/activity/service.go ErrorCode
|
||||||
|
ErrorCode* = enum
|
||||||
|
ErrorCodeSuccess = 1,
|
||||||
|
ErrorCodeFilterCanceled,
|
||||||
|
ErrorCodeFilterFailed
|
||||||
|
|
||||||
|
FilterResponse* = object
|
||||||
|
activities*: seq[ActivityEntry]
|
||||||
|
thereMightBeMore*: bool
|
||||||
|
errorCode*: ErrorCode
|
||||||
|
|
||||||
# Define toJson proc for PayloadType
|
# Define toJson proc for PayloadType
|
||||||
proc toJson*(ae: ActivityEntry): JsonNode {.inline.} =
|
proc toJson*(ae: ActivityEntry): JsonNode {.inline.} =
|
||||||
return %*(ae)
|
return %*(ae)
|
||||||
|
@ -174,7 +188,22 @@ proc `$`*(self: ActivityEntry): string =
|
||||||
tokenType* {$self.tokenType},
|
tokenType* {$self.tokenType},
|
||||||
)"""
|
)"""
|
||||||
|
|
||||||
rpc(getActivityEntries, "wallet"):
|
proc fromJson*(e: JsonNode, T: typedesc[FilterResponse]): FilterResponse {.inline.} =
|
||||||
|
var backendEntities: seq[ActivityEntry]
|
||||||
|
if e.hasKey("activities"):
|
||||||
|
let jsonEntries = e["activities"]
|
||||||
|
backendEntities = newSeq[ActivityEntry](jsonEntries.len)
|
||||||
|
for i in 0 ..< jsonEntries.len:
|
||||||
|
backendEntities[i] = fromJson(jsonEntries[i], ActivityEntry)
|
||||||
|
|
||||||
|
result = T(
|
||||||
|
activities: backendEntities,
|
||||||
|
thereMightBeMore: if e.hasKey("thereMightBeMore"): e["thereMightBeMore"].getBool()
|
||||||
|
else: false,
|
||||||
|
errorCode: ErrorCode(e["errorCode"].getInt())
|
||||||
|
)
|
||||||
|
|
||||||
|
rpc(filterActivityAsync, "wallet"):
|
||||||
addresses: seq[string]
|
addresses: seq[string]
|
||||||
chainIds: seq[int]
|
chainIds: seq[int]
|
||||||
filter: ActivityFilter
|
filter: ActivityFilter
|
||||||
|
|
|
@ -142,7 +142,7 @@ Control {
|
||||||
id: timeFilterLayout
|
id: timeFilterLayout
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Label { text: "Past Days Span: 100" }
|
Label { text: qsTr("Past Days Span: 100") }
|
||||||
Slider {
|
Slider {
|
||||||
id: fromSlider
|
id: fromSlider
|
||||||
|
|
||||||
|
@ -170,12 +170,12 @@ Control {
|
||||||
stepSize: 1
|
stepSize: 1
|
||||||
value: 0
|
value: 0
|
||||||
}
|
}
|
||||||
Label { text: "0" }
|
Label { text: qsTr("0") }
|
||||||
}
|
}
|
||||||
Label { text: `Interval: ${d.start > 0 ? root.epochToDateStr(d.start) : "all time"} - ${d.end > 0 ? root.epochToDateStr(d.end) : "now"}` }
|
Label { text: `Interval: ${d.start > 0 ? root.epochToDateStr(d.start) : qsTr("all time")} - ${d.end > 0 ? root.epochToDateStr(d.end) : qsTr("now")}` }
|
||||||
}
|
}
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Label { text: "Type" }
|
Label { text: qsTr("Type") }
|
||||||
// Models the ActivityType
|
// Models the ActivityType
|
||||||
ListModel {
|
ListModel {
|
||||||
id: typeModel
|
id: typeModel
|
||||||
|
@ -198,7 +198,7 @@ Control {
|
||||||
delegate: ItemOnOffDelegate {}
|
delegate: ItemOnOffDelegate {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label { text: "Status" }
|
Label { text: qsTr("Status") }
|
||||||
// ActivityStatus
|
// ActivityStatus
|
||||||
ListModel {
|
ListModel {
|
||||||
id: statusModel
|
id: statusModel
|
||||||
|
@ -219,7 +219,7 @@ Control {
|
||||||
delegate: ItemOnOffDelegate {}
|
delegate: ItemOnOffDelegate {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label { text: "To addresses" }
|
Label { text: qsTr("To addresses") }
|
||||||
TextField {
|
TextField {
|
||||||
id: toAddressesInput
|
id: toAddressesInput
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ Control {
|
||||||
}
|
}
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
|
||||||
Label { text: "Addresses" }
|
Label { text: qsTr("Addresses") }
|
||||||
TextField {
|
TextField {
|
||||||
id: addressesInput
|
id: addressesInput
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ Control {
|
||||||
placeholderText: qsTr("0x1234, 0x5678, ...")
|
placeholderText: qsTr("0x1234, 0x5678, ...")
|
||||||
}
|
}
|
||||||
|
|
||||||
Label { text: "Chains" }
|
Label { text: qsTr("Chains") }
|
||||||
ComboBox {
|
ComboBox {
|
||||||
displayText: qsTr("Select chains")
|
displayText: qsTr("Select chains")
|
||||||
|
|
||||||
|
@ -256,7 +256,7 @@ Control {
|
||||||
delegate: ItemOnOffDelegate {}
|
delegate: ItemOnOffDelegate {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label { text: "Assets" }
|
Label { text: qsTr("Assets") }
|
||||||
ComboBox {
|
ComboBox {
|
||||||
displayText: assetsLoader.status != Loader.Ready ? qsTr("Loading...") : qsTr("Select an asset")
|
displayText: assetsLoader.status != Loader.Ready ? qsTr("Loading...") : qsTr("Select an asset")
|
||||||
|
|
||||||
|
@ -321,6 +321,16 @@ Control {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: loadingText
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: qsTr("Loading...")
|
||||||
|
visible: controller.loadingData
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
ListView {
|
ListView {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
|
@ -328,6 +338,7 @@ Control {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
model: controller.model
|
model: controller.model
|
||||||
|
visible: !controller.loadingData
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: parent ? parent.width : 0
|
width: parent ? parent.width : 0
|
||||||
|
@ -339,25 +350,25 @@ Control {
|
||||||
id: itemLayout
|
id: itemLayout
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
Label { text: entry.isMultiTransaction ? "MT" : entry.isPendingTransaction ? "PT" : " T" }
|
Label { text: entry.isMultiTransaction ? qsTr("MT") : entry.isPendingTransaction ? qsTr("PT") : qsTr(" T") }
|
||||||
Label { text: `[${root.epochToDateStr(entry.timestamp)}] ` }
|
Label { text: `[${root.epochToDateStr(entry.timestamp)}] ` }
|
||||||
Label { text: entry.isMultiTransaction ? entry.fromAmount : entry.amount }
|
Label { text: entry.isMultiTransaction ? entry.fromAmount : entry.amount }
|
||||||
Label { text: "from"; Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
Label { text: qsTr("from"); Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
||||||
Label { text: entry.sender; Layout.maximumWidth: 200; elide: Text.ElideMiddle }
|
Label { text: entry.sender; Layout.maximumWidth: 200; elide: Text.ElideMiddle }
|
||||||
Label { text: "to"; Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
Label { text: qsTr("to"); Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
||||||
Label { text: entry.recipient; Layout.maximumWidth: 200; elide: Text.ElideMiddle }
|
Label { text: entry.recipient; Layout.maximumWidth: 200; elide: Text.ElideMiddle }
|
||||||
Label { text: "got"; Layout.leftMargin: 5; Layout.rightMargin: 5; visible: entry.isMultiTransaction }
|
Label { text: qsTr("got"); Layout.leftMargin: 5; Layout.rightMargin: 5; visible: entry.isMultiTransaction }
|
||||||
Label { text: entry.toAmount; Layout.leftMargin: 5; Layout.rightMargin: 5; visible: entry.isMultiTransaction }
|
Label { text: entry.toAmount; Layout.leftMargin: 5; Layout.rightMargin: 5; visible: entry.isMultiTransaction }
|
||||||
Label {
|
Label {
|
||||||
text: `{${
|
text: `{${
|
||||||
function() {
|
function() {
|
||||||
switch (entry.status) {
|
switch (entry.status) {
|
||||||
case Constants.TransactionStatus.Failed: return "F";
|
case Constants.TransactionStatus.Failed: return qsTr("F");
|
||||||
case Constants.TransactionStatus.Pending: return "P";
|
case Constants.TransactionStatus.Pending: return qsTr("P");
|
||||||
case Constants.TransactionStatus.Complete: return "C";
|
case Constants.TransactionStatus.Complete: return qsTr("C");
|
||||||
case Constants.TransactionStatus.Finalized: return "FZ";
|
case Constants.TransactionStatus.Finalized: return qsTr("FZ");
|
||||||
}
|
}
|
||||||
return "-"
|
return qsTr("-")
|
||||||
}()}}`
|
}()}}`
|
||||||
Layout.leftMargin: 5;
|
Layout.leftMargin: 5;
|
||||||
}
|
}
|
||||||
|
@ -365,6 +376,30 @@ Control {
|
||||||
RowLayout {} // Spacer
|
RowLayout {} // Spacer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
footer: Component {
|
||||||
|
Item {
|
||||||
|
width: listView.width
|
||||||
|
height: footerText.implicitHeight
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: footerText
|
||||||
|
text: qsTr("Loading more items...")
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: controller.model.hasMore
|
||||||
|
|
||||||
|
// Load more items when this footer comes into view.
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible) {
|
||||||
|
controller.loadMoreItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 1a2ca21070456e153aac27b8a14afa14006652ef
|
Subproject commit d8eb038d7d7026aeece4775771bbb4bcf00c88dc
|
Loading…
Reference in New Issue