From 9192df2a71425ed161b8f46bab6b3f68633da6cb Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Wed, 31 Jan 2024 05:13:03 +0300 Subject: [PATCH 1/3] Create new profile proto models --- model/src/main/proto/onboarding.proto | 23 +++++++++++++++++++++++ model/src/main/proto/oppia_logger.proto | 17 +++++++++++++++++ model/src/main/proto/profile.proto | 21 +++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/model/src/main/proto/onboarding.proto b/model/src/main/proto/onboarding.proto index 4cefc9213d7..71373953fae 100644 --- a/model/src/main/proto/onboarding.proto +++ b/model/src/main/proto/onboarding.proto @@ -33,6 +33,10 @@ message AppStartupState { // they are using an OS version that is no longer supported. The user should be shown a prompt // to update their OS. OS_IS_DEPRECATED = 5; + + // Indicates that the onboarding flow shown to the user should be the new flow. + // TODO(#): Remove after onboarding project stabilization. + ONBOARDING_FLOW_V2 = 6; } // Describes different notices that may be shown to the user on startup depending on whether @@ -69,6 +73,7 @@ message AppStartupState { // Stores the completion state of the user's progress through the app onboarding flow. message OnboardingState { + // TODO(#): Remove after onboarding project stabilization // Indicates whether user has fully completed the onboarding flow. bool already_onboarded_app = 1; @@ -83,3 +88,21 @@ message OnboardingState { // the general availability version of the app after having previously used a pre-release version. bool permanently_dismissed_ga_upgrade_notice = 4; } + +// Indicates the state of the app with regards to the number and type of existing profiles. +enum ProfileOnboardingState { + // Indicates that the number or type of profiles is unknown. + PROFILE_ONBOARDING_STATE_UNSPECIFIED = 0; + + // Indicates that this is a new app install given that there are no existing profiles. + NEW_INSTALL = 1; + + // Indicates that there is only one profile and it is a sole learner profile. + SOLE_LEARNER_PROFILE = 2; + + // Indicates that there is only one profile and it is an admin profile. + ADMIN_PROFILE_ONLY = 3; + + // Indicates that there are multiple profiles on the device. + MULTIPLE_PROFILES = 4; +} \ No newline at end of file diff --git a/model/src/main/proto/oppia_logger.proto b/model/src/main/proto/oppia_logger.proto index e2d6cc455ea..d07f01641b6 100644 --- a/model/src/main/proto/oppia_logger.proto +++ b/model/src/main/proto/oppia_logger.proto @@ -37,6 +37,11 @@ message EventLog { // The audio language selection context at the time of this event's creation. AudioTranslationLanguageSelection audio_translation_language_selection = 7; + // The profileId and profileType to which this event corresponds, or empty if this event is not tied to a particular + // profile. This is only used for diagnostic purposes as events are only ever logged anonymously + // at source. + ProfileContext profile_context = 9; + // Structure of an activity context. message Context { // Deprecated exploration context. This is now handled via the open_exploration_activity context @@ -178,6 +183,18 @@ message EventLog { } } + // Structure of a ProfileContext which contains the profileId and profileType to which this event + // corresponds. + message ProfileContext { + // The profile to which this event corresponds, or empty if this event is not tied to a particular + // profile. This is only used for diagnostic purposes as events are only ever logged anonymously + // at source. + ProfileId profile_id = 1; + + // Represents the type of user profile. + ProfileType profile_type = 2; + } + // Structure of a question context. message QuestionContext { // The active question ID when the event is logged. diff --git a/model/src/main/proto/profile.proto b/model/src/main/proto/profile.proto index aadd1f34881..b83d8702c60 100644 --- a/model/src/main/proto/profile.proto +++ b/model/src/main/proto/profile.proto @@ -87,6 +87,12 @@ message Profile { // Represents the epoch timestamp in milliseconds when the nps survey was previously shown in // this profile. int64 survey_last_shown_timestamp_ms = 18; + + // Represents the type of user which informs the configuration options available to them. + ProfileType profile_type = 19; + + // Indicates whether this user has completed the onboarding flow. + bool already_onboarded_profile = 20; } // Represents a profile avatar image. @@ -140,3 +146,18 @@ enum AudioLanguage { ARABIC_LANGUAGE = 7; NIGERIAN_PIDGIN_LANGUAGE = 8; } + +// Represents the type of learner profile. +enum ProfileType { + // The undefined ProfileType. + PROFILE_TYPE_UNSPECIFIED = 0; + + // Represents a single learner profile without an admin pin set. + SOLE_LEARNER = 1; + + // Represents an admin profile when there are more than one profiles. + SUPERVISOR = 2; + + // Represents a non-admin profile in a multiple profile setup. + ADDITIONAL_LEARNER = 3; +} From 6237fbbdd9247c20df05f3589393c08b7f768066 Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Wed, 31 Jan 2024 05:19:42 +0300 Subject: [PATCH 2/3] Plug in new onboarding and profile configuration logic --- .../app/splash/SplashActivityPresenter.kt | 74 ++++++++++++++++++- .../onboarding/AppStartupStateController.kt | 9 ++- .../PlatformParameterAlphaKenyaModule.kt | 1 - .../PlatformParameterAlphaModule.kt | 1 - .../PlatformParameterModule.kt | 3 +- .../profile/ProfileManagementController.kt | 33 ++++++++- .../TestPlatformParameterModule.kt | 1 - 7 files changed, 113 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt index dd926f56612..870bbc9f3e1 100644 --- a/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt @@ -1,5 +1,6 @@ package org.oppia.android.app.splash +import android.annotation.SuppressLint import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri @@ -13,6 +14,8 @@ import org.oppia.android.app.model.AppStartupState import org.oppia.android.app.model.AppStartupState.BuildFlavorNoticeMode import org.oppia.android.app.model.AppStartupState.StartupMode import org.oppia.android.app.model.BuildFlavor +import org.oppia.android.app.model.Profile +import org.oppia.android.app.model.ProfileOnboardingState import org.oppia.android.app.notice.AutomaticAppDeprecationNoticeDialogFragment import org.oppia.android.app.notice.BetaNoticeDialogFragment import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeDialogFragment @@ -24,6 +27,7 @@ import org.oppia.android.databinding.SplashActivityBinding import org.oppia.android.domain.locale.LocaleController import org.oppia.android.domain.onboarding.AppStartupStateController import org.oppia.android.domain.oppialogger.OppiaLogger +import org.oppia.android.domain.profile.ProfileManagementController import org.oppia.android.domain.topic.PrimeTopicAssetsController import org.oppia.android.domain.translation.TranslationController import org.oppia.android.util.data.AsyncResult @@ -31,6 +35,8 @@ import org.oppia.android.util.data.DataProvider import org.oppia.android.util.data.DataProviders.Companion.combineWith import org.oppia.android.util.data.DataProviders.Companion.toLiveData import org.oppia.android.util.locale.OppiaLocale +import org.oppia.android.util.platformparameter.EnableOnboardingFlowV2 +import org.oppia.android.util.platformparameter.PlatformParameterValue import javax.inject.Inject private const val AUTO_DEPRECATION_NOTICE_DIALOG_FRAGMENT_TAG = "auto_deprecation_notice_dialog" @@ -39,6 +45,7 @@ private const val GA_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG = "general_availability_u private const val SPLASH_INIT_STATE_DATA_PROVIDER_ID = "splash_init_state_data_provider" /** The presenter for [SplashActivity]. */ +@SuppressLint("CustomSplashScreen") @ActivityScope class SplashActivityPresenter @Inject constructor( private val activity: AppCompatActivity, @@ -49,7 +56,10 @@ class SplashActivityPresenter @Inject constructor( private val localeController: LocaleController, private val appLanguageLocaleHandler: AppLanguageLocaleHandler, private val lifecycleSafeTimerFactory: LifecycleSafeTimerFactory, - private val currentBuildFlavor: BuildFlavor + private val currentBuildFlavor: BuildFlavor, + @EnableOnboardingFlowV2 + private val enableOnboardingFlowV2: PlatformParameterValue, + private val profileManagementController: ProfileManagementController ) { lateinit var startupMode: StartupMode @@ -211,6 +221,9 @@ class SplashActivityPresenter @Inject constructor( AutomaticAppDeprecationNoticeDialogFragment::newInstance ) } + StartupMode.ONBOARDING_FLOW_V2 -> { + computeRoute() + } else -> { // In all other cases (including errors when the startup state fails to load or is // defaulted), assume the user needs to be onboarded. @@ -220,6 +233,65 @@ class SplashActivityPresenter @Inject constructor( } } + private fun computeRoute() { + // Use SplashActivityViewModel to retrieve the profile type and onboarding status + // Based on the returned profile information, compute route as follows: + when (getProfileOnboardingState()) { + ProfileOnboardingState.NEW_INSTALL -> { + // route to new app language selection screen + } + ProfileOnboardingState.SOLE_LEARNER_PROFILE -> { + // route = home screen + } + else -> { + // route = profile selection screen + if (enableOnboardingFlowV2.value) { + // new vs old profile chooser + } + activity.startActivity(ProfileChooserActivity.createProfileChooserActivity(activity)) + } + } + } + + /** Returns the state of the app based on the number of existing profiles. */ + private fun getProfileOnboardingState(): ProfileOnboardingState { + var profileList = listOf() + profileManagementController.getProfiles().toLiveData().observe( + activity, + { result -> + when (result) { + is AsyncResult.Success -> { + profileList = result.value + } + is AsyncResult.Failure -> { + oppiaLogger.e( + "SplashActivityViewModel", + "Encountered unexpected non-successful result when fetching profiles", + result.error + ) + } + else -> {} // no-op + } + } + ) + + return when { + profileList.size > 1 -> { + ProfileOnboardingState.MULTIPLE_PROFILES + } + profileList.size == 1 -> { + if (profileList.first().isAdmin && profileList.first().hasPin) { + ProfileOnboardingState.ADMIN_PROFILE_ONLY + } else { + ProfileOnboardingState.SOLE_LEARNER_PROFILE + } + } + else -> { + ProfileOnboardingState.NEW_INSTALL + } + } + } + private fun computeInitStateDataProvider(): DataProvider { val startupStateDataProvider = appStartupStateController.getAppStartupState() val systemAppLanguageLocaleDataProvider = translationController.getSystemLanguageLocale() diff --git a/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt b/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt index 31b6b203ba7..c49e7b21f21 100644 --- a/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt +++ b/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt @@ -11,6 +11,8 @@ import org.oppia.android.util.data.DataProvider import org.oppia.android.util.data.DataProviders.Companion.transform import org.oppia.android.util.extensions.getStringFromBundle import org.oppia.android.util.locale.OppiaLocale +import org.oppia.android.util.platformparameter.EnableOnboardingFlowV2 +import org.oppia.android.util.platformparameter.PlatformParameterValue import javax.inject.Inject import javax.inject.Singleton @@ -23,7 +25,9 @@ class AppStartupStateController @Inject constructor( private val oppiaLogger: OppiaLogger, private val expirationMetaDataRetriever: ExpirationMetaDataRetriever, private val machineLocale: OppiaLocale.MachineLocale, - private val currentBuildFlavor: BuildFlavor + private val currentBuildFlavor: BuildFlavor, + @EnableOnboardingFlowV2 + private val enableOnboardingFlowV2: PlatformParameterValue ) { private val onboardingFlowStore by lazy { cacheStoreFactory.create("on_boarding_flow", OnboardingState.getDefaultInstance()) @@ -119,6 +123,9 @@ class AppStartupStateController @Inject constructor( return when { hasAppExpired() -> StartupMode.APP_IS_DEPRECATED onboardingState.alreadyOnboardedApp -> StartupMode.USER_IS_ONBOARDED + enableOnboardingFlowV2.value -> { + StartupMode.ONBOARDING_FLOW_V2 + } else -> StartupMode.USER_NOT_YET_ONBOARDED } } diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaKenyaModule.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaKenyaModule.kt index 6defddaba86..68910e6ee08 100644 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaKenyaModule.kt +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaKenyaModule.kt @@ -17,7 +17,6 @@ import org.oppia.android.util.platformparameter.ENABLE_EXTRA_TOPIC_TABS_UI_DEFAU import org.oppia.android.util.platformparameter.ENABLE_INTERACTION_CONFIG_CHANGE_STATE_RETENTION_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_NPS_SURVEY import org.oppia.android.util.platformparameter.ENABLE_NPS_SURVEY_DEFAULT_VALUE -import org.oppia.android.util.platformparameter.ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_ONBOARDING_FLOW_V2_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_PERFORMANCE_METRICS_COLLECTION import org.oppia.android.util.platformparameter.ENABLE_PERFORMANCE_METRICS_COLLECTION_DEFAULT_VALUE diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaModule.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaModule.kt index 5c35d10b9b2..fe41b3d2f98 100644 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaModule.kt +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterAlphaModule.kt @@ -17,7 +17,6 @@ import org.oppia.android.util.platformparameter.ENABLE_EXTRA_TOPIC_TABS_UI_DEFAU import org.oppia.android.util.platformparameter.ENABLE_INTERACTION_CONFIG_CHANGE_STATE_RETENTION_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_NPS_SURVEY import org.oppia.android.util.platformparameter.ENABLE_NPS_SURVEY_DEFAULT_VALUE -import org.oppia.android.util.platformparameter.ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_ONBOARDING_FLOW_V2_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_PERFORMANCE_METRICS_COLLECTION import org.oppia.android.util.platformparameter.ENABLE_PERFORMANCE_METRICS_COLLECTION_DEFAULT_VALUE diff --git a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterModule.kt b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterModule.kt index 1a661c1ccbd..ff967a1a869 100644 --- a/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterModule.kt +++ b/domain/src/main/java/org/oppia/android/domain/platformparameter/PlatformParameterModule.kt @@ -17,7 +17,6 @@ import org.oppia.android.util.platformparameter.ENABLE_EXTRA_TOPIC_TABS_UI_DEFAU import org.oppia.android.util.platformparameter.ENABLE_INTERACTION_CONFIG_CHANGE_STATE_RETENTION_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_NPS_SURVEY import org.oppia.android.util.platformparameter.ENABLE_NPS_SURVEY_DEFAULT_VALUE -import org.oppia.android.util.platformparameter.ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_ONBOARDING_FLOW_V2_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_PERFORMANCE_METRICS_COLLECTION import org.oppia.android.util.platformparameter.ENABLE_PERFORMANCE_METRICS_COLLECTION_DEFAULT_VALUE @@ -31,8 +30,8 @@ import org.oppia.android.util.platformparameter.EnableFastLanguageSwitchingInLes import org.oppia.android.util.platformparameter.EnableInteractionConfigChangeStateRetention import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds -import org.oppia.android.util.platformparameter.EnableOnboardingFlowV2 import org.oppia.android.util.platformparameter.EnableNpsSurvey +import org.oppia.android.util.platformparameter.EnableOnboardingFlowV2 import org.oppia.android.util.platformparameter.EnablePerformanceMetricsCollection import org.oppia.android.util.platformparameter.EnableSpotlightUi import org.oppia.android.util.platformparameter.FAST_LANGUAGE_SWITCHING_IN_LESSON diff --git a/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt b/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt index 53cc1399317..d9f1abe739e 100644 --- a/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt +++ b/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt @@ -14,6 +14,7 @@ import org.oppia.android.app.model.Profile import org.oppia.android.app.model.ProfileAvatar import org.oppia.android.app.model.ProfileDatabase import org.oppia.android.app.model.ProfileId +import org.oppia.android.app.model.ProfileType import org.oppia.android.app.model.ReadingTextSize import org.oppia.android.data.persistence.PersistentCacheStore import org.oppia.android.data.persistence.PersistentCacheStore.PublishMode @@ -30,6 +31,7 @@ import org.oppia.android.util.data.DataProviders.Companion.transformAsync import org.oppia.android.util.locale.OppiaLocale import org.oppia.android.util.platformparameter.EnableLearnerStudyAnalytics import org.oppia.android.util.platformparameter.EnableLoggingLearnerStudyIds +import org.oppia.android.util.platformparameter.EnableOnboardingFlowV2 import org.oppia.android.util.platformparameter.PlatformParameterValue import org.oppia.android.util.profile.DirectoryManagementUtil import org.oppia.android.util.profile.ProfileNameValidator @@ -89,7 +91,9 @@ class ProfileManagementController @Inject constructor( private val enableLearnerStudyAnalytics: PlatformParameterValue, @EnableLoggingLearnerStudyIds private val enableLoggingLearnerStudyIds: PlatformParameterValue, - private val profileNameValidator: ProfileNameValidator + private val profileNameValidator: ProfileNameValidator, + @EnableOnboardingFlowV2 + private val enableOnboardingFlowV2: PlatformParameterValue ) { private var currentProfileId: Int = DEFAULT_LOGGED_OUT_INTERNAL_PROFILE_ID private val profileDataStore = @@ -290,6 +294,10 @@ class ProfileManagementController @Inject constructor( avatarImageUri = imageUri } else avatarColorRgb = colorRgb }.build() + + if (enableOnboardingFlowV2.value) { + this.profileType = computeProfileType(it) + } }.build() val wasProfileEverAdded = it.profilesCount > 0 @@ -306,6 +314,27 @@ class ProfileManagementController @Inject constructor( } } + private fun computeProfileType(profileDatabase: ProfileDatabase): ProfileType { + return if (isAdminWithPin(profileDatabase)) { + ProfileType.SUPERVISOR + } else { + if (profileDatabase.profilesCount == 1) { + ProfileType.SOLE_LEARNER + } else { + ProfileType.ADDITIONAL_LEARNER + } + } + } + + private fun isAdminWithPin(profileDatabase: ProfileDatabase): Boolean { + profileDatabase.profilesMap.values.forEach { + if (it.isAdmin && it.hasPin) { + return true + } + } + return false + } + /** * Updates the profile avatar of an existing profile. * @@ -684,7 +713,7 @@ class ProfileManagementController @Inject constructor( return@createInMemoryDataProviderAsync AsyncResult.Success(0) } AsyncResult.Failure( - ProfileNotFoundException( + ProfileManagementController.ProfileNotFoundException( "ProfileId ${profileId.internalId} is" + " not associated with an existing profile" ) diff --git a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt b/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt index 9115e1f5a43..237b15d82d5 100644 --- a/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt +++ b/testing/src/main/java/org/oppia/android/testing/platformparameter/TestPlatformParameterModule.kt @@ -14,7 +14,6 @@ import org.oppia.android.util.platformparameter.ENABLE_EDIT_ACCOUNTS_OPTIONS_UI_ import org.oppia.android.util.platformparameter.ENABLE_EXTRA_TOPIC_TABS_UI_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_INTERACTION_CONFIG_CHANGE_STATE_RETENTION_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_NPS_SURVEY_DEFAULT_VALUE -import org.oppia.android.util.platformparameter.ENABLE_LANGUAGE_SELECTION_UI_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_ONBOARDING_FLOW_V2_DEFAULT_VALUE import org.oppia.android.util.platformparameter.ENABLE_PERFORMANCE_METRICS_COLLECTION_DEFAULT_VALUE import org.oppia.android.util.platformparameter.EnableAppAndOsDeprecation From e5a396cd1bac54381511c1781ce96df382099ac3 Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Wed, 28 Feb 2024 06:39:02 +0300 Subject: [PATCH 3/3] Integrate new routing logic with deprecation flow --- .../app/splash/SplashActivityPresenter.kt | 75 +++++++++---------- .../onboarding/AppStartupStateController.kt | 59 ++++++++------- .../onboarding/DeprecationController.kt | 9 ++- 3 files changed, 76 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt index 470f9daa1bc..8061768de04 100644 --- a/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt @@ -298,7 +298,7 @@ class SplashActivityPresenter @Inject constructor( ) } StartupMode.ONBOARDING_FLOW_V2 -> { - computeRoute() + getProfileOnboardingState() } else -> { // In all other cases (including errors when the startup state fails to load or is @@ -309,65 +309,64 @@ class SplashActivityPresenter @Inject constructor( } } - private fun computeRoute() { - // Use SplashActivityViewModel to retrieve the profile type and onboarding status - // Based on the returned profile information, compute route as follows: - when (getProfileOnboardingState()) { - ProfileOnboardingState.NEW_INSTALL -> { - activity.startActivity(OnboardingActivity.createOnboardingActivity(activity)) - activity.finish() - } - ProfileOnboardingState.SOLE_LEARNER_PROFILE -> { - // TODO retrieve profileId and pass to intent - activity.startActivity(HomeActivity.createHomeActivity(activity, null)) - activity.finish() - } - else -> { - activity.startActivity(ProfileChooserActivity.createProfileChooserActivity(activity)) - activity.finish() - } - } - } - - /** Returns the state of the app based on the number of existing profiles. */ - private fun getProfileOnboardingState(): ProfileOnboardingState { - var profileList = listOf() - profileManagementController.getProfiles().toLiveData().observe( + private fun getProfileOnboardingState() { + appStartupStateController.getProfileOnboardingState().toLiveData().observe( activity, { result -> when (result) { is AsyncResult.Success -> { - profileList = result.value + computeLoginRoute(result.value) } is AsyncResult.Failure -> { oppiaLogger.e( "SplashActivity", - "Encountered unexpected non-successful result when fetching profiles", + "Encountered unexpected non-successful result when fetching onboarding state", result.error ) } - else -> {} // no-op + is AsyncResult.Pending -> {} } } ) + } - return when { - profileList.size > 1 -> { - ProfileOnboardingState.MULTIPLE_PROFILES + private fun computeLoginRoute(onboardingState: ProfileOnboardingState) { + when (onboardingState) { + ProfileOnboardingState.NEW_INSTALL -> { + activity.startActivity(OnboardingActivity.createOnboardingActivity(activity)) + activity.finish() } - profileList.size == 1 -> { - if (profileList.first().isAdmin && profileList.first().hasPin) { - ProfileOnboardingState.ADMIN_PROFILE_ONLY - } else { - ProfileOnboardingState.SOLE_LEARNER_PROFILE - } + ProfileOnboardingState.SOLE_LEARNER_PROFILE -> { + profileManagementController.getProfiles().toLiveData().observe( + activity, + { result -> + when (result) { + is AsyncResult.Success -> { + val internalProfileId = getSoleLearnerProfile(result.value)?.id?.internalId + activity.startActivity(HomeActivity.createHomeActivity(activity, internalProfileId)) + activity.finish() + } + is AsyncResult.Pending -> {} // no-op + is AsyncResult.Failure -> { + oppiaLogger.e( + "SplashActivity", "Failed to retrieve the list of profiles", result.error + ) + } + } + } + ) } else -> { - ProfileOnboardingState.NEW_INSTALL + activity.startActivity(ProfileChooserActivity.createProfileChooserActivity(activity)) + activity.finish() } } } + private fun getSoleLearnerProfile(profiles: List): Profile? { + return profiles.find { it.isAdmin } + } + private fun computeInitStateDataProvider(): DataProvider { val startupStateDataProvider = appStartupStateController.getAppStartupState() val systemAppLanguageLocaleDataProvider = translationController.getSystemLanguageLocale() diff --git a/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt b/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt index ab94490ee02..7539c9f3513 100644 --- a/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt +++ b/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt @@ -4,22 +4,21 @@ import org.oppia.android.app.model.AppStartupState import org.oppia.android.app.model.AppStartupState.BuildFlavorNoticeMode import org.oppia.android.app.model.AppStartupState.StartupMode import org.oppia.android.app.model.BuildFlavor -import org.oppia.android.app.model.DeprecationResponseDatabase import org.oppia.android.app.model.OnboardingState +import org.oppia.android.app.model.ProfileOnboardingState import org.oppia.android.data.persistence.PersistentCacheStore import org.oppia.android.domain.oppialogger.OppiaLogger +import org.oppia.android.domain.profile.ProfileManagementController import org.oppia.android.util.data.DataProvider import org.oppia.android.util.data.DataProviders.Companion.combineWith +import org.oppia.android.util.data.DataProviders.Companion.transform import org.oppia.android.util.extensions.getStringFromBundle import org.oppia.android.util.locale.OppiaLocale -import org.oppia.android.util.platformparameter.EnableAppAndOsDeprecation -import org.oppia.android.util.platformparameter.EnableOnboardingFlowV2 -import org.oppia.android.util.platformparameter.PlatformParameterValue import javax.inject.Inject -import javax.inject.Provider import javax.inject.Singleton private const val APP_STARTUP_STATE_PROVIDER_ID = "app_startup_state_data_provider_id" +private const val PROFILE_ONBOARDING_STATE_PROVIDER_ID = "profile_onboarding_state_data_provider_id" /** Controller for persisting and retrieving the user's initial app state upon opening the app. */ @Singleton @@ -30,10 +29,7 @@ class AppStartupStateController @Inject constructor( private val machineLocale: OppiaLocale.MachineLocale, private val currentBuildFlavor: BuildFlavor, private val deprecationController: DeprecationController, - @EnableAppAndOsDeprecation - private val enableAppAndOsDeprecation: Provider>, - @EnableOnboardingFlowV2 - private val enableOnboardingFlowV2: PlatformParameterValue + private val profileManagementController: ProfileManagementController ) { private val onboardingFlowStore by lazy { cacheStoreFactory.create("on_boarding_flow", OnboardingState.getDefaultInstance()) @@ -107,7 +103,10 @@ class AppStartupStateController @Inject constructor( APP_STARTUP_STATE_PROVIDER_ID ) { onboardingState, deprecationResponseDatabase -> AppStartupState.newBuilder().apply { - startupMode = computeAppStartupMode(onboardingState, deprecationResponseDatabase) + startupMode = deprecationController.processStartUpMode( + onboardingState, + deprecationResponseDatabase + ) buildFlavorNoticeMode = computeBuildNoticeMode(onboardingState, startupMode) }.build() } @@ -130,24 +129,6 @@ class AppStartupStateController @Inject constructor( } } - private fun computeAppStartupMode( - onboardingState: OnboardingState, - deprecationResponseDatabase: DeprecationResponseDatabase - ): StartupMode { - // Process and return either a StartupMode.APP_IS_DEPRECATED, StartupMode.USER_IS_ONBOARDED or - // StartupMode.USER_NOT_YET_ONBOARDED if the app and OS deprecation feature flag is not enabled. - if (!enableAppAndOsDeprecation.get().value) { - return when { - hasAppExpired() -> StartupMode.APP_IS_DEPRECATED - onboardingState.alreadyOnboardedApp -> StartupMode.USER_IS_ONBOARDED - enableOnboardingFlowV2.value -> StartupMode.ONBOARDING_FLOW_V2 - else -> StartupMode.USER_NOT_YET_ONBOARDED - } - } - - return deprecationController.processStartUpMode(onboardingState, deprecationResponseDatabase) - } - private fun computeBuildNoticeMode( onboardingState: OnboardingState, startupMode: StartupMode @@ -194,4 +175,26 @@ class AppStartupStateController @Inject constructor( expirationDate?.isBeforeToday() ?: true } else false } + + /** Returns the state of the app based on the number and type of existing profiles. */ + fun getProfileOnboardingState(): DataProvider { + return profileManagementController.getProfiles() + .transform(PROFILE_ONBOARDING_STATE_PROVIDER_ID) { profileList -> + when { + profileList.size > 1 -> { + ProfileOnboardingState.MULTIPLE_PROFILES + } + profileList.size == 1 -> { + if (profileList.first().isAdmin && profileList.first().pin.isNotBlank()) { + ProfileOnboardingState.ADMIN_PROFILE_ONLY + } else { + ProfileOnboardingState.SOLE_LEARNER_PROFILE + } + } + else -> { + ProfileOnboardingState.NEW_INSTALL + } + } + } + } } diff --git a/domain/src/main/java/org/oppia/android/domain/onboarding/DeprecationController.kt b/domain/src/main/java/org/oppia/android/domain/onboarding/DeprecationController.kt index 37afcfd0b51..86133913ff1 100644 --- a/domain/src/main/java/org/oppia/android/domain/onboarding/DeprecationController.kt +++ b/domain/src/main/java/org/oppia/android/domain/onboarding/DeprecationController.kt @@ -15,6 +15,7 @@ import org.oppia.android.util.data.DataProvider import org.oppia.android.util.data.DataProviders import org.oppia.android.util.data.DataProviders.Companion.transform import org.oppia.android.util.extensions.getVersionCode +import org.oppia.android.util.platformparameter.EnableOnboardingFlowV2 import org.oppia.android.util.platformparameter.ForcedAppUpdateVersionCode import org.oppia.android.util.platformparameter.LowestSupportedApiLevel import org.oppia.android.util.platformparameter.OptionalAppUpdateVersionCode @@ -41,7 +42,9 @@ class DeprecationController @Inject constructor( @ForcedAppUpdateVersionCode private val forcedAppUpdateVersionCode: Provider>, @LowestSupportedApiLevel - private val lowestSupportedApiLevel: Provider> + private val lowestSupportedApiLevel: Provider>, + @EnableOnboardingFlowV2 + private val enableOnboardingFlowV2: PlatformParameterValue ) { /** Create an instance of [PersistentCacheStore] that contains a [DeprecationResponseDatabase]. */ private val deprecationStore by lazy { @@ -173,6 +176,10 @@ class DeprecationController @Inject constructor( return StartupMode.OPTIONAL_UPDATE_AVAILABLE } + if (enableOnboardingFlowV2.value) { + return StartupMode.ONBOARDING_FLOW_V2 + } + return StartupMode.USER_IS_ONBOARDED } else return StartupMode.USER_NOT_YET_ONBOARDED }