feat(@desktop/keycard): adding wallet account using `Authenticate` flow
Fixes: #7509
This commit is contained in:
parent
4c3aca273a
commit
861c585d2b
|
@ -240,8 +240,11 @@ proc init*(self: Controller) =
|
|||
self.authenticateUserFlowRequestedBy.len == 0:
|
||||
return
|
||||
self.delegate.onSharedKeycarModuleFlowTerminated(args.lastStepInTheCurrentFlow)
|
||||
var password = args.password
|
||||
if password.len == 0 and args.keyUid.len > 0:
|
||||
password = args.keyUid
|
||||
let data = SharedKeycarModuleArgs(uniqueIdentifier: self.authenticateUserFlowRequestedBy,
|
||||
data: args.data,
|
||||
password: password,
|
||||
keyUid: args.keyUid,
|
||||
txR: args.txR,
|
||||
txS: args.txS,
|
||||
|
|
|
@ -2,20 +2,29 @@ import io_interface
|
|||
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
|
||||
import ../../../../../app_service/service/accounts/service as accounts_service
|
||||
|
||||
import ../../../../global/global_singleton
|
||||
import ../../../shared_modules/keycard_popup/io_interface as keycard_shared_module
|
||||
|
||||
import ../../../../core/eventemitter
|
||||
|
||||
const UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_IDENTIFIER* = "WalletSection-AccountsModule"
|
||||
|
||||
type
|
||||
Controller* = ref object of RootObj
|
||||
delegate: io_interface.AccessInterface
|
||||
events: EventEmitter
|
||||
walletAccountService: wallet_account_service.Service
|
||||
accountsService: accounts_service.Service
|
||||
|
||||
proc newController*(
|
||||
delegate: io_interface.AccessInterface,
|
||||
walletAccountService: wallet_account_service.Service
|
||||
events: EventEmitter,
|
||||
walletAccountService: wallet_account_service.Service,
|
||||
accountsService: accounts_service.Service
|
||||
): Controller =
|
||||
result = Controller()
|
||||
result.delegate = delegate
|
||||
result.events = events
|
||||
result.walletAccountService = walletAccountService
|
||||
result.accountsService = accountsService
|
||||
|
||||
|
@ -23,7 +32,11 @@ proc delete*(self: Controller) =
|
|||
discard
|
||||
|
||||
proc init*(self: Controller) =
|
||||
discard
|
||||
self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args):
|
||||
let args = SharedKeycarModuleArgs(e)
|
||||
if args.uniqueIdentifier != UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_IDENTIFIER:
|
||||
return
|
||||
self.delegate.onUserAuthenticated(args.password)
|
||||
|
||||
proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] =
|
||||
return self.walletAccountService.getWalletAccounts()
|
||||
|
@ -56,3 +69,18 @@ proc validSeedPhrase*(self: Controller, seedPhrase: string): bool =
|
|||
let err = self.accountsService.validateMnemonic(seedPhrase)
|
||||
return err.len == 0
|
||||
|
||||
proc loggedInUserUsesBiometricLogin*(self: Controller): bool =
|
||||
if(not defined(macosx)):
|
||||
return false
|
||||
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
|
||||
if (value != LS_VALUE_STORE):
|
||||
return false
|
||||
return true
|
||||
|
||||
proc getLoggedInAccount*(self: Controller): AccountDto =
|
||||
return self.accountsService.getLoggedInAccount()
|
||||
|
||||
proc authenticateUser*(self: Controller, keyUid = "") =
|
||||
let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_WALLET_SECTION_ACCOUNTS_MODULE_IDENTIFIER,
|
||||
keyUid: keyUid)
|
||||
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data)
|
||||
|
|
|
@ -47,4 +47,16 @@ method viewDidLoad*(self: AccessInterface) {.base.} =
|
|||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method validSeedPhrase*(self: AccessInterface, value: string): bool {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method authenticateUser*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onUserAuthenticated*(self: AccessInterface, password: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method loggedInUserUsesBiometricLogin*(self: AccessInterface): bool {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method isProfileKeyPairMigrated*(self: AccessInterface): bool {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
|
@ -172,3 +172,22 @@ method getDerivedAddressForPrivateKey*(self: Module, privateKey: string) =
|
|||
|
||||
method validSeedPhrase*(self: Module, value: string): bool =
|
||||
return self.controller.validSeedPhrase(value)
|
||||
|
||||
method loggedInUserUsesBiometricLogin*(self: Module): bool =
|
||||
return self.controller.loggedInUserUsesBiometricLogin()
|
||||
|
||||
method isProfileKeyPairMigrated*(self: Module): bool =
|
||||
return self.controller.getLoggedInAccount().keycardPairing.len > 0
|
||||
|
||||
method authenticateUser*(self: Module) =
|
||||
if self.isProfileKeyPairMigrated():
|
||||
let keyUid = singletonInstance.userProfile.getKeyUid()
|
||||
self.controller.authenticateUser(keyUid)
|
||||
else:
|
||||
self.controller.authenticateUser()
|
||||
|
||||
method onUserAuthenticated*(self: Module, password: string) =
|
||||
if password.len > 0:
|
||||
self.view.userAuthenticaionSuccess(password)
|
||||
else:
|
||||
self.view.userAuthentiactionFail()
|
||||
|
|
|
@ -277,3 +277,15 @@ QtObject:
|
|||
|
||||
proc validSeedPhrase*(self: View, value: string): bool {.slot.} =
|
||||
return self.delegate.validSeedPhrase(value)
|
||||
|
||||
proc userAuthenticaionSuccess*(self: View, password: string) {.signal.}
|
||||
proc userAuthentiactionFail*(self: View) {.signal.}
|
||||
|
||||
proc authenticateUser*(self: View) {.slot.} =
|
||||
self.delegate.authenticateUser()
|
||||
|
||||
proc loggedInUserUsesBiometricLogin*(self: View): bool {.slot.} =
|
||||
return self.delegate.loggedInUserUsesBiometricLogin()
|
||||
|
||||
proc isProfileKeyPairMigrated*(self: View): bool {.slot.} =
|
||||
return self.delegate.isProfileKeyPairMigrated()
|
|
@ -44,6 +44,7 @@ type
|
|||
tmpKeyUidWhichIsBeingAuthenticating: string
|
||||
tmpKeyUidWhichIsBeingUnlocking: string
|
||||
tmpUsePinFromBiometrics: bool
|
||||
tmpOfferToStoreUpdatedPinToKeychain: bool
|
||||
tmpKeycardUid: string
|
||||
|
||||
proc newController*(delegate: io_interface.AccessInterface,
|
||||
|
@ -125,7 +126,7 @@ proc init*(self: Controller) =
|
|||
if args.uniqueIdentifier != self.uniqueIdentifier:
|
||||
return
|
||||
self.connectKeycardReponseSignal()
|
||||
self.delegate.onUserAuthenticated(args.data)
|
||||
self.delegate.onUserAuthenticated(args.password)
|
||||
self.connectionIds.add(handlerId)
|
||||
|
||||
proc getKeycardData*(self: Controller): string =
|
||||
|
@ -170,6 +171,12 @@ proc setPinMatch*(self: Controller, value: bool) =
|
|||
proc getPinMatch*(self: Controller): bool =
|
||||
return self.tmpPinMatch
|
||||
|
||||
proc setOfferToStoreUpdatedPinToKeychain*(self: Controller, value: bool) =
|
||||
self.tmpOfferToStoreUpdatedPinToKeychain = value
|
||||
|
||||
proc offerToStoreUpdatedPinToKeychain*(self: Controller): bool =
|
||||
return self.tmpOfferToStoreUpdatedPinToKeychain
|
||||
|
||||
proc setPassword*(self: Controller, value: string) =
|
||||
self.tmpPassword = value
|
||||
|
||||
|
@ -307,7 +314,7 @@ proc terminateCurrentFlow*(self: Controller, lastStepInTheCurrentFlow: bool) =
|
|||
var data = SharedKeycarModuleFlowTerminatedArgs(uniqueIdentifier: self.uniqueIdentifier,
|
||||
lastStepInTheCurrentFlow: lastStepInTheCurrentFlow)
|
||||
if lastStepInTheCurrentFlow:
|
||||
data.data = self.tmpPassword
|
||||
data.password = self.tmpPassword
|
||||
data.keyUid = flowEvent.keyUid
|
||||
data.txR = flowEvent.txSignature.r
|
||||
data.txS = flowEvent.txSignature.s
|
||||
|
|
|
@ -15,6 +15,7 @@ method executePrimaryCommand*(self: BiometricsPinInvalidState, controller: Contr
|
|||
method executeSecondaryCommand*(self: BiometricsPinInvalidState, controller: Controller) =
|
||||
if self.flowType == FlowType.Authentication:
|
||||
controller.setUsePinFromBiometrics(true)
|
||||
controller.setOfferToStoreUpdatedPinToKeychain(true)
|
||||
|
||||
method getNextSecondaryState*(self: BiometricsPinInvalidState, controller: Controller): State =
|
||||
if self.flowType == FlowType.Authentication:
|
||||
|
|
|
@ -98,6 +98,8 @@ method resolveKeycardNextState*(self: EnterPinState, keycardFlowType: string, ke
|
|||
return createState(StateType.MaxPinRetriesReached, self.flowType, nil)
|
||||
if keycardFlowType == ResponseTypeValueKeycardFlowResult:
|
||||
if keycardEvent.error.len == 0:
|
||||
if controller.offerToStoreUpdatedPinToKeychain():
|
||||
controller.tryToStoreDataToKeychain(controller.getPin())
|
||||
controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true)
|
||||
return nil
|
||||
if self.flowType == FlowType.DisplayKeycardContent:
|
||||
|
|
|
@ -8,13 +8,33 @@ const SIGNAL_SHARED_KEYCARD_MODULE_FLOW_TERMINATED* = "sharedKeycarModuleFlowTer
|
|||
const SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER* = "sharedKeycarModuleAuthenticateUser"
|
||||
const SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED* = "sharedKeycarModuleUserAuthenticated"
|
||||
|
||||
## Authentication in the app is a global thing and may be used from any part of the app. How to achieve that... it's enough just to send
|
||||
## `SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER` signal with properly set `SharedKeycarModuleAuthenticationArgs` and props there:
|
||||
## -- `uniqueIdentifier` - some unique string, for the readability usually name of the module which needs authentication,
|
||||
## -- in case of non keycard user (regular) user that's enough,
|
||||
## -- in case of keycard user we want to authenticate it with a card that his profile is migrated to, that means apart of `uniqueIdentifier`
|
||||
## we need to set `keyUid` as well,
|
||||
## -- in case we want to sign a transaction for a certain wallet's account, then apart of `uniqueIdentifier` and `keyUid`of a key pair that
|
||||
## account belongs to, we need to set and `bip44Path` and `txHash`
|
||||
##
|
||||
## `SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER` will be handled in the `mainModule` (shared keycard popup module will be run) and as a
|
||||
## result, when authentication gets done `SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED` signal with properly set `SharedKeycarModuleArgs`
|
||||
## and props there will be emitted:
|
||||
## -- `uniqueIdentifier` - will be the same as one used for running authentication process
|
||||
## -- in case of success of a regular user authentication `password` will be sent, otherwise it will be empty
|
||||
## -- in case of success of a keycard user authentication `keyUid` will be sent, otherwise it will be empty
|
||||
## -- in case of success of a signing a transaction `keyUid`, `txR` and `txS` will be sent, otherwise it will be empty
|
||||
##
|
||||
## TLDR: when you need to authenticate user, from the module where it's needed you have to send `SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER`
|
||||
## signal to run authentication process and connect to `SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED` signal to get the results of it.
|
||||
|
||||
type
|
||||
SharedKeycarModuleBaseArgs* = ref object of Args
|
||||
uniqueIdentifier*: string
|
||||
|
||||
type
|
||||
SharedKeycarModuleArgs* = ref object of SharedKeycarModuleBaseArgs
|
||||
data*: string
|
||||
password*: string
|
||||
keyUid*: string
|
||||
txR*: string
|
||||
txS*: string
|
||||
|
|
|
@ -590,8 +590,6 @@ proc verifyAccountPassword*(self: Service, account: string, password: string): b
|
|||
|
||||
proc convertToKeycardAccount*(self: Service, keyUid: string, password: string): bool =
|
||||
try:
|
||||
self.setKeyStoreDir(keyUid)
|
||||
|
||||
var accountDataJson = %* {
|
||||
"name": self.getLoggedInAccount().name,
|
||||
"key-uid": keyUid
|
||||
|
|
|
@ -244,11 +244,11 @@ Item {
|
|||
This function resets the text input validation and text.
|
||||
*/
|
||||
function reset() {
|
||||
statusBaseInput.text = ""
|
||||
root.errorMessage = ""
|
||||
statusBaseInput.valid = false
|
||||
statusBaseInput.dirty = false
|
||||
statusBaseInput.pristine = true
|
||||
statusBaseInput.text = ""
|
||||
root.errorMessage = ""
|
||||
}
|
||||
|
||||
property string _previousText: text
|
||||
|
|
|
@ -24,8 +24,6 @@ StatusModal {
|
|||
|
||||
property int minPswLen: 10
|
||||
readonly property int marginBetweenInputs: 38
|
||||
readonly property alias passwordValidationError: d.passwordValidationError
|
||||
|
||||
property var emojiPopup: null
|
||||
|
||||
header.title: qsTr("Generate an account")
|
||||
|
@ -33,14 +31,6 @@ StatusModal {
|
|||
|
||||
signal afterAddAccount()
|
||||
|
||||
Timer {
|
||||
id: waitTimer
|
||||
|
||||
interval: 1000
|
||||
running: false
|
||||
onTriggered: d.getDerivedAddressList()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: emojiPopup
|
||||
enabled: root.opened
|
||||
|
@ -51,16 +41,15 @@ StatusModal {
|
|||
}
|
||||
|
||||
Connections {
|
||||
target: RootStore
|
||||
enabled: root.opened
|
||||
|
||||
function onDerivedAddressesListChanged() {
|
||||
d.isPasswordCorrect = RootStore.derivedAddressesError.length === 0
|
||||
target: walletSectionAccounts
|
||||
onUserAuthenticaionSuccess: {
|
||||
validationError.text = ""
|
||||
d.password = password
|
||||
d.getDerivedAddressList()
|
||||
}
|
||||
|
||||
function onDerivedAddressesErrorChanged() {
|
||||
if(Utils.isInvalidPasswordMessage(RootStore.derivedAddressesError))
|
||||
d.passwordValidationError = qsTr("Password must be at least %n character(s) long", "", root.minPswLen);
|
||||
onUserAuthentiactionFail: {
|
||||
d.password = ""
|
||||
validationError.text = qsTr("An authentication failed")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,33 +59,27 @@ StatusModal {
|
|||
readonly property int numOfItems: 100
|
||||
readonly property int pageNumber: 1
|
||||
|
||||
property string passwordValidationError: ""
|
||||
property bool isPasswordCorrect: false
|
||||
property string password: ""
|
||||
property int selectedAccountType: SelectGeneratedAccount.AddAccountType.GenerateNew
|
||||
readonly property bool authenticationNeeded: d.selectedAccountType !== SelectGeneratedAccount.AddAccountType.WatchOnly &&
|
||||
d.password === ""
|
||||
|
||||
|
||||
|
||||
|
||||
function getDerivedAddressList() {
|
||||
if(advancedSelection.expandableItem.addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase
|
||||
if(d.selectedAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase
|
||||
&& !!advancedSelection.expandableItem.path
|
||||
&& !!advancedSelection.expandableItem.mnemonicText) {
|
||||
RootStore.getDerivedAddressListForMnemonic(advancedSelection.expandableItem.mnemonicText,
|
||||
advancedSelection.expandableItem.path, numOfItems, pageNumber)
|
||||
} else if(!!advancedSelection.expandableItem.path && !!advancedSelection.expandableItem.derivedFromAddress
|
||||
&& (passwordInput.text.length > 0)) {
|
||||
RootStore.getDerivedAddressList(passwordInput.text, advancedSelection.expandableItem.derivedFromAddress,
|
||||
&& (d.password.length > 0)) {
|
||||
RootStore.getDerivedAddressList(d.password, advancedSelection.expandableItem.derivedFromAddress,
|
||||
advancedSelection.expandableItem.path, numOfItems, pageNumber)
|
||||
}
|
||||
}
|
||||
|
||||
function showPasswordError(errMessage) {
|
||||
if (errMessage) {
|
||||
if (Utils.isInvalidPasswordMessage(errMessage)) {
|
||||
d.passwordValidationError = qsTr("Wrong password")
|
||||
scroll.contentY = -scroll.padding
|
||||
} else {
|
||||
console.warn(`Unhandled error case. Status-go message: ${errMessage}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateNewAccount() {
|
||||
// TODO the loading doesn't work because the function freezes the view. Might need to use threads
|
||||
nextButton.loading = true
|
||||
|
@ -107,19 +90,19 @@ StatusModal {
|
|||
|
||||
let errMessage = ""
|
||||
|
||||
switch(advancedSelection.expandableItem.addAccountType) {
|
||||
switch(d.selectedAccountType) {
|
||||
case SelectGeneratedAccount.AddAccountType.GenerateNew:
|
||||
errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor,
|
||||
errMessage = RootStore.generateNewAccount(d.password, accountNameInput.text, colorSelectionGrid.selectedColor,
|
||||
accountNameInput.input.asset.emoji, advancedSelection.expandableItem.completePath,
|
||||
advancedSelection.expandableItem.derivedFromAddress)
|
||||
break
|
||||
case SelectGeneratedAccount.AddAccountType.ImportSeedPhrase:
|
||||
errMessage = RootStore.addAccountsFromSeed(advancedSelection.expandableItem.mnemonicText, passwordInput.text,
|
||||
errMessage = RootStore.addAccountsFromSeed(advancedSelection.expandableItem.mnemonicText, d.password,
|
||||
accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.asset.emoji,
|
||||
advancedSelection.expandableItem.completePath)
|
||||
break
|
||||
case SelectGeneratedAccount.AddAccountType.ImportPrivateKey:
|
||||
errMessage = RootStore.addAccountsFromPrivateKey(advancedSelection.expandableItem.privateKey, passwordInput.text,
|
||||
errMessage = RootStore.addAccountsFromPrivateKey(advancedSelection.expandableItem.privateKey, d.password,
|
||||
accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.asset.emoji)
|
||||
break
|
||||
case SelectGeneratedAccount.AddAccountType.WatchOnly:
|
||||
|
@ -131,23 +114,33 @@ StatusModal {
|
|||
nextButton.loading = false
|
||||
|
||||
if (errMessage) {
|
||||
d.showPasswordError(errMessage)
|
||||
console.warn(`Unhandled error case. Status-go message: ${errMessage}`)
|
||||
} else {
|
||||
root.afterAddAccount()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
function nextButtonClicked() {
|
||||
if (d.authenticationNeeded) {
|
||||
d.password = ""
|
||||
RootStore.authenticateUser()
|
||||
}
|
||||
else {
|
||||
d.generateNewAccount()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
accountNameInput.input.asset.emoji = StatusQUtils.Emoji.getRandomEmoji(StatusQUtils.Emoji.size.verySmall)
|
||||
colorSelectionGrid.selectedColorIndex = Math.floor(Math.random() * colorSelectionGrid.model.length)
|
||||
passwordInput.forceActiveFocus(Qt.MouseFocusReason)
|
||||
accountNameInput.input.edit.forceActiveFocus()
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
d.passwordValidationError = ""
|
||||
passwordInput.text = ""
|
||||
d.password = ""
|
||||
validationError.text = ""
|
||||
accountNameInput.reset()
|
||||
advancedSelection.expanded = false
|
||||
advancedSelection.reset()
|
||||
|
@ -169,34 +162,15 @@ StatusModal {
|
|||
spacing: Style.current.halfPadding
|
||||
topPadding: 20
|
||||
|
||||
// To-Do Password hidden option not supported in StatusQ StatusInput
|
||||
Item {
|
||||
StatusBaseText {
|
||||
id: validationError
|
||||
visible: text !== ""
|
||||
width: parent.width
|
||||
height: passwordInput.height
|
||||
visible: advancedSelection.expandableItem.addAccountType !== SelectGeneratedAccount.AddAccountType.WatchOnly
|
||||
Input {
|
||||
id: passwordInput
|
||||
anchors.fill: parent
|
||||
|
||||
placeholderText: qsTr("Enter your password...")
|
||||
label: qsTr("Password")
|
||||
textField.echoMode: TextInput.Password
|
||||
validationError: d.passwordValidationError
|
||||
textField.objectName: "accountModalPassword"
|
||||
inputLabel.font.pixelSize: 15
|
||||
inputLabel.font.weight: Font.Normal
|
||||
onTextChanged: {
|
||||
d.isPasswordCorrect = false
|
||||
d.passwordValidationError = ""
|
||||
waitTimer.restart()
|
||||
}
|
||||
onKeyPressed: {
|
||||
if(event.key === Qt.Key_Tab) {
|
||||
accountNameInput.input.edit.forceActiveFocus(Qt.MouseFocusReason)
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
height: 16
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: 12
|
||||
color: Style.current.danger
|
||||
wrapMode: TextEdit.Wrap
|
||||
}
|
||||
|
||||
StatusInput {
|
||||
|
@ -206,6 +180,7 @@ StatusModal {
|
|||
input.isIconSelectable: true
|
||||
input.asset.color: colorSelectionGrid.selectedColor ? colorSelectionGrid.selectedColor : Theme.palette.directColor1
|
||||
input.leftPadding: Style.current.padding
|
||||
enabled: !d.authenticationNeeded
|
||||
onIconClicked: {
|
||||
root.emojiPopup.open()
|
||||
root.emojiPopup.emojiSize = StatusQUtils.Emoji.size.verySmall
|
||||
|
@ -231,6 +206,7 @@ StatusModal {
|
|||
StatusColorSelectorGrid {
|
||||
id: colorSelectionGrid
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
enabled: !d.authenticationNeeded
|
||||
titleText: qsTr("color").toUpperCase()
|
||||
}
|
||||
|
||||
|
@ -262,7 +238,15 @@ StatusModal {
|
|||
return
|
||||
}
|
||||
}
|
||||
Component.onCompleted: advancedSelection.isValid = Qt.binding(() => isValid)
|
||||
|
||||
onAddAccountTypeChanged: {
|
||||
d.selectedAccountType = addAccountType
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
d.selectedAccountType = addAccountType
|
||||
advancedSelection.isValid = Qt.binding(() => isValid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -272,21 +256,42 @@ StatusModal {
|
|||
StatusButton {
|
||||
id: nextButton
|
||||
|
||||
text: loading ? qsTr("Loading...") : qsTr("Add account")
|
||||
text: {
|
||||
if (d.authenticationNeeded) {
|
||||
return qsTr("Authenticate")
|
||||
}
|
||||
if (loading) {
|
||||
return qsTr("Loading...")
|
||||
}
|
||||
return qsTr("Add account")
|
||||
}
|
||||
|
||||
|
||||
enabled: {
|
||||
if (d.authenticationNeeded) {
|
||||
return true
|
||||
}
|
||||
if (loading) {
|
||||
return false
|
||||
}
|
||||
return accountNameInput.text !== "" && advancedSelection.isValid
|
||||
}
|
||||
|
||||
return (advancedSelection.expandableItem.addAccountType === SelectGeneratedAccount.AddAccountType.WatchOnly || d.isPasswordCorrect)
|
||||
&& accountNameInput.text !== "" && advancedSelection.isValid
|
||||
icon.name: {
|
||||
if (d.authenticationNeeded) {
|
||||
if (RootStore.loggedInUserUsesBiometricLogin())
|
||||
return "touch-id"
|
||||
if (RootStore.isProfileKeyPairMigrated())
|
||||
return "keycard"
|
||||
return "password"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
highlighted: focus
|
||||
|
||||
Keys.onReturnPressed: d.generateNewAccount()
|
||||
onClicked : d.generateNewAccount()
|
||||
Keys.onReturnPressed: d.nextButtonClicked()
|
||||
onClicked : d.nextButtonClicked()
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -222,4 +222,16 @@ QtObject {
|
|||
function getNextSelectableDerivedAddressIndex() {
|
||||
return walletSectionAccounts.getNextSelectableDerivedAddressIndex()
|
||||
}
|
||||
|
||||
function authenticateUser() {
|
||||
walletSectionAccounts.authenticateUser()
|
||||
}
|
||||
|
||||
function loggedInUserUsesBiometricLogin() {
|
||||
return walletSectionAccounts.loggedInUserUsesBiometricLogin()
|
||||
}
|
||||
|
||||
function isProfileKeyPairMigrated() {
|
||||
return walletSectionAccounts.isProfileKeyPairMigrated()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -439,10 +439,11 @@ StatusModal {
|
|||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsReadyToSign ||
|
||||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.notKeycard ||
|
||||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinFailed ||
|
||||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid ||
|
||||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.wrongKeycard ||
|
||||
root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.keycardEmpty)
|
||||
return qsTr("Use PIN")
|
||||
if (root.sharedKeycardModule.currentState.stateType === Constants.keycardSharedState.biometricsPinInvalid)
|
||||
return qsTr("Update PIN")
|
||||
}
|
||||
}
|
||||
if (root.sharedKeycardModule.currentState.flowType === Constants.keycardSharedFlow.unlockKeycard) {
|
||||
|
|
|
@ -826,7 +826,9 @@ Item {
|
|||
}
|
||||
PropertyChanges {
|
||||
target: message
|
||||
text: ""
|
||||
text: qsTr("The PIN length doesn't match Keycard's PIN length")
|
||||
font.pixelSize: Constants.keycard.general.fontSize2
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 698c32f3e3684dd5918b8f38aa55fc568e1e7639
|
||||
Subproject commit d89c0c8d9e333dc9b0c4ea36fd9c49c09a9b7d19
|
Loading…
Reference in New Issue