feat(@desktop/wallet): Add derivation path to wallet account generation
fixes #5074
This commit is contained in:
parent
a379c34ceb
commit
feaa91d062
|
@ -23,17 +23,25 @@ proc init*(self: Controller) =
|
|||
proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] =
|
||||
return self.walletAccountService.getWalletAccounts()
|
||||
|
||||
proc generateNewAccount*(self: Controller, password: string, accountName: string, color: string, emoji: string): string =
|
||||
return self.walletAccountService.generateNewAccount(password, accountName, color, emoji)
|
||||
proc generateNewAccount*(self: Controller, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string =
|
||||
return self.walletAccountService.generateNewAccount(password, accountName, color, emoji, path, derivedFrom)
|
||||
|
||||
proc addAccountsFromPrivateKey*(self: Controller, privateKey: string, password: string, accountName: string, color: string, emoji: string): string =
|
||||
return self.walletAccountService.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji)
|
||||
|
||||
proc addAccountsFromSeed*(self: Controller, seedPhrase: string, password: string, accountName: string, color: string, emoji: string): string =
|
||||
return self.walletAccountService.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji)
|
||||
proc addAccountsFromSeed*(self: Controller, seedPhrase: string, password: string, accountName: string, color: string, emoji: string, path: string): string =
|
||||
return self.walletAccountService.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path)
|
||||
|
||||
proc addWatchOnlyAccount*(self: Controller, address: string, accountName: string, color: string, emoji: string): string =
|
||||
return self.walletAccountService.addWatchOnlyAccount(address, accountName, color, emoji)
|
||||
|
||||
proc deleteAccount*(self: Controller, address: string) =
|
||||
self.walletAccountService.deleteAccount(address)
|
||||
|
||||
method getDerivedAddressList*(self: Controller, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) =
|
||||
return self.walletAccountService.getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber)
|
||||
|
||||
method getDerivedAddressListForMnemonic*(self: Controller, mnemonic: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) =
|
||||
return self.walletAccountService.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import strformat
|
||||
|
||||
type
|
||||
DerivedAddressItem* = object
|
||||
address: string
|
||||
path: string
|
||||
hasActivity: bool
|
||||
|
||||
proc initDerivedAddressItem*(
|
||||
address: string,
|
||||
path: string,
|
||||
hasActivity: bool
|
||||
): DerivedAddressItem =
|
||||
result.address = address
|
||||
result.path = path
|
||||
result.hasActivity = hasActivity
|
||||
|
||||
proc `$`*(self: DerivedAddressItem): string =
|
||||
result = fmt"""DerivedAddressItem(
|
||||
address: {self.address},
|
||||
path: {self.path},
|
||||
hasActivity: {self.hasActivity}
|
||||
]"""
|
||||
|
||||
proc getAddress*(self: DerivedAddressItem): string =
|
||||
return self.address
|
||||
|
||||
proc getPath*(self: DerivedAddressItem): string =
|
||||
return self.path
|
||||
|
||||
proc getHasActivity*(self: DerivedAddressItem): bool =
|
||||
return self.hasActivity
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
import NimQml, Tables, strutils, strformat
|
||||
|
||||
import ./derived_address_item
|
||||
|
||||
type
|
||||
ModelRole {.pure.} = enum
|
||||
Address = UserRole + 1,
|
||||
Path,
|
||||
HasActivity,
|
||||
|
||||
QtObject:
|
||||
type
|
||||
DerivedAddressModel* = ref object of QAbstractListModel
|
||||
derivedWalletAddresses: seq[DerivedAddressItem]
|
||||
|
||||
proc delete(self: DerivedAddressModel) =
|
||||
self.derivedWalletAddresses = @[]
|
||||
self.QAbstractListModel.delete
|
||||
|
||||
proc setup(self: DerivedAddressModel) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc newDerivedAddressModel*(): DerivedAddressModel =
|
||||
new(result, delete)
|
||||
result.setup
|
||||
|
||||
proc `$`*(self: DerivedAddressModel): string =
|
||||
for i in 0 ..< self.derivedWalletAddresses.len:
|
||||
result &= fmt"""[{i}]:({$self.derivedWalletAddresses[i]})"""
|
||||
|
||||
proc countChanged(self: DerivedAddressModel) {.signal.}
|
||||
|
||||
proc getCount(self: DerivedAddressModel): int {.slot.} =
|
||||
self.derivedWalletAddresses.len
|
||||
|
||||
QtProperty[int] count:
|
||||
read = getCount
|
||||
notify = countChanged
|
||||
|
||||
method rowCount(self: DerivedAddressModel, index: QModelIndex = nil): int =
|
||||
return self.derivedWalletAddresses.len
|
||||
|
||||
method roleNames(self: DerivedAddressModel): Table[int, string] =
|
||||
{
|
||||
ModelRole.Address.int: "address",
|
||||
ModelRole.Path.int: "path",
|
||||
ModelRole.HasActivity.int: "hasActivity",
|
||||
}.toTable
|
||||
|
||||
method data(self: DerivedAddressModel, index: QModelIndex, role: int): QVariant =
|
||||
if (not index.isValid):
|
||||
return
|
||||
|
||||
if (index.row < 0 or index.row >= self.derivedWalletAddresses.len):
|
||||
return
|
||||
|
||||
let item = self.derivedWalletAddresses[index.row]
|
||||
let enumRole = role.ModelRole
|
||||
|
||||
case enumRole:
|
||||
of ModelRole.Address:
|
||||
result = newQVariant(item.getAddress())
|
||||
of ModelRole.Path:
|
||||
result = newQVariant(item.getPath())
|
||||
of ModelRole.HasActivity:
|
||||
result = newQVariant(item.getHasActivity())
|
||||
|
||||
proc setItems*(self: DerivedAddressModel, items: seq[DerivedAddressItem]) =
|
||||
self.beginResetModel()
|
||||
self.derivedWalletAddresses = items
|
||||
self.endResetModel()
|
||||
self.countChanged()
|
||||
|
||||
proc getDerivedAddressAtIndex*(self: DerivedAddressModel, index: int): string =
|
||||
if (index < 0 or index > self.getCount()):
|
||||
return
|
||||
let item = self.derivedWalletAddresses[index]
|
||||
result = item.getAddress()
|
||||
|
||||
|
||||
proc getDerivedAddressPathAtIndex*(self: DerivedAddressModel, index: int): string =
|
||||
if (index < 0 or index > self.getCount()):
|
||||
return
|
||||
let item = self.derivedWalletAddresses[index]
|
||||
result = item.getPath()
|
||||
|
||||
|
||||
proc getDerivedAddressHasActivityAtIndex*(self: DerivedAddressModel, index: int): bool =
|
||||
if (index < 0 or index > self.getCount()):
|
||||
return
|
||||
let item = self.derivedWalletAddresses[index]
|
||||
result = item.getHasActivity()
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import strformat
|
||||
import ./model
|
||||
|
||||
type
|
||||
GeneratedWalletItem* = object
|
||||
name: string
|
||||
iconName: string
|
||||
generatedModel: Model
|
||||
derivedfrom: string
|
||||
|
||||
proc initGeneratedWalletItem*(
|
||||
name: string,
|
||||
iconName: string,
|
||||
generatedModel: Model,
|
||||
derivedfrom: string
|
||||
): GeneratedWalletItem =
|
||||
result.name = name
|
||||
result.iconName = iconName
|
||||
result.generatedModel = generatedModel
|
||||
result.derivedfrom = derivedfrom
|
||||
|
||||
proc `$`*(self: GeneratedWalletItem): string =
|
||||
result = fmt"""GeneratedWalletItem(
|
||||
name: {self.name},
|
||||
iconName: {self.iconName},
|
||||
generatedModel: {self.generatedModel},
|
||||
derivedfrom: {self.derivedfrom}
|
||||
]"""
|
||||
|
||||
proc getName*(self: GeneratedWalletItem): string =
|
||||
return self.name
|
||||
|
||||
proc getIconName*(self: GeneratedWalletItem): string =
|
||||
return self.iconName
|
||||
|
||||
proc getGeneratedModel*(self: GeneratedWalletItem): Model =
|
||||
return self.generatedModel
|
||||
|
||||
proc getDerivedfrom*(self: GeneratedWalletItem): string =
|
||||
return self.derivedfrom
|
|
@ -0,0 +1,76 @@
|
|||
import NimQml, Tables, strutils, strformat
|
||||
|
||||
import ./generated_wallet_item
|
||||
|
||||
type
|
||||
ModelRole {.pure.} = enum
|
||||
Name = UserRole + 1,
|
||||
IconName,
|
||||
GeneratedModel,
|
||||
DerivedFrom
|
||||
|
||||
QtObject:
|
||||
type
|
||||
GeneratedWalletModel* = ref object of QAbstractListModel
|
||||
generatedWalletItems: seq[GeneratedWalletItem]
|
||||
|
||||
proc delete(self: GeneratedWalletModel) =
|
||||
self.generatedWalletItems = @[]
|
||||
self.QAbstractListModel.delete
|
||||
|
||||
proc setup(self: GeneratedWalletModel) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc newGeneratedWalletModel*(): GeneratedWalletModel =
|
||||
new(result, delete)
|
||||
result.setup
|
||||
|
||||
proc `$`*(self: GeneratedWalletModel): string =
|
||||
for i in 0 ..< self.generatedWalletItems.len:
|
||||
result &= fmt"""[{i}]:({$self.generatedWalletItems[i]})"""
|
||||
|
||||
proc countChanged(self: GeneratedWalletModel) {.signal.}
|
||||
|
||||
proc getCount(self: GeneratedWalletModel): int {.slot.} =
|
||||
self.generatedWalletItems.len
|
||||
|
||||
QtProperty[int] count:
|
||||
read = getCount
|
||||
notify = countChanged
|
||||
|
||||
method rowCount(self: GeneratedWalletModel, index: QModelIndex = nil): int =
|
||||
return self.generatedWalletItems.len
|
||||
|
||||
method roleNames(self: GeneratedWalletModel): Table[int, string] =
|
||||
{
|
||||
ModelRole.Name.int: "name",
|
||||
ModelRole.IconName.int: "iconName",
|
||||
ModelRole.GeneratedModel.int: "generatedModel",
|
||||
ModelRole.DerivedFrom.int: "derivedfrom",
|
||||
}.toTable
|
||||
|
||||
method data(self: GeneratedWalletModel, index: QModelIndex, role: int): QVariant =
|
||||
if (not index.isValid):
|
||||
return
|
||||
|
||||
if (index.row < 0 or index.row >= self.generatedWalletItems.len):
|
||||
return
|
||||
|
||||
let item = self.generatedWalletItems[index.row]
|
||||
let enumRole = role.ModelRole
|
||||
|
||||
case enumRole:
|
||||
of ModelRole.Name:
|
||||
result = newQVariant(item.getName())
|
||||
of ModelRole.IconName:
|
||||
result = newQVariant(item.getIconName())
|
||||
of ModelRole.GeneratedModel:
|
||||
result = newQVariant(item.getGeneratedModel())
|
||||
of ModelRole.DerivedFrom:
|
||||
result = newQVariant(item.getDerivedFrom())
|
||||
|
||||
proc setItems*(self: GeneratedWalletModel, items: seq[GeneratedWalletItem]) =
|
||||
self.beginResetModel()
|
||||
self.generatedWalletItems = items
|
||||
self.endResetModel()
|
||||
self.countChanged()
|
|
@ -1,3 +1,5 @@
|
|||
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
|
||||
|
||||
type
|
||||
AccessInterface* {.pure inheritable.} = ref object of RootObj
|
||||
## Abstract class for any input/interaction with this module.
|
||||
|
@ -11,13 +13,13 @@ method load*(self: AccessInterface) {.base.} =
|
|||
method isLoaded*(self: AccessInterface): bool {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method generateNewAccount*(self: AccessInterface, password: string, accountName: string, color: string, emoji: string): string {.base.} =
|
||||
method generateNewAccount*(self: AccessInterface, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method addAccountsFromPrivateKey*(self: AccessInterface, privateKey: string, password: string, accountName: string, color: string, emoji: string): string {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method addAccountsFromSeed*(self: AccessInterface, seedPhrase: string, password: string, accountName: string, color: string, emoji: string): string {.base.} =
|
||||
method addAccountsFromSeed*(self: AccessInterface, seedPhrase: string, password: string, accountName: string, color: string, emoji: string, path: string): string {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method addWatchOnlyAccount*(self: AccessInterface, address: string, accountName: string, color: string, emoji: string): string {.base.} =
|
||||
|
@ -29,6 +31,12 @@ method deleteAccount*(self: AccessInterface, address: string) {.base.} =
|
|||
method refreshWalletAccounts*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method getDerivedAddressList*(self: AccessInterface, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method getDerivedAddressListForMnemonic*(self: AccessInterface, mnemonic: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], 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.
|
||||
|
|
|
@ -14,6 +14,7 @@ type
|
|||
currencyBalance: float64
|
||||
assets: token_model.Model
|
||||
emoji: string
|
||||
derivedfrom: string
|
||||
|
||||
proc initItem*(
|
||||
name: string,
|
||||
|
@ -26,7 +27,8 @@ proc initItem*(
|
|||
isChat: bool,
|
||||
currencyBalance: float64,
|
||||
assets: token_model.Model,
|
||||
emoji: string
|
||||
emoji: string,
|
||||
derivedfrom: string
|
||||
): Item =
|
||||
result.name = name
|
||||
result.address = address
|
||||
|
@ -39,6 +41,7 @@ proc initItem*(
|
|||
result.currencyBalance = currencyBalance
|
||||
result.assets = assets
|
||||
result.emoji = emoji
|
||||
result.derivedfrom = derivedfrom
|
||||
|
||||
proc `$`*(self: Item): string =
|
||||
result = fmt"""WalletAccountItem(
|
||||
|
@ -52,7 +55,8 @@ proc `$`*(self: Item): string =
|
|||
isChat: {self.isChat},
|
||||
currencyBalance: {self.currencyBalance},
|
||||
assets.len: {self.assets.getCount()},
|
||||
emoji: {self.emoji}
|
||||
emoji: {self.emoji},
|
||||
derivedfrom: {self.derivedfrom}
|
||||
]"""
|
||||
|
||||
proc getName*(self: Item): string =
|
||||
|
@ -87,3 +91,6 @@ proc getCurrencyBalance*(self: Item): float64 =
|
|||
|
||||
proc getAssets*(self: Item): token_model.Model =
|
||||
return self.assets
|
||||
|
||||
proc getDerivedFrom*(self: Item): string =
|
||||
return self.derivedfrom
|
||||
|
|
|
@ -14,7 +14,8 @@ type
|
|||
IsChat,
|
||||
CurrencyBalance,
|
||||
Assets,
|
||||
Emoji
|
||||
Emoji,
|
||||
DerivedFrom
|
||||
|
||||
QtObject:
|
||||
type
|
||||
|
@ -38,7 +39,7 @@ QtObject:
|
|||
|
||||
proc countChanged(self: Model) {.signal.}
|
||||
|
||||
proc getCount(self: Model): int {.slot.} =
|
||||
proc getCount*(self: Model): int {.slot.} =
|
||||
self.items.len
|
||||
|
||||
QtProperty[int] count:
|
||||
|
@ -60,7 +61,8 @@ QtObject:
|
|||
ModelRole.IsChat.int:"isChat",
|
||||
ModelRole.Assets.int:"assets",
|
||||
ModelRole.CurrencyBalance.int:"currencyBalance",
|
||||
ModelRole.Emoji.int: "emoji"
|
||||
ModelRole.Emoji.int: "emoji",
|
||||
ModelRole.DerivedFrom.int: "derivedfrom"
|
||||
}.toTable
|
||||
|
||||
method data(self: Model, index: QModelIndex, role: int): QVariant =
|
||||
|
@ -96,6 +98,8 @@ QtObject:
|
|||
result = newQVariant(item.getAssets())
|
||||
of ModelRole.Emoji:
|
||||
result = newQVariant(item.getEmoji())
|
||||
of ModelRole.DerivedFrom:
|
||||
result = newQVariant(item.getDerivedFrom())
|
||||
|
||||
proc setItems*(self: Model, items: seq[Item]) =
|
||||
self.beginResetModel()
|
||||
|
|
|
@ -63,7 +63,8 @@ method refreshWalletAccounts*(self: Module) =
|
|||
w.isChat,
|
||||
w.getCurrencyBalance(),
|
||||
assets,
|
||||
w.emoji
|
||||
w.emoji,
|
||||
w.derivedfrom
|
||||
))
|
||||
|
||||
self.view.setItems(items)
|
||||
|
@ -101,17 +102,26 @@ method viewDidLoad*(self: Module) =
|
|||
self.moduleLoaded = true
|
||||
self.delegate.accountsModuleDidLoad()
|
||||
|
||||
method generateNewAccount*(self: Module, password: string, accountName: string, color: string, emoji: string): string =
|
||||
return self.controller.generateNewAccount(password, accountName, color, emoji)
|
||||
method generateNewAccount*(self: Module, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string =
|
||||
return self.controller.generateNewAccount(password, accountName, color, emoji, path, derivedFrom)
|
||||
|
||||
method addAccountsFromPrivateKey*(self: Module, privateKey: string, password: string, accountName: string, color: string, emoji: string): string =
|
||||
return self.controller.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji)
|
||||
|
||||
method addAccountsFromSeed*(self: Module, seedPhrase: string, password: string, accountName: string, color: string, emoji: string): string =
|
||||
return self.controller.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji)
|
||||
method addAccountsFromSeed*(self: Module, seedPhrase: string, password: string, accountName: string, color: string, emoji: string, path: string): string =
|
||||
return self.controller.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path)
|
||||
|
||||
method addWatchOnlyAccount*(self: Module, address: string, accountName: string, color: string, emoji: string): string =
|
||||
return self.controller.addWatchOnlyAccount(address, accountName, color, emoji)
|
||||
|
||||
method deleteAccount*(self: Module, address: string) =
|
||||
self.controller.deleteAccount(address)
|
||||
|
||||
method getDerivedAddressList*(self: Module, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): (seq[DerivedAddressDto], string) =
|
||||
return self.controller.getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber)
|
||||
|
||||
method getDerivedAddressListForMnemonic*(self: Module, mnemonic: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) =
|
||||
return self.controller.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
import NimQml
|
||||
import NimQml, sequtils, strutils, sugar
|
||||
|
||||
import ./model
|
||||
import ./item
|
||||
import ./io_interface
|
||||
import ./generated_wallet_model
|
||||
import ./generated_wallet_item
|
||||
import ./derived_address_model
|
||||
import ./derived_address_item
|
||||
|
||||
const WATCH = "watch"
|
||||
const GENERATED = "generated"
|
||||
const SEED = "seed"
|
||||
const KEY = "key"
|
||||
|
||||
QtObject:
|
||||
type
|
||||
|
@ -15,10 +21,14 @@ QtObject:
|
|||
generated: Model
|
||||
watchOnly: Model
|
||||
imported: Model
|
||||
generatedAccounts: GeneratedWalletModel
|
||||
derivedAddresses: DerivedAddressModel
|
||||
modelVariant: QVariant
|
||||
generatedVariant: QVariant
|
||||
importedVariant: QVariant
|
||||
watchOnlyVariant: QVariant
|
||||
generatedAccountsVariant: QVariant
|
||||
derivedAddressesVariant: QVariant
|
||||
tmpAddress: string
|
||||
|
||||
proc delete*(self: View) =
|
||||
|
@ -30,6 +40,10 @@ QtObject:
|
|||
self.generatedVariant.delete
|
||||
self.watchOnly.delete
|
||||
self.watchOnlyVariant.delete
|
||||
self.generatedAccounts.delete
|
||||
self.generatedAccountsVariant.delete
|
||||
self.derivedAddresses.delete
|
||||
self.derivedAddressesVariant.delete
|
||||
self.QObject.delete
|
||||
|
||||
proc newView*(delegate: io_interface.AccessInterface): View =
|
||||
|
@ -44,6 +58,10 @@ QtObject:
|
|||
result.generatedVariant = newQVariant(result.generated)
|
||||
result.watchOnly = newModel()
|
||||
result.watchOnlyVariant = newQVariant(result.watchOnly)
|
||||
result.generatedAccounts = newGeneratedWalletModel()
|
||||
result.generatedAccountsVariant = newQVariant(result.generatedAccounts)
|
||||
result.derivedAddresses = newDerivedAddressModel()
|
||||
result.derivedAddressesVariant = newQVariant(result.derivedAddresses)
|
||||
|
||||
proc load*(self: View) =
|
||||
self.delegate.viewDidLoad()
|
||||
|
@ -84,6 +102,24 @@ QtObject:
|
|||
read = getGenereated
|
||||
notify = generatedChanged
|
||||
|
||||
proc generatedAccountsChanged*(self: View) {.signal.}
|
||||
|
||||
proc getGeneratedAccounts(self: View): QVariant {.slot.} =
|
||||
return self.generatedAccountsVariant
|
||||
|
||||
QtProperty[QVariant] generatedAccounts:
|
||||
read = getGeneratedAccounts
|
||||
notify = generatedAccountsChanged
|
||||
|
||||
proc derivedAddressesChanged*(self: View) {.signal.}
|
||||
|
||||
proc getDerivedAddresses(self: View): QVariant {.slot.} =
|
||||
return self.derivedAddressesVariant
|
||||
|
||||
QtProperty[QVariant] derivedAddresses:
|
||||
read = getDerivedAddresses
|
||||
notify = derivedAddressesChanged
|
||||
|
||||
proc setItems*(self: View, items: seq[Item]) =
|
||||
self.model.setItems(items)
|
||||
|
||||
|
@ -98,19 +134,37 @@ QtObject:
|
|||
watchOnly.add(item)
|
||||
else:
|
||||
imported.add(item)
|
||||
|
||||
|
||||
self.watchOnly.setItems(watchOnly)
|
||||
self.imported.setItems(imported)
|
||||
self.generated.setItems(generated)
|
||||
|
||||
proc generateNewAccount*(self: View, password: string, accountName: string, color: string, emoji: string): string {.slot.} =
|
||||
return self.delegate.generateNewAccount(password, accountName, color, emoji)
|
||||
# create a list of imported seeds/default account created from where more accounts can be derived
|
||||
var generatedAccounts: seq[GeneratedWalletItem] = @[]
|
||||
var importedSeedIndex: int = 1
|
||||
for item in items:
|
||||
if item.getWalletType() == "":
|
||||
var generatedAccs: Model = newModel()
|
||||
generatedAccs.setItems(generated.filter(x => cmpIgnoreCase(x.getDerivedFrom(), item.getDerivedFrom()) == 0))
|
||||
generatedAccounts.add(initGeneratedWalletItem("Default", "status", generatedAccs, item.getDerivedFrom()))
|
||||
elif item.getWalletType() == SEED:
|
||||
var generatedAccs1: Model = newModel()
|
||||
var filterItems: seq[Item] = generated.filter(x => cmpIgnoreCase(x.getDerivedFrom(), item.getDerivedFrom()) == 0)
|
||||
filterItems.insert(item, 0)
|
||||
generatedAccs1.setItems(filterItems)
|
||||
generatedAccounts.add(initGeneratedWalletItem("Seed " & $importedSeedIndex , "seed-phrase", generatedAccs1, item.getDerivedFrom()))
|
||||
importedSeedIndex += 1
|
||||
self.generatedAccounts.setItems(generatedAccounts)
|
||||
|
||||
|
||||
proc generateNewAccount*(self: View, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string {.slot.} =
|
||||
return self.delegate.generateNewAccount(password, accountName, color, emoji, path, derivedFrom)
|
||||
|
||||
proc addAccountsFromPrivateKey*(self: View, privateKey: string, password: string, accountName: string, color: string, emoji: string): string {.slot.} =
|
||||
return self.delegate.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji)
|
||||
|
||||
proc addAccountsFromSeed*(self: View, seedPhrase: string, password: string, accountName: string, color: string, emoji: string): string {.slot.} =
|
||||
return self.delegate.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji)
|
||||
proc addAccountsFromSeed*(self: View, seedPhrase: string, password: string, accountName: string, color: string, emoji: string, path: string): string {.slot.} =
|
||||
return self.delegate.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path)
|
||||
|
||||
proc addWatchOnlyAccount*(self: View, address: string, accountName: string, color: string, emoji: string): string {.slot.} =
|
||||
return self.delegate.addWatchOnlyAccount(address, accountName, color, emoji)
|
||||
|
@ -129,3 +183,36 @@ QtObject:
|
|||
|
||||
proc getAccountAssetsByAddress*(self: View): QVariant {.slot.} =
|
||||
return self.model.getAccountAssetsByAddress(self.tmpAddress)
|
||||
|
||||
proc getDerivedAddressList*(self: View, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): string {.slot.} =
|
||||
var items: seq[DerivedAddressItem] = @[]
|
||||
let (result, error) = self.delegate.getDerivedAddressList(password, derivedfrom, path, pageSize, pageNumber)
|
||||
for item in result:
|
||||
items.add(initDerivedAddressItem(item.address, item.path, item.hasActivity))
|
||||
self.derivedAddresses.setItems(items)
|
||||
self.derivedAddressesChanged()
|
||||
return error
|
||||
|
||||
proc getDerivedAddressListForMnemonic*(self: View, mnemonic: string, path: string, pageSize: int, pageNumber: int): string {.slot.} =
|
||||
var items: seq[DerivedAddressItem] = @[]
|
||||
let (result, error) = self.delegate.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)
|
||||
for item in result:
|
||||
items.add(initDerivedAddressItem(item.address, item.path, item.hasActivity))
|
||||
self.derivedAddresses.setItems(items)
|
||||
self.derivedAddressesChanged()
|
||||
return error
|
||||
|
||||
proc resetDerivedAddressModel*(self: View) {.slot.} =
|
||||
var items: seq[DerivedAddressItem] = @[]
|
||||
self.derivedAddresses.setItems(items)
|
||||
self.derivedAddressesChanged()
|
||||
|
||||
proc getDerivedAddressAtIndex*(self: View, index: int): string {.slot.} =
|
||||
return self.derivedAddresses.getDerivedAddressAtIndex(index)
|
||||
|
||||
proc getDerivedAddressPathAtIndex*(self: View, index: int): string {.slot.} =
|
||||
return self.derivedAddresses.getDerivedAddressPathAtIndex(index)
|
||||
|
||||
proc getDerivedAddressHasActivityAtIndex*(self: View, index: int): bool {.slot.} =
|
||||
return self.derivedAddresses.getDerivedAddressHasActivityAtIndex(index)
|
||||
|
||||
|
|
|
@ -122,6 +122,14 @@ proc storeDerivedAccounts(self: Service, accountId, hashedPassword: string,
|
|||
|
||||
result = toDerivedAccounts(response.result)
|
||||
|
||||
proc storeAccount(self: Service, accountId, hashedPassword: string): GeneratedAccountDto =
|
||||
let response = status_account.storeAccounts(accountId, hashedPassword)
|
||||
|
||||
if response.result.contains("error"):
|
||||
raise newException(Exception, response.result["error"].getStr)
|
||||
|
||||
result = toGeneratedAccountDto(response.result)
|
||||
|
||||
proc saveAccountAndLogin(self: Service, hashedPassword: string, account,
|
||||
subaccounts, settings, config: JsonNode): AccountDto =
|
||||
try:
|
||||
|
@ -168,14 +176,16 @@ proc prepareSubaccountJsonObject(self: Service, account: GeneratedAccountDto, di
|
|||
"color": "#4360df",
|
||||
"wallet": true,
|
||||
"path": PATH_DEFAULT_WALLET,
|
||||
"name": "Status account"
|
||||
"name": "Status account",
|
||||
"derived-from": account.address
|
||||
},
|
||||
{
|
||||
"public-key": account.derivedAccounts.whisper.publicKey,
|
||||
"address": account.derivedAccounts.whisper.address,
|
||||
"name": if displayName == "": account.alias else: displayName,
|
||||
"path": PATH_WHISPER,
|
||||
"chat": true
|
||||
"chat": true,
|
||||
"derived-from": ""
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -271,6 +281,7 @@ proc setupAccount*(self: Service, accountId, password, displayName: string): str
|
|||
return description
|
||||
|
||||
let hashedPassword = hashString(password)
|
||||
discard self.storeAccount(accountId, hashedPassword)
|
||||
discard self.storeDerivedAccounts(accountId, hashedPassword, PATHS)
|
||||
|
||||
self.loggedInAccount = self.saveAccountAndLogin(hashedPassword, accountDataJson,
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import json
|
||||
|
||||
include ../../common/json_utils
|
||||
|
||||
type DerivedAddressDto* = object
|
||||
address*: string
|
||||
path*: string
|
||||
hasActivity*: bool
|
||||
|
||||
proc toDerivedAddressDto*(jsonObj: JsonNode): DerivedAddressDto =
|
||||
result = DerivedAddressDto()
|
||||
discard jsonObj.getProp("address", result.address)
|
||||
discard jsonObj.getProp("path", result.path)
|
||||
discard jsonObj.getProp("hasActivity", result.hasActivity)
|
|
@ -26,6 +26,7 @@ type
|
|||
isChat*: bool
|
||||
tokens*: seq[WalletTokenDto]
|
||||
emoji*: string
|
||||
derivedfrom*: string
|
||||
|
||||
proc newDto*(
|
||||
name: string,
|
||||
|
@ -36,7 +37,8 @@ proc newDto*(
|
|||
walletType: string,
|
||||
isWallet: bool,
|
||||
isChat: bool,
|
||||
emoji: string
|
||||
emoji: string,
|
||||
derivedfrom: string
|
||||
): WalletAccountDto =
|
||||
return WalletAccountDto(
|
||||
name: name,
|
||||
|
@ -47,7 +49,8 @@ proc newDto*(
|
|||
walletType: walletType,
|
||||
isWallet: isWallet,
|
||||
isChat: isChat,
|
||||
emoji: emoji
|
||||
emoji: emoji,
|
||||
derivedfrom: derivedfrom
|
||||
)
|
||||
|
||||
proc toWalletAccountDto*(jsonObj: JsonNode): WalletAccountDto =
|
||||
|
@ -61,6 +64,7 @@ proc toWalletAccountDto*(jsonObj: JsonNode): WalletAccountDto =
|
|||
discard jsonObj.getProp("public-key", result.publicKey)
|
||||
discard jsonObj.getProp("type", result.walletType)
|
||||
discard jsonObj.getProp("emoji", result.emoji)
|
||||
discard jsonObj.getProp("derived-from", result.derivedfrom)
|
||||
|
||||
proc getCurrencyBalance*(self: WalletAccountDto): float64 =
|
||||
return self.tokens.map(t => t.currencyBalance).foldl(a + b, 0.0)
|
||||
|
|
|
@ -8,6 +8,7 @@ import ../network/service as network_service
|
|||
import ../../common/account_constants
|
||||
|
||||
import dto
|
||||
import derived_address
|
||||
|
||||
import ../../../app/core/eventemitter
|
||||
import ../../../backend/accounts as status_go_accounts
|
||||
|
@ -16,6 +17,7 @@ import ../../../backend/eth as status_go_eth
|
|||
import ../../../backend/cache
|
||||
|
||||
export dto
|
||||
export derived_address
|
||||
|
||||
logScope:
|
||||
topics = "wallet-account-service"
|
||||
|
@ -252,13 +254,15 @@ proc addNewAccountToLocalStore(self: Service) =
|
|||
self.accounts[newAccount.address] = newAccount
|
||||
self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountSaved(account: newAccount))
|
||||
|
||||
proc generateNewAccount*(self: Service, password: string, accountName: string, color: string, emoji: string): string =
|
||||
proc generateNewAccount*(self: Service, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string =
|
||||
try:
|
||||
discard backend.generateAccount(
|
||||
discard backend.generateAccountWithDerivedPath(
|
||||
hashPassword(password),
|
||||
accountName,
|
||||
color,
|
||||
emoji)
|
||||
emoji,
|
||||
path,
|
||||
derivedFrom)
|
||||
except Exception as e:
|
||||
return fmt"Error generating new account: {e.msg}"
|
||||
|
||||
|
@ -271,21 +275,21 @@ proc addAccountsFromPrivateKey*(self: Service, privateKey: string, password: str
|
|||
hashPassword(password),
|
||||
accountName,
|
||||
color,
|
||||
emoji
|
||||
)
|
||||
emoji)
|
||||
except Exception as e:
|
||||
return fmt"Error adding account with private key: {e.msg}"
|
||||
|
||||
self.addNewAccountToLocalStore()
|
||||
|
||||
proc addAccountsFromSeed*(self: Service, mnemonic: string, password: string, accountName: string, color: string, emoji: string): string =
|
||||
proc addAccountsFromSeed*(self: Service, mnemonic: string, password: string, accountName: string, color: string, emoji: string, path: string): string =
|
||||
try:
|
||||
discard backend.addAccountWithMnemonic(
|
||||
discard backend.addAccountWithMnemonicAndPath(
|
||||
mnemonic,
|
||||
hashPassword(password),
|
||||
accountName,
|
||||
color,
|
||||
emoji
|
||||
emoji,
|
||||
path
|
||||
)
|
||||
except Exception as e:
|
||||
return fmt"Error adding account with mnemonic: {e.msg}"
|
||||
|
@ -349,3 +353,25 @@ proc updateWalletAccount*(self: Service, address: string, accountName: string, c
|
|||
account.emoji = emoji
|
||||
|
||||
self.events.emit(SIGNAL_WALLET_ACCOUNT_UPDATED, WalletAccountUpdated(account: account))
|
||||
|
||||
proc getDerivedAddressList*(self: Service, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): (seq[DerivedAddressDto], string) =
|
||||
var derivedAddress: seq[DerivedAddressDto] = @[]
|
||||
var error: string = ""
|
||||
try:
|
||||
let response = status_go_accounts.getDerivedAddressList(hashPassword(password), derivedFrom, path, pageSize, pageNumber)
|
||||
derivedAddress = response.result.getElems().map(x => x.toDerivedAddressDto())
|
||||
except Exception as e:
|
||||
error = fmt"Error getting derived address list: {e.msg}"
|
||||
return (derivedAddress, error)
|
||||
|
||||
proc getDerivedAddressListForMnemonic*(self: Service, mnemonic: string, path: string, pageSize: int, pageNumber: int): (seq[DerivedAddressDto], string) =
|
||||
var derivedAddress: seq[DerivedAddressDto] = @[]
|
||||
var error: string = ""
|
||||
try:
|
||||
let response = status_go_accounts.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)
|
||||
derivedAddress = response.result.getElems().map(x => x.toDerivedAddressDto())
|
||||
except Exception as e:
|
||||
error = fmt"Error getting derived address list for mnemonic: {e.msg}"
|
||||
return (derivedAddress, error)
|
||||
|
||||
|
||||
|
|
|
@ -288,3 +288,11 @@ proc deleteIdentityImage*(keyUID: string): RpcResponse[JsonNode] {.raises: [Exce
|
|||
proc setDisplayName*(displayName: string): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
let payload = %* [displayName]
|
||||
result = core.callPrivateRPC("setDisplayName".prefix, payload)
|
||||
|
||||
proc getDerivedAddressList*(password: string, derivedFrom: string, path: string, pageSize: int = 0, pageNumber: int = 6,): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
let payload = %* [password, derivedFrom, path, pageSize, pageNumber ]
|
||||
result = core.callPrivateRPC("accounts_getDerivedAddressesForPath", payload)
|
||||
|
||||
proc getDerivedAddressListForMnemonic*(mnemonic: string, path: string, pageSize: int = 0, pageNumber: int = 6,): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
let payload = %* [mnemonic, path, pageSize, pageNumber ]
|
||||
result = core.callPrivateRPC("accounts_getDerivedAddressesForMenominicWithPath", payload)
|
||||
|
|
|
@ -107,18 +107,21 @@ rpc(fetchPrices, "wallet"):
|
|||
symbols: seq[string]
|
||||
currency: string
|
||||
|
||||
rpc(generateAccount, "accounts"):
|
||||
rpc(generateAccountWithDerivedPath, "accounts"):
|
||||
password: string
|
||||
name: string
|
||||
color: string
|
||||
emoji: string
|
||||
path: string
|
||||
derivedFrom: string
|
||||
|
||||
rpc(addAccountWithMnemonic, "accounts"):
|
||||
rpc(addAccountWithMnemonicAndPath, "accounts"):
|
||||
mnemonic: string
|
||||
password: string
|
||||
name: string
|
||||
color: string
|
||||
emoji: string
|
||||
path: string
|
||||
|
||||
rpc(addAccountWithPrivateKey, "accounts"):
|
||||
privateKey: string
|
||||
|
@ -194,4 +197,4 @@ rpc(addDappPermissions, "permissions"):
|
|||
|
||||
rpc(deleteDappPermissionsByNameAndAddress, "permissions"):
|
||||
dapp: string
|
||||
address: string
|
||||
address: string
|
||||
|
|
|
@ -6,6 +6,7 @@ import StatusQ.Popups 0.1
|
|||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import utils 1.0
|
||||
import shared.stores 1.0
|
||||
import "../controls"
|
||||
import "../stores"
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import QtQuick 2.12
|
||||
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
||||
import utils 1.0
|
||||
import "../stores"
|
||||
|
||||
StatusSelect {
|
||||
id: derivationPathSelect
|
||||
|
||||
property string path: ""
|
||||
|
||||
function reset() {
|
||||
derivationPathSelectedItem.title = DerivationPathModel.derivationPaths.get(0).name
|
||||
derivationPathSelectedItem.subTitle = DerivationPathModel.derivationPaths.get(0).path
|
||||
}
|
||||
|
||||
label: qsTr("Derivation Path")
|
||||
selectMenu.width: 351
|
||||
menuAlignment: StatusSelect.MenuAlignment.Left
|
||||
model: DerivationPathModel.derivationPaths
|
||||
selectedItemComponent: StatusListItem {
|
||||
id: derivationPathSelectedItem
|
||||
implicitWidth: parent.width
|
||||
statusListItemTitle.wrapMode: Text.NoWrap
|
||||
statusListItemTitle.width: parent.width - Style.current.padding
|
||||
statusListItemTitle.elide: Qt.ElideMiddle
|
||||
statusListItemTitle.anchors.left: undefined
|
||||
statusListItemTitle.anchors.right: undefined
|
||||
icon.background.color: "transparent"
|
||||
border.width: 1
|
||||
border.color: Theme.palette.baseColor2
|
||||
title: DerivationPathModel.derivationPaths.get(0).name
|
||||
subTitle: DerivationPathModel.derivationPaths.get(0).path
|
||||
Component.onCompleted: {
|
||||
derivationPathSelect.path = Qt.binding(function() { return derivationPathSelectedItem.subTitle})
|
||||
}
|
||||
}
|
||||
selectMenu.delegate: StatusListItem {
|
||||
implicitWidth: parent.width
|
||||
statusListItemTitle.wrapMode: Text.NoWrap
|
||||
statusListItemTitle.width: parent.width - Style.current.padding
|
||||
statusListItemTitle.elide: Qt.ElideMiddle
|
||||
statusListItemTitle.anchors.left: undefined
|
||||
statusListItemTitle.anchors.right: undefined
|
||||
title: model.name
|
||||
subTitle: model.path
|
||||
onClicked: {
|
||||
derivationPathSelectedItem.title = title
|
||||
derivationPathSelectedItem.subTitle = subTitle
|
||||
derivationPathSelect.selectMenu.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import "../stores"
|
||||
|
||||
Item {
|
||||
id: derivedAddresses
|
||||
|
||||
property string pathSubFix: ""
|
||||
function reset() {
|
||||
RootStore.resetDerivedAddressModel()
|
||||
selectedDerivedAddress.pathSubFix = 0
|
||||
selectedDerivedAddress.title = "---"
|
||||
selectedDerivedAddress.subTitle = qsTr("No activity")
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: _internal
|
||||
property int pageSize: 6
|
||||
property int noOfPages: Math.ceil(RootStore.derivedAddressesList.count/pageSize)
|
||||
property int lastPageSize: RootStore.derivedAddressesList.count - ((noOfPages -1) * pageSize)
|
||||
property bool isLastPage: stackLayout.currentIndex == (noOfPages - 1)
|
||||
|
||||
// dimensions
|
||||
property int popupWidth: 359
|
||||
property int maxAddressWidth: 102
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: RootStore.derivedAddressesList
|
||||
onModelReset: {
|
||||
_internal.pageSize = 0
|
||||
_internal.pageSize = 6
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
width: parent.width
|
||||
spacing: 7
|
||||
StatusBaseText {
|
||||
id: inputLabel
|
||||
width: parent.width
|
||||
text: qsTr("Account")
|
||||
font.pixelSize: 15
|
||||
color: selectedDerivedAddress.enabled ? Theme.palette.directColor1 : Theme.palette.baseColor1
|
||||
}
|
||||
StatusListItem {
|
||||
id: selectedDerivedAddress
|
||||
property int pathSubFix: 0
|
||||
implicitWidth: parent.width
|
||||
color: "transparent"
|
||||
border.width: 1
|
||||
border.color: Theme.palette.baseColor2
|
||||
title: "---"
|
||||
subTitle: qsTr("No activity")
|
||||
statusListItemTitle.wrapMode: Text.NoWrap
|
||||
statusListItemTitle.width: _internal.maxAddressWidth
|
||||
statusListItemTitle.elide: Qt.ElideMiddle
|
||||
statusListItemTitle.anchors.left: undefined
|
||||
statusListItemTitle.anchors.right: undefined
|
||||
components: [
|
||||
StatusIcon {
|
||||
width: 24
|
||||
height: 24
|
||||
icon: "chevron-down"
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: derivedAddressPopup.popup(derivedAddresses.x - layout.width - Style.current.bigPadding , derivedAddresses.y + layout.height + 8)
|
||||
enabled: RootStore.derivedAddressesList.count > 0
|
||||
Component.onCompleted: derivedAddresses.pathSubFix = Qt.binding(function() { return pathSubFix})
|
||||
}
|
||||
}
|
||||
|
||||
StatusPopupMenu {
|
||||
id: derivedAddressPopup
|
||||
width: _internal.popupWidth
|
||||
contentItem: Column {
|
||||
StackLayout {
|
||||
id: stackLayout
|
||||
Layout.fillWidth:true
|
||||
Layout.fillHeight: true
|
||||
Repeater {
|
||||
id: pageModel
|
||||
model: _internal.noOfPages
|
||||
delegate: Page {
|
||||
id: page
|
||||
contentItem: ColumnLayout {
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: _internal.isLastPage ? _internal.lastPageSize : _internal.pageSize
|
||||
delegate: StatusListItem {
|
||||
id: element
|
||||
property int actualIndex: index + (stackLayout.currentIndex* _internal.pageSize)
|
||||
implicitWidth: derivedAddressPopup.width
|
||||
statusListItemTitle.wrapMode: Text.NoWrap
|
||||
statusListItemTitle.width: _internal.maxAddressWidth
|
||||
statusListItemTitle.elide: Qt.ElideMiddle
|
||||
statusListItemTitle.anchors.left: undefined
|
||||
statusListItemTitle.anchors.right: undefined
|
||||
title: RootStore.getDerivedAddressData(actualIndex)
|
||||
subTitle: RootStore.getDerivedAddressHasActivityData(actualIndex) ? qsTr("Has Activity"): qsTr("No Activity")
|
||||
components: [
|
||||
StatusBaseText {
|
||||
text: element.actualIndex
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
selectedDerivedAddress.title = title
|
||||
selectedDerivedAddress.subTitle = subTitle
|
||||
selectedDerivedAddress.pathSubFix = actualIndex
|
||||
derivedAddressPopup.close()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if(index === 0) {
|
||||
selectedDerivedAddress.title = title
|
||||
selectedDerivedAddress.subTitle = subTitle
|
||||
selectedDerivedAddress.pathSubFix = actualIndex
|
||||
stackLayout.currentIndex = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PageIndicator {
|
||||
id: pageIndicator
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
interactive: true
|
||||
currentIndex: stackLayout.currentIndex
|
||||
count: stackLayout.count
|
||||
onCurrentIndexChanged: stackLayout.currentIndex = currentIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import QtQuick 2.12
|
||||
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
import StatusQ.Controls.Validators 0.1
|
||||
|
||||
import utils 1.0
|
||||
import "../stores"
|
||||
|
||||
StatusInput {
|
||||
id: privateKey
|
||||
|
||||
function resetMe() {
|
||||
_internal.errorString = ""
|
||||
privateKey.text = ""
|
||||
privateKey.reset()
|
||||
reset()
|
||||
}
|
||||
|
||||
function validateMe() {
|
||||
if (privateKey.text === "") {
|
||||
//% "You need to enter a private key"
|
||||
_internal.errorString = qsTrId("you-need-to-enter-a-private-key")
|
||||
} else if (!Utils.isPrivateKey(privateKey.text)) {
|
||||
//% "Enter a valid private key (64 characters hexadecimal string)"
|
||||
_internal.errorString = qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
|
||||
} else {
|
||||
_internal.errorString = ""
|
||||
}
|
||||
return _internal.errorString === ""
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: _internal
|
||||
property int privateKeyCharLimit: 66
|
||||
property string errorString: ""
|
||||
}
|
||||
|
||||
//% "Private key"
|
||||
label: qsTrId("private-key")
|
||||
charLimit: _internal.privateKeyCharLimit
|
||||
input.multiline: true
|
||||
input.minimumHeight: 80
|
||||
input.maximumHeight: 108
|
||||
//% "Paste the contents of your private key"
|
||||
input.placeholderText: qsTrId("paste-the-contents-of-your-private-key")
|
||||
errorMessage: _internal.errorString
|
||||
validators: [
|
||||
StatusMinLengthValidator {
|
||||
minLength: 1
|
||||
//% "You need to enter a private key"
|
||||
errorMessage: qsTrId("you-need-to-enter-a-private-key")
|
||||
},
|
||||
StatusValidator {
|
||||
property var validate: function (value) {
|
||||
return Utils.isPrivateKey(value)
|
||||
}
|
||||
//% "Enter a valid private key (64 characters hexadecimal string)"
|
||||
errorMessage: qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
|
||||
}
|
||||
]
|
||||
onVisibleChanged: {
|
||||
if(visible)
|
||||
privateKey.input.edit.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
import QtQuick 2.12
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
import utils 1.0
|
||||
import shared.stores 1.0
|
||||
|
||||
import "../stores"
|
||||
|
||||
GridView {
|
||||
id: grid
|
||||
|
||||
property bool isValid: false
|
||||
property string mnemonicString: ""
|
||||
property int preferredHeight: (cellHeight * model/2) + footerItem.height
|
||||
|
||||
function reset() {
|
||||
_internal.errorString = ""
|
||||
mnemonicString = ""
|
||||
_internal.mnemonicInput = [];
|
||||
if (!grid.atXBeginning) {
|
||||
grid.positionViewAtBeginning();
|
||||
}
|
||||
for(var i = 0; i < grid.model; i++) {
|
||||
if(grid.itemAtIndex(i)) {
|
||||
grid.itemAtIndex(i).textEdit.text = ""
|
||||
grid.itemAtIndex(i).textEdit.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validate() {
|
||||
_internal.errorString = ""
|
||||
if (!Utils.isMnemonic(mnemonicString)) {
|
||||
//% "Invalid seed phrase"
|
||||
_internal.errorString = qsTrId("custom-seed-phrase")
|
||||
} else {
|
||||
_internal.errorString = RootStore.vaildateMnemonic(mnemonicString)
|
||||
const regex = new RegExp('word [a-z]+ not found in the dictionary', 'i');
|
||||
if (regex.test(_internal.errorString)) {
|
||||
//% "Invalid seed phrase"
|
||||
_internal.errorString = qsTrId("custom-seed-phrase") + '. ' +
|
||||
//% "This seed phrase doesn't match our supported dictionary. Check for misspelled words."
|
||||
qsTrId("custom-seed-phrase-text-1")
|
||||
}
|
||||
}
|
||||
return _internal.errorString === ""
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: _internal
|
||||
property int seedPhraseInputHeight: 44
|
||||
property int seedPhraseInputWidth: 220
|
||||
property var mnemonicInput: []
|
||||
property string errorString: ""
|
||||
readonly property int twelveWordsModel: 12
|
||||
readonly property int twentyFourWordsModel: 24
|
||||
|
||||
function getSeedPhraseString() {
|
||||
var seedPhrase = ""
|
||||
for(var i = 0; i < grid.model; i++) {
|
||||
if(!!grid.itemAtIndex(i)) {
|
||||
seedPhrase += grid.itemAtIndex(i).text + " "
|
||||
}
|
||||
}
|
||||
return seedPhrase
|
||||
}
|
||||
}
|
||||
|
||||
cellHeight: _internal.seedPhraseInputHeight + Style.current.halfPadding
|
||||
cellWidth: _internal.seedPhraseInputWidth + Style.current.halfPadding
|
||||
interactive: false
|
||||
z: 100000
|
||||
|
||||
model: _internal.twelveWordsModel
|
||||
|
||||
onModelChanged: {
|
||||
mnemonicString = "";
|
||||
_internal.mnemonicInput = [];
|
||||
}
|
||||
|
||||
|
||||
onIsValidChanged: {
|
||||
if(isValid) {
|
||||
mnemonicString = _internal.getSeedPhraseString()
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if(visible) {
|
||||
grid.itemAtIndex(0).textEdit.input.edit.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
delegate: StatusSeedPhraseInput {
|
||||
id: statusSeedInput
|
||||
width: _internal.seedPhraseInputWidth
|
||||
height: _internal.seedPhraseInputHeight
|
||||
textEdit.errorMessageCmp.visible: false
|
||||
textEdit.input.anchors.topMargin: 11
|
||||
leftComponentText: index + 1
|
||||
inputList: BIP39_en { }
|
||||
property int itemIndex: index
|
||||
z: (grid.currentIndex === index) ? 150000000 : 0
|
||||
onDoneInsertingWord: {
|
||||
_internal.mnemonicInput.push({"pos": leftComponentText, "seed": word.replace(/\s/g, '')});
|
||||
for (var j = 0; j < _internal.mnemonicInput.length; j++) {
|
||||
if (_internal.mnemonicInput[j].pos === leftComponentText && _internal.mnemonicInput[j].seed !== word) {
|
||||
_internal.mnemonicInput[j].seed = word;
|
||||
}
|
||||
}
|
||||
//remove duplicates
|
||||
var valueArr = _internal.mnemonicInput.map(function(item){ return item.pos });
|
||||
var isDuplicate = valueArr.some(function(item, idx){
|
||||
if (valueArr.indexOf(item) !== idx) {
|
||||
_internal.mnemonicInput.splice(idx, 1);
|
||||
}
|
||||
return valueArr.indexOf(item) !== idx
|
||||
});
|
||||
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
|
||||
if (parseInt(grid.itemAtIndex(i).leftComponentText) === (parseInt(leftComponentText)+1)) {
|
||||
grid.currentIndex = grid.itemAtIndex(i).itemIndex;
|
||||
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
|
||||
if (grid.currentIndex === 11) {
|
||||
grid.positionViewAtEnd();
|
||||
if (grid.count === 20) {
|
||||
grid.contentX = 1500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
grid.isValid = (_internal.mnemonicInput.length === grid.model);
|
||||
}
|
||||
onEditClicked: {
|
||||
grid.currentIndex = index;
|
||||
grid.itemAtIndex(index).textEdit.input.edit.forceActiveFocus();
|
||||
}
|
||||
onKeyPressed: {
|
||||
if (event.key === Qt.Key_Tab || event.key === Qt.Key_Right) {
|
||||
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
|
||||
if (parseInt(grid.itemAtIndex(i).leftComponentText) === ((parseInt(leftComponentText)+1) <= grid.count ? (parseInt(leftComponentText)+1) : grid.count)) {
|
||||
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
|
||||
textEdit.input.tabNavItem = grid.itemAtIndex(i).textEdit.input.edit;
|
||||
}
|
||||
}
|
||||
} else if (event.key === Qt.Key_Left) {
|
||||
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
|
||||
if (parseInt(grid.itemAtIndex(i).leftComponentText) === ((parseInt(leftComponentText)-1) >= 0 ? (parseInt(leftComponentText)-1) : 0)) {
|
||||
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
} else if (event.key === Qt.Key_Down) {
|
||||
grid.itemAtIndex((index+1 < grid.count) ? (index+1) : (grid.count-1)).textEdit.input.edit.forceActiveFocus();
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
grid.itemAtIndex((index-1 >= 0) ? (index-1) : 0).textEdit.input.edit.forceActiveFocus();
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) {
|
||||
var wordIndex = _internal.mnemonicInput.findIndex(x => x.pos === leftComponentText);
|
||||
if (wordIndex > -1) {
|
||||
_internal.mnemonicInput.splice(wordIndex , 1);
|
||||
grid.isValid = _internal.mnemonicInput.length === grid.model
|
||||
}
|
||||
}
|
||||
|
||||
grid.currentIndex = index;
|
||||
}
|
||||
}
|
||||
footer: Item {
|
||||
width: grid.width - Style.current.padding
|
||||
height: button.height + errorMessage.height + Style.current.padding*2
|
||||
StatusBaseText {
|
||||
id: errorMessage
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Style.current.padding
|
||||
|
||||
height: visible ? implicitHeight : 0
|
||||
visible: !!text
|
||||
text: _internal.errorString
|
||||
|
||||
font.pixelSize: 12
|
||||
color: Theme.palette.dangerColor1
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
StatusButton {
|
||||
id: button
|
||||
anchors.top: errorMessage.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: grid.model === _internal.twelveWordsModel ? qsTr("Use 24 word seed phrase"):
|
||||
qsTr("Use 12 word seed phrase")
|
||||
onClicked: {
|
||||
if(grid.model === _internal.twelveWordsModel) {
|
||||
grid.model = _internal.twentyFourWordsModel
|
||||
}
|
||||
else {
|
||||
grid.model = _internal.twelveWordsModel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
import QtQuick 2.12
|
||||
import QtQml.Models 2.14
|
||||
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
|
||||
import utils 1.0
|
||||
import "../stores"
|
||||
|
||||
StatusSelect {
|
||||
id: selectAccountType
|
||||
|
||||
property int addAccountType
|
||||
property string derivedFromAddress: ""
|
||||
|
||||
enum AddAccountType {
|
||||
GenerateNew,
|
||||
ImportSeedPhrase,
|
||||
ImportPrivateKey,
|
||||
WatchOnly
|
||||
}
|
||||
|
||||
function resetMe() {
|
||||
_internal.getGeneratedAccountsModel()
|
||||
addAccountType = SelectGeneratedAccount.AddAccountType.GenerateNew
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: RootStore.generatedAccountsViewModel
|
||||
onModelReset: {
|
||||
_internal.delegateModel.model = RootStore.generatedAccountsViewModel
|
||||
_internal.getGeneratedAccountsModel()
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: _internal
|
||||
property string importSeedPhraseString : qsTr("Import new Seed Phrase")
|
||||
property string importPrivateKeyString : qsTr("Import new Private Key")
|
||||
//% "Add a watch-only address"
|
||||
property string addWatchOnlyAccountString : qsTrId("add-a-watch-account")
|
||||
|
||||
property var delegateModel: DelegateModel {
|
||||
model: RootStore.generatedAccountsViewModel
|
||||
onModelUpdated: {
|
||||
_internal.getGeneratedAccountsModel()
|
||||
}
|
||||
}
|
||||
property ListModel generatedAccountsModel: ListModel{}
|
||||
|
||||
function getGeneratedAccountsModel() {
|
||||
if(generatedAccountsModel) {
|
||||
generatedAccountsModel.clear()
|
||||
for (var row = 0; row < _internal.delegateModel.model.count; row++) {
|
||||
var item = _internal.delegateModel.items.get(row).model;
|
||||
generatedAccountsModel.append({"name": item.name, "iconName": item.iconName, "generatedModel": item.generatedModel, "derivedfrom": item.derivedfrom, "isHeader": false})
|
||||
if(row === 0 && _internal.delegateModel.model.count > 1) {
|
||||
generatedAccountsModel.append({"name": qsTr("Imported"), "iconName": "", "derivedfrom": "", "isHeader": true})
|
||||
}
|
||||
}
|
||||
generatedAccountsModel.append({"name": qsTr("Add new"), "iconName": "", "derivedfrom": "", "isHeader": true})
|
||||
generatedAccountsModel.append({"name": _internal.importSeedPhraseString, "iconName": "seed-phrase", "derivedfrom": "", "isHeader": false})
|
||||
generatedAccountsModel.append({"name": _internal.importPrivateKeyString, "iconName": "password", "derivedfrom": "", "isHeader": false})
|
||||
generatedAccountsModel.append({"name": _internal.addWatchOnlyAccountString, "iconName": "show", "derivedfrom": "", "isHeader": false})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label: qsTr("Origin")
|
||||
model: _internal.generatedAccountsModel
|
||||
selectedItemComponent: StatusListItem {
|
||||
id: selectedItem
|
||||
icon.background.color: "transparent"
|
||||
border.width: 1
|
||||
border.color: Theme.palette.baseColor2
|
||||
tagsDelegate: StatusListItemTag {
|
||||
color: model.color
|
||||
height: Style.current.bigPadding
|
||||
radius: 6
|
||||
closeButtonVisible: false
|
||||
icon.emoji: model.emoji
|
||||
icon.emojiSize: Emoji.size.verySmall
|
||||
icon.isLetterIdenticon: true
|
||||
title: model.name
|
||||
titleText.font.pixelSize: 12
|
||||
titleText.color: Theme.palette.indirectColor1
|
||||
}
|
||||
}
|
||||
selectMenu.delegate: StatusListItem {
|
||||
id: defaultListItem
|
||||
title: model.name
|
||||
icon.name: model.iconName
|
||||
tagsModel : model.generatedModel
|
||||
enabled: !model.isHeader
|
||||
icon.background.color: "transparent"
|
||||
icon.color: model.generatedModel ? Theme.palette.primaryColor1 : Theme.palette.directColor5
|
||||
tagsDelegate: StatusListItemTag {
|
||||
color: model.color
|
||||
height: 24
|
||||
radius: 6
|
||||
closeButtonVisible: false
|
||||
icon.emoji: model.emoji
|
||||
icon.emojiSize: Emoji.size.verySmall
|
||||
icon.isLetterIdenticon: true
|
||||
title: model.name
|
||||
titleText.font.pixelSize: 12
|
||||
titleText.color: Theme.palette.indirectColor1
|
||||
}
|
||||
onClicked: {
|
||||
selectAccountType.addAccountType = (model.name === _internal.importSeedPhraseString) ? SelectGeneratedAccount.AddAccountType.ImportSeedPhrase :
|
||||
(model.name === _internal.importPrivateKeyString) ? SelectGeneratedAccount.AddAccountType.ImportPrivateKey :
|
||||
(model.name === _internal.addWatchOnlyAccountString) ? SelectGeneratedAccount.AddAccountType.WatchOnly :
|
||||
SelectGeneratedAccount.AddAccountType.GenerateNew
|
||||
selectedItem.title = model.name
|
||||
selectedItem.icon.name = model.iconName
|
||||
selectedItem.tagsModel = model.generatedModel
|
||||
selectedItem.enabled = !model.isHeader
|
||||
|
||||
selectAccountType.derivedFromAddress = model.derivedfrom
|
||||
selectMenu.close()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
if(index === 0) {
|
||||
selectedItem.title = model.name
|
||||
selectedItem.icon.name = model.iconName
|
||||
selectedItem.tagsModel = model.generatedModel
|
||||
selectedItem.enabled = !model.isHeader
|
||||
selectAccountType.derivedFromAddress = model.derivedfrom
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -17,6 +17,7 @@ import shared.controls 1.0
|
|||
|
||||
import "../stores"
|
||||
import "../views"
|
||||
import "../panels"
|
||||
|
||||
StatusModal {
|
||||
id: popup
|
||||
|
@ -31,6 +32,40 @@ StatusModal {
|
|||
//% "Generate an account"
|
||||
header.title: qsTrId("generate-a-new-account")
|
||||
|
||||
QtObject {
|
||||
id: _internal
|
||||
|
||||
property int numOfItems: 100
|
||||
property int pageNumber: 1
|
||||
function getDerivedAddressList() {
|
||||
if(advancedSelection.expandableItem) {
|
||||
var errMessage = ""
|
||||
if(advancedSelection.expandableItem.addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase &&
|
||||
!!advancedSelection.expandableItem.path &&
|
||||
!!advancedSelection.expandableItem.mnemonicText) {
|
||||
errMessage = RootStore.getDerivedAddressListForMnemonic(advancedSelection.expandableItem.mnemonicText, advancedSelection.expandableItem.path, numOfItems, pageNumber)
|
||||
_internal.showPasswordError(errMessage)
|
||||
}
|
||||
else if(!!advancedSelection.expandableItem.path && !!advancedSelection.expandableItem.derivedFromAddress && (passwordInput.text.length >= 6)) {
|
||||
errMessage = RootStore.getDerivedAddressList(passwordInput.text, advancedSelection.expandableItem.derivedFromAddress, advancedSelection.expandableItem.path, numOfItems, pageNumber)
|
||||
_internal.showPasswordError(errMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showPasswordError(errMessage) {
|
||||
if (errMessage) {
|
||||
if (Utils.isInvalidPasswordMessage(errMessage)) {
|
||||
//% "Wrong password"
|
||||
popup.passwordValidationError = qsTrId("wrong-password")
|
||||
} else {
|
||||
accountError.text = errMessage;
|
||||
accountError.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validate() {
|
||||
if (passwordInput.text === "") {
|
||||
//% "You need to enter a password"
|
||||
|
@ -41,8 +76,8 @@ StatusModal {
|
|||
} else {
|
||||
passwordValidationError = ""
|
||||
}
|
||||
return passwordValidationError === "" && accountNameInput.valid
|
||||
}
|
||||
return passwordValidationError === "" && accountNameInput.valid
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
passwordValidationError = "";
|
||||
|
@ -96,6 +131,10 @@ StatusModal {
|
|||
validationError: popup.passwordValidationError
|
||||
inputLabel.font.pixelSize: 15
|
||||
inputLabel.font.weight: Font.Normal
|
||||
onTextChanged: {
|
||||
popup.passwordValidationError = ""
|
||||
_internal.getDerivedAddressList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +163,6 @@ StatusModal {
|
|||
StatusColorSelectorGrid {
|
||||
id: colorSelectionGrid
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
//% "color"
|
||||
titleText: qsTr("color").toUpperCase()
|
||||
}
|
||||
|
||||
|
@ -148,7 +186,6 @@ StatusModal {
|
|||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width
|
||||
|
||||
//% "Advanced"
|
||||
primaryText: qsTr("Advanced")
|
||||
type: StatusExpandableItem.Type.Tertiary
|
||||
expandable: true
|
||||
|
@ -156,6 +193,7 @@ StatusModal {
|
|||
width: parent.width
|
||||
Layout.margins: Style.current.padding
|
||||
Component.onCompleted: advancedSelection.isValid = Qt.binding(function(){return isValid})
|
||||
onCalculateDerivedPath: _internal.getDerivedAddressList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -192,35 +230,26 @@ StatusModal {
|
|||
var errMessage = ""
|
||||
if(advancedSelection.expandableItem) {
|
||||
switch(advancedSelection.expandableItem.addAccountType) {
|
||||
case AdvancedAddAccountView.AddAccountType.GenerateNew:
|
||||
errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, emoji)
|
||||
case SelectGeneratedAccount.AddAccountType.GenerateNew:
|
||||
errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji, advancedSelection.expandableItem.completePath, advancedSelection.expandableItem.derivedFromAddress)
|
||||
break
|
||||
case AdvancedAddAccountView.AddAccountType.ImportSeedPhrase:
|
||||
errMessage = RootStore.addAccountsFromSeed(advancedSelection.expandableItem.mnemonicText, passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, emoji)
|
||||
case SelectGeneratedAccount.AddAccountType.ImportSeedPhrase:
|
||||
errMessage = RootStore.addAccountsFromSeed(advancedSelection.expandableItem.mnemonicText, passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji, advancedSelection.expandableItem.completePath)
|
||||
break
|
||||
case AdvancedAddAccountView.AddAccountType.ImportPrivateKey:
|
||||
errMessage = RootStore.addAccountsFromPrivateKey(advancedSelection.expandableItem.privateKey, passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, emoji)
|
||||
case SelectGeneratedAccount.AddAccountType.ImportPrivateKey:
|
||||
errMessage = RootStore.addAccountsFromPrivateKey(advancedSelection.expandableItem.privateKey, passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji)
|
||||
break
|
||||
case AdvancedAddAccountView.AddAccountType.WatchOnly:
|
||||
case SelectGeneratedAccount.AddAccountType.WatchOnly:
|
||||
errMessage = RootStore.addWatchOnlyAccount(advancedSelection.expandableItem.watchAddress, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, emoji)
|
||||
errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji, advancedSelection.expandableItem.completePath, advancedSelection.expandableItem.derivedFromAddress)
|
||||
}
|
||||
|
||||
loading = false
|
||||
if (errMessage) {
|
||||
Global.playErrorSound();
|
||||
if (Utils.isInvalidPasswordMessage(errMessage)) {
|
||||
//% "Wrong password"
|
||||
popup.passwordValidationError = qsTrId("wrong-password")
|
||||
} else {
|
||||
accountError.text = errMessage;
|
||||
accountError.open();
|
||||
}
|
||||
return
|
||||
}
|
||||
_internal.showPasswordError(errMessage)
|
||||
|
||||
popup.afterAddAccount();
|
||||
popup.close();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
pragma Singleton
|
||||
|
||||
import QtQuick 2.13
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
property ListModel derivationPaths: ListModel {
|
||||
ListElement {
|
||||
name: "Default"
|
||||
path: "m/44'/60'/0'/0"
|
||||
}
|
||||
ListElement {
|
||||
name: "Ethereum Classic"
|
||||
path: "m/44'/61'/0'/0"
|
||||
}
|
||||
ListElement {
|
||||
name: "Ethereum (Ledger)"
|
||||
path: "m/44'/60'/0'"
|
||||
}
|
||||
ListElement {
|
||||
name: "Ethereum Classic (Ledger)"
|
||||
path: "m/44'/60'/160720'/0"
|
||||
}
|
||||
ListElement {
|
||||
name: "Ethereum Classic (Ledger, Vintage MEW)"
|
||||
path: "m/44'/60'/160720'/0'"
|
||||
}
|
||||
ListElement {
|
||||
name: "Ethereum (KeepKey)"
|
||||
path: "m/44'/60'"
|
||||
}
|
||||
ListElement {
|
||||
name: "Ethereum Classic (KeepKey)"
|
||||
path: "m/44'/61'"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,9 @@ QtObject {
|
|||
|
||||
property var savedAddresses: walletSectionSavedAddresses.model
|
||||
|
||||
// Used for new wallet account generation
|
||||
property var generatedAccountsViewModel: walletSectionAccounts.generatedAccounts
|
||||
property var derivedAddressesList: walletSectionAccounts.derivedAddresses
|
||||
|
||||
property var layer1Networks: networksModule.layer1
|
||||
property var layer2Networks: networksModule.layer2
|
||||
|
@ -100,16 +103,16 @@ QtObject {
|
|||
walletSection.switchAccount(newIndex)
|
||||
}
|
||||
|
||||
function generateNewAccount(password, accountName, color, emoji) {
|
||||
return walletSectionAccounts.generateNewAccount(password, accountName, color, emoji)
|
||||
function generateNewAccount(password, accountName, color, emoji, path, derivedFrom) {
|
||||
return walletSectionAccounts.generateNewAccount(password, accountName, color, emoji, path, derivedFrom)
|
||||
}
|
||||
|
||||
function addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji) {
|
||||
return walletSectionAccounts.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji)
|
||||
}
|
||||
|
||||
function addAccountsFromSeed(seedPhrase, password, accountName, color, emoji) {
|
||||
return walletSectionAccounts.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji)
|
||||
function addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path) {
|
||||
return walletSectionAccounts.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path)
|
||||
}
|
||||
|
||||
function addWatchOnlyAccount(address, accountName,color, emoji) {
|
||||
|
@ -172,4 +175,32 @@ QtObject {
|
|||
function copyToClipboard(text) {
|
||||
globalUtils.copyToClipboard(text)
|
||||
}
|
||||
|
||||
function getDerivedAddressList(password, derivedFrom, path, pageSize , pageNumber) {
|
||||
return walletSectionAccounts.getDerivedAddressList(password, derivedFrom, path, pageSize , pageNumber)
|
||||
}
|
||||
|
||||
function getDerivedAddressData(index) {
|
||||
return walletSectionAccounts.getDerivedAddressAtIndex(index)
|
||||
}
|
||||
|
||||
function getDerivedAddressPathData(index) {
|
||||
return walletSectionAccounts.getDerivedAddressPathAtIndex(index)
|
||||
}
|
||||
|
||||
function getDerivedAddressHasActivityData(index) {
|
||||
return walletSectionAccounts.getDerivedAddressHasActivityAtIndex(index)
|
||||
}
|
||||
|
||||
function getDerivedAddressListForMnemonic(mnemonic, path, pageSize , pageNumber) {
|
||||
return walletSectionAccounts.getDerivedAddressListForMnemonic(mnemonic, path, pageSize , pageNumber)
|
||||
}
|
||||
|
||||
function resetDerivedAddressModel() {
|
||||
walletSectionAccounts.resetDerivedAddressModel()
|
||||
}
|
||||
|
||||
function vaildateMnemonic(mnemonic) {
|
||||
return onboardingModule.validateMnemonic(mnemonic)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
singleton RootStore 1.0 RootStore.qml
|
||||
singleton DerivationPathModel 1.0 DerivationPathModel.qml
|
||||
|
|
|
@ -7,329 +7,125 @@ import StatusQ.Core.Theme 0.1
|
|||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
import StatusQ.Controls.Validators 0.1
|
||||
|
||||
import utils 1.0
|
||||
import "../stores"
|
||||
import "../panels"
|
||||
|
||||
ColumnLayout {
|
||||
id: advancedSection
|
||||
|
||||
property alias privateKey: privateKey.text
|
||||
property int addAccountType: AdvancedAddAccountView.AddAccountType.GenerateNew
|
||||
property string mnemonicText: getSeedPhraseString()
|
||||
property int addAccountType: SelectGeneratedAccount.AddAccountType.GenerateNew
|
||||
property string derivedFromAddress: ""
|
||||
property string mnemonicText: ""
|
||||
property alias privateKey: importPrivateKeyPanel.text
|
||||
property string path: ""
|
||||
property string pathSubFix: ""
|
||||
property string completePath: path + "/" + pathSubFix
|
||||
property alias watchAddress: addressInput.text
|
||||
property string errorString: ""
|
||||
property bool isValid: addAccountType === AdvancedAddAccountView.AddAccountType.ImportSeedPhrase ? grid.isValid :
|
||||
addAccountType === AdvancedAddAccountView.AddAccountType.ImportPrivateKey ? (privateKey.text !== "" && privateKey.valid) :
|
||||
advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.WatchOnly ? (addressInput.text !== "" && addressInput.valid) : true
|
||||
property bool isValid: addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase ? importSeedPhrasePanel.isValid :
|
||||
addAccountType === SelectGeneratedAccount.AddAccountType.ImportPrivateKey ? (importPrivateKeyPanel.text !== "" && importPrivateKeyPanel.valid) :
|
||||
addAccountType === SelectGeneratedAccount.AddAccountType.WatchOnly ? (addressInput.text !== "" && addressInput.valid) : true
|
||||
|
||||
enum AddAccountType {
|
||||
GenerateNew,
|
||||
ImportSeedPhrase,
|
||||
ImportPrivateKey,
|
||||
WatchOnly
|
||||
}
|
||||
signal calculateDerivedPath()
|
||||
|
||||
function reset() {
|
||||
mnemonicText = ""
|
||||
errorString = ""
|
||||
select.currentIndex = 0
|
||||
addAccountType = AdvancedAddAccountView.AddAccountType.GenerateNew
|
||||
privateKey.text = ""
|
||||
privateKey.reset()
|
||||
//reset selectGeneratedAccount
|
||||
selectGeneratedAccount.resetMe()
|
||||
|
||||
// reset privateKey
|
||||
importPrivateKeyPanel.resetMe()
|
||||
|
||||
// reset importSeedPhrasePanel
|
||||
importSeedPhrasePanel.reset()
|
||||
|
||||
// reset derivation path
|
||||
derivationPathsPanel.reset()
|
||||
|
||||
// reset derviedAccountsList
|
||||
derivedAddressesPanel.reset()
|
||||
|
||||
// reset watch only address input
|
||||
addressInput.text = ""
|
||||
addressInput.reset()
|
||||
for(var i = 0; i < grid.model; i++) {
|
||||
if(grid.itemAtIndex(i)) {
|
||||
grid.itemAtIndex(i).textEdit.text = ""
|
||||
grid.itemAtIndex(i).textEdit.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validate() {
|
||||
errorString = "";
|
||||
if(addAccountType == AdvancedAddAccountView.AddAccountType.ImportSeedPhrase) {
|
||||
mnemonicText = getSeedPhraseString()
|
||||
|
||||
if (!Utils.isMnemonic(mnemonicText)) {
|
||||
//% "Invalid seed phrase"
|
||||
errorString = qsTrId("custom-seed-phrase")
|
||||
} else {
|
||||
errorString = onboardingModule.validateMnemonic(mnemonicText)
|
||||
const regex = new RegExp('word [a-z]+ not found in the dictionary', 'i');
|
||||
if (regex.test(errorString)) {
|
||||
//% "Invalid seed phrase"
|
||||
errorString = qsTrId("custom-seed-phrase") + '. ' +
|
||||
//% "This seed phrase doesn't match our supported dictionary. Check for misspelled words."
|
||||
qsTrId("custom-seed-phrase-text-1")
|
||||
}
|
||||
}
|
||||
return errorString === ""
|
||||
if(addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase) {
|
||||
// validate mnemonic
|
||||
return importSeedPhrasePanel.validate()
|
||||
}
|
||||
else if(addAccountType == AdvancedAddAccountView.AddAccountType.ImportPrivateKey) {
|
||||
if (privateKey.text === "") {
|
||||
//% "You need to enter a private key"
|
||||
errorString = qsTrId("you-need-to-enter-a-private-key")
|
||||
} else if (!Utils.isPrivateKey(privateKey.text)) {
|
||||
//% "Enter a valid private key (64 characters hexadecimal string)"
|
||||
errorString = qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
|
||||
} else {
|
||||
errorString = ""
|
||||
}
|
||||
return errorString === ""
|
||||
else if(addAccountType === SelectGeneratedAccount.AddAccountType.ImportPrivateKey) {
|
||||
// validate privateKey
|
||||
return importPrivateKeyPanel.validateMe()
|
||||
}
|
||||
else if(advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.WatchOnly) {
|
||||
else if(addAccountType === SelectGeneratedAccount.AddAccountType.WatchOnly) {
|
||||
return addressInput.valid
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function getSeedPhraseString() {
|
||||
var seedPhrase = ""
|
||||
for(var i = 0; i < grid.model; i++) {
|
||||
seedPhrase += grid.itemAtIndex(i).text + " "
|
||||
onPathChanged: {
|
||||
if(addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase) {
|
||||
if(importSeedPhrasePanel.isValid) {
|
||||
calculateDerivedPath()
|
||||
}
|
||||
}
|
||||
else {
|
||||
calculateDerivedPath()
|
||||
}
|
||||
return seedPhrase
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: _internal
|
||||
property int seedPhraseInputHeight: 44
|
||||
property int seedPhraseInputWidth: 220
|
||||
onDerivedFromAddressChanged: {
|
||||
// reset derviedAccountsList
|
||||
derivedAddressesPanel.reset()
|
||||
|
||||
if(addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase) {
|
||||
if(importSeedPhrasePanel.isValid) {
|
||||
calculateDerivedPath()
|
||||
}
|
||||
}
|
||||
else {
|
||||
calculateDerivedPath()
|
||||
}
|
||||
}
|
||||
|
||||
spacing: Style.current.padding
|
||||
|
||||
StatusSelect {
|
||||
id: select
|
||||
//% "Origin"
|
||||
label: qsTr("Origin")
|
||||
SelectGeneratedAccount {
|
||||
id: selectGeneratedAccount
|
||||
Layout.margins: Style.current.padding
|
||||
property int currentIndex: 0
|
||||
selectedItemComponent: StatusListItem {
|
||||
id: selectedItem
|
||||
icon.background.color: "transparent"
|
||||
border.width: 1
|
||||
border.color: Theme.palette.baseColor2
|
||||
tagsDelegate: StatusListItemTag {
|
||||
color: model.color
|
||||
height: Style.current.bigPadding
|
||||
radius: 6
|
||||
closeButtonVisible: false
|
||||
icon.emoji: model.emoji
|
||||
icon.emojiSize: Emoji.size.verySmall
|
||||
icon.isLetterIdenticon: true
|
||||
title: model.name
|
||||
titleText.font.pixelSize: 12
|
||||
titleText.color: Theme.palette.indirectColor1
|
||||
}
|
||||
}
|
||||
model: ListModel {
|
||||
Component.onCompleted: {
|
||||
//% "Default"
|
||||
append({"name": qsTr("Default"), "iconName": "status", "accountsModel": RootStore.generatedAccounts, "enabled": true})
|
||||
//% "Add new"
|
||||
append({"name": qsTr("Add new"), "iconName": "", "enabled": false})
|
||||
//% "Import new Seed Phrase"
|
||||
append({"name": qsTr("Import new Seed Phrase"), "iconName": "seed-phrase", "enabled": true})
|
||||
//% "Import new Private Key"
|
||||
append({"name": qsTr("Import new Private Key"), "iconName": "password", "enabled": true})
|
||||
//% "Import new Private Key"
|
||||
append({"name": qsTrId("add-a-watch-account"), "iconName": "show", "enabled": true})
|
||||
selectedItem.title = Qt.binding(function() {return get(select.currentIndex).name})
|
||||
selectedItem.icon.name = Qt.binding(function() {return get(select.currentIndex).iconName})
|
||||
selectedItem.tagsModel = Qt.binding(function() {return get(select.currentIndex).accountsModel})
|
||||
}
|
||||
}
|
||||
selectMenu.delegate: StatusListItem {
|
||||
id: defaultListItem
|
||||
title: model.name
|
||||
icon.name: model.iconName
|
||||
tagsModel : model.accountsModel
|
||||
enabled: model.enabled
|
||||
icon.background.color: "transparent"
|
||||
icon.color: model.accountsModel ? Theme.palette.primaryColor1 : Theme.palette.directColor5
|
||||
tagsDelegate: StatusListItemTag {
|
||||
color: model.color
|
||||
height: 24
|
||||
radius: 6
|
||||
closeButtonVisible: false
|
||||
icon.emoji: model.emoji
|
||||
icon.emojiSize: Emoji.size.verySmall
|
||||
icon.isLetterIdenticon: true
|
||||
title: model.name
|
||||
titleText.font.pixelSize: 12
|
||||
titleText.color: Theme.palette.indirectColor1
|
||||
}
|
||||
onClicked: {
|
||||
advancedSection.addAccountType = (index === 2) ? AdvancedAddAccountView.AddAccountType.ImportSeedPhrase :
|
||||
(index === 3) ? AdvancedAddAccountView.AddAccountType.ImportPrivateKey :
|
||||
(index === 4) ? AdvancedAddAccountView.AddAccountType.WatchOnly :
|
||||
AdvancedAddAccountView.AddAccountType.GenerateNew
|
||||
select.currentIndex = index
|
||||
select.selectMenu.close()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
advancedSection.addAccountType = Qt.binding(function() {return addAccountType})
|
||||
advancedSection.derivedFromAddress = Qt.binding(function() {return derivedFromAddress})
|
||||
}
|
||||
}
|
||||
|
||||
StatusInput {
|
||||
id: privateKey
|
||||
//% "Private key"
|
||||
label: qsTrId("private-key")
|
||||
charLimit: 66
|
||||
input.multiline: true
|
||||
input.minimumHeight: 80
|
||||
input.maximumHeight: 108
|
||||
//% "Paste the contents of your private key"
|
||||
input.placeholderText: qsTrId("paste-the-contents-of-your-private-key")
|
||||
visible: advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.ImportPrivateKey && advancedSection.visible
|
||||
errorMessage: advancedSection.errorString
|
||||
validators: [
|
||||
StatusMinLengthValidator {
|
||||
minLength: 1
|
||||
//% "You need to enter a private key"
|
||||
errorMessage: qsTrId("you-need-to-enter-a-private-key")
|
||||
},
|
||||
StatusValidator {
|
||||
property var validate: function (value) {
|
||||
return Utils.isPrivateKey(value)
|
||||
}
|
||||
//% "Enter a valid private key (64 characters hexadecimal string)"
|
||||
errorMessage: qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
|
||||
}
|
||||
]
|
||||
onVisibleChanged: {
|
||||
if(visible)
|
||||
privateKey.input.edit.forceActiveFocus();
|
||||
}
|
||||
ImportPrivateKeyPanel {
|
||||
id: importPrivateKeyPanel
|
||||
visible: advancedSection.addAccountType === SelectGeneratedAccount.AddAccountType.ImportPrivateKey && advancedSection.visible
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: grid
|
||||
ImportSeedPhrasePanel {
|
||||
id: importSeedPhrasePanel
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: visible ? (cellHeight * model/2) + footerItem.height: 0
|
||||
Layout.preferredHeight: visible ? importSeedPhrasePanel.preferredHeight: 0
|
||||
Layout.leftMargin: Style.current.padding
|
||||
Layout.rightMargin: Style.current.padding
|
||||
visible: advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.ImportSeedPhrase && advancedSection.visible
|
||||
cellHeight: _internal.seedPhraseInputHeight + Style.current.halfPadding
|
||||
cellWidth: _internal.seedPhraseInputWidth + Style.current.halfPadding
|
||||
model: 12
|
||||
interactive: false
|
||||
property bool isValid: checkIsValid()
|
||||
function checkIsValid() {
|
||||
var valid = model > 0 ? true: false
|
||||
for(var i = 0; i < model; i++) {
|
||||
if(grid.itemAtIndex(i))
|
||||
valid &= grid.itemAtIndex(i).isValid
|
||||
}
|
||||
return valid
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if(visible)
|
||||
grid.itemAtIndex(0).textEdit.input.edit.forceActiveFocus();
|
||||
}
|
||||
|
||||
// To-do Alex has introduced a model for bip39 dictonary, need to use it once its available
|
||||
// https://github.com/status-im/status-desktop/pull/5058
|
||||
delegate: StatusSeedPhraseInput {
|
||||
id: statusSeedInput
|
||||
width: _internal.seedPhraseInputWidth
|
||||
height: _internal.seedPhraseInputHeight
|
||||
textEdit.errorMessageCmp.visible: false
|
||||
textEdit.input.anchors.topMargin: 11
|
||||
leftComponentText: index + 1
|
||||
property bool isValid: !!text
|
||||
onIsValidChanged: {
|
||||
grid.isValid = grid.checkIsValid()
|
||||
}
|
||||
onTextChanged: {
|
||||
if (text !== "") {
|
||||
grid.currentIndex = index;
|
||||
}
|
||||
}
|
||||
// To-do Alex has introduced a model for bip39 dictonary, need to use it once its available
|
||||
// https://github.com/status-im/status-desktop/pull/5058
|
||||
// onDoneInsertingWord: {
|
||||
// advancedSection.mnemonicText += (index === 0) ? word : (" " + word);
|
||||
// for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
|
||||
// if (parseInt(grid.itemAtIndex(i).leftComponentText) === (parseInt(leftComponentText)+1)) {
|
||||
// grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
onEditClicked: {
|
||||
grid.currentIndex = index;
|
||||
grid.itemAtIndex(index).textEdit.input.edit.forceActiveFocus();
|
||||
}
|
||||
onKeyPressed: {
|
||||
if (event.key === Qt.Key_Tab || event.key === Qt.Key_Right) {
|
||||
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
|
||||
if (parseInt(grid.itemAtIndex(i).leftComponentText) === ((parseInt(leftComponentText)+1) <= grid.count ? (parseInt(leftComponentText)+1) : grid.count)) {
|
||||
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
|
||||
textEdit.input.tabNavItem = grid.itemAtIndex(i).textEdit.input.edit;
|
||||
}
|
||||
}
|
||||
} else if (event.key === Qt.Key_Left) {
|
||||
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
|
||||
if (parseInt(grid.itemAtIndex(i).leftComponentText) === ((parseInt(leftComponentText)-1) >= 0 ? (parseInt(leftComponentText)-1) : 0)) {
|
||||
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
} else if (event.key === Qt.Key_Down) {
|
||||
grid.itemAtIndex((index+1 < grid.count) ? (index+1) : (grid.count-1)).textEdit.input.edit.forceActiveFocus();
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
grid.itemAtIndex((index-1 >= 0) ? (index-1) : 0).textEdit.input.edit.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
textEdit.validators: [
|
||||
StatusMinLengthValidator {
|
||||
errorMessage: qsTr("Enter a valid word")
|
||||
minLength: 3
|
||||
}
|
||||
]
|
||||
}
|
||||
footer: Item {
|
||||
width: grid.width - Style.current.padding
|
||||
height: button.height + errorMessage.height + 16*2
|
||||
StatusBaseText {
|
||||
id: errorMessage
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Style.current.padding
|
||||
|
||||
height: visible ? implicitHeight : 0
|
||||
visible: !!text
|
||||
text: errorString
|
||||
|
||||
font.pixelSize: 12
|
||||
color: Theme.palette.dangerColor1
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
StatusButton {
|
||||
id: button
|
||||
visible: false
|
||||
anchors.top: errorMessage.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
//% "Use 24 word seed phrase"
|
||||
text: grid.model === 12 ? qsTr("Use 24 word seed phrase"):
|
||||
qsTr("Use 12 word seed phrase")
|
||||
onClicked: grid.model = grid.model === 12 ? 24 : 12
|
||||
visible: advancedSection.addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase && advancedSection.visible
|
||||
onMnemonicStringChanged: {
|
||||
advancedSection.mnemonicText = mnemonicString
|
||||
if(isValid) {
|
||||
calculateDerivedPath()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusInput {
|
||||
id: addressInput
|
||||
visible: advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.WatchOnly && advancedSection.visible
|
||||
visible: advancedSection.addAccountType === SelectGeneratedAccount.AddAccountType.WatchOnly && advancedSection.visible
|
||||
//% "Enter address..."
|
||||
input.placeholderText: qsTrId("enter-address...")
|
||||
//% "Account address"
|
||||
|
@ -351,37 +147,19 @@ ColumnLayout {
|
|||
Layout.margins: Style.current.padding
|
||||
Layout.preferredWidth: parent.width
|
||||
spacing: Style.current.bigPadding
|
||||
visible: advancedSection.addAccountType !== AdvancedAddAccountView.AddAccountType.WatchOnly &&
|
||||
advancedSection.addAccountType !== AdvancedAddAccountView.AddAccountType.ImportPrivateKey
|
||||
StatusSelect {
|
||||
visible: advancedSection.addAccountType !== SelectGeneratedAccount.AddAccountType.ImportPrivateKey &&
|
||||
advancedSection.addAccountType !== SelectGeneratedAccount.AddAccountType.WatchOnly
|
||||
DerivationPathsPanel {
|
||||
id: derivationPathsPanel
|
||||
Layout.preferredWidth: 213
|
||||
//% "Origin"
|
||||
label: qsTr("Derivation Path")
|
||||
selectedItemComponent: StatusListItem {
|
||||
width: parent.width
|
||||
icon.background.color: "transparent"
|
||||
border.width: 1
|
||||
border.color: Theme.palette.baseColor2
|
||||
title: "Default"
|
||||
subTitle: "m/44’/61’/0’/1"
|
||||
enabled: false
|
||||
}
|
||||
enabled: false
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Component.onCompleted: advancedSection.path = Qt.binding(function() { return derivationPathsPanel.path})
|
||||
}
|
||||
StatusSelect {
|
||||
DerivedAddressesPanel {
|
||||
id: derivedAddressesPanel
|
||||
Layout.preferredWidth: 213
|
||||
//% "Origin"
|
||||
label: qsTr("Account")
|
||||
width: parent.width
|
||||
enabled: false
|
||||
selectedItemComponent: StatusListItem {
|
||||
icon.background.color: "transparent"
|
||||
border.width: 1
|
||||
border.color: Theme.palette.baseColor2
|
||||
title: "0x1234...abcd"
|
||||
subTitle: "No activity"
|
||||
enabled: false
|
||||
}
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Component.onCompleted: advancedSection.pathSubFix = Qt.binding(function() { return derivedAddressesPanel.pathSubFix})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
singleton RootStore 1.0 RootStore.qml
|
||||
BIP39_en 1.0 BIP39_en.qml
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit e1d98de2440a8a631e8df7516a6a4cb13c26f1a6
|
||||
Subproject commit b83f4a6c83d4f2856a44e5fee4137f4f8ffbbaad
|
Loading…
Reference in New Issue