Skip to content

Commit

Permalink
Fixes part of #16: Implementation of AdminAuthActivity and AddProfile…
Browse files Browse the repository at this point in the history
…Activity (#327)

* 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

* Started basic admin auth and add profile

* 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

* Switched fragments to activities

* Finished admin auth styling

* Styled inputs for add profile

* Fixed name only letters toast

* Added support for upload image

* Started custom view

* added upload image and fixed input styling

* Added image rotation and compression

* Added red color

* fixed api requires for exifinterface

* Changed gradle dependency

* Added error handling ui support

* Added new asset and changed colors

* hide keyboard on create

* changed selectedImage to private

* Moved bindingadapter to companion object

* removed fragment checks

* Sorted alphabetically

* added local default to tolowercase

* updated comment

* Updated key

* added info icon

* started profile test helper

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

* Added ProfileTestHelperTest

* added test case for profilechooserfragment

* Added test for AdminAuthActivity

* using string.xml values in tests

* Started AddProfileActivityTest

* Finished test cases

* Minor fixes and added default profile avatar

* minor fixes

* added endline

* Minor fixes

* removed resources

* 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.

* Added helper methods

* Removed ExperimentalCoroutinesApi

* Removed import

* Removed ExperimentalCouroutinesApi

* Addressed comments

* Fixed failing test cases

* Updated xml to multiples of 4

* Fixed dp values
  • Loading branch information
jamesxu0 authored Dec 5, 2019
1 parent 04391ef commit 95dce9c
Show file tree
Hide file tree
Showing 34 changed files with 1,218 additions and 140 deletions.
6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize"/>
<activity android:name=".player.state.testing.StateFragmentTestActivity" />
<activity
android:name=".profile.AddProfileActivity"
android:screenOrientation="portrait" />
<activity
android:name=".profile.AdminAuthActivity"
android:screenOrientation="portrait" />
<activity
android:name=".profile.ProfileActivity"
android:theme="@style/OppiaThemeWithoutActionBar"
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/org/oppia/app/activity/ActivityComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import org.oppia.app.home.continueplaying.ContinuePlayingActivity
import org.oppia.app.player.audio.testing.AudioFragmentTestActivity
import org.oppia.app.player.exploration.ExplorationActivity
import org.oppia.app.player.state.testing.StateFragmentTestActivity
import org.oppia.app.profile.AddProfileActivity
import org.oppia.app.profile.AdminAuthActivity
import org.oppia.app.profile.ProfileActivity
import org.oppia.app.settings.profile.ProfileRenameActivity
import org.oppia.app.settings.profile.ProfileResetPinActivity
Expand Down Expand Up @@ -39,6 +41,8 @@ interface ActivityComponent {

fun getFragmentComponentBuilderProvider(): Provider<FragmentComponent.Builder>

fun inject(addProfileActivity: AddProfileActivity)
fun inject(adminAuthActivity: AdminAuthActivity)
fun inject(audioFragmentTestActivity: AudioFragmentTestActivity)
fun inject(bindableAdapterTestActivity: BindableAdapterTestActivity)
fun inject(conceptCardFragmentTestActivity: ConceptCardFragmentTestActivity)
Expand Down
4 changes: 0 additions & 4 deletions app/src/main/java/org/oppia/app/fragment/FragmentComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import org.oppia.app.player.state.StateFragment
import org.oppia.app.settings.profile.ProfileEditFragment
import org.oppia.app.settings.profile.ProfileListFragment
import org.oppia.app.player.state.itemviewmodel.InteractionViewModelModule
import org.oppia.app.profile.AddProfileFragment
import org.oppia.app.profile.AdminAuthFragment
import org.oppia.app.profile.ProfileChooserFragment
import org.oppia.app.story.StoryFragment
import org.oppia.app.testing.BindableAdapterTestFragment
Expand Down Expand Up @@ -57,6 +55,4 @@ interface FragmentComponent {
fun inject(profileListFragment: ProfileListFragment)
fun inject(profileEditFragment: ProfileEditFragment)
fun inject(profileChooserFragment: ProfileChooserFragment)
fun inject(adminAuthFragment: AdminAuthFragment)
fun inject(addProfileFragment: AddProfileFragment)
}
30 changes: 30 additions & 0 deletions app/src/main/java/org/oppia/app/profile/AddProfileActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.oppia.app.profile

import android.content.Intent
import android.os.Bundle
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.oppia.app.activity.InjectableAppCompatActivity
import javax.inject.Inject

/** Activity that allows users to create new profiles. */
class AddProfileActivity : InjectableAppCompatActivity() {
@Inject
lateinit var addProfileFragmentPresenter: AddProfileActivityPresenter

@ExperimentalCoroutinesApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityComponent.inject(this)
addProfileFragmentPresenter.handleOnCreate()
}

override fun onSupportNavigateUp(): Boolean {
finish()
return false
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
addProfileFragmentPresenter.handleOnActivityResult(requestCode, resultCode, data)
}
}
207 changes: 207 additions & 0 deletions app/src/main/java/org/oppia/app/profile/AddProfileActivityPresenter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package org.oppia.app.profile

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.MediaStore
import android.text.Editable
import android.text.TextWatcher
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.ImageView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.oppia.app.R
import org.oppia.app.activity.ActivityScope
import org.oppia.app.databinding.AddProfileActivityBinding
import org.oppia.app.viewmodel.ViewModelProvider
import org.oppia.domain.profile.ProfileManagementController
import org.oppia.util.data.AsyncResult
import javax.inject.Inject

const val GALLERY_INTENT_RESULT_CODE = 1

/** The presenter for [AddProfileActivity]. */
@ActivityScope
class AddProfileActivityPresenter @Inject constructor(
private val activity: AppCompatActivity,
private val profileManagementController: ProfileManagementController,
private val viewModelProvider: ViewModelProvider<AddProfileViewModel>
) {
private lateinit var uploadImageView: ImageView
private val profileViewModel by lazy {
getAddProfileViewModel()
}
private var selectedImage: Uri? = null
private var allowDownloadAccess = false
private var inputtedPin = false
private var inputtedConfirmPin = false

@ExperimentalCoroutinesApi
fun handleOnCreate() {
activity.title = activity.getString(R.string.add_profile_title)
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
activity.supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_close_white_24dp)

val binding = DataBindingUtil.setContentView<AddProfileActivityBinding>(activity, R.layout.add_profile_activity)

binding.apply {
viewModel = profileViewModel
}

binding.allowDownloadSwitch.setOnCheckedChangeListener { _, isChecked ->
allowDownloadAccess = isChecked
}

binding.infoIcon.setOnClickListener {
showInfoDialog()
}

uploadImageView = binding.uploadImageButton

addTextChangedListeners(binding)
addButtonListeners(binding)
}

fun handleOnActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == GALLERY_INTENT_RESULT_CODE && resultCode == Activity.RESULT_OK) {
data?.let {
selectedImage = data.data
Glide.with(activity)
.load(selectedImage)
.centerCrop()
.apply(RequestOptions.circleCropTransform())
.into(uploadImageView)
}
}
}

