feat:(@wallet): create wallet section in profile

Separate wallet module from profile wallet module
1 - they caused us many conflict regarding current
2 - better decoupling
This commit is contained in:
Anthony Laibe 2023-04-17 13:36:40 +02:00 committed by Anthony Laibe
parent fb8ea4a054
commit 4e5f7763db
27 changed files with 1169 additions and 76 deletions

View File

@ -93,4 +93,13 @@ method communitiesModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getKeycardModule*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")
raise newException(ValueError, "No implementation available")
method walletModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getWalletAccountsModule*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")
method getWalletNetworksModule*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -37,6 +37,7 @@ import ./notifications/module as notifications_module
import ./ens_usernames/module as ens_usernames_module
import ./communities/module as communities_module
import ./keycard/module as keycard_module
import ./wallet/module as wallet_module
export io_interface
@ -61,6 +62,7 @@ type
ensUsernamesModule: ens_usernames_module.AccessInterface
communitiesModule: communities_module.AccessInterface
keycardModule: keycard_module.AccessInterface
walletModule: wallet_module.AccessInterface
proc newModule*(delegate: delegate_interface.AccessInterface,
events: EventEmitter,
@ -109,6 +111,8 @@ proc newModule*(delegate: delegate_interface.AccessInterface,
result.keycardModule = keycard_module.newModule(result, events, keycardService, settingsService, networkService,
privacyService, accountsService, walletAccountService, keychainService)
result.walletModule = wallet_module.newModule(result, events, walletAccountService, settingsService, networkService)
singletonInstance.engine.setRootContextProperty("profileSectionModule", result.viewVariant)
method delete*(self: Module) =
@ -143,6 +147,7 @@ method load*(self: Module) =
self.ensUsernamesModule.load()
self.communitiesModule.load()
self.keycardModule.load()
self.walletModule.load()
method isLoaded*(self: Module): bool =
return self.moduleLoaded
@ -187,6 +192,9 @@ proc checkIfModuleDidLoad(self: Module) =
if(not self.keycardModule.isLoaded()):
return
if(not self.walletModule.isLoaded()):
return
self.moduleLoaded = true
self.delegate.profileSectionDidLoad()
@ -263,4 +271,13 @@ method communitiesModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method getKeycardModule*(self: Module): QVariant =
self.keycardModule.getModuleAsVariant()
self.keycardModule.getModuleAsVariant()
method walletModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method getWalletAccountsModule*(self: Module): QVariant =
return self.walletModule.getAccountsModuleAsVariant()
method getWalletNetworksModule*(self: Module): QVariant =
return self.walletModule.getNetworksModuleAsVariant()

View File

@ -80,3 +80,13 @@ QtObject:
return self.delegate.getKeycardModule()
QtProperty[QVariant] keycardModule:
read = getKeycardModule
proc getWalletAccountsModule(self: View): QVariant {.slot.} =
return self.delegate.getWalletAccountsModule()
QtProperty[QVariant] walletAccountsModule:
read = getWalletAccountsModule
proc getWalletNetworksModule(self: View): QVariant {.slot.} =
return self.delegate.getWalletNetworksModule()
QtProperty[QVariant] walletNetworksModule:
read = getWalletNetworksModule

View File

@ -0,0 +1,57 @@
import sugar, sequtils, tables
import io_interface
import ../../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../../app_service/service/network/service as network_service
import ../../../../../global/global_singleton
import ../../../../shared_modules/keycard_popup/io_interface as keycard_shared_module
import ../../../../../core/eventemitter
const UNIQUE_PROFILE_SECTION_ACCOUNTS_MODULE_AUTH_IDENTIFIER* = "ProfileSection-AccountsModule-Authentication"
type
Controller* = ref object of RootObj
delegate: io_interface.AccessInterface
events: EventEmitter
walletAccountService: wallet_account_service.Service
proc newController*(
delegate: io_interface.AccessInterface,
events: EventEmitter,
walletAccountService: wallet_account_service.Service,
): Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.walletAccountService = walletAccountService
proc delete*(self: Controller) =
discard
proc init*(self: Controller) =
self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args):
let args = SharedKeycarModuleArgs(e)
if args.uniqueIdentifier != UNIQUE_PROFILE_SECTION_ACCOUNTS_MODULE_AUTH_IDENTIFIER:
return
self.delegate.onUserAuthenticated(args.pin, args.password, args.keyUid)
proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] =
return self.walletAccountService.getWalletAccounts()
proc updateAccount*(self: Controller, address: string, accountName: string, color: string, emoji: string) =
discard self.walletAccountService.updateWalletAccount(address, accountName, color, emoji)
proc deleteAccount*(self: Controller, address: string, password = "", doPasswordHashing = false) =
self.walletAccountService.deleteAccount(address, password, doPasswordHashing)
proc authenticateKeyPair*(self: Controller, keyUid = "") =
let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_PROFILE_SECTION_ACCOUNTS_MODULE_AUTH_IDENTIFIER,
keyUid: keyUid)
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data)
proc getMigratedKeyPairByKeyUid*(self: Controller, keyUid: string): seq[KeyPairDto] =
return self.walletAccountService.getMigratedKeyPairByKeyUid(keyUid)
proc getWalletAccount*(self: Controller, address: string): WalletAccountDto =
return self.walletAccountService.getAccountByAddress(address)

