Skip to content

Commit 05b8859

Browse files
Fix #5981: Option Screen goes blank when user select the preferred audio language as Hindi (#6012)
<!-- READ ME FIRST: Please fill in the explanation section below and check off every point from the Essential Checklist! --> ## Explanation Fix #5981 The current implementation of the Audio Langauge Selection UI relies on the `AudioLanguage` enum to display a list of ausio langauges. This is problematic because some audio languages are not supported in prod. This PR changes the supported audio languages list to be displayed to be derived from the supported `OppiaLanguage`s list. This means that the list will vary for dev or prod depending on the definitions in `supported_languages.textproto`. An in-place migration has also been added in `TranslationController` to help users who had previously selected a language that is now unsupported due to configuration changes, recover gracefully. ## Essential Checklist <!-- Please tick the relevant boxes by putting an "x" in them. --> - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only <!-- Delete these section if this PR does not include UI-related changes. --> If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing |Before|After| |---|---| |https://github.com/user-attachments/assets/f69cb01b-2bf6-4b97-9990-97e0d3348b9b| https://github.com/user-attachments/assets/fce3e8d7-61d7-49d3-8ea2-2271bc5900c9|
1 parent 5d5d066 commit 05b8859

File tree

8 files changed

+81
-34
lines changed

8 files changed

+81
-34
lines changed

