From a565cc76024b30bd6f214e4b011f124df4104672 Mon Sep 17 00:00:00 2001 From: Vishwajith Shettigar <76042077+Vishwajith-Shettigar@users.noreply.github.com> Date: Thu, 19 Oct 2023 04:12:48 +0530 Subject: [PATCH 1/6] Fix #5187 Textview contrast issue in locked lesson (#5189) ## Explanation Fixes #5187, changing the background color of the locked lessons' TextView fixed the contrast ratio issue. ## Essential Checklist - [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 Before Fix | Light Mode| Dark Mode| |--------|--------| | ![textcontrast](https://github.com/oppia/oppia-android/assets/76042077/01ffdf6c-3a20-4ce5-ab20-54ff7a82fedee)|![testdark](https://github.com/oppia/oppia-android/assets/76042077/d3f1fb3a-f267-4ced-9a00-3bdd67d4192a)| After Fix | Light Mode| Dark Mode| |--------|--------| | ![afterlight](https://github.com/oppia/oppia-android/assets/76042077/7d00b264-0bfb-4b35-8b99-b0b44ff7149d)|![lockbackground](https://github.com/oppia/oppia-android/assets/76042077/9147686c-e573-4285-aced-0af599d68d67)| 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 --- app/src/main/res/values-night/color_palette.xml | 4 ++-- app/src/main/res/values/color_palette.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-night/color_palette.xml b/app/src/main/res/values-night/color_palette.xml index 610b29c2ac1..36917970d4f 100644 --- a/app/src/main/res/values-night/color_palette.xml +++ b/app/src/main/res/values-night/color_palette.xml @@ -171,9 +171,9 @@ @color/color_def_accessible_light_grey_2 @color/color_def_oppia_green @color/color_def_accessible_grey - @color/color_def_accessible_light_grey_2 + @color/color_def_accessible_light_grey @color/color_def_white_80 - @color/color_def_accessible_light_grey_2 + @color/color_def_accessible_light_grey @color/color_def_dark_silver @color/color_def_white @color/color_def_oppia_green diff --git a/app/src/main/res/values/color_palette.xml b/app/src/main/res/values/color_palette.xml index a542be65211..f5202bb9365 100644 --- a/app/src/main/res/values/color_palette.xml +++ b/app/src/main/res/values/color_palette.xml @@ -178,7 +178,7 @@ @color/color_def_light_green @color/color_def_accessible_light_grey_2 @color/color_def_accessible_light_grey_2 - @color/color_def_grey + @color/color_def_oppia_grey_background @color/color_def_transparent @color/color_def_bright_green @color/color_def_white From 089ddf03a7ca9d17cea7341ae8ae3389d044b1f8 Mon Sep 17 00:00:00 2001 From: Sergei Shchurov <71126152+sichchurov@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:58:26 +0300 Subject: [PATCH 2/6] Fix part of #5195: Changed method for compatibility with API < 26 (#5190) ## Explanation Fix part of #5195: For providing compatibility with API < 26, I added the else-if operator. If API 26 and higher use java.util.Base64 class else use android.util.Base64 class **Lint report before** callingNewApiBefore **Lint report after** callingNewApAfter ## Essential Checklist - [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 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 --- .../learneranalytics/ControlButtonsViewModel.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ControlButtonsViewModel.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ControlButtonsViewModel.kt index 5a2970e9652..7bfc41a7ffe 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ControlButtonsViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ControlButtonsViewModel.kt @@ -3,6 +3,7 @@ package org.oppia.android.app.administratorcontrols.learneranalytics import android.annotation.SuppressLint import android.content.ActivityNotFoundException import android.content.Intent +import android.os.Build import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -228,7 +229,11 @@ class ControlButtonsViewModel private constructor( val compressedMessage = ByteArrayOutputStream().also { byteOutputStream -> GZIPOutputStream(byteOutputStream).use(::writeTo) }.toByteArray() - return Base64.getEncoder().encodeToString(compressedMessage) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Base64.getEncoder().encodeToString(compressedMessage) + } else { + android.util.Base64.encodeToString(compressedMessage, 0) + } } private fun String.computeSha1Hash(machineLocale: OppiaLocale.MachineLocale): String { From 4fb9b585aa0bbd8c118a1856d365a78b528ca49a Mon Sep 17 00:00:00 2001 From: Vishwajith Shettigar <76042077+Vishwajith-Shettigar@users.noreply.github.com> Date: Thu, 19 Oct 2023 17:07:54 +0530 Subject: [PATCH 3/6] Fix #4739 Talkback does not read the expected text (#5152) ## Explanation Fix #4739, edittext requesting focus only when reader is off will resolve this issue. ## Essential Checklist - [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 Before fix https://github.com/oppia/oppia-android/assets/76042077/41bc96b4-3fe1-4688-a77b-02bfdbd22782 After fix Talkback On https://github.com/oppia/oppia-android/assets/76042077/a01da3dc-8848-4632-a8c0-17ce3e409beb Talkback Off https://github.com/oppia/oppia-android/assets/76042077/beac309e-16d8-496f-bc09-af8de634536b 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)) - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --- .../profile/PinPasswordActivityPresenter.kt | 10 ++++++++- .../app/profile/PinPasswordActivityTest.kt | 21 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/profile/PinPasswordActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/profile/PinPasswordActivityPresenter.kt index a3e262baf63..57c999e2586 100644 --- a/app/src/main/java/org/oppia/android/app/profile/PinPasswordActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/profile/PinPasswordActivityPresenter.kt @@ -15,6 +15,7 @@ import org.oppia.android.app.utility.lifecycle.LifecycleSafeTimerFactory import org.oppia.android.app.viewmodel.ViewModelProvider import org.oppia.android.databinding.PinPasswordActivityBinding import org.oppia.android.domain.profile.ProfileManagementController +import org.oppia.android.util.accessibility.AccessibilityService import org.oppia.android.util.data.AsyncResult import org.oppia.android.util.data.DataProviders.Companion.toLiveData import javax.inject.Inject @@ -31,6 +32,7 @@ class PinPasswordActivityPresenter @Inject constructor( private val viewModelProvider: ViewModelProvider, private val resourceHandler: AppLanguageResourceHandler ) { + @Inject lateinit var accessibilityService: AccessibilityService private val pinViewModel by lazy { getPinPasswordViewModel() } @@ -69,7 +71,13 @@ class PinPasswordActivityPresenter @Inject constructor( ) } } - binding.pinPasswordInputPinEditText.requestFocus() + + // If the screen reader is off, the EditText will receive focus. + // If the screen reader is on, the EditText won't receive focus. + // This is needed because requesting focus on the EditText when the screen reader is on gives TalkBack priority over other views in the screen, ignoring view hierachy. + if (!accessibilityService.isScreenReaderEnabled()) + binding.pinPasswordInputPinEditText.requestFocus() + // [onTextChanged] is a extension function defined at [TextInputEditTextHelper] binding.pinPasswordInputPinEditText.onTextChanged { pin -> pin?.let { inputtedPin -> diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt index f1afe3b1393..16ce38b184e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt @@ -98,6 +98,7 @@ import org.oppia.android.testing.threading.TestCoroutineDispatchers import org.oppia.android.testing.threading.TestDispatcherModule import org.oppia.android.testing.time.FakeOppiaClockModule import org.oppia.android.util.accessibility.AccessibilityTestModule +import org.oppia.android.util.accessibility.FakeAccessibilityService import org.oppia.android.util.caching.AssetModule import org.oppia.android.util.caching.testing.CachingTestModule import org.oppia.android.util.gcsresource.GcsResourceModule @@ -147,6 +148,9 @@ class PinPasswordActivityTest { @Inject lateinit var editTextInputAction: EditTextInputAction + @Inject + lateinit var fakeAccessibilityService: FakeAccessibilityService + private val adminPin = "12345" private val adminId = 0 private val userId = 1 @@ -181,7 +185,8 @@ class PinPasswordActivityTest { } @Test - fun testPinPassword_withAdmin_keyboardIsVisibleByDefault() { + fun testPinPassword_withAdmin_screenReaderOff_keyboardIsVisible() { + fakeAccessibilityService.setScreenReaderEnabled(false) ActivityScenario.launch( PinPasswordActivity.createPinPasswordActivityIntent( context = context, @@ -193,6 +198,20 @@ class PinPasswordActivityTest { } } + @Test + fun testPinPassword_withAdmin_screenReaderOn_keyboardIsNotVisible() { + fakeAccessibilityService.setScreenReaderEnabled(true) + ActivityScenario.launch( + PinPasswordActivity.createPinPasswordActivityIntent( + context = context, + adminPin = adminPin, + profileId = adminId + ) + ).use { + onView(withId(R.id.pin_password_input_pin_edit_text)).check(matches(not(hasFocus()))) + } + } + @Test fun testPinPassword_withAdmin_inputCorrectPin_opensHomeActivity() { ActivityScenario.launch( From ff0dffc4addb9c20f3d07fc7ddd74b382a522a2b Mon Sep 17 00:00:00 2001 From: MOHIT GUPTA <76530270+MohitGupta121@users.noreply.github.com> Date: Fri, 20 Oct 2023 18:21:28 +0530 Subject: [PATCH 4/6] Fix #5192, #4610 : [Developer Video] Understanding CI check failures & Replace wiki broken links (#5199) ## Explanation Fix #5192, #4610 : [Developer Video] Understanding CI check failures & Replace Wiki broken links This PR include developer video that explains how to handle failures in our Continuous Integration (CI) checks in oppia-android. It also includes the replacement of broken links in our Wiki, improving the documentation for a smoother developer experience. ## Essential Checklist - [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: ...".) - [ ] 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)). --- scripts/assets/todo_open_exemptions.textproto | 24 +++++++++---------- wiki/Accessibility-A11y-Guide.md | 2 +- wiki/Bazel-Setup-Instructions-for-Windows.md | 2 +- wiki/Buf-Guide.md | 2 +- wiki/Developing-skills.md | 4 ++-- wiki/Guidance-on-submitting-a-PR.md | 2 +- wiki/Instructions-for-making-a-code-change.md | 6 ++--- wiki/Interpreting-CI-Results.md | 8 +++++++ wiki/Ktlint-Guide.md | 2 +- wiki/RTL-Guidelines.md | 1 - wiki/Static-Analysis-Checks.md | 4 ++++ wiki/Troubleshooting-Installation.md | 2 +- wiki/Updating-Maven-Dependencies.md | 2 +- wiki/Work-Manager.md | 6 ++--- 14 files changed, 39 insertions(+), 28 deletions(-) diff --git a/scripts/assets/todo_open_exemptions.textproto b/scripts/assets/todo_open_exemptions.textproto index 0fa81fd297d..cb81317adae 100644 --- a/scripts/assets/todo_open_exemptions.textproto +++ b/scripts/assets/todo_open_exemptions.textproto @@ -294,16 +294,16 @@ todo_open_exemption { todo_open_exemption { exempted_file_path: "wiki/Static-Analysis-Checks.md" - line_number: 170 - line_number: 178 - line_number: 183 - line_number: 187 - line_number: 191 - line_number: 193 - line_number: 208 - line_number: 218 - line_number: 221 - line_number: 224 - line_number: 227 - line_number: 230 + line_number: 171 + line_number: 179 + line_number: 184 + line_number: 188 + line_number: 192 + line_number: 194 + line_number: 209 + line_number: 219 + line_number: 222 + line_number: 225 + line_number: 228 + line_number: 231 } diff --git a/wiki/Accessibility-A11y-Guide.md b/wiki/Accessibility-A11y-Guide.md index 54e713469f4..a9e01c6c59e 100644 --- a/wiki/Accessibility-A11y-Guide.md +++ b/wiki/Accessibility-A11y-Guide.md @@ -95,7 +95,7 @@ TalkBack is the Google **screen reader** included on Android devices. TalkBack g * [Presentation Slides](https://docs.google.com/presentation/d/17SeKJLKT-rUNa_Yupe97bMFSsjTNzp83jX-lZPKEtnQ/edit?usp=sharing) ## Using AccessibilityTestRule in Espresso Tests -[AccessibilityTestRule](https://github.com/oppia/oppia-android/blob/develop/testing/src/main/java/org/oppia/android/testing/AccessibilityTestRule.kt) is a JUnit rule to enable `AccessibilityChecks` in all Espresso Tests. This rule covers all errors shown by Accessibility Scanner and more but only for all those UI elements which are getting used in the test case. +[AccessibilityTestRule](https://github.com/oppia/oppia-android/blob/develop/testing/src/main/java/org/oppia/android/testing/OppiaTestRule.kt) is a JUnit rule to enable `AccessibilityChecks` in all Espresso Tests. This rule covers all errors shown by Accessibility Scanner and more but only for all those UI elements which are getting used in the test case. (**Note: If this file is not available then it has been merged with OppiaTestRule as per #3351**) diff --git a/wiki/Bazel-Setup-Instructions-for-Windows.md b/wiki/Bazel-Setup-Instructions-for-Windows.md index 42c564c51fb..7a72fa86c34 100644 --- a/wiki/Bazel-Setup-Instructions-for-Windows.md +++ b/wiki/Bazel-Setup-Instructions-for-Windows.md @@ -14,7 +14,7 @@ ## Overview & Disclaimer -This page outlines one way to allow Bazel to be used in CLI form on Windows. Please note that **this support is currently experimental**. You may run into some problems--we suggest that you [file an issue](https://github.com/oppia/oppia-android/issues/new/choose) ior contact us at [gitter](https://gitter.im/oppia/oppia-android). +This page outlines one way to allow Bazel to be used in CLI form on Windows. Please note that **this support is currently experimental**. You may run into some problems--we suggest that you [file an issue](https://github.com/oppia/oppia-android/issues/new/choose) or contact us at [github-discussions](https://github.com/oppia/oppia-android/discussions). Unlike Unix-based systems where Bazel runs natively without issue, the current solution on Windows is to install an Ubuntu-based subsystem. Windows currently only supports a terminal experience in this subsystem (though there is a prerelease version of the software with GUI support) which means Android Studio will not be supported. You will need to continue using the Windows version of Android Studio and only use the Linux subsystem for building & running Robolectric or JUnit-based tests. diff --git a/wiki/Buf-Guide.md b/wiki/Buf-Guide.md index 895e2de625a..184c747e033 100644 --- a/wiki/Buf-Guide.md +++ b/wiki/Buf-Guide.md @@ -5,7 +5,7 @@ - [Configuration File](#configuration-file) # Installation -Once you had completed all the [installation steps](https://github.com/oppia/oppia-android/wiki#prerequisites), you will be having a `buf` file in your `opensource/oppia-android-tools` folder.
+Once you have completed all the [installation steps](https://github.com/oppia/oppia-android/wiki/Installing-Oppia-Android), you will have a `buf` file in your `opensource/oppia-android-tools` folder.
**Note: Currently, Buf is not available for windows.** ## Commands diff --git a/wiki/Developing-skills.md b/wiki/Developing-skills.md index 562b07e321e..fab285b222c 100644 --- a/wiki/Developing-skills.md +++ b/wiki/Developing-skills.md @@ -7,11 +7,11 @@ That said, we strongly recommend that you be open to learning new things. If you - [Learning Branching Git](https://learngitbranching.js.org/) helps explain how git works. Try the levels below: - Levels 1, 2, and 3 from the Introduction sequence. - Levels 1, 2, 3, 4, 5, and 6 from Push and Pull Git Remotes. - - [Introduction to GitHub](https://lab.github.com/githubtraining/introduction-to-github) covers how to use GitHub. + - [Introduction to GitHub](https://learn.microsoft.com/en-us/training/modules/introduction-to-github/) covers how to use GitHub. - More advanced: - The other levels from [Learn Branching Git](https://learngitbranching.js.org/) cover git in more depth. - You may find this [git visualizer](https://git-school.github.io/visualizing-git/) helpful for understanding more advanced git operations. It can be helpful for simple ones too! - - GitHub's [managing merge conflicts page](https://lab.github.com/githubtraining/managing-merge-conflicts) explains how to address merge conflicts. + - GitHub's [managing merge conflicts page](https://github.com/skills/resolve-merge-conflicts) explains how to address merge conflicts. - Kotlin is used for Android in oppia. You can learn the basics of kotlin from Udacity -- [Kotlin bootcamp for programmers](https://www.udacity.com/course/kotlin-bootcamp-for-programmers--ud9011) by Google. - Learn the basics of android to understand the project structure and the libraries that are used in most common apps from the Udacity -- [Developing Android Apps with Kotlin](https://www.udacity.com/course/developing-android-apps-with-kotlin--ud9012) course. - To learn the advanced topics like Dependency Injection and Testing in Android check out Udacity -- [Advanced Android with Kotlin](https://www.udacity.com/course/advanced-android-with-kotlin--ud940) course. diff --git a/wiki/Guidance-on-submitting-a-PR.md b/wiki/Guidance-on-submitting-a-PR.md index 5f7541e70f9..0124b44eb90 100644 --- a/wiki/Guidance-on-submitting-a-PR.md +++ b/wiki/Guidance-on-submitting-a-PR.md @@ -1,5 +1,5 @@ **Working on your first pull request?** Pull requests (PRs) can be tricky to understand at first, so if the instructions on this page don't make sense to you, check out these resources: -- The free series [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) +- The free series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) - [Atlassian's tutorial on pull requests](https://www.atlassian.com/git/tutorials/making-a-pull-request). Here are the steps for making a PR to the Oppia Android codebase: diff --git a/wiki/Instructions-for-making-a-code-change.md b/wiki/Instructions-for-making-a-code-change.md index ca91ee920ff..850f252220c 100644 --- a/wiki/Instructions-for-making-a-code-change.md +++ b/wiki/Instructions-for-making-a-code-change.md @@ -1,12 +1,12 @@ **Important:** Please read the [Oppia Android coding style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide) before making any code changes. -**Working on your first Pull Request?** You can learn how from this free series: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). +**Working on your first Pull Request?** You can learn how from this free series: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). -*If your change isn't trivial, please [talk to us](https://gitter.im/oppia/oppia-android) before you start working on it -- this helps avoid duplication of effort, and allows us to offer advice and suggestions. For larger changes, it may be better to first create a short doc outlining a suggested implementation plan, and send it to the Android dev team for feedback.* +*If your change isn't trivial, please talk to us via [github-discussions](https://github.com/oppia/oppia-android/discussions) before you start working on it -- this helps avoid duplication of effort, and allows us to offer advice and suggestions. For larger changes, it may be better to first create a short doc outlining a suggested implementation plan, and send it to the Android dev team for feedback.* The following instructions describe how to make a one-off code change using a feature branch. (In case you're interested, we mainly use the [Gitflow workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow).) Please follow them carefully, otherwise your code review may be delayed. -You might also find this reference for the [Android Studio UI-based Github workflow](https://github.com/oppia/oppia-android/wiki/Android-Studio-UI-based-Github-workflow) helpful. +You might also find this reference for the [Android Studio UI-based Github workflow](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#making-a-local-code-change-using-android-studios-ui-based-github-workflow) helpful. 1. **Choose a descriptive branch name.** It should be lowercase and hyphen-separated, such as `splash-screen`. Also, it shouldn't start with `hotfix` or `release`. 2. **Before coding anything, create a new branch with this name, starting from 'develop'.** I.e., run: diff --git a/wiki/Interpreting-CI-Results.md b/wiki/Interpreting-CI-Results.md index b0135d27293..2adf2685b53 100644 --- a/wiki/Interpreting-CI-Results.md +++ b/wiki/Interpreting-CI-Results.md @@ -1,3 +1,8 @@ +## Table of Contents + +- [How to find the error message for a Failing CI check](#how-to-find-error-message-for-failing-ci-checks) +- [Developer Video - Understanding CI check failures](#developer-video---understanding-ci-check-failures) + ## How to find error message for Failing CI checks Creating a pr or updating a pr runs all the CI checks, which can sometimes fail if the code changes have affected some other part of the app or if the code changes don’t need some reformatting and docs. In these cases understanding the error and fixing it requires how to find the error. @@ -14,3 +19,6 @@ Example in the below check the second job has some error or failure Navigate to logs or search the keyword ‘error’ to find the error message to understand what might have caused the failure in the checks. + +### Developer Video - Understanding CI check failures +Learn how to interpret and troubleshoot oppia-android CI check failures in this insightful [developer video](https://youtu.be/I2bRf6fvgJ0?si=35sAagbUFSk6bOBA). \ No newline at end of file diff --git a/wiki/Ktlint-Guide.md b/wiki/Ktlint-Guide.md index 9c1b4c11549..83f27e8dd0c 100644 --- a/wiki/Ktlint-Guide.md +++ b/wiki/Ktlint-Guide.md @@ -7,7 +7,7 @@ - [How to fix the most common issues?](#how-to-fix-the-most-common-issues) # Installation -Once you had completed all the [installation steps](https://github.com/oppia/oppia-android/wiki#prerequisites), you will be having a `ktlint` file in your `opensource/oppia-android-tools` folder. +Once you have completed all the [installation steps](https://github.com/oppia/oppia-android/wiki/Installing-Oppia-Android), you will have a `ktlint` file in your `opensource/oppia-android-tools` folder. # Commands diff --git a/wiki/RTL-Guidelines.md b/wiki/RTL-Guidelines.md index dc004760827..cd16760ecd4 100644 --- a/wiki/RTL-Guidelines.md +++ b/wiki/RTL-Guidelines.md @@ -51,5 +51,4 @@ The screen will look something like this: # Reference Documentation * [Oppia-Android RTL Issues](https://docs.google.com/document/d/1Fl1ar5vcdLvay7ZIJLUFQro1wEf1yUEicwF-CKcvwJ0/edit#) -* [RTL Support Milestone](https://github.com/oppia/oppia-android/milestone/40) * [Guidelines for RTL](https://material.io/design/usability/bidirectionality.html) diff --git a/wiki/Static-Analysis-Checks.md b/wiki/Static-Analysis-Checks.md index a3d350fedf9..e4252c7c445 100644 --- a/wiki/Static-Analysis-Checks.md +++ b/wiki/Static-Analysis-Checks.md @@ -12,6 +12,7 @@ - [TODO open checks](#todo-open-checks) - [TODO issue resolved check](#todo-issue-resolved-check) - [How to run static checks locally](#how-to-run-static-checks-locally) + - [Developer Video - Understanding CI check failures](#developer-video---understanding-ci-check-failures) # Background Static analysis is a method of debugging by examining source code before a program is run. It’s done by analyzing a set of code against a set (or multiple sets) of coding rules. @@ -241,3 +242,6 @@ To fix failing tests from GitHub CI individually, follow the steps below. - You can also go to scripts/static_checks.sh to view the failing check and run it locally. Note: Before running the script command in your local terminal, make sure you have Bazel installed. To learn how to set up Bazel for Oppia Android, follow these [instructions](https://github.com/oppia/oppia-android/wiki/Oppia-Bazel-Setup-Instructions). Also make sure you have oppia-android-tools installed since static checks rely on these tools to be able to perform some of the checks. To install oppia-android-tools, run `bash scripts/setup.sh` in the oppia-android directory. + +### Developer Video - Understanding CI check failures +Learn how to interpret and troubleshoot oppia-android CI check failures in this insightful [developer video](https://youtu.be/I2bRf6fvgJ0?si=35sAagbUFSk6bOBA). \ No newline at end of file diff --git a/wiki/Troubleshooting-Installation.md b/wiki/Troubleshooting-Installation.md index d88c4bf01ad..a05144fa2ed 100644 --- a/wiki/Troubleshooting-Installation.md +++ b/wiki/Troubleshooting-Installation.md @@ -14,7 +14,7 @@ Here are some general troubleshooting tips for oppia-android. The specific platf 2. If you find any error which says `java: command not found`, please check you have Java installed correctly in your machine and the [environment path variable](https://www.java.com/en/download/help/path.html) is also set up correctly. -3. If you find any error related to Kotlin or Java/Checkstyle while pushing the code, please check [this link](https://github.com/oppia/oppia-android/wiki/Android-Studio-UI-based-Github-workflow#how-to-fix-push-failures). +3. If you find any error related to Kotlin or Java/Checkstyle while pushing the code, please check [this link](https://github.com/oppia/oppia-android/wiki/Frequent-Errors-and-Solutions#push-failed). 4. If you see the error diff --git a/wiki/Updating-Maven-Dependencies.md b/wiki/Updating-Maven-Dependencies.md index cb08415d296..6e0653f844b 100644 --- a/wiki/Updating-Maven-Dependencies.md +++ b/wiki/Updating-Maven-Dependencies.md @@ -70,7 +70,7 @@ If the link does point to a valid license then choose the most appropriate categ 1. scrapable_link: If the license text is plain text and the URL mentioned can be scraped directly from the original_link of the license. e.g - https://www.apache.org/licenses/LICENSE-2.0.txt 2. extracted_copy_link: If the license text is plain text but can not be scraped directly from the original_link of the license. - e.g - https://www.opensource.org/licenses/bsd-license + e.g - https://opensource.org/license/bsd-3-clause 3. direct_link_only: If the license text is not plain text, it's best to display only the link of the license. e.g - https://developer.android.com/studio/terms.html diff --git a/wiki/Work-Manager.md b/wiki/Work-Manager.md index bcede9e03f2..2547ee7b457 100644 --- a/wiki/Work-Manager.md +++ b/wiki/Work-Manager.md @@ -34,8 +34,8 @@ There are a few WorkManager classes you need to know about: In Oppia we are using WorkManager in two scenarios : -- To upload cached Logs (for Analytics) over FirebaseAnalytics whenever data connection and battery requirements are met. This was implemented by @Sarthak2601 during GSoC'20, for more details you can go through the [proposal idea](https://github.com/oppia/oppia/wiki/pdfs/GSoC2020SarthakAgarwal.pdf) -- To sync up the PlatformParameters from OppiaBackend whenever the app starts and the data + battery requirements are met. This was implemented by @ARJUPTA during GSoC'21, for more details you can go through the [proposal idea](https://github.com/oppia/oppia/wiki/pdfs/GSoC2021ArjunGupta.pdf) +- To upload cached Logs (for Analytics) over FirebaseAnalytics whenever data connection and battery requirements are met. This was implemented by @Sarthak2601 during GSoC'20, for more details you can go through the [proposal idea](https://github.com/oppia/oppia-web-developer-docs/blob/develop/pdfs/GSoC2020SarthakAgarwal.pdf) +- To sync up the PlatformParameters from OppiaBackend whenever the app starts and the data + battery requirements are met. This was implemented by @ARJUPTA during GSoC'21, for more details you can go through the [proposal idea](https://github.com/oppia/oppia-web-developer-docs/blob/develop/pdfs/GSoC2021ArjunGupta.pdf) # How to use WorkManager If you want to introduce a new feature or any change to the existing WorkManager implementation in oppia-android, here is the basic structure of files you need to keep in mind : @@ -168,4 +168,4 @@ In Oppia we write tests for both the Worker and its Initializer class. You can t Worker Tests - *[PlatformParameterSyncUpWorkerTest](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkerTest.kt) OR [LogUploadWorkerTest](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkerTest.kt)* -Initializer Tests - *[PlatformParameterSyncUpWorkManagerInitializerTest](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkManagerInitializerTest.kt) OR [LogUploadWorkManagerInitializerTest](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogUploadWorkManagerInitializerTest.kt)* \ No newline at end of file +Initializer Tests - *[PlatformParameterSyncUpWorkManagerInitializerTest](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/platformparameter/syncup/PlatformParameterSyncUpWorkManagerInitializerTest.kt) OR [LogUploadWorkManagerInitializerTest](https://github.com/oppia/oppia-android/blob/develop/domain/src/test/java/org/oppia/android/domain/oppialogger/loguploader/LogReportWorkManagerInitializerTest.kt)* \ No newline at end of file From e151bbd06b21d2b8211b4f56cde3cb4a6c2673c4 Mon Sep 17 00:00:00 2001 From: Sergei Shchurov <71126152+sichchurov@users.noreply.github.com> Date: Fri, 20 Oct 2023 21:10:46 +0300 Subject: [PATCH 5/6] Fix part of #5195: RestrictedApi. Using a private API (#5198) ## Explanation Fix part of #5195: I have modified method setButtonTint to avoid an error due to using a restricted API. This method(bindingAdapter) is not set in any of the layouts. Only test is written for it. So it is safety to modify the test also. If not modify a test - it is failed then. **Lint report before** callingNewApiBefore **Lint report after** callingNewApiBefore ## Essential Checklist - [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 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 --- .../app/databinding/AppCompatCheckBoxBindingAdapters.java | 3 ++- .../app/databinding/AppCompatCheckBoxBindingAdaptersTest.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdapters.java b/app/src/main/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdapters.java index b704faa86e2..4e7f59b0bb3 100644 --- a/app/src/main/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdapters.java +++ b/app/src/main/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdapters.java @@ -4,6 +4,7 @@ import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.appcompat.widget.AppCompatCheckBox; +import androidx.core.widget.CompoundButtonCompat; import androidx.databinding.BindingAdapter; /** @@ -13,6 +14,6 @@ public final class AppCompatCheckBoxBindingAdapters { /** Sets the button tint for the specified checkbox, via data-binding. */ @BindingAdapter("app:buttonTint") public static void setButtonTint(@NonNull AppCompatCheckBox checkBox, @ColorInt int colorRgb) { - checkBox.setSupportButtonTintList(ColorStateList.valueOf(colorRgb)); + CompoundButtonCompat.setButtonTintList(checkBox, ColorStateList.valueOf(colorRgb)); } } diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdaptersTest.kt index 57586237166..9fcf2bd1e47 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdaptersTest.kt @@ -127,7 +127,7 @@ class AppCompatCheckBoxBindingAdaptersTest { activityRule.scenario.onActivity { val appCompatCheckBox: AppCompatCheckBox = getAppCompatCheckBox(it) setButtonTint(appCompatCheckBox, colorRgb) - assertThat(appCompatCheckBox.supportButtonTintList?.defaultColor).isEqualTo(colorRgb) + assertThat(appCompatCheckBox.buttonTintList?.defaultColor).isEqualTo(colorRgb) } } From d471d79e7bcce6de8351cf045458af23711fcdb2 Mon Sep 17 00:00:00 2001 From: Kenneth Murerwa Date: Tue, 24 Oct 2023 04:34:57 +0300 Subject: [PATCH 6/6] Fix part of #5025: App and OS Deprecation Milestone 3 - Add New Deprecation Dialog Fragments (#5096) ## Explanation Fix part of #5025 When this PR is merged, it will; - Add fragments and fragment presenters for the Forced, Optional, and OS deprecation dialogs. - Add strings for the new deprecation dialogs. ## Screenshots of the introduced dialog fragments Forced Deprecation Dialog | Optional Deprecation Dialog | OS Deprecation Dialog :-------------------------:|:-------------------------:|:-------------------------: ![A screenshot of the forced deprecation dialog](https://github.com/oppia/oppia-android/assets/18438114/33808235-7867-458b-aa99-bb1166511121) | ![A screenshot of the optional deprecation dialog](https://github.com/oppia/oppia-android/assets/18438114/84e0ffc1-f1ea-49e0-a997-1871aed0134f) | ![A screenshot of the OS deprecation dialog](https://github.com/oppia/oppia-android/assets/18438114/fe4fd5dc-5461-4d12-99d7-8eb3c9160aaf) ## Videos of the dialogs in action ### Forced Deprecation Dialog https://github.com/oppia/oppia-android/assets/18438114/45e8319b-89dd-479a-a41d-5c458707ea50 ### Optional Deprecation Dialog https://github.com/oppia/oppia-android/assets/18438114/32343ff1-34be-4c53-bb64-a4624a15ea86 ### OS Deprecation Dialog https://github.com/oppia/oppia-android/assets/18438114/7d289d25-b7ba-4362-8c4f-a2f8a27f8e63 ## Essential Checklist - [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 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)) - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --------- Co-authored-by: Kenneth Murerwa Co-authored-by: Ben Henning --- app/src/main/AndroidManifest.xml | 12 + .../app/fragment/FragmentComponentImpl.kt | 11 +- .../notice/DeprecationNoticeActionListener.kt | 9 + ...orcedAppDeprecationNoticeDialogFragment.kt | 29 ++ ...eprecationNoticeDialogFragmentPresenter.kt | 47 +++ ...ionalAppDeprecationNoticeDialogFragment.kt | 30 ++ ...eprecationNoticeDialogFragmentPresenter.kt | 47 +++ .../OsDeprecationNoticeDialogFragment.kt | 33 +++ ...eprecationNoticeDialogFragmentPresenter.kt | 42 +++ .../android/app/notice/testing/BUILD.bazel | 39 +++ ...ecationNoticeDialogFragmentTestActivity.kt | 30 ++ ...ecationNoticeDialogFragmentTestActivity.kt | 30 ++ ...ecationNoticeDialogFragmentTestActivity.kt | 30 ++ .../android/app/splash/SplashActivity.kt | 16 +- .../app/splash/SplashActivityPresenter.kt | 39 ++- app/src/main/res/values/strings.xml | 45 ++- .../org/oppia/android/app/notice/BUILD.bazel | 84 ++++++ ...dAppDeprecationNoticeDialogFragmentTest.kt | 274 +++++++++++++++++ ...lAppDeprecationNoticeDialogFragmentTest.kt | 276 ++++++++++++++++++ .../OsDeprecationNoticeDialogFragmentTest.kt | 258 ++++++++++++++++ scripts/assets/test_file_exemptions.textproto | 7 + 21 files changed, 1381 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/org/oppia/android/app/notice/DeprecationNoticeActionListener.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragment.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentPresenter.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragment.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentPresenter.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragment.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentPresenter.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/testing/ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/testing/OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/testing/OsDeprecationNoticeDialogFragmentTestActivity.kt create mode 100644 app/src/sharedTest/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentTest.kt create mode 100644 app/src/sharedTest/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentTest.kt create mode 100644 app/src/sharedTest/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentTest.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 897fc034879..c324a1a7119 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -92,6 +92,18 @@ android:name=".app.notice.testing.GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity" android:label="@string/test_activity_label" android:theme="@style/OppiaThemeWithoutActionBar" /> + + + + deprecationNoticeActionListener.onActionButtonClicked( + DeprecationNoticeActionType.UPDATE + ) + } + .setNegativeButton(R.string.forced_app_update_dialog_close_button_text) { _, _ -> + deprecationNoticeActionListener.onActionButtonClicked( + DeprecationNoticeActionType.CLOSE + ) + } + .setCancelable(false) + .create() + dialog.setCanceledOnTouchOutside(false) + return dialog + } +} diff --git a/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragment.kt b/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragment.kt new file mode 100644 index 00000000000..29eb21fd005 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragment.kt @@ -0,0 +1,30 @@ +package org.oppia.android.app.notice + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import org.oppia.android.app.fragment.FragmentComponentImpl +import org.oppia.android.app.fragment.InjectableDialogFragment +import javax.inject.Inject + +/** Dialog fragment that informs the user that a new app version is available for download. */ +class OptionalAppDeprecationNoticeDialogFragment : InjectableDialogFragment() { + companion object { + /** Returns a new instance of [OptionalAppDeprecationNoticeDialogFragment]. */ + fun newInstance(): OptionalAppDeprecationNoticeDialogFragment { + return OptionalAppDeprecationNoticeDialogFragment() + } + } + + @Inject lateinit var optionalAppDeprecationNoticeDialogFragmentPresenter: + OptionalAppDeprecationNoticeDialogFragmentPresenter + + override fun onAttach(context: Context) { + super.onAttach(context) + (fragmentComponent as FragmentComponentImpl).inject(this) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return optionalAppDeprecationNoticeDialogFragmentPresenter.handleOnCreateDialog() + } +} diff --git a/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentPresenter.kt new file mode 100644 index 00000000000..be4b938c522 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentPresenter.kt @@ -0,0 +1,47 @@ +package org.oppia.android.app.notice + +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 optional update dialog to the user. */ +class OptionalAppDeprecationNoticeDialogFragmentPresenter @Inject constructor( + private val activity: AppCompatActivity, + private val resourceHandler: AppLanguageResourceHandler +) { + private val deprecationNoticeActionListener by lazy { + activity as DeprecationNoticeActionListener + } + + /** Handles dialog creation for the optional app deprecation notice. */ + fun handleOnCreateDialog(): Dialog { + val appName = resourceHandler.getStringInLocale(R.string.app_name) + + val dialog = AlertDialog.Builder(activity, R.style.DeprecationAlertDialogTheme) + .setTitle(R.string.optional_app_update_dialog_title) + .setMessage( + resourceHandler.getStringInLocaleWithWrapping( + R.string.optional_app_update_dialog_message, + appName + ) + ) + .setPositiveButton(R.string.optional_app_update_dialog_update_button_text) { _, _ -> + deprecationNoticeActionListener.onActionButtonClicked( + DeprecationNoticeActionType.UPDATE + ) + } + .setNegativeButton(R.string.optional_app_update_dialog_dismiss_button_text) { _, _ -> + deprecationNoticeActionListener.onActionButtonClicked( + DeprecationNoticeActionType.DISMISS + ) + } + .setCancelable(false) + .create() + dialog.setCanceledOnTouchOutside(false) + return dialog + } +} diff --git a/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragment.kt b/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragment.kt new file mode 100644 index 00000000000..48e5fb59181 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragment.kt @@ -0,0 +1,33 @@ +package org.oppia.android.app.notice + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import org.oppia.android.app.fragment.FragmentComponentImpl +import org.oppia.android.app.fragment.InjectableDialogFragment +import javax.inject.Inject + +/** + * Dialog fragment that informs the user that their phone OS is no longer supported by Oppia and + * they will no longer be able to update their app to the latest version. + */ +class OsDeprecationNoticeDialogFragment : InjectableDialogFragment() { + companion object { + /** Returns a new instance of [OsDeprecationNoticeDialogFragment]. */ + fun newInstance(): OsDeprecationNoticeDialogFragment { + return OsDeprecationNoticeDialogFragment() + } + } + + @Inject lateinit var osDeprecationNoticeDialogFragmentPresenter: + OsDeprecationNoticeDialogFragmentPresenter + + override fun onAttach(context: Context) { + super.onAttach(context) + (fragmentComponent as FragmentComponentImpl).inject(this) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return osDeprecationNoticeDialogFragmentPresenter.handleOnCreateDialog() + } +} diff --git a/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentPresenter.kt new file mode 100644 index 00000000000..efcfc84ed20 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentPresenter.kt @@ -0,0 +1,42 @@ +package org.oppia.android.app.notice + +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 OS deprecation dialog to the user. */ +class OsDeprecationNoticeDialogFragmentPresenter @Inject constructor( + private val activity: AppCompatActivity, + private val resourceHandler: AppLanguageResourceHandler +) { + private val deprecationNoticeActionListener by lazy { + activity as DeprecationNoticeActionListener + } + + /** Handles dialog creation for the OS deprecation notice. */ + fun handleOnCreateDialog(): Dialog { + val appName = resourceHandler.getStringInLocale(R.string.app_name) + + val dialog = AlertDialog.Builder(activity, R.style.DeprecationAlertDialogTheme) + .setTitle(R.string.os_deprecation_dialog_title) + .setMessage( + resourceHandler.getStringInLocaleWithWrapping( + R.string.os_deprecation_dialog_message, + appName + ) + ) + .setNegativeButton(R.string.os_deprecation_dialog_dismiss_button_text) { _, _ -> + deprecationNoticeActionListener.onActionButtonClicked( + DeprecationNoticeActionType.DISMISS + ) + } + .setCancelable(false) + .create() + dialog.setCanceledOnTouchOutside(false) + return dialog + } +} diff --git a/app/src/main/java/org/oppia/android/app/notice/testing/BUILD.bazel b/app/src/main/java/org/oppia/android/app/notice/testing/BUILD.bazel index 92f1a0ae62b..00ba0065c2b 100644 --- a/app/src/main/java/org/oppia/android/app/notice/testing/BUILD.bazel +++ b/app/src/main/java/org/oppia/android/app/notice/testing/BUILD.bazel @@ -18,6 +18,19 @@ kt_android_library( ], ) +kt_android_library( + name = "forced_app_deprecation_notice_dialog_fragment_test_activity", + testonly = True, + srcs = [ + "ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt", + ], + visibility = ["//app:app_testing_visibility"], + deps = [ + "//app", + "//app/src/main/java/org/oppia/android/app/testing/activity:test_activity", + ], +) + kt_android_library( name = "general_availability_upgrade_notice_dialog_fragment_test_activity", testonly = True, @@ -31,4 +44,30 @@ kt_android_library( ], ) +kt_android_library( + name = "optional_app_deprecation_notice_dialog_fragment_test_activity", + testonly = True, + srcs = [ + "OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt", + ], + visibility = ["//app:app_testing_visibility"], + deps = [ + "//app", + "//app/src/main/java/org/oppia/android/app/testing/activity:test_activity", + ], +) + +kt_android_library( + name = "os_deprecation_notice_dialog_fragment_test_activity", + testonly = True, + srcs = [ + "OsDeprecationNoticeDialogFragmentTestActivity.kt", + ], + visibility = ["//app:app_testing_visibility"], + deps = [ + "//app", + "//app/src/main/java/org/oppia/android/app/testing/activity:test_activity", + ], +) + dagger_rules() diff --git a/app/src/main/java/org/oppia/android/app/notice/testing/ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt b/app/src/main/java/org/oppia/android/app/notice/testing/ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt new file mode 100644 index 00000000000..e6b223af14d --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/testing/ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt @@ -0,0 +1,30 @@ +package org.oppia.android.app.notice.testing + +import android.os.Bundle +import org.oppia.android.app.notice.DeprecationNoticeActionListener +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. */ +class ForcedAppDeprecationNoticeDialogFragmentTestActivity : + TestActivity(), + DeprecationNoticeActionListener { + /** + * [DeprecationNoticeActionListener] that must be initialized by the test, and is presumed to be a + * Mockito mock (though this is not, strictly speaking, required). + * + * This listener will be used as the callback for the dialog in response to UI operations. + */ + lateinit var mockCallbackListener: DeprecationNoticeActionListener + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + ForcedAppDeprecationNoticeDialogFragment.newInstance() + .showNow(supportFragmentManager, "forced_app_deprecation_dialog") + } + + override fun onActionButtonClicked(noticeType: DeprecationNoticeActionType) { + mockCallbackListener.onActionButtonClicked(noticeType) + } +} diff --git a/app/src/main/java/org/oppia/android/app/notice/testing/OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt b/app/src/main/java/org/oppia/android/app/notice/testing/OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt new file mode 100644 index 00000000000..d3ffd8b519e --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/testing/OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt @@ -0,0 +1,30 @@ +package org.oppia.android.app.notice.testing + +import android.os.Bundle +import org.oppia.android.app.notice.DeprecationNoticeActionListener +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. */ +class OptionalAppDeprecationNoticeDialogFragmentTestActivity : + TestActivity(), + DeprecationNoticeActionListener { + /** + * [DeprecationNoticeActionListener] that must be initialized by the test, and is presumed to be a + * Mockito mock (though this is not, strictly speaking, required). + * + * This listener will be used as the callback for the dialog in response to UI operations. + */ + lateinit var mockCallbackListener: DeprecationNoticeActionListener + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + OptionalAppDeprecationNoticeDialogFragment.newInstance() + .showNow(supportFragmentManager, "optional_app_deprecation_dialog") + } + + override fun onActionButtonClicked(noticeType: DeprecationNoticeActionType) { + mockCallbackListener.onActionButtonClicked(noticeType) + } +} diff --git a/app/src/main/java/org/oppia/android/app/notice/testing/OsDeprecationNoticeDialogFragmentTestActivity.kt b/app/src/main/java/org/oppia/android/app/notice/testing/OsDeprecationNoticeDialogFragmentTestActivity.kt new file mode 100644 index 00000000000..13923f43fd8 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/testing/OsDeprecationNoticeDialogFragmentTestActivity.kt @@ -0,0 +1,30 @@ +package org.oppia.android.app.notice.testing + +import android.os.Bundle +import org.oppia.android.app.notice.DeprecationNoticeActionListener +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. */ +class OsDeprecationNoticeDialogFragmentTestActivity : + TestActivity(), + DeprecationNoticeActionListener { + /** + * [DeprecationNoticeActionListener] that must be initialized by the test, and is presumed to be a + * Mockito mock (though this is not, strictly speaking, required). + * + * This listener will be used as the callback for the dialog in response to UI operations. + */ + lateinit var mockCallbackListener: DeprecationNoticeActionListener + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + OsDeprecationNoticeDialogFragment.newInstance() + .showNow(supportFragmentManager, "os_deprecation_dialog") + } + + override fun onActionButtonClicked(noticeType: DeprecationNoticeActionType) { + mockCallbackListener.onActionButtonClicked(noticeType) + } +} diff --git a/app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt b/app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt index 1952e72ae3b..f9310821ddb 100644 --- a/app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt +++ b/app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt @@ -16,6 +16,16 @@ import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeClosedListen 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. @@ -47,10 +57,12 @@ class SplashActivity : override fun createFragmentComponent(fragment: Fragment): FragmentComponent { val builderInjector = activityComponent as FragmentComponentBuilderInjector - return builderInjector.getFragmentComponentBuilderProvider().get().setFragment(fragment).build() + return builderInjector.getFragmentComponentBuilderProvider().get() + .setFragment(fragment).build() } - override fun onCloseAppButtonClicked() = splashActivityPresenter.handleOnCloseAppButtonClicked() + override fun onCloseAppButtonClicked() = splashActivityPresenter + .handleOnDeprecationNoticeCloseAppButtonClicked() override fun onBetaNoticeOkayButtonClicked(permanentlyDismiss: Boolean) = splashActivityPresenter.handleOnBetaNoticeOkayButtonClicked(permanentlyDismiss) 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 cfdf37874bf..dd926f56612 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,8 @@ package org.oppia.android.app.splash +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.Uri import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import androidx.fragment.app.DialogFragment @@ -64,12 +67,46 @@ class SplashActivityPresenter @Inject constructor( subscribeToOnboardingFlow() } - fun handleOnCloseAppButtonClicked() { + /** 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() { + // 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 + + try { + activity.startActivity( + Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$packageName")) + ) + } catch (e: ActivityNotFoundException) { + activity.startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse( + "https://play.google.com/store/apps/details?id=$packageName" + ) + ) + ) + } + + // Finish splash activity to close the app in anticipation of an update. + activity.finish() + } + + /** Handles cases where the user dismisses the deprecation notice dialog. */ + fun handleOnDeprecationNoticeDialogDismissed() { + // 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)) + activity.finish() + } + /** Handles cases when the user dismisses the beta notice dialog. */ fun handleOnBetaNoticeOkayButtonClicked(permanentlyDismiss: Boolean) { if (permanentlyDismiss) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79ec1534ff1..97547bc1638 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -472,9 +472,48 @@ completed_story_list_recyclerview_tag Please select all correct choices. - Unsupported app version - This version of the app is no longer supported. Please update it through the Play Store. - Close app + + Unsupported app version + + + This version of the app is no longer supported. Please update it through the Play Store. + + + Close app + + + App update required + + + A new version of %s is now available. The new version is more secure, and improves your learning experience.\n\nThis version is no longer supported. To continue using the app, please update to the latest version. + + + Update + + + Close app + + + New update available + + + A new version of %s is now available. We recommend that you update the app for bug fixes and a better learning experience. + + + Dismiss + + + Update + + + Update your Android OS + + + We recommend updating your Android OS to take advantage of %s\'s new features and lessons.\n\nVisit your phone\'s Settings app to update your OS. + + + Dismiss + Developer Build Alpha Beta diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/BUILD.bazel b/app/src/sharedTest/java/org/oppia/android/app/notice/BUILD.bazel index 20d4828910e..d0733552b3b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/notice/BUILD.bazel +++ b/app/src/sharedTest/java/org/oppia/android/app/notice/BUILD.bazel @@ -80,4 +80,88 @@ app_test( ], ) +app_test( + name = "ForcedAppDeprecationNoticeDialogFragmentTest", + processed_src = test_with_resources("ForcedAppDeprecationNoticeDialogFragmentTest.kt"), + test_class = "org.oppia.android.app.notice.ForcedAppDeprecationNoticeDialogFragmentTest", + deps = [ + ":dagger", + "//app", + "//app/src/main/java/org/oppia/android/app/application:application_component", + "//app/src/main/java/org/oppia/android/app/application:application_injector", + "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", + "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", + "//app/src/main/java/org/oppia/android/app/notice/testing:forced_app_deprecation_notice_dialog_fragment_test_activity", + "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_espresso_espresso-core", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/accessibility:test_module", + "//utility/src/main/java/org/oppia/android/util/caching/testing:caching_test_module", + "//utility/src/main/java/org/oppia/android/util/logging:standard_event_logging_configuration_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + ], +) + +app_test( + name = "OptionalAppDeprecationNoticeDialogFragmentTest", + processed_src = test_with_resources("OptionalAppDeprecationNoticeDialogFragmentTest.kt"), + test_class = "org.oppia.android.app.notice.OptionalAppDeprecationNoticeDialogFragmentTest", + deps = [ + ":dagger", + "//app", + "//app/src/main/java/org/oppia/android/app/application:application_component", + "//app/src/main/java/org/oppia/android/app/application:application_injector", + "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", + "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", + "//app/src/main/java/org/oppia/android/app/notice/testing:optional_app_deprecation_notice_dialog_fragment_test_activity", + "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_espresso_espresso-core", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/accessibility:test_module", + "//utility/src/main/java/org/oppia/android/util/caching/testing:caching_test_module", + "//utility/src/main/java/org/oppia/android/util/logging:standard_event_logging_configuration_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + ], +) + +app_test( + name = "OsDeprecationNoticeDialogFragmentTest", + processed_src = test_with_resources("OsDeprecationNoticeDialogFragmentTest.kt"), + test_class = "org.oppia.android.app.notice.OsDeprecationNoticeDialogFragmentTest", + deps = [ + ":dagger", + "//app", + "//app/src/main/java/org/oppia/android/app/application:application_component", + "//app/src/main/java/org/oppia/android/app/application:application_injector", + "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", + "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", + "//app/src/main/java/org/oppia/android/app/notice/testing:os_deprecation_notice_dialog_fragment_test_activity", + "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_espresso_espresso-core", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/accessibility:test_module", + "//utility/src/main/java/org/oppia/android/util/caching/testing:caching_test_module", + "//utility/src/main/java/org/oppia/android/util/logging:standard_event_logging_configuration_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + ], +) + dagger_rules() diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentTest.kt new file mode 100644 index 00000000000..d0b1aa4fc7e --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentTest.kt @@ -0,0 +1,274 @@ +package org.oppia.android.app.notice + +import android.app.Application +import android.content.Context +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.RootMatchers.isDialog +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.BindsInstance +import dagger.Component +import org.hamcrest.Matcher +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.oppia.android.R +import org.oppia.android.app.activity.ActivityComponent +import org.oppia.android.app.activity.ActivityComponentFactory +import org.oppia.android.app.activity.route.ActivityRouterModule +import org.oppia.android.app.application.ApplicationComponent +import org.oppia.android.app.application.ApplicationInjector +import org.oppia.android.app.application.ApplicationInjectorProvider +import org.oppia.android.app.application.ApplicationModule +import org.oppia.android.app.application.ApplicationStartupListenerModule +import org.oppia.android.app.application.testing.TestingBuildFlavorModule +import org.oppia.android.app.devoptions.DeveloperOptionsModule +import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.notice.testing.ForcedAppDeprecationNoticeDialogFragmentTestActivity +import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule +import org.oppia.android.app.shim.ViewBindingShimModule +import org.oppia.android.app.splash.DeprecationNoticeActionType +import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule +import org.oppia.android.data.backends.gae.NetworkConfigProdModule +import org.oppia.android.data.backends.gae.NetworkModule +import org.oppia.android.domain.classify.InteractionsModule +import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule +import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule +import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule +import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule +import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule +import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule +import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule +import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule +import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule +import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule +import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule +import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule +import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule +import org.oppia.android.domain.exploration.ExplorationProgressModule +import org.oppia.android.domain.exploration.ExplorationStorageModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigFastShowTestModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule +import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule +import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule +import org.oppia.android.domain.platformparameter.PlatformParameterModule +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.question.QuestionModule +import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.testing.OppiaTestRule +import org.oppia.android.testing.TestImageLoaderModule +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.junit.InitializeDefaultLocaleRule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.accessibility.AccessibilityTestModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.caching.testing.CachingTestModule +import org.oppia.android.util.gcsresource.GcsResourceModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.EventLoggingConfigurationModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.SyncStatusModule +import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule +import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule +import org.oppia.android.util.parser.image.ImageParsingModule +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [ForcedAppDeprecationNoticeDialogFragment]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@Config( + application = ForcedAppDeprecationNoticeDialogFragmentTest.TestApplication::class, + qualifiers = "port-xxhdpi" +) +@LooperMode(LooperMode.Mode.PAUSED) +class ForcedAppDeprecationNoticeDialogFragmentTest { + @get:Rule + val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + + @get:Rule + val oppiaTestRule = OppiaTestRule() + + @field:[Rule JvmField] val mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @Mock + lateinit var mockDeprecationNoticeActionListener: DeprecationNoticeActionListener + + @Inject + lateinit var context: Context + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testFragment_hasExpectedTitle() { + launchForcedAppDeprecationNoticeDialogFragmentTestActivity { + onDialogView(withText(R.string.forced_app_update_dialog_title)).check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_hasExpectedContentMessageTextUnderTitle() { + launchForcedAppDeprecationNoticeDialogFragmentTestActivity { + val appName = context.resources.getString(R.string.app_name) + val expectedString = context.resources.getString( + R.string.forced_app_update_dialog_message, + appName + ) + onDialogView(withText(expectedString)).check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_hasUpdateButton() { + launchForcedAppDeprecationNoticeDialogFragmentTestActivity { + onDialogView(withText(R.string.forced_app_update_dialog_update_button_text)) + .check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_clickOnUpdateButton_callsCallbackListener_withUpdateDeprecationActionType() { + launchForcedAppDeprecationNoticeDialogFragmentTestActivity { + clickOnDialogView(withText(R.string.forced_app_update_dialog_update_button_text)) + + verify(mockDeprecationNoticeActionListener) + .onActionButtonClicked(DeprecationNoticeActionType.UPDATE) + } + } + + @Test + fun testFragment_hasCloseAppButton() { + launchForcedAppDeprecationNoticeDialogFragmentTestActivity { + onDialogView(withText(R.string.forced_app_update_dialog_close_button_text)) + .check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_clockOnCloseAppButton_callsCallbackListener_withCloseDeprecationActionType() { + launchForcedAppDeprecationNoticeDialogFragmentTestActivity { + clickOnDialogView(withText(R.string.forced_app_update_dialog_close_button_text)) + + verify(mockDeprecationNoticeActionListener) + .onActionButtonClicked(DeprecationNoticeActionType.CLOSE) + } + } + + private fun launchForcedAppDeprecationNoticeDialogFragmentTestActivity( + testBlock: () -> Unit + ) { + // Launch the test activity, but make sure that it's properly set up & time is given for it to + // initialize. + ActivityScenario.launch( + ForcedAppDeprecationNoticeDialogFragmentTestActivity::class.java + ).use { scenario -> + scenario.onActivity { it.mockCallbackListener = mockDeprecationNoticeActionListener } + testCoroutineDispatchers.runCurrent() + testBlock() + } + } + + private fun clickOnDialogView(matcher: Matcher) { + onDialogView(matcher).perform(ViewActions.click()) + testCoroutineDispatchers.runCurrent() + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + private companion object { + private fun onDialogView(matcher: Matcher) = onView(matcher).inRoot(isDialog()) + } + + @Singleton + @Component( + modules = [ + RobolectricModule::class, PlatformParameterModule::class, + TestDispatcherModule::class, ApplicationModule::class, LoggerModule::class, + ContinueModule::class, FractionInputModule::class, ItemSelectionInputModule::class, + MultipleChoiceInputModule::class, NumberWithUnitsRuleModule::class, + NumericInputRuleModule::class, TextInputRuleModule::class, DragDropSortInputModule::class, + ImageClickInputModule::class, InteractionsModule::class, GcsResourceModule::class, + TestImageLoaderModule::class, ImageParsingModule::class, HtmlParserEntityTypeModule::class, + QuestionModule::class, TestLogReportingModule::class, AccessibilityTestModule::class, + LogStorageModule::class, PrimeTopicAssetsControllerModule::class, + ExpirationMetaDataRetrieverModule::class, ViewBindingShimModule::class, + RatioInputModule::class, ApplicationStartupListenerModule::class, + HintsAndSolutionConfigFastShowTestModule::class, HintsAndSolutionProdModule::class, + WorkManagerConfigurationModule::class, LogReportWorkerModule::class, + FirebaseLogUploaderModule::class, FakeOppiaClockModule::class, + DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, + ExplorationStorageModule::class, NetworkConnectionUtilDebugModule::class, + NetworkConnectionDebugUtilModule::class, NetworkModule::class, NetworkConfigProdModule::class, + AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, + PlatformParameterSingletonModule::class, NumericExpressionInputModule::class, + AlgebraicExpressionInputModule::class, MathEquationInputModule::class, + SplitScreenInteractionModule::class, LoggingIdentifierModule::class, + ApplicationLifecycleModule::class, SyncStatusModule::class, TestingBuildFlavorModule::class, + CachingTestModule::class, MetricLogSchedulerModule::class, + EventLoggingConfigurationModule::class, ActivityRouterModule::class, + CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class + ] + ) + + interface TestApplicationComponent : ApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: ForcedAppDeprecationNoticeDialogFragmentTest) + } + + class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerForcedAppDeprecationNoticeDialogFragmentTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: ForcedAppDeprecationNoticeDialogFragmentTest) = component.inject(test) + + override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { + return component.getActivityComponentBuilderProvider().get().setActivity(activity).build() + } + + override fun getApplicationInjector(): ApplicationInjector = component + } +} diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentTest.kt new file mode 100644 index 00000000000..c9545e4e324 --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentTest.kt @@ -0,0 +1,276 @@ +package org.oppia.android.app.notice + +import android.app.Application +import android.content.Context +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.RootMatchers.isDialog +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.BindsInstance +import dagger.Component +import org.hamcrest.Matcher +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.oppia.android.R +import org.oppia.android.app.activity.ActivityComponent +import org.oppia.android.app.activity.ActivityComponentFactory +import org.oppia.android.app.activity.route.ActivityRouterModule +import org.oppia.android.app.application.ApplicationComponent +import org.oppia.android.app.application.ApplicationInjector +import org.oppia.android.app.application.ApplicationInjectorProvider +import org.oppia.android.app.application.ApplicationModule +import org.oppia.android.app.application.ApplicationStartupListenerModule +import org.oppia.android.app.application.testing.TestingBuildFlavorModule +import org.oppia.android.app.devoptions.DeveloperOptionsModule +import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.notice.testing.OptionalAppDeprecationNoticeDialogFragmentTestActivity +import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule +import org.oppia.android.app.shim.ViewBindingShimModule +import org.oppia.android.app.splash.DeprecationNoticeActionType +import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule +import org.oppia.android.data.backends.gae.NetworkConfigProdModule +import org.oppia.android.data.backends.gae.NetworkModule +import org.oppia.android.domain.classify.InteractionsModule +import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule +import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule +import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule +import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule +import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule +import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule +import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule +import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule +import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule +import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule +import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule +import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule +import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule +import org.oppia.android.domain.exploration.ExplorationProgressModule +import org.oppia.android.domain.exploration.ExplorationStorageModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigFastShowTestModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule +import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule +import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule +import org.oppia.android.domain.platformparameter.PlatformParameterModule +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.question.QuestionModule +import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.testing.OppiaTestRule +import org.oppia.android.testing.TestImageLoaderModule +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.junit.InitializeDefaultLocaleRule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.accessibility.AccessibilityTestModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.caching.testing.CachingTestModule +import org.oppia.android.util.gcsresource.GcsResourceModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.EventLoggingConfigurationModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.SyncStatusModule +import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule +import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule +import org.oppia.android.util.parser.image.ImageParsingModule +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [ForcedAppDeprecationNoticeDialogFragment]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@Config( + application = OptionalAppDeprecationNoticeDialogFragmentTest.TestApplication::class, + qualifiers = "port-xxhdpi" +) +@LooperMode(LooperMode.Mode.PAUSED) +class OptionalAppDeprecationNoticeDialogFragmentTest { + @get:Rule + val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + + @get:Rule + val oppiaTestRule = OppiaTestRule() + + @field:[Rule JvmField] val mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @Mock + lateinit var mockDeprecationNoticeActionListener: DeprecationNoticeActionListener + + @Inject + lateinit var context: Context + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testFragment_hasExpectedTitle() { + launchOptionalAppDeprecationNoticeDialogFragmentTestActivity { + onDialogView(withText(R.string.optional_app_update_dialog_title)) + .check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_hasExpectedContentMessageTextUnderTitle() { + launchOptionalAppDeprecationNoticeDialogFragmentTestActivity { + val appName = context.resources.getString(R.string.app_name) + val expectedString = context.resources.getString( + R.string.optional_app_update_dialog_message, + appName + ) + onDialogView(withText(expectedString)).check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_hasUpdateButton() { + launchOptionalAppDeprecationNoticeDialogFragmentTestActivity { + onDialogView(withText(R.string.optional_app_update_dialog_update_button_text)) + .check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_clickOnUpdateButton_callsCallbackListener_withUpdateDeprecationActionType() { + launchOptionalAppDeprecationNoticeDialogFragmentTestActivity { + clickOnDialogView(withText(R.string.optional_app_update_dialog_update_button_text)) + + verify(mockDeprecationNoticeActionListener) + .onActionButtonClicked(DeprecationNoticeActionType.UPDATE) + } + } + + @Test + fun testFragment_hasDismissButton() { + launchOptionalAppDeprecationNoticeDialogFragmentTestActivity { + onDialogView(withText(R.string.optional_app_update_dialog_dismiss_button_text)) + .check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_clickOnDismissButton_callsCallbackListener_withDismissDeprecationActionType() { + launchOptionalAppDeprecationNoticeDialogFragmentTestActivity { + clickOnDialogView(withText(R.string.optional_app_update_dialog_dismiss_button_text)) + + verify(mockDeprecationNoticeActionListener) + .onActionButtonClicked(DeprecationNoticeActionType.DISMISS) + } + } + + private fun launchOptionalAppDeprecationNoticeDialogFragmentTestActivity( + testBlock: () -> Unit + ) { + // Launch the test activity, but make sure that it's properly set up & time is given for it to + // initialize. + ActivityScenario.launch( + OptionalAppDeprecationNoticeDialogFragmentTestActivity::class.java + ).use { scenario -> + scenario.onActivity { it.mockCallbackListener = mockDeprecationNoticeActionListener } + testCoroutineDispatchers.runCurrent() + testBlock() + } + } + + private fun clickOnDialogView(matcher: Matcher) { + onDialogView(matcher).perform(ViewActions.click()) + testCoroutineDispatchers.runCurrent() + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + private companion object { + private fun onDialogView(matcher: Matcher) = Espresso.onView(matcher) + .inRoot(isDialog()) + } + + @Singleton + @Component( + modules = [ + RobolectricModule::class, PlatformParameterModule::class, + TestDispatcherModule::class, ApplicationModule::class, LoggerModule::class, + ContinueModule::class, FractionInputModule::class, ItemSelectionInputModule::class, + MultipleChoiceInputModule::class, NumberWithUnitsRuleModule::class, + NumericInputRuleModule::class, TextInputRuleModule::class, DragDropSortInputModule::class, + ImageClickInputModule::class, InteractionsModule::class, GcsResourceModule::class, + TestImageLoaderModule::class, ImageParsingModule::class, HtmlParserEntityTypeModule::class, + QuestionModule::class, TestLogReportingModule::class, AccessibilityTestModule::class, + LogStorageModule::class, PrimeTopicAssetsControllerModule::class, + ExpirationMetaDataRetrieverModule::class, ViewBindingShimModule::class, + RatioInputModule::class, ApplicationStartupListenerModule::class, + HintsAndSolutionConfigFastShowTestModule::class, HintsAndSolutionProdModule::class, + WorkManagerConfigurationModule::class, LogReportWorkerModule::class, + FirebaseLogUploaderModule::class, FakeOppiaClockModule::class, + DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, + ExplorationStorageModule::class, NetworkConnectionUtilDebugModule::class, + NetworkConnectionDebugUtilModule::class, NetworkModule::class, NetworkConfigProdModule::class, + AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, + PlatformParameterSingletonModule::class, NumericExpressionInputModule::class, + AlgebraicExpressionInputModule::class, MathEquationInputModule::class, + SplitScreenInteractionModule::class, LoggingIdentifierModule::class, + ApplicationLifecycleModule::class, SyncStatusModule::class, TestingBuildFlavorModule::class, + CachingTestModule::class, MetricLogSchedulerModule::class, + EventLoggingConfigurationModule::class, ActivityRouterModule::class, + CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class + ] + ) + + interface TestApplicationComponent : ApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: OptionalAppDeprecationNoticeDialogFragmentTest) + } + + class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerOptionalAppDeprecationNoticeDialogFragmentTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: OptionalAppDeprecationNoticeDialogFragmentTest) = component.inject(test) + + override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { + return component.getActivityComponentBuilderProvider().get().setActivity(activity).build() + } + + override fun getApplicationInjector(): ApplicationInjector = component + } +} diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentTest.kt new file mode 100644 index 00000000000..becd031ecdb --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentTest.kt @@ -0,0 +1,258 @@ +package org.oppia.android.app.notice + +import android.app.Application +import android.content.Context +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.RootMatchers.isDialog +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.BindsInstance +import dagger.Component +import org.hamcrest.Matcher +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.oppia.android.R +import org.oppia.android.app.activity.ActivityComponent +import org.oppia.android.app.activity.ActivityComponentFactory +import org.oppia.android.app.activity.route.ActivityRouterModule +import org.oppia.android.app.application.ApplicationComponent +import org.oppia.android.app.application.ApplicationInjector +import org.oppia.android.app.application.ApplicationInjectorProvider +import org.oppia.android.app.application.ApplicationModule +import org.oppia.android.app.application.ApplicationStartupListenerModule +import org.oppia.android.app.application.testing.TestingBuildFlavorModule +import org.oppia.android.app.devoptions.DeveloperOptionsModule +import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.notice.testing.OsDeprecationNoticeDialogFragmentTestActivity +import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule +import org.oppia.android.app.shim.ViewBindingShimModule +import org.oppia.android.app.splash.DeprecationNoticeActionType +import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule +import org.oppia.android.data.backends.gae.NetworkConfigProdModule +import org.oppia.android.data.backends.gae.NetworkModule +import org.oppia.android.domain.classify.InteractionsModule +import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule +import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule +import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule +import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule +import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule +import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule +import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule +import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule +import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule +import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule +import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule +import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule +import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule +import org.oppia.android.domain.exploration.ExplorationProgressModule +import org.oppia.android.domain.exploration.ExplorationStorageModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigFastShowTestModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule +import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule +import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule +import org.oppia.android.domain.platformparameter.PlatformParameterModule +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.question.QuestionModule +import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.testing.OppiaTestRule +import org.oppia.android.testing.TestImageLoaderModule +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.junit.InitializeDefaultLocaleRule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.accessibility.AccessibilityTestModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.caching.testing.CachingTestModule +import org.oppia.android.util.gcsresource.GcsResourceModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.EventLoggingConfigurationModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.SyncStatusModule +import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule +import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule +import org.oppia.android.util.parser.image.ImageParsingModule +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [ForcedAppDeprecationNoticeDialogFragment]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@Config( + application = OsDeprecationNoticeDialogFragmentTest.TestApplication::class, + qualifiers = "port-xxhdpi" +) +@LooperMode(LooperMode.Mode.PAUSED) +class OsDeprecationNoticeDialogFragmentTest { + @get:Rule + val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + + @get:Rule + val oppiaTestRule = OppiaTestRule() + + @field:[Rule JvmField] val mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @Mock + lateinit var mockDeprecationNoticeActionListener: DeprecationNoticeActionListener + + @Inject + lateinit var context: Context + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testFragment_hasExpectedTitle() { + launchOsDeprecationNoticeDialogFragmentTestActivity { + onDialogView(withText(R.string.os_deprecation_dialog_title)) + .check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_hasExpectedContentMessageTextUnderTitle() { + launchOsDeprecationNoticeDialogFragmentTestActivity { + val appName = context.resources.getString(R.string.app_name) + val expectedString = context.resources.getString( + R.string.os_deprecation_dialog_message, + appName + ) + onDialogView(withText(expectedString)).check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_hasDismissButton() { + launchOsDeprecationNoticeDialogFragmentTestActivity { + onDialogView(withText(R.string.os_deprecation_dialog_dismiss_button_text)) + .check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_clickOnDismissButton_callsCallbackListener_withDismissDeprecationActionType() { + launchOsDeprecationNoticeDialogFragmentTestActivity { + clickOnDialogView(withText(R.string.os_deprecation_dialog_dismiss_button_text)) + + verify(mockDeprecationNoticeActionListener) + .onActionButtonClicked(DeprecationNoticeActionType.DISMISS) + } + } + + private fun launchOsDeprecationNoticeDialogFragmentTestActivity( + testBlock: () -> Unit + ) { + // Launch the test activity, but make sure that it's properly set up & time is given for it to + // initialize. + ActivityScenario.launch( + OsDeprecationNoticeDialogFragmentTestActivity::class.java + ).use { scenario -> + scenario.onActivity { it.mockCallbackListener = mockDeprecationNoticeActionListener } + testCoroutineDispatchers.runCurrent() + testBlock() + } + } + + private fun clickOnDialogView(matcher: Matcher) { + onDialogView(matcher).perform(ViewActions.click()) + testCoroutineDispatchers.runCurrent() + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + private companion object { + private fun onDialogView(matcher: Matcher) = Espresso.onView(matcher) + .inRoot(isDialog()) + } + + @Singleton + @Component( + modules = [ + RobolectricModule::class, PlatformParameterModule::class, + TestDispatcherModule::class, ApplicationModule::class, LoggerModule::class, + ContinueModule::class, FractionInputModule::class, ItemSelectionInputModule::class, + MultipleChoiceInputModule::class, NumberWithUnitsRuleModule::class, + NumericInputRuleModule::class, TextInputRuleModule::class, DragDropSortInputModule::class, + ImageClickInputModule::class, InteractionsModule::class, GcsResourceModule::class, + TestImageLoaderModule::class, ImageParsingModule::class, HtmlParserEntityTypeModule::class, + QuestionModule::class, TestLogReportingModule::class, AccessibilityTestModule::class, + LogStorageModule::class, PrimeTopicAssetsControllerModule::class, + ExpirationMetaDataRetrieverModule::class, ViewBindingShimModule::class, + RatioInputModule::class, ApplicationStartupListenerModule::class, + HintsAndSolutionConfigFastShowTestModule::class, HintsAndSolutionProdModule::class, + WorkManagerConfigurationModule::class, LogReportWorkerModule::class, + FirebaseLogUploaderModule::class, FakeOppiaClockModule::class, + DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, + ExplorationStorageModule::class, NetworkConnectionUtilDebugModule::class, + NetworkConnectionDebugUtilModule::class, NetworkModule::class, NetworkConfigProdModule::class, + AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, + PlatformParameterSingletonModule::class, NumericExpressionInputModule::class, + AlgebraicExpressionInputModule::class, MathEquationInputModule::class, + SplitScreenInteractionModule::class, LoggingIdentifierModule::class, + ApplicationLifecycleModule::class, SyncStatusModule::class, TestingBuildFlavorModule::class, + CachingTestModule::class, MetricLogSchedulerModule::class, + EventLoggingConfigurationModule::class, ActivityRouterModule::class, + CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class + ] + ) + + interface TestApplicationComponent : ApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: OsDeprecationNoticeDialogFragmentTest) + } + + class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerOsDeprecationNoticeDialogFragmentTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: OsDeprecationNoticeDialogFragmentTest) = component.inject(test) + + override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { + return 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 699f8b9a9cd..382bd7c1efa 100644 --- a/scripts/assets/test_file_exemptions.textproto +++ b/scripts/assets/test_file_exemptions.textproto @@ -235,8 +235,15 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/MyDownl exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/MyDownloadsViewPagerAdapter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/UpdatesTabFragment.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/UpdatesTabFragmentPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/DeprecationNoticeActionListener.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/ForcedAppDeprecationNoticeDialogFragmentPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/OptionalAppDeprecationNoticeDialogFragmentPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/OsDeprecationNoticeDialogFragmentPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/BetaNoticeDialogFragmentTestActivity.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/ForcedAppDeprecationNoticeDialogFragmentTestActivity.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/OptionalAppDeprecationNoticeDialogFragmentTestActivity.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/OsDeprecationNoticeDialogFragmentTestActivity.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboadingSlideViewModel.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingActivityPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt"