Skip to content

Commit 07675f3

Browse files
authored
feat(onbaording): integrate all happy paths for the non-keycard flows
Fixes #17004
1 parent 9dea479 commit 07675f3

File tree

12 files changed

+323
-126
lines changed

12 files changed

+323
-126
lines changed

src/app/boot/app_controller.nim

+34-21
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ proc connect(self: AppController) =
148148
elif defined(production):
149149
setLogLevel(chronicles.LogLevel.INFO)
150150

151+
# TODO remove these functions once we have only the new onboarding module
152+
proc shouldStartWithOnboardingScreen(self: AppController): bool =
153+
return self.accountsService.openedAccounts().len == 0
154+
proc shouldUseTheNewOnboardingModule(self: AppController): bool =
155+
# Only the onboarding for new users is implemented in the new module for now
156+
return singletonInstance.featureFlags().getOnboardingV2Enabled() and self.shouldStartWithOnboardingScreen()
157+
151158
proc newAppController*(statusFoundation: StatusFoundation): AppController =
152159
result = AppController()
153160
result.syncKeycardBasedOnAppWalletState = false
@@ -244,17 +251,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
244251
result.walletAccountService, result.networkService, result.nodeService, result.tokenService)
245252
result.sharedUrlsService = shared_urls_service.newService(statusFoundation.events, statusFoundation.threadpool)
246253
# Modules
247-
result.startupModule = startup_module.newModule[AppController](
248-
result,
249-
statusFoundation.events,
250-
result.keychainService,
251-
result.accountsService,
252-
result.generalService,
253-
result.profileService,
254-
result.keycardService,
255-
result.devicesService
256-
)
257-
if singletonInstance.featureFlags().getOnboardingV2Enabled():
254+
if result.shouldUseTheNewOnboardingModule():
258255
result.onboardingModule = onboarding_module.newModule[AppController](
259256
result,
260257
statusFoundation.events,
@@ -263,6 +260,17 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
263260
result.devicesService,
264261
result.keycardServiceV2,
265262
)
263+
else:
264+
result.startupModule = startup_module.newModule[AppController](
265+
result,
266+
statusFoundation.events,
267+
result.keychainService,
268+
result.accountsService,
269+
result.generalService,
270+
result.profileService,
271+
result.keycardService,
272+
result.devicesService
273+
)
266274
result.mainModule = main_module.newModule[AppController](
267275
result,
268276
statusFoundation.events,
@@ -405,8 +413,7 @@ proc checkForStoringPasswordToKeychain(self: AppController) =
405413
else:
406414
self.keychainService.storeData(account.keyUid, self.startupModule.getPin())
407415

408-
proc startupDidLoad*(self: AppController) =
409-
# TODO move these functions to onboardingDidLoad
416+
proc initializeQmlContext(self: AppController) =
410417
singletonInstance.engine.setRootContextProperty("localAppSettings", self.localAppSettingsVariant)
411418
singletonInstance.engine.setRootContextProperty("localAccountSettings", self.localAccountSettingsVariant)
412419
singletonInstance.engine.setRootContextProperty("globalUtils", self.globalUtilsVariant)
@@ -415,17 +422,22 @@ proc startupDidLoad*(self: AppController) =
415422

416423
# We need to init a language service once qml is loaded
417424
self.languageService.init()
418-
# We need this to set app width/height appropriatelly on the app start.
419-
self.startupModule.startUpUIRaised()
425+
# We need this to set app width/height appropriately on the app start.
426+
if not self.startupModule.isNil:
427+
self.startupModule.startUpUIRaised()
428+
429+
proc startupDidLoad*(self: AppController) =
430+
self.initializeQmlContext()
420431

421432
proc onboardingDidLoad*(self: AppController) =
422433
debug "NEW ONBOARDING LOADED"
423-
# TODO when removing the old startup module, we should move the functions above here
434+
self.initializeQmlContext()
424435

425436
proc mainDidLoad*(self: AppController) =
426-
self.applyNecessaryActionsAfterLoggingIn()
427-
self.startupModule.moveToAppState()
428-
self.checkForStoringPasswordToKeychain()
437+
if not self.startupModule.isNil:
438+
self.applyNecessaryActionsAfterLoggingIn()
439+
self.startupModule.moveToAppState()
440+
self.checkForStoringPasswordToKeychain()
429441

430442
proc start*(self: AppController) =
431443
self.keycardService.init()
@@ -435,9 +447,10 @@ proc start*(self: AppController) =
435447
self.accountsService.init()
436448
self.devicesService.init()
437449

438-
self.startupModule.load()
439-
if singletonInstance.featureFlags().getOnboardingV2Enabled():
450+
if self.shouldUseTheNewOnboardingModule():
440451
self.onboardingModule.load()
452+
else:
453+
self.startupModule.load()
441454

442455
proc load(self: AppController) =
443456
self.settingsService.init()

src/app/modules/onboarding/controller.nim

+34-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import chronicles, strutils
22
import io_interface
3+
import uuids
34

45
import app/core/eventemitter
6+
import app/core/signals/types
57
import app_service/service/general/service as general_service
68
import app_service/service/accounts/service as accounts_service
79
import app_service/service/accounts/dto/image_crop_rectangle
@@ -15,6 +17,7 @@ type
1517
Controller* = ref object of RootObj
1618
delegate: io_interface.AccessInterface
1719
events: EventEmitter
20+
connectionIds: seq[UUID]
1821
generalService: general_service.Service
1922
accountsService: accounts_service.Service
2023
devicesService: devices_service.Service
@@ -37,11 +40,25 @@ proc newController*(
3740
result.devicesService = devicesService
3841
result.keycardServiceV2 = keycardServiceV2
3942

43+
proc disconnect*(self: Controller) =
44+
for id in self.connectionIds:
45+
self.events.disconnect(id)
46+
4047
proc delete*(self: Controller) =
41-
discard
48+
self.disconnect()
4249

4350
proc init*(self: Controller) =
44-
discard
51+
var handlerId = self.events.onWithUUID(SignalType.NodeLogin.event) do(e:Args):
52+
let signal = NodeSignal(e)
53+
self.delegate.onNodeLogin(signal.error, signal.account, signal.settings)
54+
self.connectionIds.add(handlerId)
55+
56+
handlerId = self.events.onWithUUID(SIGNAL_LOCAL_PAIRING_STATUS_UPDATE) do(e: Args):
57+
let args = LocalPairingStatus(e)
58+
if args.pairingType != PairingType.AppSync:
59+
return
60+
self.delegate.onLocalPairingStatusUpdate(args)
61+
self.connectionIds.add(handlerId)
4562

4663
proc setPin*(self: Controller, pin: string): bool =
4764
self.keycardServiceV2.setPin(pin)
@@ -91,4 +108,18 @@ proc restoreAccountAndLogin*(self: Controller, password, mnemonic: string, recov
91108
imagePath = "",
92109
ImageCropRectangle(),
93110
keycardInstanceUID,
94-
)
111+
)
112+
113+
proc setLoggedInAccount*(self: Controller, account: AccountDto) =
114+
self.accountsService.setLoggedInAccount(account)
115+
self.accountsService.updateLoggedInAccount(account.name, account.images)
116+
117+
proc loginLocalPairingAccount*(self: Controller, account: AccountDto, password, chatkey: string) =
118+
self.accountsService.login(
119+
account,
120+
password,
121+
chatPrivateKey = chatKey
122+
)
123+
124+
proc finishPairingThroughSeedPhraseProcess*(self: Controller, installationId: string) =
125+
self.devicesService.finishPairingThroughSeedPhraseProcess(installationId)

src/app/modules/onboarding/io_interface.nim

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
type
22
AccessInterface* {.pure inheritable.} = ref object of RootObj
33

4+
from app_service/service/settings/dto/settings import SettingsDto
5+
from app_service/service/accounts/dto/accounts import AccountDto
6+
from app_service/service/devices/dto/local_pairing_status import LocalPairingStatus
7+
48
method delete*(self: AccessInterface) {.base.} =
59
raise newException(ValueError, "No implementation available")
610

711
method onAppLoaded*(self: AccessInterface) {.base.} =
812
raise newException(ValueError, "No implementation available")
913

14+
method onNodeLogin*(self: AccessInterface, error: string, account: AccountDto, settings: SettingsDto) {.base.} =
15+
raise newException(ValueError, "No implementation available")
16+
1017
method load*(self: AccessInterface) {.base.} =
1118
raise newException(ValueError, "No implementation available")
1219

@@ -28,10 +35,16 @@ method validateLocalPairingConnectionString*(self: AccessInterface, connectionSt
2835
method inputConnectionStringForBootstrapping*(self: AccessInterface, connectionString: string) {.base.} =
2936
raise newException(ValueError, "No implementation available")
3037

31-
method finishOnboardingFlow*(self: AccessInterface, primaryFlowInt, secondaryFlowInt: int, dataJson: string): string {.base.} =
38+
method finishOnboardingFlow*(self: AccessInterface, flowInt: int, dataJson: string): string {.base.} =
39+
raise newException(ValueError, "No implementation available")
40+
41+
method onLocalPairingStatusUpdate*(self: AccessInterface, status: LocalPairingStatus) {.base.} =
3242
raise newException(ValueError, "No implementation available")
3343

3444
# This way (using concepts) is used only for the modules managed by AppController
3545
type
3646
DelegateInterface* = concept c
3747
c.onboardingDidLoad()
48+
c.appReady()
49+
c.finishAppLoading()
50+
c.userLoggedIn()

src/app/modules/onboarding/module.nim

+82-55
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,14 @@ import app_service/service/general/service as general_service
99
import app_service/service/accounts/service as accounts_service
1010
import app_service/service/devices/service as devices_service
1111
import app_service/service/keycardV2/service as keycard_serviceV2
12+
from app_service/service/settings/dto/settings import SettingsDto
13+
from app_service/service/accounts/dto/accounts import AccountDto
1214

1315
export io_interface
1416

1517
logScope:
1618
topics = "onboarding-module"
1719

18-
type PrimaryFlow* {.pure} = enum
19-
Unknown = 0,
20-
CreateProfile,
21-
Login
22-
2320
type SecondaryFlow* {.pure} = enum
2421
Unknown = 0,
2522
CreateProfileWithPassword,
@@ -37,6 +34,7 @@ type
3734
view: View
3835
viewVariant: QVariant
3936
controller: Controller
37+
localPairingStatus: LocalPairingStatus
4038

4139
proc newModule*[T](
4240
delegate: T,
@@ -67,6 +65,7 @@ method delete*[T](self: Module[T]) =
6765
self.controller.delete
6866

6967
method onAppLoaded*[T](self: Module[T]) =
68+
self.view.appLoaded()
7069
singletonInstance.engine.setRootContextProperty("onboardingModule", newQVariant())
7170
self.view.delete
7271
self.view = nil
@@ -98,65 +97,93 @@ method validateLocalPairingConnectionString*[T](self: Module[T], connectionStrin
9897
method inputConnectionStringForBootstrapping*[T](self: Module[T], connectionString: string) =
9998
self.controller.inputConnectionStringForBootstrapping(connectionString)
10099

101-
method finishOnboardingFlow*[T](self: Module[T], primaryFlowInt, secondaryFlowInt: int, dataJson: string): string =
100+
method finishOnboardingFlow*[T](self: Module[T], flowInt: int, dataJson: string): string =
102101
try:
103-
let primaryFlow = PrimaryFlow(primaryFlowInt)
104-
let secondaryFlow = SecondaryFlow(secondaryFlowInt)
102+
let flow = SecondaryFlow(flowInt)
105103

106104
let data = parseJson(dataJson)
107105
let password = data["password"].str
108-
let seedPhrase = data["seedPhrase"].str
106+
let seedPhrase = data["seedphrase"].str
109107

110108
var err = ""
111109

112-
# CREATE PROFILE PRIMARY FLOW
113-
if primaryFlow == PrimaryFlow.CreateProfile:
114-
case secondaryFlow:
115-
of SecondaryFlow.CreateProfileWithPassword:
116-
err = self.controller.createAccountAndLogin(password)
117-
of SecondaryFlow.CreateProfileWithSeedphrase:
118-
err = self.controller.restoreAccountAndLogin(
119-
password,
120-
seedPhrase,
121-
recoverAccount = false,
122-
keycardInstanceUID = "",
123-
)
124-
of SecondaryFlow.CreateProfileWithKeycard:
125-
# TODO implement keycard function
126-
discard
127-
of SecondaryFlow.CreateProfileWithKeycardNewSeedphrase:
128-
# TODO implement keycard function
129-
discard
130-
of SecondaryFlow.CreateProfileWithKeycardExistingSeedphrase:
131-
# TODO implement keycard function
132-
discard
133-
else:
134-
raise newException(ValueError, "Unknown secondary flow for CreateProfile: " & $secondaryFlow)
135-
136-
# LOGIN PRIMARY FLOW
137-
elif primaryFlow == PrimaryFlow.Login:
138-
case secondaryFlow:
139-
of SecondaryFlow.LoginWithSeedphrase:
140-
err = self.controller.restoreAccountAndLogin(
141-
password,
142-
seedPhrase,
143-
recoverAccount = true,
144-
keycardInstanceUID = "",
145-
)
146-
of SecondaryFlow.LoginWithSyncing:
147-
self.controller.inputConnectionStringForBootstrapping(data["connectionString"].str)
148-
of SecondaryFlow.LoginWithKeycard:
149-
# TODO implement keycard function
150-
discard
151-
else:
152-
raise newException(ValueError, "Unknown secondary flow for Login: " & $secondaryFlow)
153-
if err != "":
154-
raise newException(ValueError, err)
155-
else:
156-
raise newException(ValueError, "Unknown primary flow: " & $primaryFlow)
157-
110+
case flow:
111+
# CREATE PROFILE FLOWS
112+
of SecondaryFlow.CreateProfileWithPassword:
113+
err = self.controller.createAccountAndLogin(password)
114+
of SecondaryFlow.CreateProfileWithSeedphrase:
115+
err = self.controller.restoreAccountAndLogin(
116+
password,
117+
seedPhrase,
118+
recoverAccount = false,
119+
keycardInstanceUID = "",
120+
)
121+
of SecondaryFlow.CreateProfileWithKeycard:
122+
# TODO implement keycard function
123+
discard
124+
of SecondaryFlow.CreateProfileWithKeycardNewSeedphrase:
125+
# TODO implement keycard function
126+
discard
127+
of SecondaryFlow.CreateProfileWithKeycardExistingSeedphrase:
128+
# TODO implement keycard function
129+
discard
130+
131+
# LOGIN FLOWS
132+
of SecondaryFlow.LoginWithSeedphrase:
133+
err = self.controller.restoreAccountAndLogin(
134+
password,
135+
seedPhrase,
136+
recoverAccount = true,
137+
keycardInstanceUID = "",
138+
)
139+
of SecondaryFlow.LoginWithSyncing:
140+
# The pairing was already done directly through inputConnectionStringForBootstrapping, we can login
141+
self.controller.loginLocalPairingAccount(
142+
self.localPairingStatus.account,
143+
self.localPairingStatus.password,
144+
self.localPairingStatus.chatKey,
145+
)
146+
of SecondaryFlow.LoginWithKeycard:
147+
# TODO implement keycard function
148+
discard
149+
else:
150+
raise newException(ValueError, "Unknown flow: " & $flow)
151+
152+
return err
158153
except Exception as e:
159154
error "Error finishing Onboarding Flow", msg = e.msg
160155
return e.msg
161156

157+
proc finishAppLoading2[T](self: Module[T]) =
158+
self.delegate.appReady()
159+
160+
# TODO get the flow to send the right metric
161+
# let currStateObj = self.view.currentStartupStateObj()
162+
# if not currStateObj.isNil:
163+
# var eventType = "user-logged-in"
164+
# if currStateObj.flowType() != FlowType.AppLogin:
165+
# eventType = "onboarding-completed"
166+
# singletonInstance.globalEvents.addCentralizedMetricIfEnabled(eventType,
167+
# $(%*{"flowType": currStateObj.flowType()}))
168+
169+
self.delegate.finishAppLoading()
170+
171+
method onNodeLogin*[T](self: Module[T], error: string, account: AccountDto, settings: SettingsDto) =
172+
if error.len != 0:
173+
# TODO: Handle error
174+
echo "ERROR from onNodeLogin: ", error
175+
return
176+
177+
self.controller.setLoggedInAccount(account)
178+
179+
if self.localPairingStatus != nil and self.localPairingStatus.installation != nil and self.localPairingStatus.installation.id != "":
180+
# We tried to login by pairing, so finilize the process
181+
self.controller.finishPairingThroughSeedPhraseProcess(self.localPairingStatus.installation.id)
182+
183+
self.finishAppLoading2()
184+
185+
method onLocalPairingStatusUpdate*[T](self: Module[T], status: LocalPairingStatus) =
186+
self.localPairingStatus = status
187+
self.view.setSyncState(status.state.int)
188+
162189
{.pop.}

0 commit comments

Comments
 (0)