fix(@desktop/wallet): `Wallet -> Settings -> Account order` - drag and drop accounts in account list is not smooth

Fixes: #11508
This commit is contained in:
Sale Djenic 2023-07-18 16:25:42 +02:00 committed by saledjenic
parent c47a432bae
commit 9b17a66935
20 changed files with 213 additions and 131 deletions

View File

@ -37,6 +37,7 @@ type MessageSignal* = ref object of Signal
savedAddresses*: seq[SavedAddressDto]
keypairs*: seq[KeypairDto]
watchOnlyAccounts*: seq[WalletAccountDto]
accountsPositions*: seq[WalletAccountDto]
type MessageDeliveredSignal* = ref object of Signal
chatId*: string
@ -151,5 +152,9 @@ proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal =
for jsonAcc in e["watchOnlyAccounts"]:
signal.watchOnlyAccounts.add(jsonAcc.toWalletAccountDto())
if e.contains("accountsPositions"):
for jsonAcc in e["accountsPositions"]:
signal.accountsPositions.add(jsonAcc.toWalletAccountDto())
result = signal

View File

@ -92,8 +92,7 @@ proc init*(self: Controller) =
self.delegate.onWalletAccountChange(args.account)
self.events.on(SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED) do(e: Args):
let args = AccountArgs(e)
self.delegate.onWalletAccountChange(args.account)
self.delegate.rebuildAllKeycards()
self.events.on(SIGNAL_WALLET_ACCOUNT_SAVED) do(e: Args):
let args = AccountArgs(e)

View File