View File

@ -0,0 +1,41 @@
import NimQml
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for any input/interaction with this module.
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method syncKeycard*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method deleteAccount*(self: AccessInterface, keyUid: string, address: string) {.base.} =
raise newException(ValueError, "No implementation available")
method refreshWalletAccounts*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method updateAccount*(self: AccessInterface, address: string, accountName: string, color: string, emoji: string) {.base.} =
raise newException(ValueError, "No implementation available")
# View Delegate Interface
# Delegate for the view must be declared here due to use of QtObject and multi
# inheritance, which is not well supported in Nim.
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method authenticateUser*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onUserAuthenticated*(self: AccessInterface, pin: string, password: string, keyUid: string) {.base.} =
raise newException(ValueError, "No implementation available")
method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,68 @@
import strformat
import ./related_accounts_model as related_accounts_model
type
Item* = object
name*: string
address: string
color*: string
emoji*: string
walletType: string
path: string
relatedAccounts: related_accounts_model.Model
keyUid: string
proc initItem*(
name: string = "",
address: string = "",
path: string = "",
color: string = "",
walletType: string = "",
emoji: string = "",
relatedAccounts: related_accounts_model.Model = nil,
keyUid: string = "",
): Item =
result.name = name
result.address = address
result.path = path
result.color = color
result.walletType = walletType
result.emoji = emoji
result.relatedAccounts = relatedAccounts
result.keyUid = keyUid
proc `$`*(self: Item): string =
result = fmt"""WalletAccountItem(
name: {self.name},
address: {self.address},
path: {self.path},
color: {self.color},
walletType: {self.walletType},
emoji: {self.emoji},
relatedAccounts: {self.relatedAccounts}
keyUid: {self.keyUid},
]"""
proc getName*(self: Item): string =
return self.name
proc getAddress*(self: Item): string =
return self.address
proc getPath*(self: Item): string =
return self.path
proc getEmoji*(self: Item): string =
return self.emoji
proc getColor*(self: Item): string =
return self.color
proc getWalletType*(self: Item): string =
return self.walletType
proc getRelatedAccounts*(self: Item): related_accounts_model.Model =
return self.relatedAccounts
proc getKeyUid*(self: Item): string =
return self.keyUid

View File

@ -0,0 +1,106 @@
import NimQml, Tables, strutils, strformat
import ./item
type
ModelRole {.pure.} = enum
Name = UserRole + 1,
Address,
Path,
Color,
WalletType,
Emoji,
RelatedAccounts,
KeyUid,
QtObject:
type
Model* = ref object of QAbstractListModel
items: seq[Item]
proc delete(self: Model) =
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: Model) =
self.QAbstractListModel.setup
proc newModel*(): Model =
new(result, delete)
result.setup
proc `$`*(self: Model): string =
for i in 0 ..< self.items.len:
result &= fmt"""[{i}]:({$self.items[i]})"""
proc countChanged(self: Model) {.signal.}
proc getCount*(self: Model): int {.slot.} =
self.items.len
QtProperty[int] count:
read = getCount
notify = countChanged
method rowCount(self: Model, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: Model): Table[int, string] =
{
ModelRole.Name.int:"name",
ModelRole.Address.int:"address",
ModelRole.Path.int:"path",
ModelRole.Color.int:"color",
ModelRole.WalletType.int:"walletType",
ModelRole.Emoji.int: "emoji",
ModelRole.RelatedAccounts.int: "relatedAccounts",
ModelRole.KeyUid.int: "keyUid",
}.toTable
proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel()
self.items = items
self.endResetModel()
self.countChanged()
proc onUpdatedAccount*(self: Model, account: Item) =
var i = 0
for item in self.items.mitems:
if account.getAddress() == item.getAddress():
item.name = account.name
item.color = account.color
item.emoji = account.emoji
let index = self.createIndex(i, 0, nil)
self.dataChanged(index, index, @[ModelRole.Name.int])
self.dataChanged(index, index, @[ModelRole.Color.int])
self.dataChanged(index, index, @[ModelRole.Emoji.int])
break
i.inc
method data(self: Model, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.items.len):
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.Name:
result = newQVariant(item.getName())
of ModelRole.Address:
result = newQVariant(item.getAddress())
of ModelRole.Path:
result = newQVariant(item.getPath())
of ModelRole.Color:
result = newQVariant(item.getColor())
of ModelRole.WalletType:
result = newQVariant(item.getWalletType())
of ModelRole.Emoji:
result = newQVariant(item.getEmoji())
of ModelRole.RelatedAccounts:
result = newQVariant(item.getRelatedAccounts())
of ModelRole.KeyUid:
result = newQVariant(item.getKeyUid())

View File