private fun addButtonListeners(binding: AddProfileActivityBinding) {
binding.uploadImageButton.setOnClickListener {
val galleryIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
activity.startActivityForResult(galleryIntent, GALLERY_INTENT_RESULT_CODE)
}

binding.createButton.setOnClickListener {
profileViewModel.clearAllErrorMessages()

val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.hideSoftInputFromWindow(activity.currentFocus?.windowToken, 0)

val name = binding.inputName.getInput()
val pin = binding.inputPin.getInput()
val confirmPin = binding.inputConfirmPin.getInput()

if (checkInputsAreValid(name, pin, confirmPin)) {
binding.scroll.smoothScrollTo(0, 0)
return@setOnClickListener
}

profileManagementController.addProfile(name, pin, selectedImage, allowDownloadAccess, isAdmin = false)
.observe(activity, Observer {
handleAddProfileResult(it, binding)
})
}
}

private fun checkInputsAreValid(name: String, pin: String, confirmPin: String): Boolean {
var failed = false
if (name.isEmpty()) {
profileViewModel.nameErrorMsg.set(activity.resources.getString(R.string.add_profile_error_name_empty))
failed = true
}
if (pin.isNotEmpty() && pin.length < 3) {
profileViewModel.pinErrorMsg.set(activity.resources.getString(R.string.add_profile_error_pin_length))
failed = true
}
if (pin != confirmPin) {
profileViewModel.confirmPinErrorMsg.set(activity.resources.getString(R.string.add_profile_error_pin_confirm_wrong))
failed = true
}
return failed
}