@ -65,6 +65,9 @@ method runCreateNewPairingCodePopup*(self: AccessInterface, keyUid: string) {.ba
method onLoggedInUserImageChanged*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method rebuildAllKeycards*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onKeypairSynced*(self: AccessInterface, keypair: KeypairDto) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -334,6 +334,11 @@ method resolveRelatedKeycardsForKeypair(self: Module, keypair: KeypairDto) =
return
self.view.keycardDetailsModel().setItems(detailsViewItems)
method rebuildAllKeycards*(self: Module) =
# We don't need to take care about details model here, cause since this is called only when account
# reordering occurs it's impossible to have details keycard view displayed.
self.buildKeycardList()
method onKeypairSynced*(self: Module, keypair: KeypairDto) =
self.resolveRelatedKeycardsForKeypair(keypair)

View File

@ -27,8 +27,8 @@ proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAcco
proc updateAccount*(self: Controller, address: string, accountName: string, colorId: string, emoji: string) =
discard self.walletAccountService.updateWalletAccount(address, accountName, colorId, emoji)
proc updateAccountPosition*(self: Controller, address: string, position: int) =
self.walletAccountService.updateWalletAccountPosition(address, position)
proc moveAccountFinally*(self: Controller, fromPosition: int, toPosition: int) =
self.walletAccountService.moveAccountFinally(fromPosition, toPosition)
proc renameKeypair*(self: Controller, keyUid: string, name: string) =
self.walletAccountService.updateKeypairName(keyUid, name)

View File

@ -25,7 +25,7 @@ method refreshWalletAccounts*(self: AccessInterface) {.base.} =
method updateAccount*(self: AccessInterface, address: string, accountName: string, colorId: string, emoji: string) {.base.} =
raise newException(ValueError, "No implementation available")
method updateAccountPosition*(self: AccessInterface, address: string, position: int) {.base.} =
method moveAccountFinally*(self: AccessInterface, fromPosition: int, toPosition: int) {.base.} =
raise newException(ValueError, "No implementation available")
method renameKeypair*(self: AccessInterface, keyUid: string, name: string) {.base.} =

View File

@ -6,7 +6,6 @@ export wallet_account_item
type
Item* = ref object of WalletAccountItem
position: int
relatedAccounts: related_accounts_model.Model
proc initItem*(
@ -31,8 +30,9 @@ proc initItem*(
path,
keyUid,
keycardAccount,
position,
operability)
result.position = position
result.relatedAccounts = relatedAccounts
proc `$`*(self: Item): string =
@ -43,6 +43,3 @@ proc `$`*(self: Item): string =
proc relatedAccounts*(self: Item): related_accounts_model.Model =
return self.relatedAccounts
proc position*(self: Item): int =
return self.position

View File

@ -1,4 +1,4 @@
import NimQml, Tables, strutils, strformat
import NimQml, Tables, strutils, sequtils, strformat
import ./item
export item
@ -14,6 +14,7 @@ type
RelatedAccounts,
KeyUid,
Position,
KeycardAccount,
QtObject:
type
@ -58,6 +59,7 @@ QtObject:
ModelRole.RelatedAccounts.int: "relatedAccounts",
ModelRole.KeyUid.int: "keyUid",
ModelRole.Position.int: "position",
ModelRole.KeycardAccount.int: "keycardAccount",
}.toTable
@ -109,4 +111,26 @@ QtObject:
of ModelRole.KeyUid:
result = newQVariant(item.keyUid())
of ModelRole.Position:
result = newQVariant(item.position())
result = newQVariant(item.getPosition())
of ModelRole.KeycardAccount:
result = newQVariant(item.keycardAccount())
proc moveItem*(self: Model, fromRow: int, toRow: int): bool =
if toRow < 0 or toRow > self.items.len - 1:
return false
let sourceIndex = newQModelIndex()
defer: sourceIndex.delete
let destIndex = newQModelIndex()
defer: destIndex.delete
var destRow = toRow
if toRow > fromRow:
inc(destRow)
let currentItem = self.items[fromRow]
self.beginMoveRows(sourceIndex, fromRow, fromRow, destIndex, destRow)
self.items.delete(fromRow)
self.items.insert(@[currentItem], toRow)
self.endMoveRows()
return true

View File

@ -135,8 +135,8 @@ method viewDidLoad*(self: Module) =
method updateAccount*(self: Module, address: string, accountName: string, colorId: string, emoji: string) =
self.controller.updateAccount(address, accountName, colorId, emoji)
method updateAccountPosition*(self: Module, address: string, position: int) =
self.controller.updateAccountPosition(address, position)
method moveAccountFinally*(self: Module, fromPosition: int, toPosition: int) =
self.controller.moveAccountFinally(fromPosition, toPosition)
method deleteAccount*(self: Module, address: string) =
self.controller.deleteAccount(address)

View File

@ -45,9 +45,6 @@ QtObject:
proc updateAccount(self: View, address: string, accountName: string, colorId: string, emoji: string) {.slot.} =
self.delegate.updateAccount(address, accountName, colorId, emoji)
proc updateAccountPosition(self: View, address: string, position: int) {.slot.} =
self.delegate.updateAccountPosition(address, position)
proc onUpdatedAccount*(self: View, account: Item) =
self.accounts.onUpdatedAccount(account)
self.keyPairModel.onUpdatedAccount(account.keyUid, account.address, account.name, account.colorId, account.emoji)
@ -87,4 +84,10 @@ QtObject:
return self.keyPairModel.keypairNameExists(name)
proc renameKeypair*(self: View, keyUid: string, name: string) {.slot.} =
self.delegate.renameKeypair(keyUid, name)
self.delegate.renameKeypair(keyUid, name)
proc moveAccount(self: View, fromRow: int, toRow: int) {.slot.} =
discard self.accounts.moveItem(fromRow, toRow)
proc moveAccountFinally(self: View, fromRow: int, toRow: int) {.slot.} =
self.delegate.moveAccountFinally(fromRow, toRow)

View File

@ -6,7 +6,6 @@ export wallet_account_item
type
Item* = ref object of WalletAccountItem
position: int
createdAt: int
assetsLoading: bool
currencyBalance: CurrencyAmount
@ -35,9 +34,9 @@ proc initItem*(
walletType,
path,
keyUid,
keycardAccount)
keycardAccount,
position)
result.createdAt = createdAt
result.position = position
result.assetsLoading = assetsLoading
result.currencyBalance = currencyBalance
result.isWallet = isWallet
@ -58,8 +57,5 @@ proc assetsLoading*(self: Item): bool =
proc createdAt*(self: Item): int =
return self.createdAt
proc position*(self: Item): int =
return self.position
proc isWallet*(self: Item): bool =
return self.isWallet

View File

@ -110,7 +110,7 @@ QtObject:
of ModelRole.CreatedAt:
result = newQVariant(item.createdAt())
of ModelRole.Position:
result = newQVariant(item.position())
result = newQVariant(item.getPosition())
of ModelRole.KeycardAccount:
result = newQVariant(item.keycardAccount())
of ModelRole.AssetsLoading:

View File

@ -7,7 +7,6 @@ export wallet_account_item
QtObject:
type AccountItem* = ref object of WalletAccountItem
position: int
assets: token_model.Model
currencyBalance: CurrencyAmount
@ -29,9 +28,9 @@ QtObject:
walletType,
path = "",
keyUid = "",
keycardAccount = false)
keycardAccount = false,
position)
self.assets = assets
self.position = position
self.currencyBalance = currencyBalance
proc delete*(self: AccountItem) =
@ -73,7 +72,4 @@ QtObject:
return newQVariant(self.currencyBalance)
QtProperty[QVariant] currencyBalance:
read = getCurrencyBalanceAsQVariant
notify = currencyBalanceChanged
proc position*(self: AccountItem): int =
return self.position
notify = currencyBalanceChanged