@ -0,0 +1,136 @@
import tables, NimQml, sequtils, sugar, chronicles
import ./io_interface, ./view, ./item, ./controller, ./utils
import ../io_interface as delegate_interface
import ../../../../../global/global_singleton
import ../../../../../core/eventemitter
import ../../../../../../app_service/common/account_constants
import ../../../../../../app_service/service/keycard/service as keycard_service
import ../../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../../app_service/service/network/service as network_service
import ../../../../shared_modules/keycard_popup/io_interface as keycard_shared_module
export io_interface
# TODO: remove it completely if after wallet settings part refactore this is not needed.
type
AuthenticationReason {.pure.} = enum
DeleteAccountAuthentication = 0
# TODO: remove it completely if after wallet settings part refactore this is not needed.
type WalletAccountDetails = object
address: string
path: string
addressAccountIsDerivedFrom: string
publicKey: string
keyUid: string
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
events: EventEmitter
view: View
viewVariant: QVariant
controller: Controller
moduleLoaded: bool
walletAccountService: wallet_account_service.Service
processingWalletAccount: WalletAccountDetails
authentiactionReason: AuthenticationReason
proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
walletAccountService: wallet_account_service.Service,
networkService: network_service.Service,
): Module =
result = Module()
result.delegate = delegate
result.events = events
result.walletAccountService = walletAccountService
result.view = newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, walletAccountService)
result.moduleLoaded = false
result.authentiactionReason = AuthenticationReason.DeleteAccountAuthentication
method delete*(self: Module) =
self.view.delete
self.viewVariant.delete
self.controller.delete
method getModuleAsVariant*(self: Module): QVariant =
return self.viewVariant
method refreshWalletAccounts*(self: Module) =
let walletAccounts = self.controller.getWalletAccounts()
let items = walletAccounts.map(w => (block:
walletAccountToItem(w)
))
self.view.setItems(items)
method load*(self: Module) =
self.events.on(SIGNAL_WALLET_ACCOUNT_SAVED) do(e:Args):
self.refreshWalletAccounts()
self.events.on(SIGNAL_WALLET_ACCOUNT_DELETED) do(e:Args):
self.refreshWalletAccounts()
self.events.on(SIGNAL_WALLET_ACCOUNT_UPDATED) do(e:Args):
let args = WalletAccountUpdated(e)
self.view.onUpdatedAccount(walletAccountToItem(args.account))
self.events.on(SIGNAL_NEW_KEYCARD_SET) do(e: Args):
let args = KeycardActivityArgs(e)
if not args.success:
return
self.refreshWalletAccounts()
self.events.on(SIGNAL_KEYCARDS_SYNCHRONIZED) do(e: Args):
let args = KeycardActivityArgs(e)
if not args.success:
return
self.refreshWalletAccounts()
self.controller.init()
self.view.load()
method isLoaded*(self: Module): bool =
return self.moduleLoaded
method viewDidLoad*(self: Module) =
self.refreshWalletAccounts()
self.moduleLoaded = true
self.delegate.accountsModuleDidLoad()
method updateAccount*(self: Module, address: string, accountName: string, color: string, emoji: string) =
self.controller.updateAccount(address, accountName, color, emoji)
method authenticateActivityForKeyUid(self: Module, keyUid: string, reason: AuthenticationReason) =
self.authentiactionReason = reason
let keyPair = self.controller.getMigratedKeyPairByKeyUid(keyUid)
let keyPairMigratedToKeycard = keyPair.len > 0
if keyPairMigratedToKeycard:
self.controller.authenticateKeyPair(keyUid)
else:
self.processingWalletAccount.keyUid = singletonInstance.userProfile.getKeyUid()
self.controller.authenticateKeyPair()
method deleteAccount*(self: Module, keyUid: string, address: string) =
let accountDto = self.controller.getWalletAccount(address)
if accountDto.walletType == WalletTypeWatch:
self.controller.deleteAccount(address)
return
self.processingWalletAccount = WalletAccountDetails(keyUid: keyUid, address: address)
self.authenticateActivityForKeyUid(keyUid, AuthenticationReason.DeleteAccountAuthentication)
method onUserAuthenticated*(self: Module, pin: string, password: string, keyUid: string) =
if self.authentiactionReason == AuthenticationReason.DeleteAccountAuthentication:
if self.processingWalletAccount.keyUid != keyUid:
error "cannot resolve key uid of an account being deleted", keyUid=keyUid
return
if password.len == 0:
return
let doPasswordHashing = pin.len != PINLengthForStatusApp
self.controller.deleteAccount(self.processingWalletAccount.address, password, doPasswordHashing)

View File

@ -0,0 +1,32 @@
import strformat
type
Item* = object
name: string
color: string
emoji: string
proc initItem*(
name: string = "",
color: string = "",
emoji: string = "",
): Item =
result.name = name
result.color = color
result.emoji = emoji
proc `$`*(self: Item): string =
result = fmt"""WalletAccountItem(
name: {self.name},
color: {self.color},
emoji: {self.emoji},
]"""
proc getName*(self: Item): string =
return self.name
proc getEmoji*(self: Item): string =
return self.emoji
proc getColor*(self: Item): string =
return self.color

