mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-23 12:08:53 +00:00
543 lines
18 KiB
QML
543 lines
18 KiB
QML
import QtQuick 2.15
|
|
import QtQuick.Controls 2.15
|
|
import QtQml 2.15
|
|
|
|
import StatusQ 0.1
|
|
import StatusQ.Popups 0.1
|
|
import StatusQ.Core.Utils 0.1 as SQUtils
|
|
|
|
import AppLayouts.Onboarding2.pages 1.0
|
|
import AppLayouts.Onboarding.enums 1.0
|
|
|
|
SQUtils.QObject {
|
|
id: root
|
|
|
|
required property StackView stackView
|
|
|
|
required property var loginAccountsModel
|
|
|
|
required property int keycardState
|
|
required property int pinSettingState
|
|
required property int authorizationState
|
|
required property int restoreKeysExportState
|
|
required property int addKeyPairState
|
|
required property int syncState
|
|
required property var generateMnemonic
|
|
required property int remainingPinAttempts
|
|
required property int remainingPukAttempts
|
|
|
|
required property bool isBiometricsLogin // FIXME should come from the loginAccountsModel for each profile separately?
|
|
|
|
required property bool biometricsAvailable
|
|
required property bool displayKeycardPromoBanner
|
|
required property bool networkChecksEnabled
|
|
|
|
property int keycardPinInfoPageDelay: 2000
|
|
|
|
// functions
|
|
required property var passwordStrengthScoreFunction
|
|
required property var isSeedPhraseValid
|
|
required property var validateConnectionString
|
|
required property var tryToSetPukFunction
|
|
|
|
readonly property LoginScreen loginScreen: d.loginScreen
|
|
|
|
signal biometricsRequested(string profileId)
|
|
signal dismissBiometricsRequested
|
|
signal loginRequested(string keyUid, int method, var data)
|
|
signal setPinRequested(string pin)
|
|
signal enableBiometricsRequested(bool enable)
|
|
signal shareUsageDataRequested(bool enabled)
|
|
signal syncProceedWithConnectionString(string connectionString)
|
|
signal seedphraseSubmitted(string seedphrase)
|
|
signal keyUidSubmitted(string keyUid)
|
|
signal setPasswordRequested(string password)
|
|
signal exportKeysRequested
|
|
signal loadMnemonicRequested
|
|
signal authorizationRequested(string pin)
|
|
signal performKeycardFactoryResetRequested
|
|
|
|
signal linkActivated(string link)
|
|
|
|
signal finished(int flow)
|
|
|
|
function init() {
|
|
root.stackView.push(entryPage)
|
|
}
|
|
|
|
function setBiometricResponse(secret: string, error = "",
|
|
detailedError = "",
|
|
wrongFingerprint = false) {
|
|
if (!loginScreen)
|
|
return
|
|
|
|
loginScreen.setBiometricResponse(secret, error, detailedError,
|
|
wrongFingerprint)
|
|
}
|
|
|
|
QtObject {
|
|
id: d
|
|
|
|
property int flow
|
|
property LoginScreen loginScreen: null
|
|
|
|
property bool seenUsageDataPrompt
|
|
|
|
function pushOrSkipBiometricsPage() {
|
|
if (root.biometricsAvailable) {
|
|
root.stackView.replace(null, enableBiometricsPage)
|
|
} else {
|
|
root.finished(d.flow)
|
|
}
|
|
}
|
|
|
|
function openPrivacyPolicyPopup() {
|
|
privacyPolicyPopup.createObject(root.stackView).open()
|
|
}
|
|
|
|
function openTermsOfUsePopup() {
|
|
termsOfUsePopup.createObject(root.stackView).open()
|
|
}
|
|
|
|
function handleKeycardProgressFailedState(state) {
|
|
if (state === Onboarding.ProgressState.Failed)
|
|
handleKeycardFailedState()
|
|
}
|
|
|
|
function handleKeycardAuthorizationErrorState(state) {
|
|
if (state === Onboarding.AuthorizationState.Error)
|
|
handleKeycardFailedState()
|
|
}
|
|
|
|
function handleKeycardFailedState() {
|
|
// find index of first page in the flow
|
|
let idx = 0
|
|
const entryItem = stackView.find((item, index) => {
|
|
idx = index
|
|
// Loader is the type of first page (wrapping welcome page or login screen)
|
|
return item instanceof Loader
|
|
})
|
|
|
|
// when the initial page is not found, because e.g. the flow is not initialized
|
|
// or the stack was cleared
|
|
if (!entryItem)
|
|
return
|
|
|
|
// replace the second page in the flow
|
|
stackView.replace(stackView.get(idx + 1), errorPage)
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
enabled: !(root.stackView.currentItem instanceof Loader) &&
|
|
!(root.stackView.currentItem instanceof KeycardErrorPage) &&
|
|
!(root.stackView.currentItem instanceof EnableBiometricsPage)
|
|
|
|
function onPinSettingStateChanged() {
|
|
d.handleKeycardProgressFailedState(pinSettingState)
|
|
}
|
|
|
|
function onAuthorizationStateChanged() {
|
|
d.handleKeycardAuthorizationErrorState(authorizationState)
|
|
}
|
|
|
|
function onRestoreKeysExportStateChanged() {
|
|
d.handleKeycardProgressFailedState(restoreKeysExportState)
|
|
}
|
|
|
|
function onAddKeyPairStateChanged() {
|
|
d.handleKeycardProgressFailedState(addKeyPairState)
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: entryPage
|
|
|
|
Loader {
|
|
sourceComponent: loginAccountsModel.ModelCount.empty ? welcomePage
|
|
: loginScreenComponent
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: errorPage
|
|
|
|
KeycardErrorPage {
|
|
readonly property bool backAvailableHint: false
|
|
|
|
onTryAgainRequested: root.stackView.pop()
|
|
onFactoryResetRequested: keycardFactoryResetFlow.init()
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: welcomePage
|
|
|
|
WelcomePage {
|
|
function pushWithProxy(component) {
|
|
if (d.seenUsageDataPrompt) { // don't ask for "Share usage data" a second time (e.g. after a factory reset)
|
|
root.stackView.push(component)
|
|
} else {
|
|
const page = root.stackView.push(helpUsImproveStatusPage)
|
|
|
|
page.shareUsageDataRequested.connect(enabled => {
|
|
root.shareUsageDataRequested(enabled)
|
|
root.stackView.push(component)
|
|
d.seenUsageDataPrompt = true
|
|
})
|
|
}
|
|
}
|
|
|
|
onCreateProfileRequested: pushWithProxy(createProfilePage)
|
|
onLoginRequested: pushWithProxy(loginPage)
|
|
|
|
onPrivacyPolicyRequested: d.openPrivacyPolicyPopup()
|
|
onTermsOfUseRequested: d.openTermsOfUsePopup()
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: loginScreenComponent
|
|
|
|
LoginScreen {
|
|
id: loginScreen
|
|
|
|
keycardState: root.keycardState
|
|
keycardRemainingPinAttempts: root.remainingPinAttempts
|
|
keycardRemainingPukAttempts: root.remainingPukAttempts
|
|
|
|
loginAccountsModel: root.loginAccountsModel
|
|
biometricsAvailable: root.biometricsAvailable
|
|
isBiometricsLogin: root.isBiometricsLogin
|
|
|
|
onBiometricsRequested: (profileId) => root.biometricsRequested(profileId)
|
|
onDismissBiometricsRequested: root.dismissBiometricsRequested()
|
|
onLoginRequested: (keyUid, method, data) => root.loginRequested(keyUid, method, data)
|
|
onOnboardingCreateProfileFlowRequested: root.stackView.push(createProfilePage)
|
|
onOnboardingLoginFlowRequested: root.stackView.push(loginPage)
|
|
onLostKeycardFlowRequested: () => {
|
|
root.keyUidSubmitted(loginScreen.selectedProfileKeyId)
|
|
root.stackView.push(keycardLostPage)
|
|
}
|
|
|
|
onUnblockWithSeedphraseRequested: unblockWithSeedphraseFlow.init()
|
|
onUnblockWithPukRequested: unblockWithPukFlow.init()
|
|
|
|
onVisibleChanged: {
|
|
if (!visible)
|
|
root.dismissBiometricsRequested()
|
|
}
|
|
|
|
Component.onDestruction: root.dismissBiometricsRequested()
|
|
|
|
Binding {
|
|
target: d
|
|
restoreMode: Binding.RestoreValue
|
|
property: "loginScreen"
|
|
value: loginScreen
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: helpUsImproveStatusPage
|
|
|
|
HelpUsImproveStatusPage {
|
|
onPrivacyPolicyRequested: d.openPrivacyPolicyPopup()
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: createProfilePage
|
|
|
|
CreateProfilePage {
|
|
onCreateProfileWithPasswordRequested: createNewProfileFlow.init()
|
|
onCreateProfileWithSeedphraseRequested: {
|
|
d.flow = Onboarding.OnboardingFlow.CreateProfileWithSeedphrase
|
|
useRecoveryPhraseFlow.init(UseRecoveryPhraseFlow.Type.NewProfile)
|
|
}
|
|
onCreateProfileWithEmptyKeycardRequested: keycardCreateProfileFlow.init()
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: loginPage
|
|
|
|
NewAccountLoginPage {
|
|
networkChecksEnabled: root.networkChecksEnabled
|
|
|
|
onLoginWithSyncingRequested: logInBySyncingFlow.init()
|
|
onLoginWithKeycardRequested: loginWithKeycardFlow.init()
|
|
|
|
onLoginWithSeedphraseRequested: {
|
|
d.flow = Onboarding.OnboardingFlow.LoginWithSeedphrase
|
|
useRecoveryPhraseFlow.init(UseRecoveryPhraseFlow.Type.Login)
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: keycardLostPage
|
|
|
|
KeycardLostPage {
|
|
onCreateReplacementKeycardRequested: {
|
|
d.flow = Onboarding.OnboardingFlow.LoginWithRestoredKeycard
|
|
keycardCreateReplacementFlow.init()
|
|
}
|
|
|
|
onUseProfileWithoutKeycardRequested: {
|
|
d.flow = Onboarding.OnboardingFlow.LoginWithLostKeycardSeedphrase
|
|
useRecoveryPhraseFlow.init(UseRecoveryPhraseFlow.Type.KeycardRecovery)
|
|
}
|
|
}
|
|
}
|
|
|
|
CreateNewProfileFlow {
|
|
id: createNewProfileFlow
|
|
|
|
stackView: root.stackView
|
|
passwordStrengthScoreFunction: root.passwordStrengthScoreFunction
|
|
|
|
onFinished: (password) => {
|
|
root.setPasswordRequested(password)
|
|
d.flow = Onboarding.OnboardingFlow.CreateProfileWithPassword
|
|
d.pushOrSkipBiometricsPage()
|
|
}
|
|
}
|
|
|
|
UseRecoveryPhraseFlow {
|
|
id: useRecoveryPhraseFlow
|
|
|
|
stackView: root.stackView
|
|
isSeedPhraseValid: root.isSeedPhraseValid
|
|
passwordStrengthScoreFunction: root.passwordStrengthScoreFunction
|
|
|
|
onSeedphraseSubmitted: (seedphrase) => root.seedphraseSubmitted(seedphrase)
|
|
onSetPasswordRequested: (password) => root.setPasswordRequested(password)
|
|
onFinished: d.pushOrSkipBiometricsPage()
|
|
}
|
|
|
|
KeycardCreateProfileFlow {
|
|
id: keycardCreateProfileFlow
|
|
|
|
stackView: root.stackView
|
|
keycardState: root.keycardState
|
|
pinSettingState: root.pinSettingState
|
|
authorizationState: root.authorizationState
|
|
addKeyPairState: root.addKeyPairState
|
|
generateMnemonic: root.generateMnemonic
|
|
displayKeycardPromoBanner: root.displayKeycardPromoBanner
|
|
isSeedPhraseValid: root.isSeedPhraseValid
|
|
|
|
keycardPinInfoPageDelay: root.keycardPinInfoPageDelay
|
|
|
|
onKeycardFactoryResetRequested: keycardFactoryResetFlow.init()
|
|
onLoadMnemonicRequested: root.loadMnemonicRequested()
|
|
onSetPinRequested: (pin) => root.setPinRequested(pin)
|
|
onLoginWithKeycardRequested: loginWithKeycardFlow.init()
|
|
onAuthorizationRequested: root.authorizationRequested("") // Pin was saved locally already
|
|
onSeedphraseSubmitted: (seedphrase) => root.seedphraseSubmitted(seedphrase)
|
|
|
|
onFinished: (withNewSeedphrase) => {
|
|
d.flow = withNewSeedphrase
|
|
? Onboarding.OnboardingFlow.CreateProfileWithKeycardNewSeedphrase
|
|
: Onboarding.OnboardingFlow.CreateProfileWithKeycardExistingSeedphrase
|
|
|
|
d.pushOrSkipBiometricsPage()
|
|
}
|
|
}
|
|
|
|
LoginBySyncingFlow {
|
|
id: logInBySyncingFlow
|
|
|
|
stackView: root.stackView
|
|
validateConnectionString: root.validateConnectionString
|
|
syncState: root.syncState
|
|
|
|
onSyncProceedWithConnectionString: (connectionString) =>
|
|
root.syncProceedWithConnectionString(connectionString)
|
|
|
|
onLoginWithSeedphraseRequested: {
|
|
d.flow = Onboarding.OnboardingFlow.LoginWithSeedphrase
|
|
useRecoveryPhraseFlow.init(UseRecoveryPhraseFlow.Type.Login)
|
|
}
|
|
|
|
onFinished: {
|
|
d.flow = Onboarding.OnboardingFlow.LoginWithSyncing
|
|
d.pushOrSkipBiometricsPage()
|
|
}
|
|
}
|
|
|
|
LoginWithKeycardFlow {
|
|
id: loginWithKeycardFlow
|
|
|
|
stackView: root.stackView
|
|
keycardState: root.keycardState
|
|
authorizationState: root.authorizationState
|
|
restoreKeysExportState: root.restoreKeysExportState
|
|
remainingPinAttempts: root.remainingPinAttempts
|
|
remainingPukAttempts: root.remainingPukAttempts
|
|
displayKeycardPromoBanner: root.displayKeycardPromoBanner
|
|
onAuthorizationRequested: root.authorizationRequested(pin)
|
|
|
|
keycardPinInfoPageDelay: root.keycardPinInfoPageDelay
|
|
|
|
onCreateProfileWithEmptyKeycardRequested: keycardCreateProfileFlow.init()
|
|
onExportKeysRequested: root.exportKeysRequested()
|
|
onKeycardFactoryResetRequested: keycardFactoryResetFlow.init()
|
|
onUnblockWithSeedphraseRequested: unblockWithSeedphraseFlow.init()
|
|
onUnblockWithPukRequested: unblockWithPukFlow.init()
|
|
|
|
onFinished: {
|
|
d.flow = Onboarding.OnboardingFlow.LoginWithKeycard
|
|
d.pushOrSkipBiometricsPage()
|
|
}
|
|
}
|
|
|
|
UnblockWithSeedphraseFlow {
|
|
id: unblockWithSeedphraseFlow
|
|
|
|
property string pin
|
|
|
|
stackView: root.stackView
|
|
|
|
isSeedPhraseValid: root.isSeedPhraseValid
|
|
pinSettingState: root.pinSettingState
|
|
|
|
onSeedphraseSubmitted: (seedphrase) => root.seedphraseSubmitted(seedphrase)
|
|
|
|
onSetPinRequested: (pin) => {
|
|
unblockWithSeedphraseFlow.pin = pin
|
|
root.setPinRequested(pin)
|
|
}
|
|
|
|
onFinished: {
|
|
if (root.loginScreen) {
|
|
root.loginRequested(root.loginScreen.selectedProfileKeyId,
|
|
Onboarding.LoginMethod.Keycard, { pin })
|
|
} else {
|
|
d.flow = Onboarding.SecondaryFlow.LoginWithKeycard
|
|
d.pushOrSkipBiometricsPage()
|
|
}
|
|
}
|
|
}
|
|
|
|
UnblockWithPukFlow {
|
|
id: unblockWithPukFlow
|
|
|
|
property string pin
|
|
|
|
stackView: root.stackView
|
|
keycardState: root.keycardState
|
|
pinSettingState: root.pinSettingState
|
|
tryToSetPukFunction: root.tryToSetPukFunction
|
|
remainingAttempts: root.remainingPukAttempts
|
|
|
|
onSetPinRequested: (pin) => {
|
|
unblockWithPukFlow.pin = pin
|
|
root.setPinRequested(pin)
|
|
}
|
|
onKeycardFactoryResetRequested: keycardFactoryResetFlow.init()
|
|
|
|
onFinished: (success) => {
|
|
if (!success)
|
|
return
|
|
if (root.loginScreen) {
|
|
root.loginRequested(root.loginScreen.selectedProfileKeyId,
|
|
Onboarding.LoginMethod.Keycard, { pin })
|
|
} else {
|
|
d.flow = Onboarding.OnboardingFlow.LoginWithKeycard
|
|
d.pushOrSkipBiometricsPage()
|
|
}
|
|
}
|
|
}
|
|
|
|
KeycardCreateReplacementFlow {
|
|
id: keycardCreateReplacementFlow
|
|
|
|
stackView: root.stackView
|
|
|
|
keycardState: root.keycardState
|
|
pinSettingState: root.pinSettingState
|
|
authorizationState: root.authorizationState
|
|
addKeyPairState: root.addKeyPairState
|
|
|
|
displayKeycardPromoBanner: root.displayKeycardPromoBanner
|
|
isSeedPhraseValid: root.isSeedPhraseValid
|
|
|
|
keycardPinInfoPageDelay: root.keycardPinInfoPageDelay
|
|
|
|
onKeycardFactoryResetRequested: keycardFactoryResetFlow.init(true)
|
|
onSetPinRequested: (pin) => root.setPinRequested(pin)
|
|
onLoginWithKeycardRequested: loginWithKeycardFlow.init()
|
|
onAuthorizationRequested: root.authorizationRequested("") // Pin was saved locally already
|
|
onLoadMnemonicRequested: root.loadMnemonicRequested()
|
|
|
|
onCreateProfileWithoutKeycardRequested: {
|
|
const page = stackView.find(
|
|
item => item instanceof HelpUsImproveStatusPage)
|
|
|
|
stackView.replace(page, createProfilePage, StackView.PopTransition)
|
|
}
|
|
|
|
onSeedphraseSubmitted: (seedphrase) => root.seedphraseSubmitted(seedphrase)
|
|
|
|
onFinished: d.pushOrSkipBiometricsPage()
|
|
}
|
|
|
|
KeycardFactoryResetFlow {
|
|
id: keycardFactoryResetFlow
|
|
|
|
stackView: root.stackView
|
|
keycardState: root.keycardState
|
|
|
|
onPerformKeycardFactoryResetRequested: root.performKeycardFactoryResetRequested()
|
|
onFinished: {
|
|
stackView.clear()
|
|
root.init()
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: enableBiometricsPage
|
|
|
|
EnableBiometricsPage {
|
|
onEnableBiometricsRequested: (enable) => {
|
|
root.enableBiometricsRequested(enable)
|
|
root.finished(d.flow)
|
|
}
|
|
}
|
|
}
|
|
|
|
// popups
|
|
Component {
|
|
id: privacyPolicyPopup
|
|
|
|
StatusSimpleTextPopup {
|
|
title: qsTr("Status Software Privacy Policy")
|
|
content {
|
|
textFormat: Text.MarkdownText
|
|
}
|
|
okButtonText: qsTr("Done")
|
|
destroyOnClose: true
|
|
onOpened: content.text = SQUtils.StringUtils.readTextFile(Qt.resolvedUrl("../../../imports/assets/docs/privacy.mdwn"))
|
|
onLinkActivated: (link) => root.linkActivated(link)
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: termsOfUsePopup
|
|
|
|
StatusSimpleTextPopup {
|
|
title: qsTr("Status Software Terms of Use")
|
|
content {
|
|
textFormat: Text.MarkdownText
|
|
}
|
|
okButtonText: qsTr("Done")
|
|
destroyOnClose: true
|
|
onOpened: content.text = SQUtils.StringUtils.readTextFile(Qt.resolvedUrl("../../../imports/assets/docs/terms-of-use.mdwn"))
|
|
onLinkActivated: (link) => root.linkActivated(link)
|
|
}
|
|
}
|
|
}
|