Skip to content

Commit

Permalink
Fix part of #5025: App and OS Deprecation Milestone 4 - Gate the new …
Browse files Browse the repository at this point in the history
…Deprecation Dialogs and Add Logic to Display Them (#5249)

<!-- READ ME FIRST: Please fill in the explanation section below and
check off every point from the Essential Checklist! -->
## Explanation
<!--
- Explain what your PR does. If this PR fixes an existing bug, please
include
- "Fixes #bugnum:" in the explanation so that GitHub can auto-close the
issue
  - when this PR is merged.
  -->
Fix part of #5025 - When this PR is merged, it will;
- Add ability to display the new deprecation dialogs when the
app-and-os-deprecation feature flag is enabled.
- Add ability to cache previous responses so that the user is not
spammed with dialogs every time they open the app.
 - Include tests for all added functionalities.

## Essential Checklist
<!-- Please tick the relevant boxes by putting an "x" in them. -->
- [x] The PR title and explanation each start with "Fix #bugnum: " (If
this PR fixes part of an issue, prefix the title with "Fix part of
#bugnum: ...".)
- [x] Any changes to
[scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets)
files have their rationale included in the PR explanation.
- [x] The PR follows the [style
guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide).
- [x] The PR does not contain any unnecessary code changes from Android
Studio
([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)).
- [x] The PR is made from a branch that's **not** called "develop" and
is up-to-date with "develop".
- [x] The PR is **assigned** to the appropriate reviewers
([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)).

## For UI-specific PRs only
<!-- Delete these section if this PR does not include UI-related
changes. -->
If your PR includes UI-related changes, then:
- Add screenshots for portrait/landscape for both a tablet & phone of
the before & after UI changes
- For the screenshots above, include both English and pseudo-localized
(RTL) screenshots (see [RTL
guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines))
- Add a video showing the full UX flow with a screen reader enabled (see
[accessibility
guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide))
- For PRs introducing new UI elements or color changes, both light and
dark mode screenshots must be included
- Add a screenshot demonstrating that you ran affected Espresso tests
locally & that they're passing
  • Loading branch information
kkmurerwa authored Feb 23, 2024
1 parent 58845ad commit 67c7a92
Show file tree
Hide file tree
Showing 29 changed files with 666 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import androidx.lifecycle.ViewModel
import org.oppia.android.R
import org.oppia.android.app.fragment.FragmentScope
import org.oppia.android.app.translation.AppLanguageResourceHandler
import org.oppia.android.app.utility.getLastUpdateTime
import org.oppia.android.app.utility.getVersionName
import org.oppia.android.app.viewmodel.ObservableViewModel
import org.oppia.android.util.extensions.getLastUpdateTime
import org.oppia.android.util.extensions.getVersionName
import javax.inject.Inject

/** [ViewModel] for [AppVersionFragment]. */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
package org.oppia.android.app.notice

import org.oppia.android.app.splash.DeprecationNoticeActionType
import org.oppia.android.app.model.DeprecationNoticeType

/** Listener for when an option on any deprecation dialog is clicked. */
interface DeprecationNoticeActionListener {
/** Called when a dialog button is clicked. */
fun onActionButtonClicked(noticeType: DeprecationNoticeActionType)
fun onActionButtonClicked(noticeActionResponse: DeprecationNoticeActionResponse)
}

/** Sealed data class for the response to a deprecation notice action. */
sealed class DeprecationNoticeActionResponse {
/** Action for when the user presses the 'Close' button on a deprecation dialog. */
object Close : DeprecationNoticeActionResponse()

/** Action for when the user presses the 'Dismiss' button on a deprecation dialog. */
data class Dismiss(
val deprecationNoticeType: DeprecationNoticeType,
val deprecatedVersion: Int,
) : DeprecationNoticeActionResponse()

/** Action for when the user presses the 'Update' button on a deprecation dialog. */
object Update : DeprecationNoticeActionResponse()
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import android.app.Dialog
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import org.oppia.android.R
import org.oppia.android.app.splash.DeprecationNoticeActionType
import org.oppia.android.app.translation.AppLanguageResourceHandler
import javax.inject.Inject

/** Presenter class responsible for showing an app deprecation dialog to the user. */
class ForcedAppDeprecationNoticeDialogFragmentPresenter @Inject constructor(
private val activity: AppCompatActivity,
private val resourceHandler: AppLanguageResourceHandler
private val resourceHandler: AppLanguageResourceHandler,
) {
private val deprecationNoticeActionListener by lazy {
activity as DeprecationNoticeActionListener
Expand All @@ -31,12 +30,12 @@ class ForcedAppDeprecationNoticeDialogFragmentPresenter @Inject constructor(
)
.setPositiveButton(R.string.forced_app_update_dialog_update_button_text) { _, _ ->
deprecationNoticeActionListener.onActionButtonClicked(
DeprecationNoticeActionType.UPDATE
DeprecationNoticeActionResponse.Update
)
}
.setNegativeButton(R.string.forced_app_update_dialog_close_button_text) { _, _ ->
deprecationNoticeActionListener.onActionButtonClicked(
DeprecationNoticeActionType.CLOSE
DeprecationNoticeActionResponse.Close
)
}
.setCancelable(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import android.app.Dialog
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import org.oppia.android.R
import org.oppia.android.app.splash.DeprecationNoticeActionType
import org.oppia.android.app.model.DeprecationNoticeType
import org.oppia.android.app.translation.AppLanguageResourceHandler
import org.oppia.android.util.platformparameter.OptionalAppUpdateVersionCode
import org.oppia.android.util.platformparameter.PlatformParameterValue
import javax.inject.Inject

/** Presenter class responsible for showing an optional update dialog to the user. */
class OptionalAppDeprecationNoticeDialogFragmentPresenter @Inject constructor(
private val activity: AppCompatActivity,
private val resourceHandler: AppLanguageResourceHandler
private val resourceHandler: AppLanguageResourceHandler,
@OptionalAppUpdateVersionCode
private val optionalAppUpdateVersionCode: PlatformParameterValue<Int>,
) {
private val deprecationNoticeActionListener by lazy {
activity as DeprecationNoticeActionListener
Expand All @@ -31,12 +35,15 @@ class OptionalAppDeprecationNoticeDialogFragmentPresenter @Inject constructor(
)
.setPositiveButton(R.string.optional_app_update_dialog_update_button_text) { _, _ ->
deprecationNoticeActionListener.onActionButtonClicked(
DeprecationNoticeActionType.UPDATE
DeprecationNoticeActionResponse.Update
)
}
.setNegativeButton(R.string.optional_app_update_dialog_dismiss_button_text) { _, _ ->
deprecationNoticeActionListener.onActionButtonClicked(
DeprecationNoticeActionType.DISMISS
DeprecationNoticeActionResponse.Dismiss(
deprecationNoticeType = DeprecationNoticeType.APP_DEPRECATION,
deprecatedVersion = optionalAppUpdateVersionCode.value
)
)
}
.setCancelable(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import android.app.Dialog
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import org.oppia.android.R
import org.oppia.android.app.splash.DeprecationNoticeActionType
import org.oppia.android.app.model.DeprecationNoticeType
import org.oppia.android.app.translation.AppLanguageResourceHandler
import org.oppia.android.util.platformparameter.LowestSupportedApiLevel
import org.oppia.android.util.platformparameter.PlatformParameterValue
import javax.inject.Inject

/** Presenter class responsible for showing an OS deprecation dialog to the user. */
class OsDeprecationNoticeDialogFragmentPresenter @Inject constructor(
private val activity: AppCompatActivity,
private val resourceHandler: AppLanguageResourceHandler
private val resourceHandler: AppLanguageResourceHandler,
@LowestSupportedApiLevel
private val lowestSupportedApiLevel: PlatformParameterValue<Int>
) {
private val deprecationNoticeActionListener by lazy {
activity as DeprecationNoticeActionListener
Expand All @@ -31,7 +35,10 @@ class OsDeprecationNoticeDialogFragmentPresenter @Inject constructor(
)
.setNegativeButton(R.string.os_deprecation_dialog_dismiss_button_text) { _, _ ->
deprecationNoticeActionListener.onActionButtonClicked(
DeprecationNoticeActionType.DISMISS
DeprecationNoticeActionResponse.Dismiss(
deprecationNoticeType = DeprecationNoticeType.OS_DEPRECATION,
deprecatedVersion = lowestSupportedApiLevel.value
)
)
}
.setCancelable(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package org.oppia.android.app.notice.testing

import android.os.Bundle
import org.oppia.android.app.notice.DeprecationNoticeActionListener
import org.oppia.android.app.notice.DeprecationNoticeActionResponse
import org.oppia.android.app.notice.ForcedAppDeprecationNoticeDialogFragment
import org.oppia.android.app.splash.DeprecationNoticeActionType
import org.oppia.android.app.testing.activity.TestActivity

/** [TestActivity] for setting up a test environment for testing the beta notice dialog. */
Expand All @@ -24,7 +24,7 @@ class ForcedAppDeprecationNoticeDialogFragmentTestActivity :
.showNow(supportFragmentManager, "forced_app_deprecation_dialog")
}

override fun onActionButtonClicked(noticeType: DeprecationNoticeActionType) {
mockCallbackListener.onActionButtonClicked(noticeType)
override fun onActionButtonClicked(noticeActionResponse: DeprecationNoticeActionResponse) {
mockCallbackListener.onActionButtonClicked(noticeActionResponse)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package org.oppia.android.app.notice.testing

import android.os.Bundle
import org.oppia.android.app.notice.DeprecationNoticeActionListener
import org.oppia.android.app.notice.DeprecationNoticeActionResponse
import org.oppia.android.app.notice.OptionalAppDeprecationNoticeDialogFragment
import org.oppia.android.app.splash.DeprecationNoticeActionType
import org.oppia.android.app.testing.activity.TestActivity

/** [TestActivity] for setting up a test environment for testing the beta notice dialog. */
Expand All @@ -24,7 +24,7 @@ class OptionalAppDeprecationNoticeDialogFragmentTestActivity :
.showNow(supportFragmentManager, "optional_app_deprecation_dialog")
}

override fun onActionButtonClicked(noticeType: DeprecationNoticeActionType) {
mockCallbackListener.onActionButtonClicked(noticeType)
override fun onActionButtonClicked(noticeActionResponse: DeprecationNoticeActionResponse) {
mockCallbackListener.onActionButtonClicked(noticeActionResponse)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package org.oppia.android.app.notice.testing

import android.os.Bundle
import org.oppia.android.app.notice.DeprecationNoticeActionListener
import org.oppia.android.app.notice.DeprecationNoticeActionResponse
import org.oppia.android.app.notice.OsDeprecationNoticeDialogFragment
import org.oppia.android.app.splash.DeprecationNoticeActionType
import org.oppia.android.app.testing.activity.TestActivity

/** [TestActivity] for setting up a test environment for testing the beta notice dialog. */
Expand All @@ -24,7 +24,7 @@ class OsDeprecationNoticeDialogFragmentTestActivity :
.showNow(supportFragmentManager, "os_deprecation_dialog")
}

override fun onActionButtonClicked(noticeType: DeprecationNoticeActionType) {
mockCallbackListener.onActionButtonClicked(noticeType)
override fun onActionButtonClicked(noticeActionResponse: DeprecationNoticeActionResponse) {
mockCallbackListener.onActionButtonClicked(noticeActionResponse)
}
}
16 changes: 6 additions & 10 deletions app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,13 @@ import org.oppia.android.app.fragment.FragmentComponentBuilderInjector
import org.oppia.android.app.fragment.FragmentComponentFactory
import org.oppia.android.app.model.ScreenName.SPLASH_ACTIVITY
import org.oppia.android.app.notice.BetaNoticeClosedListener
import org.oppia.android.app.notice.DeprecationNoticeActionListener
import org.oppia.android.app.notice.DeprecationNoticeActionResponse
import org.oppia.android.app.notice.DeprecationNoticeExitAppListener
import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeClosedListener
import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.decorateWithScreenName
import javax.inject.Inject

/** Enum class for the various deprecation notice actions available to the user. */
enum class DeprecationNoticeActionType {
/** Action for when the user presses the 'Close' option on a deprecation dialog. */
CLOSE,
/** Action for when the user presses the 'Dismiss' option on a deprecation dialog. */
DISMISS,
/** Action for when the user presses the 'Update' option on a deprecation dialog. */
UPDATE
}

/**
* An activity that shows a temporary loading page until the app is fully loaded then navigates to
* the profile selection screen.
Expand All @@ -38,6 +30,7 @@ class SplashActivity :
AppCompatActivity(),
FragmentComponentFactory,
DeprecationNoticeExitAppListener,
DeprecationNoticeActionListener,
BetaNoticeClosedListener,
GeneralAvailabilityUpgradeNoticeClosedListener {

Expand Down Expand Up @@ -69,4 +62,7 @@ class SplashActivity :

override fun onGaUpgradeNoticeOkayButtonClicked(permanentlyDismiss: Boolean) =
splashActivityPresenter.handleOnGaUpgradeNoticeOkayButtonClicked(permanentlyDismiss)

override fun onActionButtonClicked(noticeActionResponse: DeprecationNoticeActionResponse) =
splashActivityPresenter.handleOnDeprecationNoticeActionClicked(noticeActionResponse)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,23 @@ 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.DeprecationNoticeType
import org.oppia.android.app.model.DeprecationResponse
import org.oppia.android.app.notice.AutomaticAppDeprecationNoticeDialogFragment
import org.oppia.android.app.notice.BetaNoticeDialogFragment
import org.oppia.android.app.notice.DeprecationNoticeActionResponse
import org.oppia.android.app.notice.ForcedAppDeprecationNoticeDialogFragment
import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeDialogFragment
import org.oppia.android.app.notice.OptionalAppDeprecationNoticeDialogFragment
import org.oppia.android.app.notice.OsDeprecationNoticeDialogFragment
import org.oppia.android.app.onboarding.OnboardingActivity
import org.oppia.android.app.profile.ProfileChooserActivity
import org.oppia.android.app.translation.AppLanguageLocaleHandler
import org.oppia.android.app.utility.lifecycle.LifecycleSafeTimerFactory
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.onboarding.DeprecationController
import org.oppia.android.domain.oppialogger.OppiaLogger
import org.oppia.android.domain.topic.PrimeTopicAssetsController
import org.oppia.android.domain.translation.TranslationController
Expand All @@ -31,11 +38,16 @@ 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.EnableAppAndOsDeprecation
import org.oppia.android.util.platformparameter.PlatformParameterValue
import javax.inject.Inject

private const val AUTO_DEPRECATION_NOTICE_DIALOG_FRAGMENT_TAG = "auto_deprecation_notice_dialog"
private const val FORCED_DEPRECATION_NOTICE_DIALOG_FRAGMENT_TAG = "forced_deprecation_notice_dialog"
private const val BETA_NOTICE_DIALOG_FRAGMENT_TAG = "beta_notice_dialog"
private const val GA_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG = "general_availability_update_notice_dialog"
private const val OPTIONAL_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG = "optional_update_notice_dialog"
private const val OS_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG = "os_update_notice_dialog"
private const val SPLASH_INIT_STATE_DATA_PROVIDER_ID = "splash_init_state_data_provider"

/** The presenter for [SplashActivity]. */
Expand All @@ -47,9 +59,12 @@ class SplashActivityPresenter @Inject constructor(
private val primeTopicAssetsController: PrimeTopicAssetsController,
private val translationController: TranslationController,
private val localeController: LocaleController,
private val deprecationController: DeprecationController,
private val appLanguageLocaleHandler: AppLanguageLocaleHandler,
private val lifecycleSafeTimerFactory: LifecycleSafeTimerFactory,
private val currentBuildFlavor: BuildFlavor
private val currentBuildFlavor: BuildFlavor,
@EnableAppAndOsDeprecation
private val enableAppAndOsDeprecation: PlatformParameterValue<Boolean>,
) {
lateinit var startupMode: StartupMode

Expand All @@ -67,15 +82,28 @@ class SplashActivityPresenter @Inject constructor(
subscribeToOnboardingFlow()
}

fun handleOnDeprecationNoticeActionClicked(
noticeActionResponse: DeprecationNoticeActionResponse
) {
when (noticeActionResponse) {
is DeprecationNoticeActionResponse.Close -> handleOnDeprecationNoticeCloseAppButtonClicked()
is DeprecationNoticeActionResponse.Dismiss -> handleOnDeprecationNoticeDialogDismissed(
deprecationNoticeType = noticeActionResponse.deprecationNoticeType,
deprecatedVersion = noticeActionResponse.deprecatedVersion
)
is DeprecationNoticeActionResponse.Update -> handleOnDeprecationNoticeUpdateButtonClicked()
}
}

/** Handles cases where the user clicks the close app option on a deprecation notice dialog. */
fun handleOnDeprecationNoticeCloseAppButtonClicked() {
// If the app close button is clicked for the deprecation notice, finish the activity to close
// the app.
activity.finish()
}

/** Handles cases where the user clicks the update option on a deprecation notice dialog. */
fun handleOnDeprecationNoticeUpdateButtonClicked() {
/** Handles cases where the user clicks the update button on a deprecation notice dialog. */
private fun handleOnDeprecationNoticeUpdateButtonClicked() {
// If the Update button is clicked for the deprecation notice, launch the Play Store and open
// the Oppia app's page.
val packageName = activity.packageName
Expand All @@ -100,7 +128,17 @@ class SplashActivityPresenter @Inject constructor(
}

/** Handles cases where the user dismisses the deprecation notice dialog. */
fun handleOnDeprecationNoticeDialogDismissed() {
private fun handleOnDeprecationNoticeDialogDismissed(
deprecationNoticeType: DeprecationNoticeType,
deprecatedVersion: Int
) {
val deprecationResponse = DeprecationResponse.newBuilder()
.setDeprecationNoticeType(deprecationNoticeType)
.setDeprecatedVersion(deprecatedVersion)
.build()

deprecationController.saveDeprecationResponse(deprecationResponse)

// If the Dismiss button is clicked for the deprecation notice, the dialog is automatically
// dismissed. Navigate to profile chooser activity.
activity.startActivity(ProfileChooserActivity.createProfileChooserActivity(activity))
Expand Down Expand Up @@ -200,6 +238,47 @@ class SplashActivityPresenter @Inject constructor(
}

private fun processStartupMode() {
if (enableAppAndOsDeprecation.value) {
processAppAndOsDeprecationEnabledStartUpMode()
} else {
processLegacyStartupMode()
}
}

private fun processAppAndOsDeprecationEnabledStartUpMode() {
when (startupMode) {
StartupMode.USER_IS_ONBOARDED -> {
activity.startActivity(ProfileChooserActivity.createProfileChooserActivity(activity))
activity.finish()
}
StartupMode.APP_IS_DEPRECATED -> {
showDialog(
FORCED_DEPRECATION_NOTICE_DIALOG_FRAGMENT_TAG,
ForcedAppDeprecationNoticeDialogFragment::newInstance
)
}
StartupMode.OPTIONAL_UPDATE_AVAILABLE -> {
showDialog(
OPTIONAL_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG,
OptionalAppDeprecationNoticeDialogFragment::newInstance
)
}
StartupMode.OS_IS_DEPRECATED -> {
showDialog(
OS_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG,
OsDeprecationNoticeDialogFragment::newInstance
)
}
else -> {
// In all other cases (including errors when the startup state fails to load or is
// defaulted), assume the user needs to be onboarded.
activity.startActivity(OnboardingActivity.createOnboardingActivity(activity))
activity.finish()
}
}
}

private fun processLegacyStartupMode() {
when (startupMode) {
StartupMode.USER_IS_ONBOARDED -> {
activity.startActivity(ProfileChooserActivity.createProfileChooserActivity(activity))
Expand Down
Loading

0 comments on commit 67c7a92

Please sign in to comment.