View File

@ -0,0 +1,73 @@
import NimQml, Tables, strutils, strformat
import ./related_account_item
type
ModelRole {.pure.} = enum
Name = UserRole + 1,
Color,
Emoji,
QtObject:
type
Model* = ref object of QAbstractListModel
items: seq[Item]
proc delete(self: Model) =
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: Model) =
self.QAbstractListModel.setup
proc newModel*(): Model =
new(result, delete)
result.setup
proc `$`*(self: Model): string =
for i in 0 ..< self.items.len:
result &= fmt"""[{i}]:({$self.items[i]})"""
proc countChanged(self: Model) {.signal.}
proc getCount*(self: Model): int {.slot.} =
self.items.len
QtProperty[int] count:
read = getCount
notify = countChanged
method rowCount(self: Model, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: Model): Table[int, string] =
{
ModelRole.Name.int:"name",
ModelRole.Color.int:"color",
ModelRole.Emoji.int: "emoji",
}.toTable
proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel()
self.items = items
self.endResetModel()
self.countChanged()
method data(self: Model, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.items.len):
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.Name:
result = newQVariant(item.getName())
of ModelRole.Color:
result = newQVariant(item.getColor())
of ModelRole.Emoji:
result = newQVariant(item.getEmoji())

View File

@ -0,0 +1,34 @@
import tables, sequtils, sugar
import ../../../../../../app_service/service/wallet_account/service as wallet_account_service
import ./item
import ./related_account_item as related_account_item
import ./related_accounts_model as related_accounts_model
proc walletAccountToRelatedAccountItem*(w: WalletAccountDto) : related_account_item.Item =
return related_account_item.initItem(
w.name,
w.color,
w.emoji,
)
proc walletAccountToItem*(
w: WalletAccountDto,
) : item.Item =
let relatedAccounts = related_accounts_model.newModel()
if w.isNil:
return item.initItem()
relatedAccounts.setItems(
w.relatedAccounts.map(x => walletAccountToRelatedAccountItem(x))
)
return item.initItem(
w.name,
w.address,
w.path,
w.color,
w.walletType,
w.emoji,
relatedAccounts,
w.keyUid,
)

View File

@ -0,0 +1,48 @@
import NimQml, sequtils, strutils, sugar
import ./model
import ./item
import ./io_interface
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
accounts: Model
accountsVariant: QVariant
proc delete*(self: View) =
self.accounts.delete
self.accountsVariant.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.QObject.setup
result.delegate = delegate
result.accounts = newModel()
result.accountsVariant = newQVariant(result.accounts)
proc load*(self: View) =
self.delegate.viewDidLoad()
proc accountsChanged*(self: View) {.signal.}
proc getAccounts(self: View): QVariant {.slot.} =
return self.accountsVariant
QtProperty[QVariant] accounts:
read = getAccounts
notify = accountsChanged
proc setItems*(self: View, items: seq[Item]) =
self.accounts.setItems(items)
proc updateAccount(self: View, address: string, accountName: string, color: string, emoji: string) {.slot.} =
self.delegate.updateAccount(address, accountName, color, emoji)
proc onUpdatedAccount*(self: View, account: Item) =
self.accounts.onUpdatedAccount(account)
proc deleteAccount*(self: View, keyUid: string, address: string) {.slot.} =
self.delegate.deleteAccount(keyUid, address)

View File

@ -0,0 +1,33 @@
import NimQml
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for any input/interaction with this module.
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
# View Delegate Interface
# Delegate for the view must be declared here due to use of QtObject and multi
# inheritance, which is not well supported in Nim.
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
# Methods called by submodules of this module
method accountsModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method networksModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getAccountsModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")
method getNetworksModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,79 @@
import NimQml, chronicles
import ./io_interface as io_interface
import ../io_interface as delegate_interface
import ./accounts/module as accounts_module
import ./networks/module as networks_module
import ../../../../global/global_singleton
import ../../../../core/eventemitter
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../app_service/service/network/service as network_service
import ../../../../../app_service/service/settings/service as settings_service
logScope:
topics = "profile-section-wallet-module"
import io_interface
export io_interface
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
events: EventEmitter
moduleLoaded: bool
accountsModule: accounts_module.AccessInterface
networksModule: networks_module.AccessInterface
proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
walletAccountService: wallet_account_service.Service,
settingsService: settings_service.Service,
networkService: network_service.Service,
): Module =
result = Module()
result.delegate = delegate
result.events = events
result.moduleLoaded = false
result.accountsModule = accounts_module.newModule(result, events, walletAccountService, networkService)
result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService)
method delete*(self: Module) =
self.accountsModule.delete
self.networksModule.delete
method load*(self: Module) =
self.accountsModule.load()
self.networksModule.load()
method isLoaded*(self: Module): bool =
return self.moduleLoaded
method getAccountsModuleAsVariant*(self: Module): QVariant =
return self.accountsModule.getModuleAsVariant()
method getNetworksModuleAsVariant*(self: Module): QVariant =
return self.networksModule.getModuleAsVariant()
proc checkIfModuleDidLoad(self: Module) =
if(not self.accountsModule.isLoaded()):
return
if(not self.networksModule.isLoaded()):
return
self.moduleLoaded = true
self.delegate.walletModuleDidLoad()
method viewDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method accountsModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method networksModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad()