app/src/main/java/org/oppia/android/app/onboarding/AudioLanguageFragmentPresenter.kt

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,10 @@ class AudioLanguageFragmentPresenter @Inject constructor(
175175
when (result) {
176176
is AsyncResult.Success -> {
177177
if (parentScreen == ParentScreen.OPTIONS_SCREEN) {
178-
updateAudioLanguage(getAudioLanguageFromOppiaLanguage(selectedLanguage))
178+
updateAudioLanguage(
179+
audioLanguageSelectionViewModel
180+
.getAudioLanguageFromOppiaLanguage(selectedLanguage)
181+
)
179182
}
180183
}
181184
is AsyncResult.Failure ->
@@ -199,18 +202,6 @@ class AudioLanguageFragmentPresenter @Inject constructor(
199202
}
200203
}
201204

202-
private fun getAudioLanguageFromOppiaLanguage(oppiaLanguage: OppiaLanguage): AudioLanguage {
203-
return when (oppiaLanguage) {
204-
OppiaLanguage.UNRECOGNIZED, OppiaLanguage.LANGUAGE_UNSPECIFIED, OppiaLanguage.HINGLISH,
205-
OppiaLanguage.PORTUGUESE, OppiaLanguage.SWAHILI -> AudioLanguage.AUDIO_LANGUAGE_UNSPECIFIED
206-
OppiaLanguage.ARABIC -> AudioLanguage.ARABIC_LANGUAGE
207-
OppiaLanguage.ENGLISH -> AudioLanguage.ENGLISH_AUDIO_LANGUAGE
208-
OppiaLanguage.HINDI -> AudioLanguage.HINDI_AUDIO_LANGUAGE
209-
OppiaLanguage.BRAZILIAN_PORTUGUESE -> AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE
210-
OppiaLanguage.NIGERIAN_PIDGIN -> AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE
211-
}
212-
}
213-
214205
private fun hideNavigationViews(parentScreen: ParentScreen) {
215206
if (parentScreen == ParentScreen.OPTIONS_SCREEN) {
216207
binding.onboardingStepsCount?.visibility = View.GONE

app/src/main/java/org/oppia/android/app/options/AudioLanguageFragmentPresenterV1.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class AudioLanguageFragmentPresenterV1 @Inject constructor(
3333
audioLanguageSelectionViewModel.selectedLanguage.value = audioLanguage
3434
audioLanguageRecyclerView.apply {
3535
viewModel = audioLanguageSelectionViewModel
36+
lifecycleOwner = fragment
3637
adapter = createRecyclerViewAdapter()
3738
}
3839
}.root

app/src/main/java/org/oppia/android/app/options/AudioLanguageSelectionViewModel.kt

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,13 @@ class AudioLanguageSelectionViewModel @Inject constructor(
5252
/** The [AudioLanguage] currently selected in the radio button list. */
5353
val selectedLanguage = MutableLiveData<AudioLanguage>()
5454

55-
/** The list of [AudioLanguageItemViewModel]s which can be bound to a recycler view. */
56-
val recyclerViewAudioLanguageList: List<AudioLanguageItemViewModel> by lazy {
57-
val languages = AudioLanguage.values().filter { it !in IGNORED_AUDIO_LANGUAGES }
58-
val sortedLanguages = languages.sortedWith(
59-
compareBy<AudioLanguage> { it != AudioLanguage.ENGLISH_AUDIO_LANGUAGE }
60-
.thenBy { appLanguageResourceHandler.computeLocalizedDisplayName(it) }
61-
)
62-
sortedLanguages.map(::createItemViewModel)
63-
}
64-
6555
/** Sets the list of audio languages supported by the app based on [OppiaLanguage]. */
66-
val supportedOppiaLanguagesLiveData: LiveData<List<OppiaLanguage>> by lazy {
56+
// TODO(#6020): Replace getSupportedAppLanguages with an audio languages specific API.
57+
val supportedOppiaLanguagesLiveData: LiveData<List<OppiaLanguage>> =
6758
Transformations.map(
6859
translationController.getSupportedAppLanguages().toLiveData()
6960
) { supportedLanguagesResult ->
70-
return@map when (supportedLanguagesResult) {
61+
when (supportedLanguagesResult) {
7162
is AsyncResult.Failure -> {
7263
oppiaLogger.e(
7364
"AudioLanguageFragment",
@@ -80,7 +71,22 @@ class AudioLanguageSelectionViewModel @Inject constructor(
8071
is AsyncResult.Success -> supportedLanguagesResult.value
8172
}
8273
}
83-
}
74+
75+
/** The list of [AudioLanguageItemViewModel]s which can be bound to a recycler view. */
76+
val recyclerViewAudioLanguageList: LiveData<List<AudioLanguageItemViewModel>> =
77+
Transformations.map(supportedOppiaLanguagesLiveData) { languages ->
78+
// Order starting with English in the list, then alphabetically by the localized name of the
79+
// language to match what is done by the Android OS.
80+
val sortedLanguages = languages.sortedWith(
81+
compareBy<OppiaLanguage> { it != OppiaLanguage.ENGLISH }
82+
.thenBy { appLanguageResourceHandler.computeLocalizedDisplayName(it) }
83+
)
84+
85+
sortedLanguages
86+
.map(::getAudioLanguageFromOppiaLanguage)
87+
.filter { it !in IGNORED_AUDIO_LANGUAGES }
88+
.map(::createItemViewModel)
89+
}
8490

8591
private val languagePreselectionProvider: DataProvider<OppiaLanguage> by lazy {
8692
translationController.getAudioLanguagePreselection(profileId)
@@ -91,6 +97,19 @@ class AudioLanguageSelectionViewModel @Inject constructor(
9197
this.profileId = profileId
9298
}
9399

100+
/** Maps an [OppiaLanguage] to an [AudioLanguage]. */
101+
fun getAudioLanguageFromOppiaLanguage(oppiaLanguage: OppiaLanguage): AudioLanguage {
102+
return when (oppiaLanguage) {
103+
OppiaLanguage.UNRECOGNIZED, OppiaLanguage.LANGUAGE_UNSPECIFIED, OppiaLanguage.HINGLISH,
104+
OppiaLanguage.PORTUGUESE, OppiaLanguage.SWAHILI -> AudioLanguage.AUDIO_LANGUAGE_UNSPECIFIED
105+
OppiaLanguage.ARABIC -> AudioLanguage.ARABIC_LANGUAGE
106+
OppiaLanguage.ENGLISH -> AudioLanguage.ENGLISH_AUDIO_LANGUAGE
107+
OppiaLanguage.HINDI -> AudioLanguage.HINDI_AUDIO_LANGUAGE
108+
OppiaLanguage.BRAZILIAN_PORTUGUESE -> AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE
109+
OppiaLanguage.NIGERIAN_PIDGIN -> AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE
110+
}
111+
}
112+
94113
private fun createItemViewModel(language: AudioLanguage): AudioLanguageItemViewModel {
95114
return AudioLanguageItemViewModel(
96115
language,

app/src/main/res/layout-sw600dp/audio_language_fragment.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@
1818
android:paddingBottom="@dimen/audio_language_recycler_view_padding_bottom"
1919
android:scrollbars="none"
2020
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
21-
app:list="@{viewModel.recyclerViewAudioLanguageList}" />
21+
app:data="@{viewModel.recyclerViewAudioLanguageList}" />
2222
</layout>

app/src/main/res/layout/audio_language_fragment.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
android:paddingBottom="@dimen/audio_language_recycler_view_padding_bottom"
2323
android:scrollbars="none"
2424
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
25-
app:list="@{viewModel.recyclerViewAudioLanguageList}" />
25+
app:data="@{viewModel.recyclerViewAudioLanguageList}" />
2626

2727
<View
2828
android:id="@+id/toolbar_shadow_view"

app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ import javax.inject.Singleton
133133
@LooperMode(LooperMode.Mode.PAUSED)
134134
@Config(application = AudioLanguageFragmentTest.TestApplication::class)
135135
class AudioLanguageFragmentTest {
136+
// TODO(#6022): Add tests for validating that when an unsupported language was previously
137+
// selected, it goes back to English
136138
private companion object {
137139
private const val ENGLISH_BUTTON_INDEX = 0
138140
private const val NIGERIAN_PIDGIN_BUTTON_INDEX = 1

domain/src/main/java/org/oppia/android/domain/translation/TranslationController.kt

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.oppia.android.domain.translation
33
import com.google.protobuf.MessageLite
44
import org.oppia.android.app.model.AppLanguageSelection
55
import org.oppia.android.app.model.AudioTranslationLanguageSelection
6+
import org.oppia.android.app.model.AudioTranslationLanguageSelection.SelectionTypeCase
67
import org.oppia.android.app.model.LanguageSupportDefinition
78
import org.oppia.android.app.model.LanguageSupportDefinition.LanguageId.LanguageTypeCase.IETF_BCP47_ID
89
import org.oppia.android.app.model.LanguageSupportDefinition.LanguageId.LanguageTypeCase.LANGUAGETYPE_NOT_SET
@@ -51,6 +52,7 @@ private const val AUDIO_TRANSLATION_CONTENT_LANG_RES_DATA_PROVIDER_ID =
5152
"audio_translation_content_language_resolution"
5253
private const val AUDIO_TRANSLATION_CONTENT_SELECTION_DATA_PROVIDER_ID =
5354
"audio_translation_content_selection"
55+
private const val SUPPORTED_AUDIO_LANGUAGES_DATA_PROVIDER_ID = "supported_audio_languages"
5456
private const val UPDATE_AUDIO_TRANSLATION_CONTENT_DATA_PROVIDER_ID =
5557
"update_audio_translation_content"
5658
private const val PROFILE_AUDIO_LANGUAGE_PROVIDER_ID = "profile_audio_language"
@@ -295,12 +297,29 @@ class TranslationController @Inject constructor(
295297
fun getAudioTranslationContentLocale(
296298
profileId: ProfileId
297299
): DataProvider<OppiaLocale.ContentLocale> {
300+
// TODO(#6020): Replace getSupportedAppLanguages with an audio languages specific API.
298301
val resolvedLanguageProvider =
299302
getAudioTranslationContentLanguageSelection(profileId).combineWith(
300-
getAppLanguageSelection(profileId), AUDIO_TRANSLATION_CONTENT_LANG_RES_DATA_PROVIDER_ID
301-
) { audioLanguageSelection, appLanguageSelection ->
302-
computeAudioTranslationContentLanguage(appLanguageSelection, audioLanguageSelection)
303+
getSupportedAppLanguages(), SUPPORTED_AUDIO_LANGUAGES_DATA_PROVIDER_ID
304+
) { audioLanguageSelection, supportedAppLanguages ->
305+
// Before a profile sets an audio language, LANGUAGE_UNSPECIFIED is always returned.
306+
// In some cases, a language might not be supported but has a fallback configured.
307+
if (audioLanguageSelection.selectedLanguage in supportedAppLanguages ||
308+
audioLanguageSelection.selectionTypeCase == SelectionTypeCase.USE_APP_LANGUAGE ||
309+
audioLanguageSelection.selectedLanguage == OppiaLanguage.LANGUAGE_UNSPECIFIED
310+
) {
311+
audioLanguageSelection
312+
} else {
313+
AudioTranslationLanguageSelection.newBuilder()
314+
.setSelectedLanguage(OppiaLanguage.ENGLISH)
315+
.build()
316+
}
303317
}
318+
.combineWith(
319+
getAppLanguageSelection(profileId), AUDIO_TRANSLATION_CONTENT_LANG_RES_DATA_PROVIDER_ID
320+
) { audioLanguageSelection, appLanguageSelection ->
321+
computeAudioTranslationContentLanguage(appLanguageSelection, audioLanguageSelection)
322+
}
304323
return getSystemLanguage().combineWithAsync(
305324
resolvedLanguageProvider, AUDIO_TRANSLATION_CONTENT_LOCALE_DATA_PROVIDER_ID
306325
) { systemLanguage, resolutionStatus ->
@@ -435,10 +454,9 @@ class TranslationController @Inject constructor(
435454
audioLanguageSelection: AudioTranslationLanguageSelection
436455
): LanguageResolutionStatus {
437456
return when (audioLanguageSelection.selectionTypeCase) {
438-
AudioTranslationLanguageSelection.SelectionTypeCase.SELECTED_LANGUAGE ->
457+
SelectionTypeCase.SELECTED_LANGUAGE ->
439458
LanguageResolutionStatus.Resolved(audioLanguageSelection.selectedLanguage)
440-
AudioTranslationLanguageSelection.SelectionTypeCase.USE_APP_LANGUAGE,
441-
AudioTranslationLanguageSelection.SelectionTypeCase.SELECTIONTYPE_NOT_SET, null ->
459+
SelectionTypeCase.USE_APP_LANGUAGE, SelectionTypeCase.SELECTIONTYPE_NOT_SET, null ->
442460
computeAppLanguage(appLanguageSelection)
443461
}
444462
}

domain/src/test/java/org/oppia/android/domain/translation/TranslationControllerTest.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import org.oppia.android.app.model.OppiaLanguage.ARABIC
2525
import org.oppia.android.app.model.OppiaLanguage.BRAZILIAN_PORTUGUESE
2626
import org.oppia.android.app.model.OppiaLanguage.ENGLISH
2727
import org.oppia.android.app.model.OppiaLanguage.HINDI
28+
import org.oppia.android.app.model.OppiaLanguage.HINGLISH
2829
import org.oppia.android.app.model.OppiaLanguage.LANGUAGE_UNSPECIFIED
2930
import org.oppia.android.app.model.OppiaLanguage.NIGERIAN_PIDGIN
3031
import org.oppia.android.app.model.OppiaLanguage.PORTUGUESE
@@ -1149,6 +1150,21 @@ class TranslationControllerTest {
11491150
assertThat(context.regionDefinition.region).isEqualTo(BRAZIL)
11501151
}
11511152

1153+
@Test
1154+
fun testGetAudioLocale_updateLanguageToHinglish_returnsEnglishLocale() {
1155+
forceDefaultLocale(Locale.ROOT)
1156+
ensureAudioTranslationsLanguageIsUpdatedTo(PROFILE_ID_0, HINGLISH)
1157+
1158+
val localeProvider = translationController.getAudioTranslationContentLocale(PROFILE_ID_0)
1159+
1160+
val locale = monitorFactory.waitForNextSuccessfulResult(localeProvider)
1161+
val context = locale.localeContext
1162+
assertThat(context.usageMode).isEqualTo(AUDIO_TRANSLATIONS)
1163+
assertThat(context.languageDefinition.language).isEqualTo(ENGLISH)
1164+
// This region comes from the default locale.
1165+
assertThat(context.regionDefinition.region).isEqualTo(REGION_UNSPECIFIED)
1166+
}
1167+
11521168
@Test
11531169
fun testGetAudioLocale_updateLanguageToUseApp_returnsAppLanguage() {
11541170
// First, initialize the language to Hindi before overwriting to use the app language.

0 commit comments

Comments
 (0)