diff --git a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt index 9a861937346..cab45a1b1b6 100644 --- a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt +++ b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt @@ -63,6 +63,7 @@ import org.oppia.android.app.profile.ResetPinDialogFragment import org.oppia.android.app.profileprogress.ProfilePictureEditDialogFragment import org.oppia.android.app.profileprogress.ProfileProgressFragment import org.oppia.android.app.resumelesson.ResumeLessonFragment +import org.oppia.android.app.settings.profile.ProfileDeleteSuccessDialogFragment import org.oppia.android.app.settings.profile.ProfileEditDeletionDialogFragment import org.oppia.android.app.settings.profile.ProfileEditFragment import org.oppia.android.app.settings.profile.ProfileListFragment @@ -128,6 +129,7 @@ interface FragmentComponentImpl : FragmentComponent, ViewComponentBuilderInjecto fun inject(cellularAudioDialogFragment: CellularAudioDialogFragment) fun inject(completedStoryListFragment: CompletedStoryListFragment) fun inject(conceptCardFragment: ConceptCardFragment) + fun inject(profileDeleteSuccessDialogFragment: ProfileDeleteSuccessDialogFragment) fun inject(developerOptionsFragment: DeveloperOptionsFragment) fun inject(downloadsTabFragment: DownloadsTabFragment) fun inject(dragDropTestFragment: DragDropTestFragment) diff --git a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileDeleteSuccessDialogFragment.kt b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileDeleteSuccessDialogFragment.kt new file mode 100644 index 00000000000..cf8aa01e941 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileDeleteSuccessDialogFragment.kt @@ -0,0 +1,61 @@ +package org.oppia.android.app.settings.profile + +import android.app.Dialog +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import org.oppia.android.R +import org.oppia.android.app.administratorcontrols.AdministratorControlsActivity +import org.oppia.android.app.fragment.FragmentComponentImpl +import org.oppia.android.app.fragment.InjectableDialogFragment +import org.oppia.android.app.translation.AppLanguageResourceHandler +import javax.inject.Inject + +/** [DialogFragment] that notifies the user after a profile is successfully deleted. */ +class ProfileDeleteSuccessDialogFragment : InjectableDialogFragment() { + @Inject + lateinit var resourceHandler: AppLanguageResourceHandler + + companion object { + /** Tag for [ProfileDeleteSuccessDialogFragment]. */ + const val DELETE_PROFILE_SUCCESS_DIALOG_FRAGMENT_TAG = "DELETE_PROFILE_SUCCESS_DIALOG_FRAGMENT" + + /** Returns a new instance of [ProfileDeleteSuccessDialogFragment]. */ + fun createNewInstance(): ProfileDeleteSuccessDialogFragment = + ProfileDeleteSuccessDialogFragment() + } + + override fun onAttach(context: Context) { + super.onAttach(context) + (fragmentComponent as FragmentComponentImpl).inject(this) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val alertDialog = + AlertDialog + .Builder(requireContext(), R.style.OppiaAlertDialogTheme) + .apply { + setMessage( + resourceHandler.getStringInLocale(R.string.profile_edit_delete_successful_message), + ) + setPositiveButton( + resourceHandler + .getStringInLocale(R.string.profile_edit_delete_success_dialog_positive_button), + ) { _, _ -> + if (requireContext().resources.getBoolean(R.bool.isTablet)) { + val intent = Intent(requireContext(), AdministratorControlsActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(intent) + } else { + val intent = Intent(requireContext(), ProfileListActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(intent) + } + } + }.create() + alertDialog.setCanceledOnTouchOutside(true) + return alertDialog + } +} diff --git a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditFragmentPresenter.kt index c5df7aa5439..6b3cd984335 100644 --- a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditFragmentPresenter.kt @@ -1,13 +1,11 @@ package org.oppia.android.app.settings.profile -import android.content.Intent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.lifecycle.Observer -import org.oppia.android.R import org.oppia.android.app.administratorcontrols.AdministratorControlsActivity import org.oppia.android.app.administratorcontrols.ProfileEditDeletionDialogListener import org.oppia.android.app.devoptions.markchapterscompleted.MarkChaptersCompletedActivity @@ -151,16 +149,11 @@ class ProfileEditFragmentPresenter @Inject constructor( fragment, Observer { if (it is AsyncResult.Success) { - if (fragment.requireContext().resources.getBoolean(R.bool.isTablet)) { - val intent = - Intent(fragment.requireContext(), AdministratorControlsActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - fragment.startActivity(intent) - } else { - val intent = Intent(fragment.requireContext(), ProfileListActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - fragment.startActivity(intent) - } + ProfileDeleteSuccessDialogFragment.createNewInstance() + .showNow( + fragment.childFragmentManager, + ProfileDeleteSuccessDialogFragment.DELETE_PROFILE_SUCCESS_DIALOG_FRAGMENT_TAG + ) } } ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0c43c5e2c26..48ed440dcf6 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -347,7 +347,9 @@ All progress will be deleted and cannot be recovered. Delete Cancel + Profile deleted successfully. Allow Download Access + OK User is able to download and delete content without Administrator password Profile Picture diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt index d63a296f378..59751b29705 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt @@ -244,6 +244,10 @@ class ProfileEditActivityTest { .inRoot(isDialog()) .perform(click()) testCoroutineDispatchers.runCurrent() + onView(withText(R.string.profile_edit_delete_success_dialog_positive_button)) + .inRoot(isDialog()) + .perform(click()) + testCoroutineDispatchers.runCurrent() if (context.resources.getBoolean(R.bool.isTablet)) { intended(hasComponent(AdministratorControlsActivity::class.java.name)) } else { @@ -266,6 +270,10 @@ class ProfileEditActivityTest { .inRoot(isDialog()) .perform(click()) testCoroutineDispatchers.runCurrent() + onView(withText(R.string.profile_edit_delete_success_dialog_positive_button)) + .inRoot(isDialog()) + .perform(click()) + testCoroutineDispatchers.runCurrent() if (context.resources.getBoolean(R.bool.isTablet)) { intended(hasComponent(AdministratorControlsActivity::class.java.name)) } else { diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt index b6ae6b60b22..326627850b7 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt @@ -125,7 +125,6 @@ import javax.inject.Singleton @LooperMode(LooperMode.Mode.PAUSED) @Config(application = ProfileEditFragmentTest.TestApplication::class, qualifiers = "port-xxhdpi") class ProfileEditFragmentTest { - @get:Rule val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() @@ -167,49 +166,68 @@ class ProfileEditFragmentTest { } @Test - fun testProfileEdit_startWithUserProfile_clickProfileDeletionButton_checkOpensDeletionDialog() { + fun testProfileEdit_clickProfileDeletion_checkOpensDeletionDialog_checkOpensSuccessDialog() { launchFragmentTestActivity(internalProfileId = 1).use { onView(withId(R.id.profile_delete_button)).perform(click()) onView(withText(R.string.profile_edit_delete_dialog_message)) - .inRoot(isDialog()).check(matches(isDisplayed())) + .inRoot(isDialog()) + .check(matches(isDisplayed())) + onView(withText(R.string.profile_edit_delete_dialog_positive)).perform(click()) + testCoroutineDispatchers.runCurrent() + onView(withText(R.string.profile_edit_delete_successful_message)) + .inRoot(isDialog()) + .check(matches(isCompletelyDisplayed())) } } @Test @Config(qualifiers = "land") - fun testProfileEdit_configChange_startWithUserProfile_clickDelete_checkOpensDeletionDialog() { + fun testProfileEdit_configChange_clickDelete_checkOpensDeletionDialog_checkOpensSuccessDialog() { launchFragmentTestActivity(internalProfileId = 1).use { onView(isRoot()).perform(orientationLandscape()) onView(withId(R.id.profile_delete_button)).perform(scrollTo()).perform(click()) testCoroutineDispatchers.runCurrent() onView(withText(R.string.profile_edit_delete_dialog_message)) - .inRoot(isDialog()).check(matches(isDisplayed())) + .inRoot(isDialog()) + .check(matches(isDisplayed())) + onView(withText(R.string.profile_edit_delete_dialog_positive)).perform(click()) + testCoroutineDispatchers.runCurrent() + onView(withText(R.string.profile_edit_delete_successful_message)) + .inRoot(isDialog()) + .check(matches(isDisplayed())) } } @Test @Config(qualifiers = "land") - fun testProfileEdit_startWithUserProfile_clickDelete_configChange_checkDeletionDialogIsVisible() { + fun testProfileEdit_clickDelete_landscapeMode_checkOpensDeletionDialog() { launchFragmentTestActivity(internalProfileId = 1).use { onView(withId(R.id.profile_delete_button)).perform(scrollTo()).perform(click()) onView(isRoot()).perform(orientationLandscape()) testCoroutineDispatchers.runCurrent() onView(withText(R.string.profile_edit_delete_dialog_message)) - .inRoot(isDialog()).check(matches(isCompletelyDisplayed())) + .inRoot(isDialog()) + .check(matches(isDisplayed())) + onView(withText(R.string.profile_edit_delete_dialog_positive)).perform(click()) + testCoroutineDispatchers.runCurrent() + onView(withText(R.string.profile_edit_delete_successful_message)) + .inRoot(isDialog()) + .check(matches(isCompletelyDisplayed())) } } @Test fun testProfileEdit_startWithUserHasDownloadAccess_downloadsDisabled_switchIsNotDisplayed() { TestPlatformParameterModule.forceEnableDownloadsSupport(false) - profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = true, - colorRgb = -10710042, - isAdmin = false - ).toLiveData() + profileManagementController + .addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = true, + colorRgb = -10710042, + isAdmin = false, + ).toLiveData() launchFragmentTestActivity(internalProfileId = 4).use { onView(withId(R.id.profile_edit_allow_download_container)).check(matches(not(isDisplayed()))) } @@ -226,14 +244,15 @@ class ProfileEditFragmentTest { @Test fun testProfileEdit_startWithUserHasDownloadAccess_downloadsEnabled_checkSwitchIsChecked() { TestPlatformParameterModule.forceEnableDownloadsSupport(true) - profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = true, - colorRgb = -10710042, - isAdmin = false - ).toLiveData() + profileManagementController + .addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = true, + colorRgb = -10710042, + isAdmin = false, + ).toLiveData() launchFragmentTestActivity(internalProfileId = 4).use { onView(withId(R.id.profile_edit_allow_download_switch)).check(matches(isChecked())) } @@ -243,14 +262,15 @@ class ProfileEditFragmentTest { @Config(qualifiers = "land") fun testProfileEdit_configChange_userHasDownloadAccess_downloadsEnabled_checkSwitchIsChecked() { TestPlatformParameterModule.forceEnableDownloadsSupport(true) - val addProfileProvider = profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = true, - colorRgb = -10710042, - isAdmin = false - ) + val addProfileProvider = + profileManagementController.addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = true, + colorRgb = -10710042, + isAdmin = false, + ) monitorFactory.waitForNextSuccessfulResult(addProfileProvider) launchFragmentTestActivity(internalProfileId = 4).use { onView(isRoot()).perform(orientationLandscape()) @@ -261,14 +281,15 @@ class ProfileEditFragmentTest { @Test fun testProfileEdit_userHasDownloadAccess_downloadsEnabled_clickAllowDownloads_checkChanged() { TestPlatformParameterModule.forceEnableDownloadsSupport(true) - profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = true, - colorRgb = -10710042, - isAdmin = false - ).toLiveData() + profileManagementController + .addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = true, + colorRgb = -10710042, + isAdmin = false, + ).toLiveData() launchFragmentTestActivity(internalProfileId = 4).use { onView(withId(R.id.profile_edit_allow_download_switch)).check(matches(isChecked())) onView(withId(R.id.profile_edit_allow_download_container)).perform(click()) @@ -279,14 +300,15 @@ class ProfileEditFragmentTest { @Test fun testProfileEdit_userDoesNotHaveDownloadAccess_downloadsEnabled_switchIsNotClickable() { TestPlatformParameterModule.forceEnableDownloadsSupport(true) - profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = false, - colorRgb = -10710042, - isAdmin = false - ).toLiveData() + profileManagementController + .addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = false, + colorRgb = -10710042, + isAdmin = false, + ).toLiveData() launchFragmentTestActivity(internalProfileId = 4).use { onView(withId(R.id.profile_edit_allow_download_switch)).check(matches(not(isClickable()))) } @@ -295,14 +317,15 @@ class ProfileEditFragmentTest { @Test fun testProfileEdit_userHasDownloadAccess_downloadsEnabled_switchContainerIsFocusable() { TestPlatformParameterModule.forceEnableDownloadsSupport(true) - profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = true, - colorRgb = -10710042, - isAdmin = false - ).toLiveData() + profileManagementController + .addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = true, + colorRgb = -10710042, + isAdmin = false, + ).toLiveData() launchFragmentTestActivity(internalProfileId = 4).use { onView(withId(R.id.profile_edit_allow_download_container)).check(matches(isFocusable())) } @@ -311,14 +334,15 @@ class ProfileEditFragmentTest { @Test fun testProfileEdit_startWithUserHasDownloadAccess_downloadsEnabled_switchContainerIsDisplayed() { TestPlatformParameterModule.forceEnableDownloadsSupport(true) - profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = true, - colorRgb = -10710042, - isAdmin = false - ).toLiveData() + profileManagementController + .addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = true, + colorRgb = -10710042, + isAdmin = false, + ).toLiveData() launchFragmentTestActivity(internalProfileId = 4).use { onView(withId(R.id.profile_edit_allow_download_container)).check(matches(isDisplayed())) } @@ -365,11 +389,13 @@ class ProfileEditFragmentTest { launchFragmentTestActivity(internalProfileId = 0).use { onView(withId(R.id.profile_mark_chapters_for_completion_button)).perform(click()) - val args = MarkChaptersCompletedActivityParams.newBuilder().apply { - this.internalProfileId = 0 - this.showConfirmationNotice = true - } - .build() + val args = + MarkChaptersCompletedActivityParams + .newBuilder() + .apply { + this.internalProfileId = 0 + this.showConfirmationNotice = true + }.build() intended(hasComponent(MarkChaptersCompletedActivity::class.java.name)) intended(hasProtoExtra(MARK_CHAPTERS_COMPLETED_ACTIVITY_PARAMS, args)) } @@ -428,10 +454,11 @@ class ProfileEditFragmentTest { fun testProfileEdit_featureOn_hasSwitchingPermission_enableLanguageSwitchingIsOn() { TestPlatformParameterModule.forceEnableFastLanguageSwitchingInLesson(true) - val updateLangProvider = profileManagementController.updateEnableInLessonQuickLanguageSwitching( - profileId = ProfileId.newBuilder().apply { internalId = 0 }.build(), - allowInLessonQuickLanguageSwitching = true - ) + val updateLangProvider = + profileManagementController.updateEnableInLessonQuickLanguageSwitching( + profileId = ProfileId.newBuilder().apply { internalId = 0 }.build(), + allowInLessonQuickLanguageSwitching = true, + ) monitorFactory.waitForNextSuccessfulResult(updateLangProvider) // With the permission to switch languages, the setting should be on by default. @@ -450,7 +477,7 @@ class ProfileEditFragmentTest { // The user should not have permission to switch languages (since the switch wasn't toggled). val profileProvider = profileManagementController.getProfile( - ProfileId.newBuilder().apply { internalId = 0 }.build() + ProfileId.newBuilder().apply { internalId = 0 }.build(), ) val profile = monitorFactory.waitForNextSuccessfulResult(profileProvider) @@ -470,7 +497,7 @@ class ProfileEditFragmentTest { // The user should have permission to switch languages (since the switch was toggled). val profileProvider = profileManagementController.getProfile( - ProfileId.newBuilder().apply { internalId = 0 }.build() + ProfileId.newBuilder().apply { internalId = 0 }.build(), ) val profile = monitorFactory.waitForNextSuccessfulResult(profileProvider) assertThat(profile.allowInLessonQuickLanguageSwitching).isTrue() @@ -481,22 +508,26 @@ class ProfileEditFragmentTest { launchFragmentTestActivity(internalProfileId = 1).use { scenario -> scenario.onActivity { activity -> - val activityArgs = activity.intent.getProtoExtra( - ProfileEditActivity.PROFILE_EDIT_ACTIVITY_PARAMS_KEY, - ProfileEditActivityParams.getDefaultInstance() - ) + val activityArgs = + activity.intent.getProtoExtra( + ProfileEditActivity.PROFILE_EDIT_ACTIVITY_PARAMS_KEY, + ProfileEditActivityParams.getDefaultInstance(), + ) val isMultipane = activityArgs?.isMultipane ?: false - val fragment = activity.supportFragmentManager - .findFragmentById(R.id.profile_edit_fragment_placeholder) as ProfileEditFragment - - val arguments = checkNotNull(fragment.arguments) { - "Expected variables to be passed to ProfileEditFragment" - } - val args = arguments.getProto( - ProfileEditFragment.PROFILE_EDIT_FRAGMENT_ARGUMENTS_KEY, - ProfileEditFragmentArguments.getDefaultInstance() - ) + val fragment = + activity.supportFragmentManager + .findFragmentById(R.id.profile_edit_fragment_placeholder) as ProfileEditFragment + + val arguments = + checkNotNull(fragment.arguments) { + "Expected variables to be passed to ProfileEditFragment" + } + val args = + arguments.getProto( + ProfileEditFragment.PROFILE_EDIT_FRAGMENT_ARGUMENTS_KEY, + ProfileEditFragmentArguments.getDefaultInstance(), + ) val receivedInternalProfileId = args.internalProfileId val receivedIsMultipane = args.isMultipane @@ -508,7 +539,7 @@ class ProfileEditFragmentTest { private fun launchFragmentTestActivity(internalProfileId: Int) = launch( - createProfileEditFragmentTestActivity(context, internalProfileId) + createProfileEditFragmentTestActivity(context, internalProfileId), ).also { testCoroutineDispatchers.runCurrent() } @Singleton @@ -539,10 +570,9 @@ class ProfileEditFragmentTest { SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class, ActivityRouterModule::class, CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class, - TestAuthenticationModule::class - ] + TestAuthenticationModule::class, + ], ) - interface TestApplicationComponent : ApplicationComponent { @Component.Builder interface Builder : ApplicationComponent.Builder { @@ -552,9 +582,13 @@ class ProfileEditFragmentTest { fun inject(profileEditFragmentTest: ProfileEditFragmentTest) } - class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + class TestApplication : + Application(), + ActivityComponentFactory, + ApplicationInjectorProvider { private val component: TestApplicationComponent by lazy { - DaggerProfileEditFragmentTest_TestApplicationComponent.builder() + DaggerProfileEditFragmentTest_TestApplicationComponent + .builder() .setApplication(this) .build() as TestApplicationComponent } @@ -562,9 +596,12 @@ class ProfileEditFragmentTest { fun inject(profileEditFragmentTest: ProfileEditFragmentTest) = component.inject(profileEditFragmentTest) - override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { - return component.getActivityComponentBuilderProvider().get().setActivity(activity).build() - } + override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent = + component + .getActivityComponentBuilderProvider() + .get() + .setActivity(activity) + .build() override fun getApplicationInjector(): ApplicationInjector = component } diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto index 08f1cf99f8e..c358c7d68a4 100644 --- a/scripts/assets/test_file_exemptions.textproto +++ b/scripts/assets/test_file_exemptions.textproto @@ -2086,6 +2086,10 @@ test_file_exemption { exempted_file_path: "app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditActivity.kt" source_file_is_incompatible_with_code_coverage: true } +test_file_exemption { + exempted_file_path: "app/src/main/java/org/oppia/android/app/settings/profile/ProfileDeleteSuccessDialogFragment.kt" + test_file_not_required: true +} test_file_exemption { exempted_file_path: "app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditActivityPresenter.kt" test_file_not_required: true