View File

@ -0,0 +1,44 @@
import ../../../../../core/eventemitter
import ../../../../../../app_service/service/network/service as network_service
import ../../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../../app_service/service/settings/service as settings_service
import ./io_interface
type
Controller* = ref object of RootObj
delegate: io_interface.AccessInterface
events: EventEmitter
networkService: network_service.Service
walletAccountService: wallet_account_service.Service
settingsService: settings_service.Service
proc newController*(
delegate: io_interface.AccessInterface,
events: EventEmitter,
networkService: network_service.Service,
walletAccountService: wallet_account_service.Service,
settingsService: settings_service.Service,
): Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.networkService = networkService
result.walletAccountService = walletAccountService
result.settingsService = settingsService
proc delete*(self: Controller) =
discard
proc init*(self: Controller) =
self.events.on(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED) do(e: Args):
self.delegate.refreshNetworks()
proc getNetworks*(self: Controller): seq[NetworkDto] =
return self.networkService.getNetworks()
proc areTestNetworksEnabled*(self: Controller): bool =
return self.settingsService.areTestNetworksEnabled()
proc toggleTestNetworksEnabled*(self: Controller) =
self.walletAccountService.toggleTestNetworksEnabled()

View File

@ -0,0 +1,29 @@
import NimQml
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for any input/interaction with this module.
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
# View Delegate Interface
# Delegate for the view must be declared here due to use of QtObject and multi
# inheritance, which is not well supported in Nim.
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method refreshNetworks*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method toggleTestNetworksEnabled*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,39 @@
import strformat
type
Item* = object
chainId: int
layer: int
chainName: string
iconUrl: string
proc initItem*(
chainId: int,
layer: int,
chainName: string,
iconUrl: string,
): Item =
result.chainId = chainId
result.layer = layer
result.chainName = chainName
result.iconUrl = iconUrl
proc `$`*(self: Item): string =
result = fmt"""NetworkItem(
chainId: {self.chainId},
chainName: {self.chainName},
layer: {self.layer},
iconUrl:{self.iconUrl},
]"""
proc getChainId*(self: Item): int =
return self.chainId
proc getLayer*(self: Item): int =
return self.layer
proc getChainName*(self: Item): string =
return self.chainName
proc getIconURL*(self: Item): string =
return self.iconUrl

View File

@ -0,0 +1,88 @@
import NimQml, Tables, strutils, strformat
import ./item
const EXPLORER_TX_PREFIX* = "/tx/"
type
ModelRole* {.pure.} = enum
ChainId = UserRole + 1,
Layer
ChainName
IconUrl
QtObject:
type
Model* = ref object of QAbstractListModel
items: seq[Item]
proc delete(self: Model) =
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: Model) =
self.QAbstractListModel.setup
proc newModel*(): Model =
new(result, delete)
result.setup
proc `$`*(self: Model): string =
for i in 0 ..< self.items.len:
result &= fmt"""[{i}]:({$self.items[i]})"""
proc countChanged(self: Model) {.signal.}
proc getCount(self: Model): int {.slot.} =
self.items.len
QtProperty[int] count:
read = getCount
notify = countChanged
method rowCount*(self: Model, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: Model): Table[int, string] =
{
ModelRole.ChainId.int:"chainId",
ModelRole.Layer.int:"layer",
ModelRole.ChainName.int:"chainName",
ModelRole.IconUrl.int:"iconUrl",
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.items.len):
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.ChainId:
result = newQVariant(item.getChainId())
of ModelRole.Layer:
result = newQVariant(item.getLayer())
of ModelRole.ChainName:
result = newQVariant(item.getChainName())
of ModelRole.IconUrl:
result = newQVariant(item.getIconURL())
proc rowData*(self: Model, index: int, column: string): string {.slot.} =
if (index >= self.items.len):
return
let item = self.items[index]
case column:
of "chainId": result = $item.getChainId()
of "layer": result = $item.getLayer()
of "chainName": result = $item.getChainName()
of "iconUrl": result = $item.getIconURL()
proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel()
self.items = items
self.endResetModel()
self.countChanged()

View File

