Skip to content

Commit

Permalink
Fixes part of #16: Implement ProfileEditActivity, ProfileRenameActivi…
Browse files Browse the repository at this point in the history
…ty, and ProfileResetPinActivity in settings [Blocked #22, #48] (#543)

* 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

* Started activity

* removed fragment checks

* Finished basic flow of pin password

* Added show password button

* Added input to dialog

* Sorted alphabetically

* added local default to tolowercase

* updated comment

* Renamed keys

* Updated key

* Changed to getProfile from database

* Added change pin dialogs

* Added pin input animation and go to play store

* 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

* Finished test cases and adjusted sizes

* Minor fixes and added default profile avatar

* minor fixes

* added endline

* Minor fixes

* removed resources

* Minor fixes

* Minor fixes.

* Started updating to DataProviders

* Fixed recyclerview

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

* Addressed all comments.

* Fixed failing test cases

* Updated xml to multiples of 4

* Fixed dp values

* Minor fixes

* Fixed dp

* Fixed ProfileTestHelper

* Added avatar color support.

* Added Ben's createTimer

* Addressed comments in previous PRs that didn't get pushed before.

* Removed lines

* Added admin pin creation flow

* Fixed tests

* Merge branch 'profile-avatar-colors' into profile-admin-flow

* Added back button dialog to home activity

* Initial changes, fragments to activities

* Added basic recyclerview

* Added recyclerview databinding

* added new line

* Initial profile read

* Updated log statement

* Added edit UI

* Finished profile updating

* Updated domain test case

* Added AdminPinTests

* Removed unused imports

* Finished test cases

* Finished rest of tests

* Removed imports

* Updated ResetPinActivity to include isAdmin

* Cleaner profile avatar design

* Updated profile:src

* Removed textUtils

* Updated profile edit avatar src

* Reverted splash activity

* Removed unused imports

* Updated to now store colorHex and other fixes.

* Updated comment.

* Updated to colorRgb

* Added fixes

* Updated to force admin.

* Updated comments.

* Nit fixes.

* Nit fixes.
  • Loading branch information
jamesxu0 authored Dec 14, 2019
1 parent e87347a commit 6639fa8
Show file tree
Hide file tree
Showing 21 changed files with 1,291 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package org.oppia.app.databinding

import android.graphics.Color
import android.graphics.PorterDuff
import android.widget.ImageView
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.databinding.BindingAdapter
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
Expand Down Expand Up @@ -83,7 +81,8 @@ fun setImageDrawable(imageView: ImageView, thumbnailGraphic: SkillThumbnailGraph
* @param profileAvatar Represents either a colorId or local image uri.
*/
@BindingAdapter("profile:src")
fun setProfileImage(imageView: ImageView, profileAvatar: ProfileAvatar) {
fun setProfileImage(imageView: ImageView, profileAvatar: ProfileAvatar?) {
if (profileAvatar == null) return
if (profileAvatar.avatarTypeCase == ProfileAvatar.AvatarTypeCase.AVATAR_COLOR_RGB) {
Glide.with(imageView.context)
.load(R.drawable.ic_default_avatar)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.oppia.app.databinding

import android.widget.TextView
import androidx.databinding.BindingAdapter
import org.oppia.app.R
import java.text.SimpleDateFormat
import java.util.*

/** Binds date text with relative time. */
@BindingAdapter("profile:created")
fun setProfileDataText(textView: TextView, timestamp: Long) {
// TODO(#555): Create one central utility file from where we should access date format or even convert date timestamp to string from that file.
val sdf = SimpleDateFormat("dd MMM yyyy", Locale.getDefault())
val time = sdf.format(Date(timestamp))
textView.text = String.format(textView.context.getString(R.string.profile_edit_created, time))
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class PinPasswordActivityPresenter @Inject constructor(
}

private fun showAdminForgotPin() {
AlertDialog.Builder(activity as Context, R.style.AlertDialogTheme)
AlertDialog.Builder(activity, R.style.AlertDialogTheme)
.setTitle(R.string.pin_password_forgot_title)
.setMessage(R.string.pin_password_forgot_message)
.setNegativeButton(R.string.admin_settings_cancel) { dialog, _ ->
Expand All @@ -135,7 +135,7 @@ class PinPasswordActivityPresenter @Inject constructor(
}

private fun showSuccessDialog() {
AlertDialog.Builder(activity as Context, R.style.AlertDialogTheme)
AlertDialog.Builder(activity, R.style.AlertDialogTheme)
.setMessage(R.string.pin_password_success)
.setPositiveButton(R.string.pin_password_close) { dialog, _ ->
dialog.dismiss()
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/java/org/oppia/app/profile/ProfileInputView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ class ProfileInputView @JvmOverloads constructor(
defStyle: Int = 0
) : LinearLayout(context, attrs, defStyle) {
companion object {
@JvmStatic
@BindingAdapter("profile:label")
fun setLabel(profileInputView: ProfileInputView, label: String) {
profileInputView.label.text = label
}

@JvmStatic
@BindingAdapter("profile:inputLength")
fun setInputLength(profileInputView: ProfileInputView, inputLength: Int) {
profileInputView.input.filters = arrayOf(InputFilter.LengthFilter(inputLength))
}

@JvmStatic
@BindingAdapter("profile:error")
fun setProfileImage(profileInputView: ProfileInputView, errorMessage: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import javax.inject.Inject

const val KEY_PROFILE_EDIT_PROFILE_ID = "KEY_PROFILE_EDIT_PROFILE_ID"

/** Activity that allows user to select a profile to edit from settings. */
/** Activity that allows user to edit a profile. */
class ProfileEditActivity : InjectableAppCompatActivity() {
@Inject lateinit var profileEditActivityPresenter: ProfileEditActivityPresenter

Expand All @@ -25,4 +25,17 @@ class ProfileEditActivity : InjectableAppCompatActivity() {
activityComponent.inject(this)
profileEditActivityPresenter.handleOnCreate()
}

override fun onSupportNavigateUp(): Boolean {
val intent = Intent(this, ProfileListActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
return false
}

override fun onBackPressed() {
val intent = Intent(this, ProfileListActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,86 @@
package org.oppia.app.settings.profile

import android.content.Intent
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import org.oppia.app.R
import org.oppia.app.activity.ActivityScope
import org.oppia.app.databinding.ProfileEditActivityBinding
import org.oppia.app.model.ProfileId
import org.oppia.app.viewmodel.ViewModelProvider
import org.oppia.domain.profile.ProfileManagementController
import org.oppia.util.logging.Logger
import javax.inject.Inject

/** The presenter for [ProfileEditActivity]. */
@ActivityScope
class ProfileEditActivityPresenter @Inject constructor(
private val activity: AppCompatActivity,
private val logger: Logger,
private val profileManagementController: ProfileManagementController,
private val viewModelProvider: ViewModelProvider<ProfileEditViewModel>
) {
private val editViewModel: ProfileEditViewModel by lazy {
getProfileEditViewModel()
}

fun handleOnCreate() {
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
activity.supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_arrow_back_white_24dp)

val binding = DataBindingUtil.setContentView<ProfileEditActivityBinding>(activity, R.layout.profile_edit_activity)
val profileId = activity.intent.getIntExtra(KEY_PROFILE_EDIT_PROFILE_ID, 0)
editViewModel.setProfileId(profileId)
binding.apply {
viewModel = editViewModel
lifecycleOwner = activity
}

binding.profileRenameButton.setOnClickListener {
activity.startActivity(ProfileRenameActivity.createProfileRenameActivity(activity, profileId))
}

binding.profileResetButton.setOnClickListener {
activity.startActivity(ProfileResetPinActivity.createProfileResetPinActivity(activity, profileId, editViewModel.isAdmin))
}

binding.profileDeleteButton.setOnClickListener {
showDeletionDialog(profileId)
}

binding.profileEditAllowDownloadSwitch.setOnCheckedChangeListener { compoundButton, checked ->
if (compoundButton.isPressed) {
profileManagementController.updateAllowDownloadAccess(
ProfileId.newBuilder().setInternalId(profileId).build(),
checked
).observe(activity, Observer {
if (it.isFailure()) {
logger.e("ProfileEditActivityPresenter", "Failed to updated allow download access", it.getErrorOrNull()!!)
}
})
}
}
}

private fun showDeletionDialog(profileId: Int) {
AlertDialog.Builder(activity, R.style.AlertDialogTheme)
.setTitle(R.string.profile_edit_delete_dialog_title)
.setMessage(R.string.profile_edit_delete_dialog_message)
.setNegativeButton(R.string.profile_edit_delete_dialog_negative) { dialog, _ ->
dialog.dismiss()
}
.setPositiveButton(R.string.profile_edit_delete_dialog_positive) { dialog, _ ->
profileManagementController.deleteProfile(ProfileId.newBuilder().setInternalId(profileId).build())
.observe(activity, Observer {
if (it.isSuccess()) {
val intent = Intent(activity, ProfileListActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
activity.startActivity(intent)
}
})
}.create().show()
}

private fun getProfileEditViewModel(): ProfileEditViewModel {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,51 @@
package org.oppia.app.settings.profile

import android.widget.Switch
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import org.oppia.app.R
import org.oppia.app.activity.ActivityScope
import org.oppia.app.model.Profile
import org.oppia.app.model.ProfileId
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 javax.inject.Inject

/** The ViewModel for [ProfileEditActivity]. */
@ActivityScope
class ProfileEditViewModel @Inject constructor() : ObservableViewModel() {
class ProfileEditViewModel @Inject constructor(
private val activity: AppCompatActivity,
private val logger: Logger,
private val profileManagementController: ProfileManagementController
) : ObservableViewModel() {
private lateinit var profileId: ProfileId

val profile: LiveData<Profile> by lazy {
Transformations.map(profileManagementController.getProfile(profileId), ::processGetProfileResult)
}

var isAdmin = false

fun setProfileId(id: Int) {
profileId = ProfileId.newBuilder().setInternalId(id).build()
}

private fun processGetProfileResult(profileResult: AsyncResult<Profile>): Profile {
if (profileResult.isFailure()) {
logger.e(
"ProfileEditViewModel",
"Failed to retrieve the profile with ID: ${profileId.internalId}",
profileResult.getErrorOrNull()!!
)
}
val profile = profileResult.getOrDefault(Profile.getDefaultInstance())
val switch = activity.findViewById<Switch>(R.id.profile_edit_allow_download_switch)
switch.isChecked = profile.allowDownloadAccess
activity.title = profile.name
isAdmin = profile.isAdmin
return profile
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
package org.oppia.app.settings.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_PROFILE_RENAME_PROFILE_ID = "KEY_PROFILE_RENAME_PROFILE_ID"

/** Activity that allows user to rename a profile. */
class ProfileRenameActivity : InjectableAppCompatActivity() {
@Inject lateinit var profileRenameActivityPresenter: ProfileRenameActivityPresenter

companion object {
fun createProfileRenameActivity(context: Context, profileId: Int): Intent {
val intent = Intent(context, ProfileRenameActivity::class.java)
intent.putExtra(KEY_PROFILE_RENAME_PROFILE_ID, profileId)
return intent
}
}

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

override fun onSupportNavigateUp(): Boolean {
finish()
return false
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,89 @@
package org.oppia.app.settings.profile

import android.content.Intent
import android.text.Editable
import android.text.TextWatcher
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import org.oppia.app.R
import org.oppia.app.activity.ActivityScope
import org.oppia.app.databinding.ProfileRenameActivityBinding
import org.oppia.app.model.ProfileId
import org.oppia.app.viewmodel.ViewModelProvider
import org.oppia.domain.profile.ProfileManagementController
import org.oppia.util.data.AsyncResult
import javax.inject.Inject

/** The presenter for [ProfileRenameActivity]. */
@ActivityScope
class ProfileRenameActivityPresenter @Inject constructor(private val activity: AppCompatActivity) {
class ProfileRenameActivityPresenter @Inject constructor(
private val activity: AppCompatActivity,
private val profileManagementController: ProfileManagementController,
private val viewModelProvider: ViewModelProvider<ProfileRenameViewModel>
) {
private val renameViewModel: ProfileRenameViewModel by lazy {
getProfileRenameViewModel()
}

fun handleOnCreate() {
val binding = DataBindingUtil.setContentView<ProfileRenameActivityBinding>(activity, R.layout.profile_rename_activity)
activity.title = activity.getString(R.string.profile_rename_title)
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
activity.supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_arrow_back_white_24dp)

val binding =
DataBindingUtil.setContentView<ProfileRenameActivityBinding>(activity, R.layout.profile_rename_activity)
val profileId = activity.intent.getIntExtra(KEY_PROFILE_RENAME_PROFILE_ID, 0)

binding.apply {
viewModel = renameViewModel
lifecycleOwner = activity
}

binding.profileRenameSaveButton.setOnClickListener {
val name = binding.inputName.getInput()
if (name.isEmpty()) {
renameViewModel.nameErrorMsg.set(activity.resources.getString(R.string.add_profile_error_name_empty))
return@setOnClickListener
}
profileManagementController.updateName(ProfileId.newBuilder().setInternalId(profileId).build(), name)
.observe(activity, Observer {
handleAddProfileResult(it, profileId)
})
}

binding.inputName.addTextChangedListener(object : TextWatcher {
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
renameViewModel.nameErrorMsg.set("")
}

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

private fun handleAddProfileResult(result: AsyncResult<Any?>, profileId: Int) {
if (result.isSuccess()) {
val intent = ProfileEditActivity.createProfileEditActivity(activity, profileId)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
activity.startActivity(intent)
} else if (result.isFailure()) {
when (result.getErrorOrNull()) {
is ProfileManagementController.ProfileNameNotUniqueException -> renameViewModel.nameErrorMsg.set(
activity.resources.getString(
R.string.add_profile_error_name_not_unique
)
)
is ProfileManagementController.ProfileNameOnlyLettersException -> renameViewModel.nameErrorMsg.set(
activity.resources.getString(
R.string.add_profile_error_name_only_letters
)
)
}
}
}

private fun getProfileRenameViewModel(): ProfileRenameViewModel {
return viewModelProvider.getForActivity(activity, ProfileRenameViewModel::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.oppia.app.settings.profile

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

/** The ViewModel for [ProfileRenameActivity]. */
@ActivityScope
class ProfileRenameViewModel @Inject constructor() : ObservableViewModel() {
val nameErrorMsg = ObservableField("")
}
Loading

0 comments on commit 6639fa8

Please sign in to comment.