private fun handleAddProfileResult(result: AsyncResult<Any?>, binding: AddProfileActivityBinding) {
if (result.isSuccess()) {
val intent = Intent(activity, ProfileActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
activity.startActivity(intent)
} else if (result.isFailure()) {
when (result.getErrorOrNull()) {
is ProfileManagementController.ProfileNameNotUniqueException -> profileViewModel.nameErrorMsg.set(
activity.resources.getString(
R.string.add_profile_error_name_not_unique
)
)
is ProfileManagementController.ProfileNameOnlyLettersException -> profileViewModel.nameErrorMsg.set(
activity.resources.getString(
R.string.add_profile_error_name_only_letters
)
)
}
binding.scroll.smoothScrollTo(0, 0)
}
}

private fun addTextChangedListeners(binding: AddProfileActivityBinding) {
fun setValidPin() {
if (inputtedPin && inputtedConfirmPin) {
profileViewModel.validPin.set(true)
} else {
binding.allowDownloadSwitch.isChecked = false
profileViewModel.validPin.set(false)
}
}

addTextChangedListener(binding.inputPin) { pin ->
pin?.let {
profileViewModel.pinErrorMsg.set("")
inputtedPin = pin.isNotEmpty()
setValidPin()
}
}

addTextChangedListener(binding.inputConfirmPin) { confirmPin ->
confirmPin?.let {
profileViewModel.confirmPinErrorMsg.set("")
inputtedConfirmPin = confirmPin.isNotEmpty()
setValidPin()
}
}

addTextChangedListener(binding.inputName) { name ->
name?.let {
profileViewModel.nameErrorMsg.set("")
}
}
}

private fun addTextChangedListener(profileInputView: ProfileInputView, onTextChanged: (CharSequence?) -> Unit) {
profileInputView.addTextChangedListener(object : TextWatcher {
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
onTextChanged(p0)
}

override fun afterTextChanged(p0: Editable?) {}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
}

private fun showInfoDialog() {
AlertDialog.Builder(activity as Context, R.style.AlertDialogTheme)
.setMessage(R.string.add_profile_pin_info)
.setPositiveButton(R.string.add_profile_close) { dialog, _ ->
dialog.dismiss()
}.create().show()
}

private fun getAddProfileViewModel(): AddProfileViewModel {
return viewModelProvider.getForActivity(activity, AddProfileViewModel::class.java)
}
}
23 changes: 0 additions & 23 deletions app/src/main/java/org/oppia/app/profile/AddProfileFragment.kt

This file was deleted.

This file was deleted.

21 changes: 21 additions & 0 deletions app/src/main/java/org/oppia/app/profile/AddProfileViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.oppia.app.profile

import androidx.databinding.ObservableField
import org.oppia.app.activity.ActivityScope
import org.oppia.app.viewmodel.ObservableViewModel
import javax.inject.Inject

/** The ViewModel for [AddProfileActivity]. */
@ActivityScope
class AddProfileViewModel @Inject constructor() : ObservableViewModel() {
val validPin = ObservableField(false)
val pinErrorMsg = ObservableField("")
val confirmPinErrorMsg = ObservableField("")
val nameErrorMsg = ObservableField("")

fun clearAllErrorMessages() {
pinErrorMsg.set("")
confirmPinErrorMsg.set("")
nameErrorMsg.set("")
}
}
35 changes: 35 additions & 0 deletions app/src/main/java/org/oppia/app/profile/AdminAuthActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.oppia.app.profile

import android.content.Context
import android.content.Intent
import android.os.Bundle
import org.oppia.app.activity.InjectableAppCompatActivity
import javax.inject.Inject

const val KEY_ADMIN_AUTH_ADMIN_PIN = "ADMIN_AUTH_ADMIN_PIN"

/** Activity that authenticates by checking for admin's PIN. */
class AdminAuthActivity : InjectableAppCompatActivity() {
@Inject
lateinit var adminAuthFragmentPresenter: AdminAuthActivityPresenter

companion object {
fun createAdminAuthActivityIntent(context: Context, adminPin: String): Intent {
val intent = Intent(context, AdminAuthActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
intent.putExtra(KEY_ADMIN_AUTH_ADMIN_PIN, adminPin)
return intent
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityComponent.inject(this)
adminAuthFragmentPresenter.handleOnCreate()
}

override fun onSupportNavigateUp(): Boolean {
finish()
return false
}
}
Loading

0 comments on commit 95dce9c

Please sign in to comment.