Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix part of #5025: App and OS Deprecation Milestone 4 - Gate the new Deprecation Dialogs and Add Logic to Display Them #5249

Merged
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
695c2b2
feat: Add logic to tie together all pieces of the previous deprecatio…
kkmurerwa Nov 28, 2023
f218fec
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Nov 30, 2023
67f646b
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Dec 6, 2023
07ae734
fix: Fix failing tests
kkmurerwa Dec 6, 2023
23b176f
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Dec 6, 2023
cab5142
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Dec 6, 2023
53c5024
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Dec 8, 2023
216cebe
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Dec 14, 2023
3e186dd
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Dec 20, 2023
eb0fd34
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Dec 23, 2023
967b3c0
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Jan 4, 2024
9f82091
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Jan 15, 2024
3d34951
fix: Make changes and fixes requested after review
kkmurerwa Jan 17, 2024
5d2949e
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Jan 23, 2024
b723dca
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Jan 26, 2024
5fe98d2
feat: Temp code to ensure build works
kkmurerwa Jan 26, 2024
49c18f5
feat: Add ability to check previous responses before showing an updat…
kkmurerwa Jan 26, 2024
8af68a1
fix: Fix lint checks
kkmurerwa Jan 26, 2024
6e471d3
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Jan 29, 2024
472fe49
feat: Add tests for the new deprecation flow
kkmurerwa Jan 29, 2024
ca88493
Merge branch 'app-and-os-deprecation-milestone-4' of github.com:kkmur…
kkmurerwa Jan 29, 2024
d5d37fe
feat: Add tests for the app startup controller
kkmurerwa Jan 31, 2024
19ad9bd
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Feb 3, 2024
f38c87f
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Feb 12, 2024
1100c65
fix: Fix failing static checks
kkmurerwa Feb 12, 2024
45dbcc6
fix: Fix failing dialog tests
kkmurerwa Feb 12, 2024
eba9210
fix: Fix failing tests
kkmurerwa Feb 12, 2024
51ce11a
fix: Fix failing DeprecationController build
kkmurerwa Feb 12, 2024
5a9f03c
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Feb 13, 2024
31d6afe
test: Add tests to the DeprecationController and AppStartupStateContr…
kkmurerwa Feb 13, 2024
92d426c
chore: Add and improve tests on AppStartupStateControllerTest and Dep…
kkmurerwa Feb 13, 2024
b09e951
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Feb 19, 2024
e15d665
Merge branch 'develop' into app-and-os-deprecation-milestone-4
kkmurerwa Feb 20, 2024
427d69a
chore: Modified the test names and reduced repetition
kkmurerwa Feb 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,17 @@ 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 org.oppia.android.util.platformparameter.ForcedAppUpdateVersionCode
import org.oppia.android.util.platformparameter.PlatformParameterValue
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,
@ForcedAppUpdateVersionCode
private val forcedAppUpdateVersionCode: PlatformParameterValue<Int>,
adhiamboperes marked this conversation as resolved.
Show resolved Hide resolved
) {
private val deprecationNoticeActionListener by lazy {
activity as DeprecationNoticeActionListener
Expand All @@ -31,12 +34,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)
}
}
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) {
adhiamboperes marked this conversation as resolved.
Show resolved Hide resolved
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
Loading