Skip to content

Commit

Permalink
Use TranslationController to back audio language.
Browse files Browse the repository at this point in the history
This updates ProfileManagementController to use TranslationController as
the actual source of truth for a profile's audio language setting. It
doesn't move any code away from AudioLanguage, but it does make
OppiaLanguage the true proto backing this setting now (just in a way
hidden from UI code). Long-term, AudioLanguage should be removed.

This updates calling code that needs the audio language property to use
a new getter in ProfileManagementController. The old audioLanguage
property in Profile has been removed (which will result in a setting
regression for users, but this is considered fine since we're in beta
and it's a small regression most users are unlikely to even notice).

French and Chinese have been removed entirely from AudioLanguage since
they aren't valid languages for the app at the moment (per the
OppiaLanguage enum and supported languages configurations).

TranslationController was updated to persist written translation and
audio language settings since, before, only the app language selection
was persisted.
  • Loading branch information
BenHenning committed Aug 15, 2024
1 parent f9106d9 commit 10a72b4
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import androidx.databinding.ObservableField
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import org.oppia.android.app.fragment.FragmentScope
import org.oppia.android.app.model.AudioLanguage
import org.oppia.android.app.model.OppiaLanguage
import org.oppia.android.app.model.Profile
import org.oppia.android.app.model.ProfileId
Expand All @@ -20,7 +20,8 @@ import org.oppia.android.util.data.DataProviders.Companion.combineWith
import org.oppia.android.util.data.DataProviders.Companion.toLiveData
import javax.inject.Inject

/** [ViewModel] for [OptionsFragment]. */
private const val OPTIONS_ITEM_VIEW_MODEL_APP_AUDIO_LANGUAGE_PROVIDER_ID =
"OPTIONS_ITEM_VIEW_MODEL_APP_AUDIO_LANGUAGE_PROVIDER_ID"
private const val OPTIONS_ITEM_VIEW_MODEL_LIST_PROVIDER_ID =
"OPTIONS_ITEM_VIEW_MODEL_LIST_PROVIDER_ID"

Expand Down Expand Up @@ -67,11 +68,14 @@ class OptionControlsViewModel @Inject constructor(
}

private fun createOptionsItemViewModelProvider(): DataProvider<List<OptionsItemViewModel>> {
val appAudioLangProvider =
translationController.getAppLanguage(profileId).combineWith(
profileManagementController.getAudioLanguage(profileId),
OPTIONS_ITEM_VIEW_MODEL_APP_AUDIO_LANGUAGE_PROVIDER_ID
) { appLanguage, audioLanguage -> appLanguage to audioLanguage }
return profileManagementController.getProfile(profileId).combineWith(
translationController.getAppLanguage(profileId),
OPTIONS_ITEM_VIEW_MODEL_LIST_PROVIDER_ID,
::processViewModelList
)
appAudioLangProvider, OPTIONS_ITEM_VIEW_MODEL_LIST_PROVIDER_ID
) { profile, (appLang, audioLang) -> processViewModelList(profile, appLang, audioLang) }
}