@ -0,0 +1,64 @@
import Tables, NimQml
import ../io_interface as delegate_interface
import io_interface, view, controller
import ../../../../../core/eventemitter
import ../../../../../../app_service/service/network/service as network_service
import ../../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../../app_service/service/settings/service as settings_service
export io_interface
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
view: View
viewVariant: QVariant
controller: Controller
moduleLoaded: bool
proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
networkService: networkService.Service,
walletAccountService: wallet_account_service.Service,
settingsService: settings_service.Service,
): Module =
result = Module()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, networkService, walletAccountService, settingsService)
result.moduleLoaded = false
method delete*(self: Module) =
self.view.delete
self.viewVariant.delete
self.controller.delete
method getModuleAsVariant*(self: Module): QVariant =
return self.viewVariant
method refreshNetworks*(self: Module) =
self.view.load(self.controller.getNetworks())
method load*(self: Module) =
self.controller.init()
self.view.setAreTestNetworksEnabled(self.controller.areTestNetworksEnabled())
self.refreshNetworks()
method isLoaded*(self: Module): bool =
return self.moduleLoaded
proc checkIfModuleDidLoad(self: Module) =
self.moduleLoaded = true
self.delegate.networksModuleDidLoad()
method viewDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method areTestNetworksEnabled*(self: Module): bool =
return self.controller.areTestNetworksEnabled()
method toggleTestNetworksEnabled*(self: Module) =
self.controller.toggleTestNetworksEnabled()
self.refreshNetworks()

View File

@ -0,0 +1,65 @@
import Tables, NimQml, sequtils, sugar
import ../../../../../../app_service/service/network/dto
import ./io_interface
import ./model
import ./item
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
networks: Model
areTestNetworksEnabled: bool
proc setup(self: View) =
self.QObject.setup
proc delete*(self: View) =
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.delegate = delegate
result.networks = newModel()
result.setup()
proc areTestNetworksEnabledChanged*(self: View) {.signal.}
proc getAreTestNetworksEnabled(self: View): bool {.slot.} =
return self.areTestNetworksEnabled
QtProperty[bool] areTestNetworksEnabled:
read = getAreTestNetworksEnabled
notify = areTestNetworksEnabledChanged
proc setAreTestNetworksEnabled*(self: View, areTestNetworksEnabled: bool) =
self.areTestNetworksEnabled = areTestNetworksEnabled
self.areTestNetworksEnabledChanged()
proc toggleTestNetworksEnabled*(self: View) {.slot.} =
self.delegate.toggleTestNetworksEnabled()
self.areTestNetworksEnabled = not self.areTestNetworksEnabled
self.areTestNetworksEnabledChanged()
proc networksChanged*(self: View) {.signal.}
proc getNetworks(self: View): QVariant {.slot.} =
return newQVariant(self.networks)
QtProperty[QVariant] networks:
read = getNetworks
notify = networksChanged
proc load*(self: View, networks: seq[NetworkDto]) =
var items: seq[Item] = @[]
for n in networks:
items.add(initItem(
n.chainId,
n.layer,
n.chainName,
n.iconUrl,
))
self.networks.setItems(items)
self.delegate.viewDidLoad()

View File

