Skip to content

Commit

Permalink
Merge branch 'implement-pin-password' into temp-integration
Browse files Browse the repository at this point in the history
Conflicts:
	app/src/main/res/values/colors.xml
	app/src/main/res/values/styles.xml
  • Loading branch information
BenHenning committed Nov 14, 2019
2 parents 5a5d00c + 0684cff commit 1e8fddd
Show file tree
Hide file tree
Showing 70 changed files with 3,555 additions and 154 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ dependencies {
'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1',
'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1',
'org.mockito:mockito-core:2.7.22',
'de.hdodenhof:circleimageview:3.0.1',
'com.chaos.view:pinview:1.4.3'
)
testImplementation(
'androidx.test:core:1.2.0',
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@
android:theme="@style/OppiaThemeWithoutActionBar" />
<activity android:name=".home.HomeActivity" />
<activity android:name=".player.audio.testing.AudioFragmentTestActivity" />
<activity android:name=".profile.AdminAuthActivity" />
<activity android:name=".profile.AddProfileActivity" />
<activity android:name=".profile.PinPasswordActivity"
android:theme="@style/OppiaThemeWithoutActionBar" />
<activity
android:name=".profile.ProfileActivity"
android:theme="@style/OppiaThemeWithoutActionBar"/>
<activity
android:name=".player.exploration.ExplorationActivity"
android:theme="@style/OppiaThemeWithoutActionBar" />
<activity android:name=".player.state.testing.StateFragmentTestActivity" />
<activity android:name=".profile.ProfileActivity" />
<activity
android:name=".splash.SplashActivity"
android:theme="@style/SplashScreenTheme">
Expand Down
6 changes: 6 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,9 @@ 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.PinPasswordActivity
import org.oppia.app.profile.ProfileActivity
import org.oppia.app.story.StoryActivity
import org.oppia.app.story.testing.StoryFragmentTestActivity
Expand Down Expand Up @@ -37,6 +40,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 All @@ -45,6 +50,7 @@ interface ActivityComponent {
fun inject(continuePlayingFragmentTestActivity: ContinuePlayingFragmentTestActivity)
fun inject(explorationActivity: ExplorationActivity)
fun inject(homeActivity: HomeActivity)
fun inject(pinPasswordActivity: PinPasswordActivity)
fun inject(htmlParserTestActivity: HtmlParserTestActivity)
fun inject(profileActivity: ProfileActivity)
fun inject(questionPlayerActivity: QuestionPlayerActivity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,11 @@ fun setImageDrawable(imageView: ImageView, thumbnailGraphic: LessonThumbnailGrap
}
)
}

@BindingAdapter("profile:src")
fun setProfileImage(imageView: ImageView, imageUrl: String) {
Glide.with(imageView.context)
.load(imageUrl)
.placeholder(R.drawable.default_avatar)
.into(imageView)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.oppia.app.databinding

import android.text.format.DateUtils
import android.widget.ImageView
import android.widget.TextView
import androidx.databinding.BindingAdapter
import java.util.*

/** Converts time in ms to readable relative time string. */
@BindingAdapter("profile:date")
fun setTextWithDate(textView: TextView, timeMs: Long) {
val dateText = "Last used " + DateUtils.getRelativeTimeSpanString(timeMs, Date().time, DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE)
textView.text = dateText
}

8 changes: 4 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 @@ -8,10 +8,10 @@ import org.oppia.app.home.continueplaying.ContinuePlayingFragment
import org.oppia.app.player.audio.AudioFragment
import org.oppia.app.player.exploration.ExplorationFragment
import org.oppia.app.player.state.StateFragment
import org.oppia.app.profile.AdminSettingsDialogFragment
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.profile.ResetPinDialogFragment
import org.oppia.app.story.StoryFragment
import org.oppia.app.testing.BindableAdapterTestFragment
import org.oppia.app.topic.TopicFragment
Expand All @@ -38,8 +38,6 @@ interface FragmentComponent {

fun getViewComponentBuilderProvider(): Provider<ViewComponent.Builder>

fun inject(addProfileFragment: AddProfileFragment)
fun inject(adminAuthFragment: AdminAuthFragment)
fun inject(audioFragment: AudioFragment)
fun inject(bindableAdapterTestFragment: BindableAdapterTestFragment)
fun inject(conceptCardFragment: ConceptCardFragment)
Expand All @@ -55,4 +53,6 @@ interface FragmentComponent {
fun inject(topicPlayFragment: TopicPlayFragment)
fun inject(topicReviewFragment: TopicReviewFragment)
fun inject(topicTrainFragment: TopicTrainFragment)
fun inject(adminSettingsDialogFragment: AdminSettingsDialogFragment)
fun inject(resetPinDialogFragment: ResetPinDialogFragment)
}
32 changes: 32 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,32 @@
package org.oppia.app.profile

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

/** Fragment 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)
if (requestCode == GALLERY_INTENT_RESULT_CODE && resultCode == Activity.RESULT_OK) {
addProfileFragmentPresenter.handleOnActivityResult(data)
}
}
}
178 changes: 178 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,178 @@
package org.oppia.app.profile

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.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 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>
) {
lateinit var uploadImageView: ImageView
private val addViewModel by lazy {
getAddProfileViewModel()
}
private var selectedImage: Uri? = null
var allowDownloadAccess = false
var inputtedPin = false
var inputtedConfirmPin = false

@ExperimentalCoroutinesApi
fun handleOnCreate() {
activity.title = "Add Profile"
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 = addViewModel
}

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

binding.infoIcon.setOnClickListener {
showInfoDialog()
}

addTextChangeListeners(binding)

binding.uploadImageButton.setOnClickListener {
val galleryIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
activity.startActivityForResult(galleryIntent, GALLERY_INTENT_RESULT_CODE)
}
uploadImageView = binding.uploadImageButton

binding.createButton.setOnClickListener {
addViewModel.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()
var failed = false
if (name.isEmpty()) {
addViewModel.nameErrorMsg.set(activity.resources.getString(R.string.add_profile_error_name_empty))
failed = true
}
if (pin.isNotEmpty() && pin.length < 3) {
addViewModel.pinErrorMsg.set(activity.resources.getString(R.string.add_profile_error_pin_length))
failed = true
}
if (pin != confirmPin) {
addViewModel.confirmPinErrorMsg.set(activity.resources.getString(R.string.add_profile_error_pin_confirm_wrong))
failed = true
}
if (failed) {
binding.scroll.smoothScrollTo(0,0)
return@setOnClickListener
}
profileManagementController.addProfile(name, pin, selectedImage, allowDownloadAccess, isAdmin = false).observe(activity, Observer {
if (it.isSuccess()) {
val intent = Intent(activity, ProfileActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
activity.startActivity(intent)
} else if (it.isFailure()) {
when (it.getErrorOrNull()) {
is ProfileManagementController.ProfileNameNotUniqueException -> addViewModel.nameErrorMsg.set(activity.resources.getString(R.string.add_profile_error_name_not_unique))
is ProfileManagementController.ProfileNameOnlyLettersException -> addViewModel.nameErrorMsg.set(activity.resources.getString(R.string.add_profile_error_name_only_letters))
}
binding.scroll.smoothScrollTo(0,0)
}
})
}
}

fun handleOnActivityResult(data: Intent?) {
data?.let {
selectedImage = data.data
Glide.with(activity)
.load(selectedImage)
.centerCrop()
.apply(RequestOptions.circleCropTransform())
.into(uploadImageView)
}
}

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

binding.inputPin.addTextChangedListener(object: TextWatcher {
override fun onTextChanged(pin: CharSequence?, start: Int, before: Int, count: Int) {
pin?.let {
addViewModel.pinErrorMsg.set("")
inputtedPin = it.isNotEmpty()
setValidPin()
}
}
override fun afterTextChanged(confirmPin: Editable?) {}
override fun beforeTextChanged(p0: CharSequence?, start: Int, count: Int, after: Int) {}
})

binding.inputConfirmPin.addTextChangedListener(object: TextWatcher {
override fun onTextChanged(confirmPin: CharSequence?, start: Int, before: Int, count: Int) {
confirmPin?.let {
addViewModel.confirmPinErrorMsg.set("")
inputtedConfirmPin = confirmPin.isNotEmpty()
setValidPin()
}
}
override fun afterTextChanged(confirmPin: Editable?) {}
override fun beforeTextChanged(p0: CharSequence?, start: Int, count: Int, after: Int) {}
})

binding.inputName.addTextChangedListener(object: TextWatcher {
override fun onTextChanged(confirmPin: CharSequence?, start: Int, before: Int, count: Int) {
confirmPin?.let {
addViewModel.nameErrorMsg.set("")
}
}
override fun afterTextChanged(confirmPin: Editable?) {}
override fun beforeTextChanged(p0: CharSequence?, start: Int, count: Int, after: 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.

20 changes: 20 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,20 @@
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

@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("")
}
}
Loading

0 comments on commit 1e8fddd

Please sign in to comment.