Skip to content

Commit

Permalink
Merge branch 'develop' into console-logger-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
adhiamboperes authored Oct 21, 2024
2 parents 3124bf0 + 95699f9 commit 4fd0a5c
Show file tree
Hide file tree
Showing 65 changed files with 2,270 additions and 252 deletions.
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,9 @@
android:name=".app.onboarding.IntroActivity"
android:label="@string/onboarding_learner_intro_activity_title"
android:theme="@style/OppiaThemeWithoutActionBar" />
<activity
android:name=".app.testing.TextInputLayoutBindingAdaptersTestActivity"
android:theme="@style/OppiaThemeWithoutActionBar" />
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package org.oppia.android.app.databinding;

import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.view.View;
import android.widget.AutoCompleteTextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.BindingAdapter;
import com.google.android.material.textfield.TextInputLayout;
import org.oppia.android.app.model.OppiaLanguage;
import org.oppia.android.app.translation.AppLanguageActivityInjectorProvider;
import org.oppia.android.app.translation.AppLanguageResourceHandler;

/** Holds all custom binding adapters that bind to [TextInputLayout]. */
public final class TextInputLayoutBindingAdapters {
Expand All @@ -15,4 +24,37 @@ public static void setErrorMessage(
) {
textInputLayout.setError(errorMessage);
}

/** Binding adapter for setting the text of an [AutoCompleteTextView]. */
@BindingAdapter({"languageSelection", "filter"})
public static void setLanguageSelection(
@NonNull AutoCompleteTextView textView,
@Nullable OppiaLanguage selectedItem,
Boolean filter) {
textView.setText(getAppLanguageResourceHandler(textView)
.computeLocalizedDisplayName(selectedItem), filter);
}

private static AppLanguageResourceHandler getAppLanguageResourceHandler(View view) {
AppLanguageActivityInjectorProvider provider =
(AppLanguageActivityInjectorProvider) getAttachedActivity(view);
return provider.getAppLanguageActivityInjector().getAppLanguageResourceHandler();
}

private static Activity getAttachedActivity(View view) {
Context context = view.getContext();
while (context != null && !(context instanceof Activity)) {
if (!(context instanceof ContextWrapper)) {
throw new IllegalStateException(
"Encountered context in view (" + view + ") that doesn't wrap a parent context: "
+ context
);
}
context = ((ContextWrapper) context).getBaseContext();
}
if (context == null) {
throw new IllegalStateException("Failed to find base Activity for view: " + view);
}
return (Activity) context;
}
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,56 @@
package org.oppia.android.app.onboarding

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.google.android.material.appbar.AppBarLayout
import org.oppia.android.R
import org.oppia.android.app.home.HomeActivity
import org.oppia.android.app.model.AudioLanguageFragmentStateBundle
import org.oppia.android.app.model.AudioTranslationLanguageSelection
import org.oppia.android.app.model.OppiaLanguage
import org.oppia.android.app.model.ProfileId
import org.oppia.android.app.options.AudioLanguageFragment.Companion.FRAGMENT_SAVED_STATE_KEY
import org.oppia.android.app.options.AudioLanguageSelectionViewModel
import org.oppia.android.app.translation.AppLanguageResourceHandler
import org.oppia.android.databinding.AudioLanguageSelectionFragmentBinding
import org.oppia.android.domain.oppialogger.OppiaLogger
import org.oppia.android.domain.translation.TranslationController
import org.oppia.android.util.data.AsyncResult
import org.oppia.android.util.data.DataProviders.Companion.toLiveData
import org.oppia.android.util.extensions.getProto
import org.oppia.android.util.extensions.putProto
import javax.inject.Inject

/** The presenter for [AudioLanguageFragment]. */
class AudioLanguageFragmentPresenter @Inject constructor(
private val fragment: Fragment,
private val activity: AppCompatActivity,
private val appLanguageResourceHandler: AppLanguageResourceHandler,
private val audioLanguageSelectionViewModel: AudioLanguageSelectionViewModel
private val audioLanguageSelectionViewModel: AudioLanguageSelectionViewModel,
private val translationController: TranslationController,
private val oppiaLogger: OppiaLogger
) {
private lateinit var binding: AudioLanguageSelectionFragmentBinding
private lateinit var selectedLanguage: OppiaLanguage
private lateinit var supportedLanguages: List<OppiaLanguage>

/**
* Returns a newly inflated view to render the fragment with an evaluated audio language as the
* initial selected language, based on current locale.
*/
fun handleCreateView(
inflater: LayoutInflater,
container: ViewGroup?
container: ViewGroup?,
profileId: ProfileId,
outState: Bundle?
): View {

// Hide toolbar as it's not needed in this layout. The toolbar is created by a shared activity
// and is required in OptionsFragment.
activity.findViewById<AppBarLayout>(R.id.reading_list_app_bar_layout).visibility = View.GONE
Expand All @@ -41,33 +60,110 @@ class AudioLanguageFragmentPresenter @Inject constructor(
container,
/* attachToRoot= */ false
)
binding.lifecycleOwner = fragment

val savedSelectedLanguage = outState?.getProto(
FRAGMENT_SAVED_STATE_KEY,
AudioLanguageFragmentStateBundle.getDefaultInstance()
)?.selectedLanguage

binding.apply {
lifecycleOwner = fragment
viewModel = audioLanguageSelectionViewModel
}

audioLanguageSelectionViewModel.updateProfileId(profileId)

savedSelectedLanguage?.let {
if (it != OppiaLanguage.LANGUAGE_UNSPECIFIED) {
setSelectedLanguage(it)
} else {
observePreselectedLanguage()
}
} ?: observePreselectedLanguage()

binding.audioLanguageText.text = appLanguageResourceHandler.getStringInLocaleWithWrapping(
R.string.audio_language_fragment_text,
appLanguageResourceHandler.getStringInLocale(R.string.app_name)
)

binding.onboardingNavigationBack.setOnClickListener {
activity.finish()
}
binding.onboardingNavigationBack.setOnClickListener { activity.finish() }

val adapter = ArrayAdapter(
fragment.requireContext(),
R.layout.onboarding_language_dropdown_item,
R.id.onboarding_language_text_view,
audioLanguageSelectionViewModel.availableAudioLanguages
audioLanguageSelectionViewModel.supportedOppiaLanguagesLiveData.observe(
fragment,
{ languages ->
supportedLanguages = languages
val adapter = ArrayAdapter(
fragment.requireContext(),
R.layout.onboarding_language_dropdown_item,
R.id.onboarding_language_text_view,
languages.map { appLanguageResourceHandler.computeLocalizedDisplayName(it) }
)
binding.audioLanguageDropdownList.setAdapter(adapter)
}
)

binding.audioLanguageDropdownList.apply {
setAdapter(adapter)
setText(
audioLanguageSelectionViewModel.defaultLanguageSelection,
false
)
setRawInputType(EditorInfo.TYPE_NULL)

onItemClickListener =
AdapterView.OnItemClickListener { _, _, position, _ ->
val selectedItem = adapter.getItem(position) as? String
selectedItem?.let {
selectedLanguage = supportedLanguages.associateBy { oppiaLanguage ->
appLanguageResourceHandler.computeLocalizedDisplayName(oppiaLanguage)
}[it] ?: OppiaLanguage.ENGLISH
}
}
}

binding.onboardingNavigationContinue.setOnClickListener {
updateSelectedAudioLanguage(selectedLanguage, profileId).also {
val intent = HomeActivity.createHomeActivity(fragment.requireContext(), profileId)
fragment.startActivity(intent)
// Finish this activity as well as all activities immediately below it in the current
// task so that the user cannot navigate back to the onboarding flow by pressing the
// back button once onboarding is complete
fragment.activity?.finishAffinity()
}
}

return binding.root
}

private fun observePreselectedLanguage() {
audioLanguageSelectionViewModel.languagePreselectionLiveData.observe(
fragment,
{ selectedLanguage -> setSelectedLanguage(selectedLanguage) }
)
}

private fun setSelectedLanguage(selectedLanguage: OppiaLanguage) {
this.selectedLanguage = selectedLanguage
audioLanguageSelectionViewModel.selectedAudioLanguage.set(selectedLanguage)
}

private fun updateSelectedAudioLanguage(selectedLanguage: OppiaLanguage, profileId: ProfileId) {
val audioLanguageSelection =
AudioTranslationLanguageSelection.newBuilder().setSelectedLanguage(selectedLanguage).build()
translationController.updateAudioTranslationContentLanguage(profileId, audioLanguageSelection)
.toLiveData().observe(fragment) {
when (it) {
is AsyncResult.Failure ->
oppiaLogger.e(
"AudioLanguageFragment",
"Failed to set the selected language.",
it.error
)
else -> {} // Do nothing.
}
}
}

/** Save the current dropdown selection to be retrieved on configuration change. */
fun handleSavedState(outState: Bundle) {
outState.putProto(
FRAGMENT_SAVED_STATE_KEY,
AudioLanguageFragmentStateBundle.newBuilder().setSelectedLanguage(selectedLanguage).build()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import android.content.Intent
import android.os.Bundle
import org.oppia.android.app.activity.ActivityComponentImpl
import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity
import org.oppia.android.app.model.CreateProfileActivityParams
import org.oppia.android.app.model.ScreenName.CREATE_PROFILE_ACTIVITY
import org.oppia.android.util.extensions.getProtoExtra
import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.decorateWithScreenName
import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId
import javax.inject.Inject

/** Activity for displaying a new learner profile creation flow. */
Expand All @@ -18,7 +21,13 @@ class CreateProfileActivity : InjectableAutoLocalizedAppCompatActivity() {
super.onCreate(savedInstanceState)
(activityComponent as ActivityComponentImpl).inject(this)

learnerProfileActivityPresenter.handleOnCreate()
val profileId = intent.extractCurrentUserProfileId()
val profileType = intent.getProtoExtra(
CREATE_PROFILE_PARAMS_KEY,
CreateProfileActivityParams.getDefaultInstance()
).profileType

learnerProfileActivityPresenter.handleOnCreate(profileId, profileType)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package org.oppia.android.app.onboarding

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import org.oppia.android.R
import org.oppia.android.app.model.CreateProfileFragmentArguments
import org.oppia.android.app.model.ProfileId
import org.oppia.android.app.model.ProfileType
import org.oppia.android.databinding.CreateProfileActivityBinding
import org.oppia.android.util.extensions.putProto
import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.decorateWithUserProfileId
import javax.inject.Inject

/** Argument key for [CreateProfileFragment] arguments. */
const val CREATE_PROFILE_FRAGMENT_ARGS = "CreateProfileFragment.args"

private const val TAG_CREATE_PROFILE_ACTIVITY_FRAGMENT = "TAG_CREATE_PROFILE_ACTIVITY_FRAGMENT"

/** Presenter for [CreateProfileActivity]. */
Expand All @@ -15,14 +24,24 @@ class CreateProfileActivityPresenter @Inject constructor(
private lateinit var binding: CreateProfileActivityBinding

/** Handle creation and binding of the CreateProfileActivity layout. */
fun handleOnCreate() {
fun handleOnCreate(profileId: ProfileId, profileType: ProfileType) {
binding = DataBindingUtil.setContentView(activity, R.layout.create_profile_activity)
binding.apply {
lifecycleOwner = activity
}

if (getNewLearnerProfileFragment() == null) {
val createLearnerProfileFragment = CreateProfileFragment()

val args = Bundle().apply {
val fragmentArgs =
CreateProfileFragmentArguments.newBuilder().setProfileType(profileType).build()
putProto(CREATE_PROFILE_FRAGMENT_ARGS, fragmentArgs)
decorateWithUserProfileId(profileId)
}

createLearnerProfileFragment.arguments = args

activity.supportFragmentManager.beginTransaction().add(
R.id.profile_fragment_placeholder,
createLearnerProfileFragment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import org.oppia.android.app.fragment.FragmentComponentImpl
import org.oppia.android.app.fragment.InjectableFragment
import org.oppia.android.app.model.CreateProfileFragmentArguments
import org.oppia.android.util.extensions.getProto
import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId
import javax.inject.Inject

/** Fragment for displaying a new learner profile creation flow. */
Expand All @@ -33,6 +36,23 @@ class CreateProfileFragment : InjectableFragment() {
createProfileFragmentPresenter.handleOnActivityResult(result.data)
}
}
return createProfileFragmentPresenter.handleCreateView(inflater, container)

val profileId = checkNotNull(arguments?.extractCurrentUserProfileId()) {
"Expected CreateProfileFragment to have a profileId argument."
}
val profileType = checkNotNull(
arguments?.getProto(
CREATE_PROFILE_FRAGMENT_ARGS, CreateProfileFragmentArguments.getDefaultInstance()
)?.profileType
) {
"Expected CreateProfileFragment to have a profileType argument."
}

return createProfileFragmentPresenter.handleCreateView(
inflater,
container,
profileId,
profileType
)
}
}
Loading

0 comments on commit 4fd0a5c

Please sign in to comment.