@ -20,10 +20,10 @@ StatusModal {
id: popup
property WalletStore walletStore
property var currentAccount: walletStore.currentAccount
property var account
property var emojiPopup
header.title: qsTr("Rename %1").arg(currentAccount.name)
header.title: qsTr("Rename %1").arg(popup.account.name)
property int marginBetweenInputs: 30
@ -53,10 +53,10 @@ StatusModal {
input.edit.objectName: "renameAccountNameInput"
input.isIconSelectable: true
placeholderText: qsTr("Enter an account name...")
input.text: currentAccount.name
input.asset.emoji: currentAccount.emoji
input.asset.color: currentAccount.color
input.asset.name: !currentAccount.emoji ? "filled-account": ""
input.text: popup.account.name
input.asset.emoji: popup.account.emoji
input.asset.color: popup.account.color
input.asset.name: !popup.account.emoji ? "filled-account": ""
validationMode: StatusInput.ValidationMode.Always
@ -85,16 +85,16 @@ StatusModal {
anchors.horizontalCenter: parent.horizontalCenter
model: Constants.preDefinedWalletAccountColors
titleText: qsTr("color").toUpperCase()
selectedColor: currentAccount.color
selectedColor: popup.account.color
selectedColorIndex: {
for (let i = 0; i < model.length; i++) {
if(model[i] === currentAccount.color)
if(model[i] === popup.account.color)
return i
}
return -1
}
onSelectedColorChanged: {
if(selectedColor !== currentAccount.color) {
if(selectedColor !== popup.account.color) {
accountNameInput.input.asset.color = selectedColor
}
}
@ -113,8 +113,8 @@ StatusModal {
text: qsTr("Change Name")
enabled: accountNameInput.text !== "" && accountNameInput.valid
&& (accountNameInput.text !== currentAccount.name
|| (accountColorInput.selectedColorIndex >= 0 && accountColorInput.selectedColor !== currentAccount.color))
&& (accountNameInput.text !== popup.account.name
|| (accountColorInput.selectedColorIndex >= 0 && accountColorInput.selectedColor !== popup.account.color))
MessageDialog {
id: changeError
@ -128,7 +128,7 @@ StatusModal {
return
}
const error = walletStore.updateCurrentAccount(currentAccount.address, accountNameInput.text, accountColorInput.selectedColor, accountNameInput.input.asset.emoji);
const error = walletStore.updateAccount(popup.account.address, accountNameInput.text, accountColorInput.selectedColor, accountNameInput.input.asset.emoji);
if (error) {
Global.playErrorSound();

View File

@ -56,6 +56,8 @@ QtObject {
}
property WalletStore walletStore: WalletStore {
accountsModule: profileSectionModuleInst.walletAccountsModule
networksModule: profileSectionModuleInst.walletNetworksModule
}
property KeycardStore keycardStore: KeycardStore {

View File

@ -1,41 +1,32 @@
import QtQuick 2.13
import "../../Wallet/stores"
import utils 1.0
QtObject {
id: root
property var accountsModule
property var networksModule
property var accountSensitiveSettings: Global.appIsReady? localAccountSensitiveSettings : null
property var areTestNetworksEnabled: networksModule.areTestNetworksEnabled
property var layer1Networks: networksModule.layer1
property var layer2Networks: networksModule.layer2
property var testNetworks: networksModule.test
property var networks: networksModule.networks
function toggleTestNetworksEnabled(){
networksModule.toggleTestNetworksEnabled()
}
property var accounts: Global.appIsReady? walletSectionAccounts.model : null
property var importedAccounts: Global.appIsReady? walletSectionAccounts.imported : null
property var generatedAccounts: Global.appIsReady? walletSectionAccounts.generated : null
property var watchOnlyAccounts: Global.appIsReady? walletSectionAccounts.watchOnly : null
// TODO(alaibe): there should be no access to wallet section, create collectible in profile
property var flatCollectibles: Global.appIsReady ? walletSectionCollectibles.model : null
property var accounts: Global.appIsReady? accountsModule.accounts : null
property var currentAccount: Global.appIsReady? walletSectionCurrent : null
function switchAccountByAddress(address) {
walletSection.switchAccountByAddress(address)
}
function deleteAccount(keyUid, address) {
return walletSectionAccounts.deleteAccount(keyUid, address)
return accountsModule.deleteAccount(keyUid, address)
}
function updateCurrentAccount(address, accountName, color, emoji) {
return walletSectionCurrent.update(address, accountName, color, emoji)
function updateAccount(address, accountName, color, emoji) {
return accountsModule.updateAccount(address, accountName, color, emoji)
}
property var dappList: Global.appIsReady? dappPermissionsModule.dapps : null

View File

@ -71,7 +71,7 @@ SettingsContentBase {
}
onGoToAccountView: {
root.walletStore.switchAccountByAddress(address)
accountView.account = account
stackContainer.currentIndex = accountViewIndex
}
@ -89,6 +89,7 @@ SettingsContentBase {
}
AccountView {
id: accountView
walletStore: root.walletStore
emojiPopup: root.emojiPopup

View File

@ -21,6 +21,7 @@ Item {
property WalletStore walletStore
property var emojiPopup
property var account
Column {
id: column
@ -42,11 +43,11 @@ Item {
asset: StatusAssetSettings {
width: isLetterIdenticon ? 40 : 20
height: isLetterIdenticon ? 40 : 20
color: walletStore.currentAccount.color
emoji: walletStore.currentAccount.emoji
name: !walletStore.currentAccount.emoji ? "filled-account": ""
color: root.account ? root.account.color : "#ffffff"
emoji: root.account ? root.account.emoji : ""
name: root.account && !root.account.emoji ? "filled-account": ""
letterSize: 14
isLetterIdenticon: !!walletStore.currentAccount.emoji
isLetterIdenticon: !!root.account && !!root.account.emoji
bgWidth: 40
bgHeight: 40
bgColor: Theme.palette.primaryColor3
@ -59,7 +60,7 @@ Item {
StatusBaseText {
objectName: "walletAccountViewAccountName"
id: accountName
text: walletStore.currentAccount.name
text:root.account ? root.account.name : ""
font.weight: Font.Bold
font.pixelSize: 28
color: Theme.palette.directColor1
@ -76,7 +77,7 @@ Item {
}
}
StatusAddressPanel {
value: walletStore.currentAccount.address
value: root.account ? root.account.address : ""
font.weight: Font.Normal
@ -98,7 +99,10 @@ Item {
maxWidth: parent.width
primaryText: qsTr("Type")
secondaryText: {
const walletType = walletStore.currentAccount.walletType
if (!root.account) {
return ""
}
const walletType = root.account.walletType
if (walletType === "watch") {
return qsTr("Watch-Only Account")
} else if (walletType === "generated" || walletType === "") {
@ -118,15 +122,15 @@ Item {
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Derivation Path")
secondaryText: walletStore.currentAccount.path
visible: walletStore.currentAccount.path
secondaryText: root.account ? root.account.path : ""
visible: !!root.account && root.account.path
}
InformationTile {
maxWidth: parent.width
visible: walletStore.currentAccount.relatedAccounts.count > 0
visible:root.account ? root.account.relatedAccounts.count > 0 : false
primaryText: qsTr("Related Accounts")
tagsModel: walletStore.currentAccount.relatedAccounts
tagsModel: root.account ? root.account.relatedAccounts : []
tagsDelegate: StatusListItemTag {
bgColor: model.color
bgRadius: 6
@ -144,20 +148,20 @@ Item {
StatusButton {
objectName: "deleteAccountButton"
visible: walletStore.currentAccount.walletType !== ""
visible: !!root.account && root.account.walletType !== ""
text: qsTr("Remove from your profile")
type: StatusBaseButton.Type.Danger
ConfirmationDialog {
id: confirmationPopup
confirmButtonObjectName: "confirmDeleteAccountButton"
header.title: qsTr("Confirm %1 Removal").arg(walletStore.currentAccount.name)
header.title: qsTr("Confirm %1 Removal").arg(root.account ? root.account.name : "")
confirmationText: qsTr("You will not be able to restore viewing access to this account in the future unless you enter this accounts address again.")
confirmButtonLabel: qsTr("Remove Account")
onConfirmButtonClicked: {
confirmationPopup.close();
root.goBack();
root.walletStore.deleteAccount(walletStore.currentAccount.keyUid, walletStore.currentAccount.address);
root.walletStore.deleteAccount(root.account.keyUid, root.account.address);
}
}
@ -171,6 +175,7 @@ Item {
Component {
id: renameAccountModalComponent
RenameAccontModal {
account: root.account
anchors.centerIn: parent
onClosed: destroy()
walletStore: root.walletStore

View File

@ -1,4 +1,5 @@
import QtQuick 2.13
import SortFilterProxyModel 0.2
import utils 1.0
import shared.status 1.0
@ -17,7 +18,7 @@ Column {
property WalletStore walletStore
signal goToNetworksView()
signal goToAccountView(address: string)
signal goToAccountView(var account)
signal goToDappPermissionsView()
Component.onCompleted: {
@ -84,11 +85,28 @@ Column {
width: parent.width
height: childrenRect.height
objectName: "generatedAccounts"
model: walletStore.generatedAccounts
model: SortFilterProxyModel {
sourceModel: walletStore.accounts
filters: ExpressionFilter {
expression: {
return model.walletType === "generated" || model.walletType === ""
}
}
}
delegate: WalletAccountDelegate {
account: model
onGoToAccountView: {
root.goToAccountView(model.address)
root.goToAccountView(model)
}
}
}
SortFilterProxyModel {
id: importedAccounts
sourceModel: walletStore.accounts
filters: ExpressionFilter {
expression: {
return model.walletType !== "generated" && model.walletType !== "watch" && model.walletType !== ""
}
}
}
@ -98,33 +116,42 @@ Column {
leftPadding: Style.current.padding
topPadding: Style.current.halfPadding
bottomPadding: Style.current.halfPadding/2
visible: walletStore.importedAccounts.count > 0
visible: importedAccounts.count > 0
}
Repeater {
model: walletStore.importedAccounts
model: importedAccounts
delegate: WalletAccountDelegate {
account: model
onGoToAccountView: {
root.goToAccountView(model.address)
root.goToAccountView(model)
}
}
}
SortFilterProxyModel {
id: watchOnlyAccounts
sourceModel: walletStore.accounts
filters: ValueFilter {
roleName: "walletType"
value: "watch"
}
}
StatusSectionHeadline {
text: qsTr("Watch-Only")
leftPadding: Style.current.padding
topPadding: Style.current.halfPadding
bottomPadding: Style.current.halfPadding/2
visible: walletStore.watchOnlyAccounts.count > 0
visible: watchOnlyAccounts.count > 0
}
Repeater {
model: walletStore.watchOnlyAccounts
model: watchOnlyAccounts
delegate: WalletAccountDelegate {
account: model
onGoToAccountView: {
root.goToAccountView(model.address)
root.goToAccountView(model)
}
}
}

View File

@ -1,4 +1,5 @@
import QtQuick 2.13
import SortFilterProxyModel 0.2
import shared.status 1.0
import StatusQ.Controls 0.1
@ -23,7 +24,13 @@ Item {
Repeater {
id: layer1List
model: walletStore.layer1Networks
model: SortFilterProxyModel {
sourceModel: walletStore.networks
filters: ValueFilter {
roleName: "layer"
value: 1
}
}
delegate: WalletNetworkDelegate {
network: model
}
@ -39,28 +46,16 @@ Item {
Repeater {
id: layer2List
model: walletStore.layer2Networks
model: SortFilterProxyModel {
sourceModel: walletStore.networks
filters: ValueFilter {
roleName: "layer"
value: 2
}
}
delegate: WalletNetworkDelegate {
network: model
}
}
Item {
height: Style.current.bigPadding
width: parent.width
}
StatusButton {
// Disable for now
visible: false
anchors.right: parent.right
anchors.rightMargin: Style.current.bigPadding
id: addCustomNetworkButton
type: StatusFlatRoundButton.Type.Primary
text: qsTr("Add Custom Network")
onClicked: {
root.goBack()
}
}
}
}