From df472536554d53f21181f3e564ea57c0074d7d79 Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Mon, 1 Jul 2024 01:30:19 +0300 Subject: [PATCH] Fix Part of #4938: Introduce Create profile screen (#5380) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Explanation Fix Part of #4938: Add a new Activity and associated Fragments and Presenters to allow a new learner to create a profile. This does not include domain changes. - Learner should be able to click “Continue” if they have entered their nickname, even without adding a profile picture. - Learner should not be able to click “Continue” if they have not entered their nickname and an error message should be displayed. - The learner can select a profile picture. Placeholder tests have been added to ensure navigation tests are not forgotten. These will fail once navigation has been implemented. ## 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 |||| | --- | --- | --- | || Portrait | Landscape | |Mobile Light Mode|![Screenshot_1719774012](https://github.com/oppia/oppia-android/assets/59600948/189f7e62-8761-4d38-b859-e73df23d1221)|![Screenshot_1719774028](https://github.com/oppia/oppia-android/assets/59600948/9e8b01c5-4b60-40aa-94ae-da4093241759)| |Tablet Dark Mode|![Screenshot_1719774236](https://github.com/oppia/oppia-android/assets/59600948/e5e161f9-77e7-4b1d-a2bc-b7b0646b71c2)|![Screenshot_1719774244](https://github.com/oppia/oppia-android/assets/59600948/97820f77-a628-48b0-a325-f929118594bc)| ## All Tests Passing on Espresso ![Screenshot 2024-05-24 at 01 42 41](https://github.com/oppia/oppia-android/assets/59600948/9e301e33-d11f-48a5-9f55-fca3e155b00e) --- app/BUILD.bazel | 1 + app/src/main/AndroidManifest.xml | 5 +- .../app/activity/ActivityComponentImpl.kt | 2 + .../app/fragment/FragmentComponentImpl.kt | 2 + .../app/onboarding/CreateProfileActivity.kt | 32 + .../CreateProfileActivityPresenter.kt | 39 ++ .../app/onboarding/CreateProfileFragment.kt | 38 ++ .../CreateProfileFragmentPresenter.kt | 120 ++++ .../app/onboarding/CreateProfileViewModel.kt | 14 + .../OnboardingProfileTypeFragmentPresenter.kt | 6 + .../drawable/create_profile_picture_icon.xml | 33 ++ ...dit_text_white_background_error_border.xml | 9 + ...edit_text_white_background_with_border.xml | 9 + .../main/res/drawable/ic_outline_edit_24.xml | 5 + app/src/main/res/drawable/ic_profile_icon.xml | 9 + .../layout-land/create_profile_fragment.xml | 155 +++++ .../create_profile_fragment.xml | 153 +++++ .../create_profile_fragment.xml | 159 +++++ .../res/layout/create_profile_activity.xml | 10 + .../res/layout/create_profile_fragment.xml | 163 ++++++ .../main/res/values-night/color_palette.xml | 9 +- app/src/main/res/values/color_defs.xml | 2 +- app/src/main/res/values/color_palette.xml | 7 +- app/src/main/res/values/component_colors.xml | 7 +- app/src/main/res/values/strings.xml | 10 + app/src/main/res/values/styles.xml | 46 ++ .../onboarding/CreateProfileActivityTest.kt | 214 +++++++ .../onboarding/CreateProfileFragmentTest.kt | 553 ++++++++++++++++++ .../OnboardingProfileTypeFragmentTest.kt | 7 +- model/src/main/proto/screens.proto | 3 + scripts/assets/test_file_exemptions.textproto | 12 + .../util/logging/EventBundleCreator.kt | 1 + 32 files changed, 1823 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/org/oppia/android/app/onboarding/CreateProfileActivity.kt create mode 100644 app/src/main/java/org/oppia/android/app/onboarding/CreateProfileActivityPresenter.kt create mode 100644 app/src/main/java/org/oppia/android/app/onboarding/CreateProfileFragment.kt create mode 100644 app/src/main/java/org/oppia/android/app/onboarding/CreateProfileFragmentPresenter.kt create mode 100644 app/src/main/java/org/oppia/android/app/onboarding/CreateProfileViewModel.kt create mode 100644 app/src/main/res/drawable/create_profile_picture_icon.xml create mode 100644 app/src/main/res/drawable/edit_text_white_background_error_border.xml create mode 100644 app/src/main/res/drawable/edit_text_white_background_with_border.xml create mode 100644 app/src/main/res/drawable/ic_outline_edit_24.xml create mode 100644 app/src/main/res/drawable/ic_profile_icon.xml create mode 100644 app/src/main/res/layout-land/create_profile_fragment.xml create mode 100644 app/src/main/res/layout-sw600dp-land/create_profile_fragment.xml create mode 100644 app/src/main/res/layout-sw600dp-port/create_profile_fragment.xml create mode 100644 app/src/main/res/layout/create_profile_activity.xml create mode 100644 app/src/main/res/layout/create_profile_fragment.xml create mode 100644 app/src/sharedTest/java/org/oppia/android/app/onboarding/CreateProfileActivityTest.kt create mode 100644 app/src/sharedTest/java/org/oppia/android/app/onboarding/CreateProfileFragmentTest.kt diff --git a/app/BUILD.bazel b/app/BUILD.bazel index 7ddcebe5300..26ee5509faf 100644 --- a/app/BUILD.bazel +++ b/app/BUILD.bazel @@ -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", diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b25b8ccfc86..0c545bf411e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -340,7 +340,10 @@ android:name=".app.onboarding.OnboardingProfileTypeActivity" android:label="@string/onboarding_profile_type_activity_title" android:theme="@style/OppiaThemeWithoutActionBar" /> - + + if (result.resultCode == Activity.RESULT_OK) { + createProfileFragmentPresenter.handleOnActivityResult(result.data) + } + } + return createProfileFragmentPresenter.handleCreateView(inflater, container) + } +} diff --git a/app/src/main/java/org/oppia/android/app/onboarding/CreateProfileFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/CreateProfileFragmentPresenter.kt new file mode 100644 index 00000000000..d3a57c61988 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/onboarding/CreateProfileFragmentPresenter.kt @@ -0,0 +1,120 @@ +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 + + /** 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()) + } + + 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 + ) + } + } +} diff --git a/app/src/main/java/org/oppia/android/app/onboarding/CreateProfileViewModel.kt b/app/src/main/java/org/oppia/android/app/onboarding/CreateProfileViewModel.kt new file mode 100644 index 00000000000..e6ef763f23c --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/onboarding/CreateProfileViewModel.kt @@ -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) +} diff --git a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingProfileTypeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingProfileTypeFragmentPresenter.kt index 893960b55c7..72ae543dd0c 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/OnboardingProfileTypeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/OnboardingProfileTypeFragmentPresenter.kt @@ -27,6 +27,11 @@ class OnboardingProfileTypeFragmentPresenter @Inject constructor( binding.apply { lifecycleOwner = fragment + profileTypeLearnerNavigationCard.setOnClickListener { + val intent = CreateProfileActivity.createProfileActivityIntent(activity) + fragment.startActivity(intent) + } + profileTypeSupervisorNavigationCard.setOnClickListener { val intent = ProfileChooserActivity.createProfileChooserActivity(activity) fragment.startActivity(intent) @@ -36,6 +41,7 @@ class OnboardingProfileTypeFragmentPresenter @Inject constructor( activity.finish() } } + return binding.root } } diff --git a/app/src/main/res/drawable/create_profile_picture_icon.xml b/app/src/main/res/drawable/create_profile_picture_icon.xml new file mode 100644 index 00000000000..0aa1cd192f0 --- /dev/null +++ b/app/src/main/res/drawable/create_profile_picture_icon.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/edit_text_white_background_error_border.xml b/app/src/main/res/drawable/edit_text_white_background_error_border.xml new file mode 100644 index 00000000000..2851e1fc4cb --- /dev/null +++ b/app/src/main/res/drawable/edit_text_white_background_error_border.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/edit_text_white_background_with_border.xml b/app/src/main/res/drawable/edit_text_white_background_with_border.xml new file mode 100644 index 00000000000..90e111c7c1a --- /dev/null +++ b/app/src/main/res/drawable/edit_text_white_background_with_border.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_outline_edit_24.xml b/app/src/main/res/drawable/ic_outline_edit_24.xml new file mode 100644 index 00000000000..407f3e2f737 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_edit_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_profile_icon.xml b/app/src/main/res/drawable/ic_profile_icon.xml new file mode 100644 index 00000000000..7b7c22f999b --- /dev/null +++ b/app/src/main/res/drawable/ic_profile_icon.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout-land/create_profile_fragment.xml b/app/src/main/res/layout-land/create_profile_fragment.xml new file mode 100644 index 00000000000..93c01c2f32d --- /dev/null +++ b/app/src/main/res/layout-land/create_profile_fragment.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +