From 3dd5fa9443569de2bd5483c3ac7c3f48b3bcd4a9 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Thu, 19 Dec 2024 11:00:00 -0500 Subject: [PATCH] chore(onboarding): add scaffolding for the new onboarding (#16980) Part of #16832 Adds the basic files needed for the new onboarding, aka onboarding V2. It does not do anything yet, but it's ready to be implemented. It is locked behind a feature flag. To enable it, run the app with `export FLAG_ONBOARDING_V2_ENABLED=1` --- src/app/boot/app_controller.nim | 24 +++++++++- src/app/global/feature_flags.nim | 9 ++++ src/app/modules/onboarding/controller.nim | 24 ++++++++++ src/app/modules/onboarding/io_interface.nim | 16 +++++++ src/app/modules/onboarding/module.nim | 49 +++++++++++++++++++++ src/app/modules/onboarding/view.nim | 15 +++++++ 6 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/app/modules/onboarding/controller.nim create mode 100644 src/app/modules/onboarding/io_interface.nim create mode 100644 src/app/modules/onboarding/module.nim create mode 100644 src/app/modules/onboarding/view.nim diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index e753973acd..f19d633d4e 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -37,9 +37,10 @@ import app_service/service/metrics/service as metrics_service import app/modules/shared_modules/keycard_popup/module as keycard_shared_module import app/modules/startup/module as startup_module +import app/modules/onboarding/module as onboarding_module import app/modules/main/module as main_module import app/core/notifications/notifications_manager -import app/global/global_singleton +import app/global/[global_singleton, feature_flags] import app/global/app_signals import app/core/[main] @@ -105,6 +106,7 @@ type # Modules startupModule: startup_module.AccessInterface + onboardingModule: onboarding_module.AccessInterface mainModule: main_module.AccessInterface ################################################# @@ -116,6 +118,7 @@ proc applyNecessaryActionsAfterLoggingIn(self: AppController) # Startup Module Delegate Interface proc startupDidLoad*(self: AppController) +proc onboardingDidLoad*(self: AppController) proc userLoggedIn*(self: AppController): string proc appReady*(self: AppController) proc logout*(self: AppController) @@ -248,6 +251,11 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = result.keycardService, result.devicesService ) + if singletonInstance.featureFlags().getOnboardingV2Enabled(): + result.onboardingModule = onboarding_module.newModule[AppController]( + result, + statusFoundation.events, + ) result.mainModule = main_module.newModule[AppController]( result, statusFoundation.events, @@ -304,6 +312,9 @@ proc delete*(self: AppController) = if not self.startupModule.isNil: self.startupModule.delete self.startupModule = nil + if not self.onboardingModule.isNil: + self.onboardingModule.delete + self.onboardingModule = nil self.mainModule.delete self.languageService.delete @@ -387,6 +398,7 @@ proc checkForStoringPasswordToKeychain(self: AppController) = self.keychainService.storeData(account.keyUid, self.startupModule.getPin()) proc startupDidLoad*(self: AppController) = + # TODO move these functions to onboardingDidLoad singletonInstance.engine.setRootContextProperty("localAppSettings", self.localAppSettingsVariant) singletonInstance.engine.setRootContextProperty("localAccountSettings", self.localAccountSettingsVariant) singletonInstance.engine.setRootContextProperty("globalUtils", self.globalUtilsVariant) @@ -398,6 +410,10 @@ proc startupDidLoad*(self: AppController) = # We need this to set app width/height appropriatelly on the app start. self.startupModule.startUpUIRaised() +proc onboardingDidLoad*(self: AppController) = + debug "NEW ONBOARDING LOADED" + # TODO when removing the old startup module, we should move the functions above here + proc mainDidLoad*(self: AppController) = self.applyNecessaryActionsAfterLoggingIn() self.startupModule.moveToAppState() @@ -411,6 +427,8 @@ proc start*(self: AppController) = self.devicesService.init() self.startupModule.load() + if singletonInstance.featureFlags().getOnboardingV2Enabled(): + self.onboardingModule.load() proc load(self: AppController) = self.settingsService.init() @@ -484,6 +502,10 @@ proc finishAppLoading*(self: AppController) = self.startupModule.onAppLoaded() self.startupModule = nil + if not self.onboardingModule.isNil: + self.onboardingModule.onAppLoaded() + self.onboardingModule = nil + self.mainModule.checkAndPerformProfileMigrationIfNeeded() proc logout*(self: AppController) = diff --git a/src/app/global/feature_flags.nim b/src/app/global/feature_flags.nim index e24e0d92a5..18134957dd 100644 --- a/src/app/global/feature_flags.nim +++ b/src/app/global/feature_flags.nim @@ -7,6 +7,7 @@ const DEFAULT_FLAG_CONNECTOR_ENABLED* = true const DEFAULT_FLAG_SEND_VIA_PERSONAL_CHAT_ENABLED = true const DEFAULT_FLAG_PAYMENT_REQUEST_ENABLED = true const DEFAULT_FLAG_SIMPLE_SEND_ENABLED = false +const DEFAULT_FLAG_ONBOARDING_V2_ENABLED = false proc boolToEnv*(defaultValue: bool): string = return if defaultValue: "1" else: "0" @@ -19,6 +20,7 @@ QtObject: sendViaPersonalChatEnabled: bool paymentRequestEnabled: bool simpleSendEnabled: bool + onboardingV2Enabled: bool proc setup(self: FeatureFlags) = self.QObject.setup() @@ -28,6 +30,7 @@ QtObject: self.sendViaPersonalChatEnabled = getEnv("FLAG_SEND_VIA_PERSONAL_CHAT_ENABLED", boolToEnv(DEFAULT_FLAG_SEND_VIA_PERSONAL_CHAT_ENABLED)) != "0" self.paymentRequestEnabled = getEnv("FLAG_PAYMENT_REQUEST_ENABLED", boolToEnv(DEFAULT_FLAG_PAYMENT_REQUEST_ENABLED)) != "0" self.simpleSendEnabled = getEnv("FLAG_SIMPLE_SEND_ENABLED", boolToEnv(DEFAULT_FLAG_SIMPLE_SEND_ENABLED)) != "0" + self.onboardingV2Enabled = getEnv("FLAG_ONBOARDING_V2_ENABLED", boolToEnv(DEFAULT_FLAG_ONBOARDING_V2_ENABLED)) != "0" proc delete*(self: FeatureFlags) = self.QObject.delete() @@ -71,3 +74,9 @@ QtObject: QtProperty[bool] simpleSendEnabled: read = getSimpleSendEnabled + + proc getOnboardingV2Enabled*(self: FeatureFlags): bool {.slot.} = + return self.onboardingV2Enabled + + QtProperty[bool] onboardingV2Enabled: + read = getOnboardingV2Enabled diff --git a/src/app/modules/onboarding/controller.nim b/src/app/modules/onboarding/controller.nim new file mode 100644 index 0000000000..c013fa413d --- /dev/null +++ b/src/app/modules/onboarding/controller.nim @@ -0,0 +1,24 @@ +import chronicles +import io_interface + +import app/core/eventemitter + +logScope: + topics = "onboarding-controller" + +type + Controller* = ref object of RootObj + delegate: io_interface.AccessInterface + events: EventEmitter + +proc newController*(delegate: io_interface.AccessInterface, events: EventEmitter): + Controller = + result = Controller() + result.delegate = delegate + result.events = events + +proc delete*(self: Controller) = + discard + +proc init*(self: Controller) = + discard diff --git a/src/app/modules/onboarding/io_interface.nim b/src/app/modules/onboarding/io_interface.nim new file mode 100644 index 0000000000..ad9d4c7fbf --- /dev/null +++ b/src/app/modules/onboarding/io_interface.nim @@ -0,0 +1,16 @@ +type + AccessInterface* {.pure inheritable.} = ref object of RootObj + +method delete*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method onAppLoaded*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method load*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +# This way (using concepts) is used only for the modules managed by AppController +type + DelegateInterface* = concept c + c.onboardingDidLoad() diff --git a/src/app/modules/onboarding/module.nim b/src/app/modules/onboarding/module.nim new file mode 100644 index 0000000000..398a68a888 --- /dev/null +++ b/src/app/modules/onboarding/module.nim @@ -0,0 +1,49 @@ +import NimQml, chronicles, json + +import io_interface +import view, controller + +import app/global/global_singleton +import app/core/eventemitter + +export io_interface + +logScope: + topics = "onboarding-module" + +type + Module*[T: io_interface.DelegateInterface] = ref object of io_interface.AccessInterface + delegate: T + view: View + viewVariant: QVariant + controller: Controller + +proc newModule*[T](delegate: T, events: EventEmitter): Module[T] = + result = Module[T]() + result.delegate = delegate + result.view = view.newView(result) + result.viewVariant = newQVariant(result.view) + result.controller = controller.newController(result, events) + +{.push warning[Deprecated]: off.} + +method delete*[T](self: Module[T]) = + self.view.delete + self.viewVariant.delete + self.controller.delete + +method onAppLoaded*[T](self: Module[T]) = + singletonInstance.engine.setRootContextProperty("onboardingModule", newQVariant()) + self.view.delete + self.view = nil + self.viewVariant.delete + self.viewVariant = nil + self.controller.delete + self.controller = nil + +method load*[T](self: Module[T]) = + singletonInstance.engine.setRootContextProperty("onboardingModule", self.viewVariant) + self.controller.init() + self.delegate.onboardingDidLoad() + +{.pop.} diff --git a/src/app/modules/onboarding/view.nim b/src/app/modules/onboarding/view.nim new file mode 100644 index 0000000000..650594a238 --- /dev/null +++ b/src/app/modules/onboarding/view.nim @@ -0,0 +1,15 @@ +import NimQml, json +import io_interface + +QtObject: + type + View* = ref object of QObject + delegate: io_interface.AccessInterface + + proc delete*(self: View) = + self.QObject.delete + + proc newView*(delegate: io_interface.AccessInterface): View = + new(result, delete) + result.QObject.setup + result.delegate = delegate