Skip to content

Commit

Permalink
Fixes part of #16 and #44: Implementation of ProfileChooserFragment (#…
Browse files Browse the repository at this point in the history
…326)

* implemented get and delete

* Finished test cases

* added observing cachestore

* Fixed test cases

* Finished first draft of implementation

* Added additional checks to setCurrentProfileId

* Added more test cases

* Finished test stubs

* Finished test cases

* Added query string to gravatar

* primed cache on init

* Fixed typo

* Fixed addProfile test case

* added create method to PersistentCacheStore

* added setTimestamp and setAdmin

* changed isAdmin to default to false

* Added update last logged in

* Started working on chooser

* debugging

* Added UI for chooser

* added click functionality

* Minor fixes

* Updated to use deferred value from persistentCacheStore

* Updated to use login

* Ensured result is error to post error value

* name can only be letters and unique check now case insensitive

* Added profile sorting by last accessed

* Fixed margins and added gradient

* allow names to have spaces

* Added image rotation and compression

* fixed api requires for exifinterface

* Changed gradle dependency

* Added new asset and changed colors

* Sorted alphabetically

* added local default to tolowercase

* started profile test helper

* Removed broken test, couldn't get mockito to work

* Added ProfileTestHelperTest

* added test case for profilechooserfragment

* using string.xml values in tests

* Minor fixes and added default profile avatar

* minor fixes

* added endline

* Minor fixes.

* Started updating to DataProviders

* Fixed loginToProfile

* Minor fixes

* Added comments and new test.

* minor fixes

* Fixed bug

* Added vector version of avatar

* Minor fixes.

* Converted livedata to dataproviders

* Minor fixes

* Removed coroutines from test.

* Removed ExperimentalCoroutinesApi

* Removed import

* Removed ExperimentalCouroutinesApi

* Fixed failing test cases

* Updated xml to multiples of 4

* Added annotations.

* Fixed test

* Fixed splash activity test

* Fixed ProfileTestHelper
  • Loading branch information
jamesxu0 authored Dec 5, 2019
1 parent bca905c commit 04391ef
Show file tree
Hide file tree
Showing 24 changed files with 733 additions and 32 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ dependencies {
'com.google.android.material:material:1.0.0',
'com.google.dagger:dagger:2.24',
'com.google.guava:guava:28.1-android',
'de.hdodenhof:circleimageview:3.0.1',
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version",
'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1',
'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1',
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<activity android:name=".player.state.testing.StateFragmentTestActivity" />
<activity
android:name=".profile.ProfileActivity"
android:theme="@style/OppiaThemeWithoutActionBar"
android:screenOrientation="portrait" />
<activity
android:name=".settings.profile.ProfileRenameActivity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,11 @@ fun setImageDrawable(imageView: ImageView, thumbnailGraphic: SkillThumbnailGraph
}
)
}

@BindingAdapter("profile:src")
fun setProfileImage(imageView: ImageView, imageUrl: String) {
Glide.with(imageView.context)
.load(imageUrl)
.placeholder(R.drawable.ic_default_avatar)
.into(imageView)
}
3 changes: 2 additions & 1 deletion app/src/main/java/org/oppia/app/profile/ProfileActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import javax.inject.Inject