private fun processViewModelListsResult(
Expand All @@ -93,12 +97,13 @@ class OptionControlsViewModel @Inject constructor(

private fun processViewModelList(
profile: Profile,
oppiaLanguage: OppiaLanguage
appLanguage: OppiaLanguage,
audioLanguage: AudioLanguage
): List<OptionsItemViewModel> {
return listOfNotNull(
createReadingTextSizeViewModel(profile),
createAppLanguageViewModel(oppiaLanguage),
createAudioLanguageViewModel(profile)
createAppLanguageViewModel(appLanguage),
createAudioLanguageViewModel(audioLanguage)
)
}

Expand All @@ -117,12 +122,14 @@ class OptionControlsViewModel @Inject constructor(
)
}

private fun createAudioLanguageViewModel(profile: Profile): OptionsAudioLanguageViewModel {
private fun createAudioLanguageViewModel(
audioLanguage: AudioLanguage
): OptionsAudioLanguageViewModel {
return OptionsAudioLanguageViewModel(
routeToAudioLanguageListListener,
loadAudioLanguageListListener,
profile.audioLanguage,
resourceHandler.computeLocalizedDisplayName(profile.audioLanguage)
audioLanguage,
resourceHandler.computeLocalizedDisplayName(audioLanguage)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import org.oppia.android.R
import org.oppia.android.app.fragment.FragmentScope
import org.oppia.android.app.model.AudioLanguage
import org.oppia.android.app.model.CellularDataPreference
import org.oppia.android.app.model.Profile
import org.oppia.android.app.model.ProfileId
import org.oppia.android.app.model.Spotlight
import org.oppia.android.app.model.State
Expand Down Expand Up @@ -147,15 +146,15 @@ class AudioFragmentPresenter @Inject constructor(
) as? SpotlightManager
}

private fun getProfileData(): LiveData<String> {
private fun retrieveAudioLanguageCode(): LiveData<String> {
return Transformations.map(
profileManagementController.getProfile(profileId).toLiveData(),
::processGetProfileResult
profileManagementController.getAudioLanguage(profileId).toLiveData(),
::processAudioLanguageResult
)
}

private fun subscribeToAudioLanguageLiveData() {
getProfileData().observe(
retrieveAudioLanguageCode().observe(
activity,
Observer<String> { result ->
audioViewModel.selectedLanguageCode = result
Expand All @@ -165,11 +164,9 @@ class AudioFragmentPresenter @Inject constructor(
}

/** Gets language code by [AudioLanguage]. */
private fun getAudioLanguage(audioLanguage: AudioLanguage): String {
private fun computeLanguageCode(audioLanguage: AudioLanguage): String {
return when (audioLanguage) {
AudioLanguage.HINDI_AUDIO_LANGUAGE -> "hi"
AudioLanguage.FRENCH_AUDIO_LANGUAGE -> "fr"
AudioLanguage.CHINESE_AUDIO_LANGUAGE -> "zh"
AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE -> "pt"
AudioLanguage.ARABIC_LANGUAGE -> "ar"
AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE -> "pcm"
Expand All @@ -178,16 +175,16 @@ class AudioFragmentPresenter @Inject constructor(
}
}

private fun processGetProfileResult(profileResult: AsyncResult<Profile>): String {
val profile = when (profileResult) {
private fun processAudioLanguageResult(languageResult: AsyncResult<AudioLanguage>): String {
val audioLanguage = when (languageResult) {
is AsyncResult.Failure -> {
oppiaLogger.e("AudioFragment", "Failed to retrieve profile", profileResult.error)
Profile.getDefaultInstance()
oppiaLogger.e("AudioFragment", "Failed to retrieve audio language", languageResult.error)
AudioLanguage.AUDIO_LANGUAGE_UNSPECIFIED
}
is AsyncResult.Pending -> Profile.getDefaultInstance()
is AsyncResult.Success -> profileResult.value
is AsyncResult.Pending -> AudioLanguage.AUDIO_LANGUAGE_UNSPECIFIED
is AsyncResult.Success -> languageResult.value
}
return getAudioLanguage(profile.audioLanguage)
return computeLanguageCode(audioLanguage)
}

/** Sets selected language code in presenter and ViewModel. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ class LanguageDialogFragment : InjectableDialogFragment() {
for (languageCode in languageCodeArrayList) {
val audioLanguage = when (machineLocale.run { languageCode.toMachineLowerCase() }) {
"hi" -> AudioLanguage.HINDI_AUDIO_LANGUAGE
"fr" -> AudioLanguage.FRENCH_AUDIO_LANGUAGE
"zh" -> AudioLanguage.CHINESE_AUDIO_LANGUAGE
"pt", "pt-br" -> AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE
"ar" -> AudioLanguage.ARABIC_LANGUAGE
"pcm" -> AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,6 @@ class AppLanguageResourceHandler @Inject constructor(
fun computeLocalizedDisplayName(audioLanguage: AudioLanguage): String {
return when (audioLanguage) {
AudioLanguage.HINDI_AUDIO_LANGUAGE -> getLocalizedDisplayName("hi")
AudioLanguage.FRENCH_AUDIO_LANGUAGE -> getLocalizedDisplayName("fr")
AudioLanguage.CHINESE_AUDIO_LANGUAGE -> getLocalizedDisplayName("zh")
AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE -> getLocalizedDisplayName("pt", "BR")
AudioLanguage.ARABIC_LANGUAGE -> getLocalizedDisplayName("ar", "EG")
AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ import javax.inject.Singleton
class AudioLanguageFragmentTest {
private companion object {
private const val ENGLISH_BUTTON_INDEX = 0
private const val PORTUGUESE_BUTTON_INDEX = 4
private const val ARABIC_BUTTON_INDEX = 5
private const val NIGERIAN_PIDGIN_BUTTON_INDEX = 6
private const val PORTUGUESE_BUTTON_INDEX = 2
private const val ARABIC_BUTTON_INDEX = 3
private const val NIGERIAN_PIDGIN_BUTTON_INDEX = 4
}

@get:Rule val initializeDefaultLocaleRule = InitializeDefaultLocaleRule()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -494,8 +494,6 @@ class AppLanguageResourceHandlerTest {
// TODO(#3793): Remove this once OppiaLanguage is used as the source of truth.
@Test
@Iteration("hi", "lang=HINDI_AUDIO_LANGUAGE", "expectedDisplayText=हिन्दी")
@Iteration("fr", "lang=FRENCH_AUDIO_LANGUAGE", "expectedDisplayText=Français")
@Iteration("zh", "lang=CHINESE_AUDIO_LANGUAGE", "expectedDisplayText=中文")
@Iteration("pr-pt", "lang=BRAZILIAN_PORTUGUESE_LANGUAGE", "expectedDisplayText=Português")
@Iteration("ar", "lang=ARABIC_LANGUAGE", "expectedDisplayText=العربية")
@Iteration("pcm", "lang=NIGERIAN_PIDGIN_LANGUAGE", "expectedDisplayText=Naijá")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import android.provider.MediaStore
import androidx.exifinterface.media.ExifInterface
import kotlinx.coroutines.Deferred
import org.oppia.android.app.model.AudioLanguage
import org.oppia.android.app.model.AudioTranslationLanguageSelection
import org.oppia.android.app.model.DeviceSettings
import org.oppia.android.app.model.OppiaLanguage
import org.oppia.android.app.model.Profile
import org.oppia.android.app.model.ProfileAvatar
import org.oppia.android.app.model.ProfileDatabase
Expand All @@ -22,6 +24,7 @@ import org.oppia.android.domain.oppialogger.LoggingIdentifierController
import org.oppia.android.domain.oppialogger.OppiaLogger
import org.oppia.android.domain.oppialogger.analytics.LearnerAnalyticsLogger
import org.oppia.android.domain.oppialogger.exceptions.ExceptionsController
import org.oppia.android.domain.translation.TranslationController
import org.oppia.android.util.data.AsyncResult
import org.oppia.android.util.data.DataProvider
import org.oppia.android.util.data.DataProviders
Expand Down Expand Up @@ -64,8 +67,8 @@ private const val SET_CURRENT_PROFILE_ID_PROVIDER_ID = "set_current_profile_id_p
private const val UPDATE_READING_TEXT_SIZE_PROVIDER_ID =
"update_reading_text_size_provider_id"
private const val UPDATE_APP_LANGUAGE_PROVIDER_ID = "update_app_language_provider_id"
private const val UPDATE_AUDIO_LANGUAGE_PROVIDER_ID =
"update_audio_language_provider_id"
private const val GET_AUDIO_LANGUAGE_PROVIDER_ID = "get_audio_language_provider_id"
private const val UPDATE_AUDIO_LANGUAGE_PROVIDER_ID = "update_audio_language_provider_id"
private const val UPDATE_LEARNER_ID_PROVIDER_ID = "update_learner_id_provider_id"
private const val SET_SURVEY_LAST_SHOWN_TIMESTAMP_PROVIDER_ID =
"record_survey_last_shown_timestamp_provider_id"
Expand Down Expand Up @@ -93,7 +96,8 @@ class ProfileManagementController @Inject constructor(
private val enableLearnerStudyAnalytics: PlatformParameterValue<Boolean>,
@EnableLoggingLearnerStudyIds
private val enableLoggingLearnerStudyIds: PlatformParameterValue<Boolean>,
private val profileNameValidator: ProfileNameValidator
private val profileNameValidator: ProfileNameValidator,
private val translationController: TranslationController
) {
private var currentProfileId: Int = DEFAULT_LOGGED_OUT_INTERNAL_PROFILE_ID
private val profileDataStore =
Expand Down Expand Up @@ -275,7 +279,6 @@ class ProfileManagementController @Inject constructor(
dateCreatedTimestampMs = oppiaClock.getCurrentTimeMs()
this.isAdmin = isAdmin
readingTextSize = ReadingTextSize.MEDIUM_TEXT_SIZE
audioLanguage = AudioLanguage.ENGLISH_AUDIO_LANGUAGE
numberOfLogins = 0

if (enableLoggingLearnerStudyIds.value) {
Expand Down Expand Up @@ -628,34 +631,52 @@ class ProfileManagementController @Inject constructor(
}
}

/**
* Returns the current audio language configured for the specified profile ID, as possibly set by
* [updateAudioLanguage].
*
* The return [DataProvider] will automatically update for subsequent calls to
* [updateAudioLanguage] for this [profileId].
*/
fun getAudioLanguage(profileId: ProfileId): DataProvider<AudioLanguage> {
return translationController.getAudioTranslationContentLanguage(
profileId
).transform(GET_AUDIO_LANGUAGE_PROVIDER_ID) { oppiaLanguage ->
when (oppiaLanguage) {
OppiaLanguage.UNRECOGNIZED, OppiaLanguage.LANGUAGE_UNSPECIFIED, OppiaLanguage.HINGLISH,
OppiaLanguage.PORTUGUESE, OppiaLanguage.SWAHILI -> AudioLanguage.AUDIO_LANGUAGE_UNSPECIFIED
OppiaLanguage.ARABIC -> AudioLanguage.ARABIC_LANGUAGE
OppiaLanguage.ENGLISH -> AudioLanguage.ENGLISH_AUDIO_LANGUAGE
OppiaLanguage.HINDI -> AudioLanguage.HINDI_AUDIO_LANGUAGE
OppiaLanguage.BRAZILIAN_PORTUGUESE -> AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE
OppiaLanguage.NIGERIAN_PIDGIN -> AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE
}
}
}

/**
* Updates the audio language of the profile.
*
* @param profileId the ID corresponding to the profile being updated.
* @param audioLanguage New audio language for the profile being updated.
* @return a [DataProvider] that indicates the success/failure of this update operation.
* @param profileId the ID corresponding to the profile being updated
* @param audioLanguage New audio language for the profile being updated
* @return a [DataProvider] that indicates the success/failure of this update operation
*/
fun updateAudioLanguage(profileId: ProfileId, audioLanguage: AudioLanguage): DataProvider<Any?> {
val deferred = profileDataStore.storeDataWithCustomChannelAsync(
updateInMemoryCache = true
) {
val profile =
it.profilesMap[profileId.internalId] ?: return@storeDataWithCustomChannelAsync Pair(
it,
ProfileActionStatus.PROFILE_NOT_FOUND
)
val updatedProfile = profile.toBuilder().setAudioLanguage(audioLanguage).build()
val profileDatabaseBuilder = it.toBuilder().putProfiles(
profileId.internalId,
updatedProfile
)
Pair(profileDatabaseBuilder.build(), ProfileActionStatus.SUCCESS)
}
return dataProviders.createInMemoryDataProviderAsync(
UPDATE_AUDIO_LANGUAGE_PROVIDER_ID
) {
return@createInMemoryDataProviderAsync getDeferredResult(profileId, null, deferred)
}
val audioSelection = AudioTranslationLanguageSelection.newBuilder().apply {
this.selectedLanguage = when (audioLanguage) {
AudioLanguage.UNRECOGNIZED, AudioLanguage.AUDIO_LANGUAGE_UNSPECIFIED,
AudioLanguage.NO_AUDIO -> OppiaLanguage.LANGUAGE_UNSPECIFIED
AudioLanguage.ENGLISH_AUDIO_LANGUAGE -> OppiaLanguage.ENGLISH
AudioLanguage.HINDI_AUDIO_LANGUAGE -> OppiaLanguage.HINDI
AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE -> OppiaLanguage.BRAZILIAN_PORTUGUESE
AudioLanguage.ARABIC_LANGUAGE -> OppiaLanguage.ARABIC
AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE -> OppiaLanguage.NIGERIAN_PIDGIN
}
}.build()
// The transformation is needed to reinterpreted the result of the update to 'Any?'.
return translationController.updateAudioTranslationContentLanguage(
profileId, audioSelection
).transform(UPDATE_AUDIO_LANGUAGE_PROVIDER_ID) { value -> value }
}

/**
Expand Down
Loading

0 comments on commit 10a72b4

Please sign in to comment.