Skip to content

Commit

Permalink
Merge branch 'develop' into centralize-hasprotoextra
Browse files Browse the repository at this point in the history
  • Loading branch information
Vishwajith-Shettigar authored Jul 1, 2024
2 parents 9bc668d + d8f7635 commit 688695b
Show file tree
Hide file tree
Showing 60 changed files with 5,404 additions and 6 deletions.
1 change: 1 addition & 0 deletions app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ VIEW_MODELS_WITH_RESOURCE_IMPORTS = [
"src/main/java/org/oppia/android/app/home/recentlyplayed/PromotedStoryViewModel.kt",
"src/main/java/org/oppia/android/app/home/recentlyplayed/RecentlyPlayedViewModel.kt",
"src/main/java/org/oppia/android/app/home/topiclist/TopicSummaryViewModel.kt",
"src/main/java/org/oppia/android/app/onboarding/CreateProfileViewModel.kt",
"src/main/java/org/oppia/android/app/onboarding/OnboadingSlideViewModel.kt",
"src/main/java/org/oppia/android/app/onboarding/OnboardingViewModel.kt",
"src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicItemViewModel.kt",
Expand Down
13 changes: 12 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,18 @@
android:name=".app.classroom.ClassroomListActivity"
android:label="@string/classroom_list_activity_title"
android:theme="@style/OppiaThemeWithoutActionBar" />

<activity
android:name=".app.onboarding.OnboardingProfileTypeActivity"
android:label="@string/onboarding_profile_type_activity_title"
android:theme="@style/OppiaThemeWithoutActionBar" />
<activity
android:name=".app.onboarding.CreateProfileActivity"
android:label="@string/create_profile_activity_title"
android:theme="@style/OppiaThemeWithoutActionBar" />
<activity
android:name=".app.onboarding.IntroActivity"
android:label="@string/onboarding_learner_intro_activity_title"
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
Expand Up @@ -32,7 +32,10 @@ import org.oppia.android.app.help.thirdparty.ThirdPartyDependencyListActivity
import org.oppia.android.app.home.HomeActivity
import org.oppia.android.app.home.recentlyplayed.RecentlyPlayedActivity
import org.oppia.android.app.mydownloads.MyDownloadsActivity
import org.oppia.android.app.onboarding.CreateProfileActivity
import org.oppia.android.app.onboarding.IntroActivity
import org.oppia.android.app.onboarding.OnboardingActivity
import org.oppia.android.app.onboarding.OnboardingProfileTypeActivity
import org.oppia.android.app.ongoingtopiclist.OngoingTopicListActivity
import org.oppia.android.app.options.AppLanguageActivity
import org.oppia.android.app.options.AudioLanguageActivity
Expand Down Expand Up @@ -220,4 +223,7 @@ interface ActivityComponentImpl :
fun inject(surveyActivity: SurveyActivity)
fun inject(colorBindingAdaptersTestActivity: ColorBindingAdaptersTestActivity)
fun inject(classroomListActivity: ClassroomListActivity)
fun inject(onboardingProfileTypeActivity: OnboardingProfileTypeActivity)
fun inject(createProfileActivity: CreateProfileActivity)
fun inject(introActivity: IntroActivity)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ import org.oppia.android.app.notice.ForcedAppDeprecationNoticeDialogFragment
import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeDialogFragment
import org.oppia.android.app.notice.OptionalAppDeprecationNoticeDialogFragment
import org.oppia.android.app.notice.OsDeprecationNoticeDialogFragment
import org.oppia.android.app.onboarding.CreateProfileFragment
import org.oppia.android.app.onboarding.IntroFragment
import org.oppia.android.app.onboarding.OnboardingFragment
import org.oppia.android.app.onboarding.OnboardingProfileTypeFragment
import org.oppia.android.app.ongoingtopiclist.OngoingTopicListFragment
import org.oppia.android.app.options.AppLanguageFragment
import org.oppia.android.app.options.AudioLanguageFragment
Expand Down Expand Up @@ -196,4 +199,7 @@ interface FragmentComponentImpl : FragmentComponent, ViewComponentBuilderInjecto
fun inject(surveyWelcomeDialogFragment: SurveyWelcomeDialogFragment)
fun inject(surveyOutroDialogFragment: SurveyOutroDialogFragment)
fun inject(classroomListFragment: ClassroomListFragment)
fun inject(onboardingProfileTypeFragment: OnboardingProfileTypeFragment)
fun inject(createProfileFragment: CreateProfileFragment)
fun inject(introFragment: IntroFragment)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.oppia.android.app.onboarding

import android.content.Context
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.ScreenName.CREATE_PROFILE_ACTIVITY
import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.decorateWithScreenName
import javax.inject.Inject

/** Activity for displaying a new learner profile creation flow. */
class CreateProfileActivity : InjectableAutoLocalizedAppCompatActivity() {
@Inject
lateinit var learnerProfileActivityPresenter: CreateProfileActivityPresenter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(activityComponent as ActivityComponentImpl).inject(this)

learnerProfileActivityPresenter.handleOnCreate()
}

companion object {
/** Returns a new [Intent] open a [CreateProfileActivity] with the specified params. */
fun createProfileActivityIntent(context: Context): Intent {
return Intent(context, CreateProfileActivity::class.java).apply {
decorateWithScreenName(CREATE_PROFILE_ACTIVITY)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.oppia.android.app.onboarding

import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import org.oppia.android.R
import org.oppia.android.databinding.CreateProfileActivityBinding
import javax.inject.Inject

private const val TAG_CREATE_PROFILE_ACTIVITY_FRAGMENT = "TAG_CREATE_PROFILE_ACTIVITY_FRAGMENT"

/** Presenter for [CreateProfileActivity]. */
class CreateProfileActivityPresenter @Inject constructor(
private val activity: AppCompatActivity
) {
private lateinit var binding: CreateProfileActivityBinding

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

if (getNewLearnerProfileFragment() == null) {
val createLearnerProfileFragment = CreateProfileFragment()
activity.supportFragmentManager.beginTransaction().add(
R.id.profile_fragment_placeholder,
createLearnerProfileFragment,
TAG_CREATE_PROFILE_ACTIVITY_FRAGMENT
).commitNow()
}
}

private fun getNewLearnerProfileFragment(): CreateProfileFragment? {
return activity.supportFragmentManager.findFragmentByTag(
TAG_CREATE_PROFILE_ACTIVITY_FRAGMENT
) as? CreateProfileFragment
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.oppia.android.app.onboarding

import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
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 javax.inject.Inject

/** Fragment for displaying a new learner profile creation flow. */
class CreateProfileFragment : InjectableFragment() {
@Inject
lateinit var createProfileFragmentPresenter: CreateProfileFragmentPresenter

override fun onAttach(context: Context) {
super.onAttach(context)
(fragmentComponent as FragmentComponentImpl).inject(this)
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
createProfileFragmentPresenter.activityResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
createProfileFragmentPresenter.handleOnActivityResult(result.data)
}
}
return createProfileFragmentPresenter.handleCreateView(inflater, container)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package org.oppia.android.app.onboarding

import android.content.Intent
import android.graphics.PorterDuff
import android.provider.MediaStore
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import org.oppia.android.R
import org.oppia.android.app.fragment.FragmentScope
import org.oppia.android.databinding.CreateProfileFragmentBinding
import org.oppia.android.util.parser.image.ImageLoader
import org.oppia.android.util.parser.image.ImageViewTarget
import javax.inject.Inject

/** Presenter for [CreateProfileFragment]. */
@FragmentScope
class CreateProfileFragmentPresenter @Inject constructor(
private val fragment: Fragment,
private val activity: AppCompatActivity,
private val createProfileViewModel: CreateProfileViewModel,
private val imageLoader: ImageLoader
) {
private lateinit var binding: CreateProfileFragmentBinding
private lateinit var uploadImageView: ImageView
private lateinit var selectedImage: String

/** Launcher for picking an image from device gallery. */
lateinit var activityResultLauncher: ActivityResultLauncher<Intent>

/** Initialize layout bindings. */
fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View {
binding = CreateProfileFragmentBinding.inflate(
inflater,
container,
/* attachToRoot= */ false
)
binding.let {
it.lifecycleOwner = fragment
it.viewModel = createProfileViewModel
}

uploadImageView = binding.createProfileUserImageView

uploadImageView.apply {
setColorFilter(
ResourcesCompat.getColor(
activity.resources,
R.color.component_color_avatar_background_25_color,
null
),
PorterDuff.Mode.DST_OVER
)

imageLoader.loadDrawable(
R.drawable.ic_profile_icon,
ImageViewTarget(this)
)
}

binding.onboardingNavigationContinue.setOnClickListener {
val nickname = binding.createProfileNicknameEdittext.text.toString().trim()

createProfileViewModel.hasErrorMessage.set(nickname.isBlank())

if (createProfileViewModel.hasErrorMessage.get() != true) {
val intent = IntroActivity.createIntroActivity(activity, nickname)
fragment.startActivity(intent)
}
}

binding.createProfileNicknameEdittext.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun afterTextChanged(s: Editable?) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
createProfileViewModel.hasErrorMessage.set(false)
}
})

addViewOnClickListeners(binding)

return binding.root
}

/** Receive the result of image upload and load it into the image view. */
fun handleOnActivityResult(intent: Intent?) {
intent?.let {
binding.createProfilePicturePrompt.visibility = View.GONE
selectedImage =
checkNotNull(intent.data.toString()) { "Could not find the selected image." }
imageLoader.loadBitmap(
selectedImage,
ImageViewTarget(uploadImageView)
)
}
}

private fun addViewOnClickListeners(binding: CreateProfileFragmentBinding) {
val galleryIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)

binding.onboardingNavigationBack.setOnClickListener { activity.finish() }
binding.createProfileEditPictureIcon.setOnClickListener {
activityResultLauncher.launch(
galleryIntent
)
}
binding.createProfilePicturePrompt.setOnClickListener {
activityResultLauncher.launch(
galleryIntent
)
}
binding.createProfileUserImageView.setOnClickListener {
activityResultLauncher.launch(
galleryIntent
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.oppia.android.app.onboarding

import androidx.databinding.ObservableField
import org.oppia.android.app.fragment.FragmentScope
import org.oppia.android.app.viewmodel.ObservableViewModel
import javax.inject.Inject

/** The ViewModel for [CreateProfileFragment]. */
@FragmentScope
class CreateProfileViewModel @Inject constructor() : ObservableViewModel() {

/** ObservableField that tracks whether creating a nickname has triggered an error condition. */
val hasErrorMessage = ObservableField(false)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.oppia.android.app.onboarding

import android.content.Context
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.IntroActivityParams
import org.oppia.android.app.model.ScreenName.INTRO_ACTIVITY
import org.oppia.android.util.extensions.getProtoExtra
import org.oppia.android.util.extensions.putProtoExtra
import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.decorateWithScreenName
import javax.inject.Inject

/** The activity for showing the learner welcome screen. */
class IntroActivity : InjectableAutoLocalizedAppCompatActivity() {
@Inject
lateinit var onboardingLearnerIntroActivityPresenter: IntroActivityPresenter

private lateinit var profileNickname: String

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(activityComponent as ActivityComponentImpl).inject(this)

val params = intent.extractParams()
this.profileNickname = params.profileNickname

onboardingLearnerIntroActivityPresenter.handleOnCreate(profileNickname)
}

companion object {
private const val PARAMS_KEY = "OnboardingIntroActivity.params"

/**
* A convenience function for creating a new [OnboardingLearnerIntroActivity] intent by prefilling
* common params needed by the activity.
*/
fun createIntroActivity(context: Context, profileNickname: String): Intent {
val params = IntroActivityParams.newBuilder()
.setProfileNickname(profileNickname)
.build()
return createOnboardingLearnerIntroActivity(context, params)
}

private fun createOnboardingLearnerIntroActivity(
context: Context,
params: IntroActivityParams
): Intent {
return Intent(context, IntroActivity::class.java).apply {
putProtoExtra(PARAMS_KEY, params)
decorateWithScreenName(INTRO_ACTIVITY)
}
}

private fun Intent.extractParams() =
getProtoExtra(PARAMS_KEY, IntroActivityParams.getDefaultInstance())
}
}
Loading

0 comments on commit 688695b

Please sign in to comment.