/** Activity that controls profile creation and selection. */
class ProfileActivity : InjectableAppCompatActivity() {
@Inject lateinit var profileActivityPresenter: ProfileActivityPresenter
@Inject
lateinit var profileActivityPresenter: ProfileActivityPresenter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import javax.inject.Inject

/** The presenter for [ProfileActivity]. */
@ActivityScope
class ProfileActivityPresenter @Inject constructor(private val activity: AppCompatActivity){
class ProfileActivityPresenter @Inject constructor(private val activity: AppCompatActivity) {
fun handleOnCreate() {
activity.setContentView(R.layout.profile_activity)
if (getProfileChooserFragment() == null) {
activity.supportFragmentManager.beginTransaction().add(
R.id.profile_chooser_fragment_placeholder,
ProfileChooserFragment()
).commitNow()
}
activity.setContentView(R.layout.profile_activity)
if (getProfileChooserFragment() == null) {
activity.supportFragmentManager.beginTransaction().add(
R.id.profile_chooser_fragment_placeholder,
ProfileChooserFragment()
).commitNow()
}
}

private fun getProfileChooserFragment(): ProfileChooserFragment? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.oppia.app.fragment.InjectableFragment
import javax.inject.Inject

/** Fragment that allows user to select a profile or create new ones. */
class ProfileChooserFragment : InjectableFragment() {
@Inject lateinit var profileChooserFragmentPresenter: ProfileChooserFragmentPresenter
@Inject
lateinit var profileChooserFragmentPresenter: ProfileChooserFragmentPresenter

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

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return profileChooserFragmentPresenter.handleCreateView(inflater, container)
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,97 @@
package org.oppia.app.profile

import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import org.oppia.app.R
import org.oppia.app.databinding.ProfileChooserAddViewBinding
import org.oppia.app.databinding.ProfileChooserFragmentBinding
import org.oppia.app.databinding.ProfileChooserProfileViewBinding
import org.oppia.app.fragment.FragmentScope
import org.oppia.app.home.HomeActivity
import org.oppia.app.model.ProfileChooserModel
import org.oppia.app.recyclerview.BindableAdapter
import org.oppia.app.viewmodel.ViewModelProvider
import org.oppia.domain.profile.ProfileManagementController
import javax.inject.Inject

/** The presenter for [ProfileChooserFragment]. */
@FragmentScope
class ProfileChooserFragmentPresenter @Inject constructor(
private val fragment: Fragment,
private val viewModelProvider: ViewModelProvider<ProfileChooserViewModel>
private val viewModelProvider: ViewModelProvider<ProfileChooserViewModel>,
private val profileManagementController: ProfileManagementController
) {
/** Binds ViewModel and sets up RecyclerView Adapter. */
fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View? {
val binding = ProfileChooserFragmentBinding.inflate(inflater, container, /* attachToRoot= */ false)
val binding =
ProfileChooserFragmentBinding.inflate(inflater, container, /* attachToRoot= */ false)
binding.apply {
viewModel = getProfileChooserViewModel()
lifecycleOwner = fragment
}
binding.profileRecyclerView.apply {
adapter = createRecyclerViewAdapter()
}
return binding.root
}

private fun getProfileChooserViewModel(): ProfileChooserViewModel {
return viewModelProvider.getForFragment(fragment, ProfileChooserViewModel::class.java)
}

private fun createRecyclerViewAdapter(): BindableAdapter<ProfileChooserModel> {
return BindableAdapter.MultiTypeBuilder
.newBuilder<ProfileChooserModel, ProfileChooserModel.ModelTypeCase>(ProfileChooserModel::getModelTypeCase)
.registerViewDataBinderWithSameModelType(
viewType = ProfileChooserModel.ModelTypeCase.PROFILE,
inflateDataBinding = ProfileChooserProfileViewBinding::inflate,
setViewModel = this::bindProfileView
)
.registerViewDataBinderWithSameModelType(
viewType = ProfileChooserModel.ModelTypeCase.ADDPROFILE,
inflateDataBinding = ProfileChooserAddViewBinding::inflate,
setViewModel = this::bindAddView
)
.build()
}

private fun bindProfileView(
binding: ProfileChooserProfileViewBinding,
data: ProfileChooserModel
) {
binding.viewModel = data
binding.root.setOnClickListener {
profileManagementController.loginToProfile(data.profile.id).observe(fragment, Observer {
if (it.isSuccess()) {
fragment.requireActivity()
.startActivity(Intent(fragment.context, HomeActivity::class.java))
}
})
}
}

private fun bindAddView(binding: ProfileChooserAddViewBinding, data: ProfileChooserModel) {
binding.root.setOnClickListener {
if (getAdminAuthFragment() == null) {
fragment.requireActivity().supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.slide_up,
R.anim.slide_down,
R.anim.slide_up,
R.anim.slide_down
).add(
R.id.profile_chooser_fragment_placeholder,
AdminAuthFragment()
).addToBackStack(null).commit()
}
}
}

private fun getAdminAuthFragment(): AdminAuthFragment? {
return fragment.requireActivity().supportFragmentManager.findFragmentById(R.id.profile_chooser_fragment_placeholder) as? AdminAuthFragment?
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,56 @@
package org.oppia.app.profile

import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import org.oppia.app.fragment.FragmentScope
import org.oppia.app.model.Profile
import org.oppia.app.model.ProfileChooserModel
import org.oppia.app.viewmodel.ObservableViewModel
import org.oppia.domain.profile.ProfileManagementController
import org.oppia.util.data.AsyncResult
import org.oppia.util.logging.Logger
import java.util.Locale
import javax.inject.Inject

/** The ViewModel for [ProfileChooserFragment]. */
@FragmentScope
class ProfileChooserViewModel @Inject constructor(): ObservableViewModel() {
class ProfileChooserViewModel @Inject constructor(
private val profileManagementController: ProfileManagementController, private val logger: Logger
) : ObservableViewModel() {
val profiles: LiveData<List<ProfileChooserModel>> by lazy {
Transformations.map(profileManagementController.getProfiles(), ::processGetProfilesResult)
}

lateinit var adminPin: String

/** Sorts profiles alphabetically by name and put Admin in front. */
private fun processGetProfilesResult(profilesResult: AsyncResult<List<Profile>>): List<ProfileChooserModel> {
if (profilesResult.isFailure()) {
logger.e(
"ProfileChooserViewModel",
"Failed to retrieve the list of profiles: ",
profilesResult.getErrorOrNull()!!
)
}
val profileList = profilesResult.getOrDefault(emptyList()).map {
ProfileChooserModel.newBuilder().setProfile(it).build()
}.toMutableList()

val sortedProfileList = profileList.sortedBy {
it.profile.name.toLowerCase(Locale.getDefault())
}.toMutableList()

val adminProfile = sortedProfileList.find { it.profile.isAdmin }
adminProfile?.let {
sortedProfileList.remove(adminProfile)
adminPin = it.profile.pin
sortedProfileList.add(0, it)
}

if (sortedProfileList.size < 10) {
sortedProfileList.add(ProfileChooserModel.newBuilder().setAddProfile(true).build())
}

return sortedProfileList
}
}
8 changes: 4 additions & 4 deletions app/src/main/java/org/oppia/app/splash/SplashActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import org.oppia.app.R
import org.oppia.app.home.HomeActivity
import org.oppia.app.profile.ProfileActivity

/** An activity that shows a temporary loading page until the app is fully loaded then navigates to [HomeActivity]. */
/** An activity that shows a temporary loading page until the app is fully loaded then navigates to [ProfileActivity]. */
class SplashActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.splash_activity)

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
val intent = Intent(this@SplashActivity, HomeActivity::class.java)
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
val intent = Intent(this@SplashActivity, ProfileActivity::class.java)
startActivity(intent)
finish()
}
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/res/drawable/ic_add_profile.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<vector android:height="80dp" android:viewportHeight="73"
android:viewportWidth="73" android:width="80dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="0.4" android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M36.5,36.5m-36.5,0a36.5,36.5 0,1 1,73 0a36.5,36.5 0,1 1,-73 0"
android:strokeAlpha="0.4" android:strokeColor="#00000000" android:strokeWidth="1"/>
<path android:fillAlpha="0.4" android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M36.5,36.5m-35.9931,0a35.9931,35.9931 0,1 1,71.9861 0a35.9931,35.9931 0,1 1,-71.9861 0"
android:strokeAlpha="0.4" android:strokeColor="#707070" android:strokeWidth="1"/>
<path android:fillColor="#FFFFFF" android:fillType="nonZero"
android:pathData="M48,38.714l-10.286,0l0,10.286l-3.428,0l0,-10.286l-10.286,0l0,-3.428l10.286,0l0,-10.286l3.429,0l0,10.286l10.285,0z"
android:strokeColor="#00000000" android:strokeWidth="1"/>
</vector>
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/ic_default_avatar.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="48dp"
android:viewportHeight="48" android:viewportWidth="48"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#fff" android:pathData="M48,48H0v-0.52A21.65,21.65 0,0 1,0.47 42.6a4.62,4.62 0,0 1,3.71 -3.49c4.05,-1 8.08,-2 12.12,-3.08a0.55,0.55 0,0 0,0.5 -0.62c0,-1 0,-2.09 0,-3.14a1,1 0,0 0,-0.18 -0.56,14.57 14.57,0 0,1 -3.21,-6.06c-0.07,-0.26 -0.2,-0.29 -0.47,-0.29a2,2 0,0 1,-1.12 -0.25,5 5,0 0,1 -1.19,-1.19c-1.11,-1.55 -1,-3.31 -0.77,-5.06a1.7,1.7 0,0 1,1.28 -1.54,0.4 0.4,0 0,0 0.33,-0.52A45,45 0,0 1,11.09 8a3.68,3.68 0,0 1,0.67 -2,10.11 10.11,0 0,1 0.71,-0.88 10.76,10.76 0,0 1,2.9 -2.19,0.86 0.86,0 0,0 0.2,-0.18 1.09,1.09 0,0 0,-0.25 0l-0.71,0.1a1.69,1.69 0,0 1,-0.23 0c0,-0.06 0.09,-0.15 0.16,-0.18 0.79,-0.35 1.57,-0.71 2.37,-1A9.17,9.17 0,0 1,22.54 0.85a0.66,0.66 0,0 0,0.24 0l0.53,-0.14L22.86,0.5 22.63,0.36A1.1,1.1 0,0 1,22.89 0.3c0.95,-0.1 1.9,-0.21 2.85,-0.26a4.44,4.44 0,0 1,1.79 0.1,7.77 7.77,0 0,1 3.89,3.12 0.68,0.68 0,0 0,0.56 0.26,4.37 4.37,0 0,1 3,0.55 1.66,1.66 0,0 1,0.62 0.67c0.31,0.7 0.55,1.43 0.81,2.15a1.09,1.09 0,0 1,0.06 0.36,3.07 3.07,0 0,0 -0.31,-0.17L35.9,7a1.29,1.29 0,0 0,0.05 0.35,2.08 2.08,0 0,0 0.32,0.4 2.33,2.33 0,0 1,0.66 1.75c-0.13,2.4 -0.24,4.8 -0.38,7.2 0,0.36 0,0.58 0.4,0.69a1.59,1.59 0,0 1,1.19 1.45,16.27 16.27,0 0,1 0,3.12 4.29,4.29 0,0 1,-1.66 3,2.57 2.57,0 0,1 -1.33,0.49c-0.4,0 -0.56,0.1 -0.65,0.47a14.44,14.44 0,0 1,-3 5.73,1 1,0 0,0 -0.25,0.63c0,1.07 0,2.15 0,3.23a0.54,0.54 0,0 0,0.48 0.59c4,1 7.91,2.08 11.89,3 2.25,0.53 3.67,1.76 4,4C47.86,44.66 47.88,46.34 48,48Z"/>
</vector>
10 changes: 8 additions & 2 deletions app/src/main/res/layout/admin_auth_fragment.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/admin_auth_container"
android:layout_width="match_parent"
android:layout_height="match_parent"></androidx.constraintlayout.widget.ConstraintLayout>
android:layout_height="match_parent"
android:background="@color/white">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Admin Auth" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
27 changes: 27 additions & 0 deletions app/src/main/res/layout/profile_chooser_add_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:gravity="center">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/profile_add_button"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/ic_add_profile"
android:layout_marginBottom="8dp"
app:civ_border_width="1dp"
app:civ_border_color="@color/avatarBorder"/>
<TextView
android:id="@+id/add_profile_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:text="@string/profile_chooser_add"
android:textSize="16sp"/>
</LinearLayout>
</layout>
38 changes: 33 additions & 5 deletions app/src/main/res/layout/profile_chooser_fragment.xml
Original file line number Diff line number Diff line change
@@ -1,14 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>

<variable
name="viewModel"
type="org.oppia.app.profile.ProfileChooserViewModel" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/profile_chooser_container"
android:layout_width="match_parent"
android:layout_height="match_parent"></androidx.constraintlayout.widget.ConstraintLayout>
android:layout_height="match_parent"
android:background="@color/profileChooserBackground">
<TextView
android:id="@+id/profile_select_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:text="@string/profile_chooser_select"
android:layout_marginStart="24dp"
android:layout_marginTop="40dp"
android:paddingBottom="20dp"
android:textSize="24sp"
android:textColor="@color/white"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/profile_recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:orientation="vertical"
app:data="@{viewModel.profiles}"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
android:requiresFadingEdge="vertical"
android:fadingEdge="horizontal"
android:fadingEdgeLength="72dp"
app:layout_constraintTop_toBottomOf="@id/profile_select_text"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Loading

0 comments on commit 04391ef

Please sign in to comment.