View File

@ -86,7 +86,7 @@ QtObject:
of ModelRole.Emoji:
result = newQVariant(item.emoji())
of ModelRole.Position:
result = newQVariant(item.position())
result = newQVariant(item.getPosition())
of ModelRole.Assets:
result = newQVariant(item.getAssetsAsQVariant())
of ModelRole.CurrencyBalance:
@ -104,4 +104,4 @@ method getItemByAddress*(self: AccountsModel, address: string): tuple[account: A
if self.items.len > 0:
return (self.items[0], 0)

View File

@ -13,6 +13,7 @@ QtObject:
path: string
keyUid: string
keycardAccount: bool
position: int
operability: string
proc setup*(self: WalletAccountItem,
@ -24,6 +25,7 @@ QtObject:
path: string = "",
keyUid: string = "",
keycardAccount: bool = false,
position: int = 0,
operability: string = wa_dto.AccountFullyOperable
) =
self.QObject.setup
@ -35,6 +37,8 @@ QtObject:
self.path = path
self.keyUid = keyUid
self.keycardAccount = keycardAccount
self.position = position
self.operability = operability
proc delete*(self: WalletAccountItem) =
self.QObject.delete
@ -49,6 +53,8 @@ QtObject:
path: {self.path},
keyUid: {self.keyUid},
keycardAccount: {self.keycardAccount},
position: {self.position},
operability: {self.operability},
]"""
proc nameChanged*(self: WalletAccountItem) {.signal.}
@ -119,6 +125,16 @@ QtObject:
read = keycardAccount
notify = keycardAccountChanged
proc positionChanged*(self: WalletAccountItem) {.signal.}
proc getPosition*(self: WalletAccountItem): int {.slot.} =
return self.position
proc setPosition*(self: WalletAccountItem, value: int) {.slot.} =
self.position = value
self.positionChanged()
QtProperty[int] position:
read = getPosition
write = setPosition
notify = positionChanged
proc operabilityChanged*(self: WalletAccountItem) {.signal.}
proc getOperability*(self: WalletAccountItem): string {.slot.} =

View File

@ -40,6 +40,7 @@ const SIGNAL_WALLET_ACCOUNT_CHAIN_ID_FOR_URL_FETCHED* = "walletAccount/chainIdFo
const SIGNAL_KEYPAIR_SYNCED* = "keypairSynced"
const SIGNAL_KEYPAIR_NAME_CHANGED* = "keypairNameChanged"
const SIGNAL_NEW_KEYCARD_SET* = "newKeycardSet"
const SIGNAL_KEYCARD_DELETED* = "keycardDeleted"
const SIGNAL_ALL_KEYCARDS_DELETED* = "allKeycardsDeleted"
@ -61,6 +62,9 @@ proc priorityTokenCmp(a, b: WalletTokenDto): int =
cmp(a.name, b.name)
proc walletAccountsCmp(x, y: WalletAccountDto): int =
cmp(x.position, y.position)
proc hex2Balance*(input: string, decimals: int): string =
var value = fromHex(Stuint[256], input)
@ -140,6 +144,7 @@ QtObject:
proc handleKeypair(self: Service, keypair: KeypairDto)
proc getAllKnownKeycards*(self: Service): seq[KeycardDto]
proc removeMigratedAccountsForKeycard*(self: Service, keyUid: string, keycardUid: string, accountsToRemove: seq[string])
proc updateAccountsPositions(self: Service)
proc delete*(self: Service) =
self.closingApp = true
@ -318,6 +323,7 @@ QtObject:
proc getWalletAccounts*(self: Service): seq[WalletAccountDto] =
result = toSeq(self.walletAccounts.values)
result.sort(walletAccountsCmp)
proc getWalletAccountsForKeypair*(self: Service, keyUid: string): seq[WalletAccountDto] =
return self.getWalletAccounts().filter(kp => kp.keyUid == keyUid)
@ -350,6 +356,9 @@ QtObject:
if receivedData.keypairs.len > 0:
for kp in receivedData.keypairs:
self.handleKeypair(kp)
if receivedData.accountsPositions.len > 0:
self.updateAccountsPositions()
self.events.emit(SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED, Args())
self.events.on(SignalType.Wallet.event) do(e:Args):
var data = WalletSignal(e)
@ -429,26 +438,41 @@ QtObject:
if notify:
self.events.emit(SIGNAL_WALLET_ACCOUNT_DELETED, AccountArgs(account: removedAcc))
proc updateAccountsPositions(self: Service) =
let dbAccounts = self.getAccounts()
for dbAcc in dbAccounts:
var localAcc = self.getAccountByAddress(dbAcc.address)
if localAcc.isNil:
continue
localAcc.position = dbAcc.position
self.storeAccount(localAcc, updateRelatedAccounts = false)
proc updateAccountInLocalStoreAndNotify(self: Service, address, name, colorId, emoji: string,
position: Option[int] = none(int), notify: bool = true) =
if not self.walletAccountsContainsAddress(address):
return
var account = self.getAccountByAddress(address)
if name.len > 0 or colorId.len > 0 or emoji.len > 0:
if name.len > 0 and name != account.name:
account.name = name
if colorId.len > 0 and colorId != account.colorId:
account.colorId = colorId
if emoji.len > 0 and emoji != account.emoji:
account.emoji = emoji
self.storeAccount(account, updateRelatedAccounts = false)
positionUpdated: Option[bool] = none(bool), notify: bool = true) =
if address.len > 0:
if not self.walletAccountsContainsAddress(address):
return
var account = self.getAccountByAddress(address)
if account.isNil:
return
if name.len > 0 or colorId.len > 0 or emoji.len > 0:
if name.len > 0 and name != account.name:
account.name = name
if colorId.len > 0 and colorId != account.colorId:
account.colorId = colorId
if emoji.len > 0 and emoji != account.emoji:
account.emoji = emoji
self.storeAccount(account, updateRelatedAccounts = false)
if notify:
self.events.emit(SIGNAL_WALLET_ACCOUNT_UPDATED, AccountArgs(account: account))
else:
if not positionUpdated.isSome:
return
if positionUpdated.get:
## if reordering was successfully stored, we need to update local storage
self.updateAccountsPositions()
if notify:
self.events.emit(SIGNAL_WALLET_ACCOUNT_UPDATED, AccountArgs(account: account))
if position.isSome and position.get != account.position:
account.position = position.get
self.storeAccount(account, updateRelatedAccounts = false)
if notify:
self.events.emit(SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED, AccountArgs(account: account))
self.events.emit(SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED, Args())
## if password is not provided local keystore file won't be created
proc addWalletAccount*(self: Service, password: string, doPasswordHashing: bool, name, address, path, publicKey,
@ -575,18 +599,16 @@ QtObject:
error "error: ", procName="updateWalletAccount", errName=e.name, errDesription=e.msg
return false
proc updateWalletAccountPosition*(self: Service, address: string, position: int) =
if not self.walletAccountsContainsAddress(address):
error "account's address is not among known addresses: ", address=address
return
proc moveAccountFinally*(self: Service, fromPosition: int, toPosition: int) =
var updated = false
try:
let response = backend.updateAccountPosition(address, position)
let response = backend.moveWalletAccount(fromPosition, toPosition)
if not response.error.isNil:
error "status-go error", procName="updateAccountPosition", errCode=response.error.code, errDesription=response.error.message
return
self.updateAccountInLocalStoreAndNotify(address, name = "", colorId = "", emoji = "", some(position))
error "status-go error", procName="moveAccountFinally", errCode=response.error.code, errDesription=response.error.message
updated = true
except Exception as e:
error "error: ", procName="updateAccountPosition", errName=e.name, errDesription=e.msg
error "error: ", procName="moveAccountFinally", errName=e.name, errDesription=e.msg
self.updateAccountInLocalStoreAndNotify(address = "", name = "", colorId = "", emoji = "", some(updated))
proc updateKeypairName*(self: Service, keyUid: string, name: string) =
try:
@ -968,11 +990,12 @@ QtObject:
proc handleWalletAccount(self: Service, account: WalletAccountDto, notify: bool = true) =
if account.removed:
self.updateAccountsPositions()
self.removeAccountFromLocalStoreAndNotify(account.address, notify)
else:
if self.walletAccountsContainsAddress(account.address):
self.updateAccountInLocalStoreAndNotify(account.address, account.name, account.colorId, account.emoji,
some(account.position), notify)
none(bool), notify)
else:
self.addNewAccountToLocalStoreAndNotify(notify)
@ -1019,7 +1042,7 @@ QtObject:
url: response{"url"}.getStr,
))
proc fetchChainIdForUrl*(self: Service, url: string) =
proc fetchChainIdForUrl*(self: Service, url: string) =
let arg = FetchChainIdForUrlTaskArg(
tptr: cast[ByteAddress](fetchChainIdForUrlTask),
vptr: cast[ByteAddress](self.vptr),

View File

@ -262,9 +262,9 @@ rpc(deleteKeycard, "accounts"):
rpc(deleteAllKeycardsWithKeyUID, "accounts"):
keyUid: string
rpc(updateAccountPosition, "accounts"):
address: string
position: int
rpc(moveWalletAccount, "accounts"):
fromPosition: int
toPosition: int
rpc(updateKeypairName, "accounts"):
keyUid: string

View File

@ -39,8 +39,12 @@ QtObject {
return accountsModule.updateAccount(address, accountName, colorId, emoji)
}
function updateAccountPosition(address, position) {
return accountsModule.updateAccountPosition(address, position)
function moveAccount(from, to) {
root.accountsModule.moveAccount(from, to)
}
function moveAccountFinally(from, to) {
root.accountsModule.moveAccountFinally(from, to)
}
function getAllNetworksSupportedPrefix() {

View File

@ -16,84 +16,95 @@ import utils 1.0
import "../../stores"
import "../../controls"
StatusListView {
id: accountsView
signal goBack
ColumnLayout {
id: root
property WalletStore walletStore
header: StatusBaseText {
text: accountsList.count > 1? qsTr("Move your most freqently used accounts to the top of your wallet list") :
signal goBack
spacing: Style.current.padding
QtObject {
id: d
readonly property string walletAccountDnDKey: "status-wallet-account-item"
property int indexMoveFrom: -1
property int indexMoveTo: -1
}
StatusBaseText {
Layout.fillWidth: true
text: accountsList.count > 1? qsTr("Move your most frequently used accounts to the top of your wallet list") :
qsTr("This account looks a little lonely. Add another account to enable re-ordering.")
color: Theme.palette.baseColor1
font.pixelSize: Style.current.primaryTextFontSize
bottomPadding: Style.current.padding
}
model: SortFilterProxyModel {
sourceModel: walletStore.accounts
sorters: [
RoleSorter {
roleName: "position"
priority: 2
}
]
}
StatusListView {
id: accountsList
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
model: walletStore.accounts
displaced: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
delegate: DropArea {
id: delegateRoot
property int visualIndex: index
width: ListView.view.width
height: draggableDelegate.height
keys: ["x-status-draggable-list-item-internal"]
onEntered: function(drag) {
const from = drag.source.visualIndex
const to = draggableDelegate.visualIndex
if (to === from)
return
drag.accept()
displaced: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }
}
onDropped: function(drop) {
walletStore.updateAccountPosition(drop.source.address, draggableDelegate.position)
drop.accept()
}
delegate: DropArea {
id: delegateRoot
StatusDraggableListItem {
id: draggableDelegate
property int visualIndex: index
property int position: model.position
property string address: model.address
width: parent.width
height: implicitHeight
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
width: ListView.view.width
height: draggableDelegate.height
keys: [d.walletAccountDnDKey]
onEntered: function(drag) {
const from = drag.source.visualIndex
const to = draggableDelegate.visualIndex
if (to === from)
return
if (d.indexMoveFrom === -1)
d.indexMoveFrom = from
d.indexMoveTo = to
root.walletStore.moveAccount(from, to)
drag.accept()
}
dragParent: accountsView
visualIndex: delegateRoot.visualIndex
draggable: accountsView.count > 1
title: {
return model.name
onDropped: function(drop) {
let from = d.indexMoveFrom
let to = d.indexMoveTo
d.indexMoveFrom = -1
d.indexMoveTo = -1
root.walletStore.moveAccountFinally(from, to)
}
StatusDraggableListItem {
id: draggableDelegate
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: implicitHeight
dragParent: accountsList
visualIndex: delegateRoot.visualIndex
draggable: accountsList.count > 1
Drag.keys: [d.walletAccountDnDKey]
title: model.name
bgColor: Theme.palette.baseColor1
secondaryTitle: model.address
secondaryTitleIcon: model.walletType === Constants.watchWalletType? "show" :
model.keycardAccount ? "keycard" : ""
hasEmoji: true
icon.width: 40
icon.height: 40
icon.name: model.emoji
icon.color: Utils.getColorForId(model.colorId)
actions: []
}
secondaryTitle: model.address
secondaryTitleIcon: model.walletType === Constants.watchWalletType? "show" :
model.keycardAccount ? "keycard" : ""
hasEmoji: true
icon.width: 40
icon.height: 40
icon.name: model.emoji
icon.color: Utils.getColorForId(model.colorId)
actions: []
}
}
}

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 12dc86fe1b570b0cbe0ada5960f1d07aa147d763
Subproject commit 42d5d36cf574674d5639cfdfa57810ce2319fa0c