Skip to content

Commit

Permalink
Hint functionality for user password (#18)
Browse files Browse the repository at this point in the history
* Added hint textfield

* Added hint textfield

* Changed the Id according to naming convention

* Branch updated

* Reverted the accidental changes

* Reverted the change

* Added functionality to display and hide the hint inside login fragment.

* Added functionality to display and hide the hint inside login fragment.

* Added functionality to display and hide the hint inside login fragment.

* Updated functionality to show and hide hint in Login Fragment with Util methods.

* Updated Java Version to match build.gradle

* made a few changes

* Updated Util methods

* Updated the code for displaying hint

* Added Encryption function to encrypt hint inside UserDetailDaoSecure

* Added encryption to hint

* Added function to call hint in UserDetailsDao

* Added getHint function in UserDetailsDaoSecure

* Updated the code to get hint

* Added method to call dao in repository

* Calling repo method in viewmodel

* Added functionality to display hint in login fragment

* Made the required changes

* Updated the code

* Display hint in login screen

* Updated the code

* Added validation rule for hint length

* Added validation check for hint

* Fixed issues with hint error

* handling hint validation

Signed-off-by: Nitin Verma <[email protected]>

* doing validation in background

Signed-off-by: Nitin Verma <[email protected]>

Co-authored-by: Nitin Verma <[email protected]>
  • Loading branch information
Bhawna-Ad and Ni3verma authored Jul 29, 2021
1 parent 0a8a913 commit 3a35079
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ interface UserDetailsDao {

@Query("select * from user_details limit 1")
suspend fun getUserDetails(): UserDetailsEntity

@Query("select hint from user_details")
suspend fun getHint(): String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ data class UserDetailsEntity(
@PrimaryKey(autoGenerate = true)
val key: Int,
val password: String,
val hint: String,
val hint: String?,
val creationDate: Date,
val updateDate: Date
) {
constructor(password: String, hint: String, creationDate: Date, updateDate: Date) : this(
constructor(password: String, hint: String?, creationDate: Date, updateDate: Date) : this(
0,
password,
hint,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
package com.andryoga.safebox.data.db.secureDao

import com.andryoga.safebox.common.Utils.encryptNullableString
import com.andryoga.safebox.data.db.dao.UserDetailsDao
import com.andryoga.safebox.data.db.entity.UserDetailsEntity
import com.andryoga.safebox.security.interfaces.HashingUtils
import com.andryoga.safebox.security.interfaces.SymmetricKeyUtils
import javax.inject.Inject

class UserDetailsDaoSecure @Inject constructor(
private val userDetailsDao: UserDetailsDao,
private val hashingUtils: HashingUtils
private val hashingUtils: HashingUtils,
private val symmetricKeyUtils: SymmetricKeyUtils
) : UserDetailsDao {
override suspend fun insertUserDetailsData(userDetailsEntity: UserDetailsEntity) {
userDetailsDao.insertUserDetailsData(hash(userDetailsEntity))
var entity = hash(userDetailsEntity)
entity = encrypt(entity)
userDetailsDao.insertUserDetailsData(entity)
}

override suspend fun getUserDetails(): UserDetailsEntity {
return userDetailsDao.getUserDetails()
}

override suspend fun getHint(): String? {
return userDetailsDao.getHint()?.let { symmetricKeyUtils.decrypt(it) }
}

suspend fun checkPassword(password: String): Boolean {
val userDetailsEntity = getUserDetails()
return hashingUtils.compareHash(password, userDetailsEntity.password)
Expand All @@ -33,4 +42,16 @@ class UserDetailsDaoSecure @Inject constructor(
)
}
}

private fun encrypt(userDetailsEntity: UserDetailsEntity): UserDetailsEntity {
userDetailsEntity.let {
return UserDetailsEntity(
it.key,
it.password,
it.hint.encryptNullableString(symmetricKeyUtils),
it.creationDate,
it.updateDate
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import javax.inject.Inject
class UserDetailsRepositoryImpl @Inject constructor(
private val userDetailsDaoSecure: UserDetailsDaoSecure
) : UserDetailsRepository {
override suspend fun insertUserDetailsData(password: String, hint: String) {
override suspend fun insertUserDetailsData(password: String, hint: String?) {
val entity = UserDetailsEntity(
password, hint, Date(), Date()
)
Expand All @@ -19,4 +19,8 @@ class UserDetailsRepositoryImpl @Inject constructor(
override suspend fun checkPassword(password: String): Boolean {
return userDetailsDaoSecure.checkPassword(password)
}

override suspend fun getHint(): String? {
return userDetailsDaoSecure.getHint()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.andryoga.safebox.data.repository.interfaces

interface UserDetailsRepository {
suspend fun insertUserDetailsData(password: String, hint: String)
suspend fun insertUserDetailsData(password: String, hint: String?)
suspend fun checkPassword(password: String): Boolean
suspend fun getHint(): String?
}
30 changes: 30 additions & 0 deletions app/src/main/java/com/andryoga/safebox/ui/common/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ object Utils {
}
}

fun switchText(view: TextView, originalText: String, changeToText: String) {
if (view.text == originalText) {
view.text = changeToText
} else {
view.text = originalText
}
}

fun startMotionLayoutTransition(
motionLayout: MotionLayout,
endState: Int,
Expand All @@ -48,4 +56,26 @@ object Utils {
motionLayout.transitionToEnd()
}
}

fun longestCommonSubstring(string1: String, string2: String): Int {
val matrix = Array(string1.length + 1) {
IntArray(string2.length + 1)
}
var maxLength = 0
for (i in 1 until matrix.size) {
for (j in 1 until matrix[0].size) {
val text1 = string1[i - 1]
val text2 = string2[j - 1]
if (text1 != text2) {
matrix[i][j] = 0
} else {
matrix[i][j] = matrix[i - 1][j - 1] + 1
}
if (matrix[i][j] > maxLength) {
maxLength = matrix[i][j]
}
}
}
return maxLength
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.widget.addTextChangedListener
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.andryoga.safebox.R
import com.andryoga.safebox.databinding.ChooseMasterPswrdFragmentBinding
import com.andryoga.safebox.ui.common.Utils
import com.andryoga.safebox.ui.view.chooseMasterPswrd.PasswordValidationFailureCode.*
import com.andryoga.safebox.ui.view.chooseMasterPswrd.ChooseMasterPswrdValidationFailureCode.*
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber

Expand All @@ -21,7 +22,7 @@ class ChooseMasterPswrdFragment : Fragment() {
private val viewModel: ChooseMasterPswrdViewModel by viewModels()

private lateinit var binding: ChooseMasterPswrdFragmentBinding
private lateinit var validatorMapping: Map<PasswordValidationFailureCode, TextView>
private lateinit var pswrdValidatorMapping: Map<ChooseMasterPswrdValidationFailureCode, TextView>

override fun onCreateView(
inflater: LayoutInflater,
Expand All @@ -37,38 +38,54 @@ class ChooseMasterPswrdFragment : Fragment() {
binding.viewModel = viewModel
binding.lifecycleOwner = this

initValidatorMapping()
binding.pswrdText.addTextChangedListener {
viewModel.evaluateValidationRules()
}

binding.confirmPswrdText.addTextChangedListener {
viewModel.evaluateValidationRules()
}

binding.hintText.addTextChangedListener {
viewModel.evaluateValidationRules()
}

initPasswordValidatorMapping()
setupObservers()

return binding.root
}

private fun setupObservers() {
viewModel.pswrdValidationFailures.observe(viewLifecycleOwner) { failureCode ->
viewModel.validationFailureCode.observe(viewLifecycleOwner) { failureCode ->
// by default make every validation as pass
validatorMapping.values.forEach { validationView ->
pswrdValidatorMapping.values.forEach { validationView ->
Utils.setTextViewLeftDrawable(validationView, R.drawable.ic_check_24)
}
binding.hint.error = null

// save button will be enabled only if there is no validation failure
binding.saveBtn.isEnabled = failureCode.isNullOrEmpty()

// change icon for those where validation failed
Timber.d("failure validation codes : $failureCode")
for (code in failureCode) {
if (validatorMapping.containsKey(code))
Utils.setTextViewLeftDrawable(validatorMapping[code]!!, R.drawable.ic_error_24)
else {
Timber.e("$code not found in validatorMapping")
when {
pswrdValidatorMapping.containsKey(code) -> Utils.setTextViewLeftDrawable(
pswrdValidatorMapping[code]!!,
R.drawable.ic_error_24
)
code == HINT_IS_SUBSET -> {
binding.hint.isErrorEnabled = true
binding.hint.error = getString(R.string.hint_error)
}
else -> {
Timber.e("$code not found in validatorMapping")
}
}
}
}

viewModel.isBothPasswordsMatch.observe(viewLifecycleOwner) { isMatch ->
if (isMatch) {
Utils.setTextViewLeftDrawable(binding.pswrdMatchValidation, R.drawable.ic_check_24)
} else {
Utils.setTextViewLeftDrawable(binding.pswrdMatchValidation, R.drawable.ic_error_24)
}
}

viewModel.navigateToHome.observe(viewLifecycleOwner) { isNavigate ->
if (isNavigate) {
Timber.i("navigating to home")
Expand All @@ -77,8 +94,8 @@ class ChooseMasterPswrdFragment : Fragment() {
}
}

private fun initValidatorMapping() {
validatorMapping = mapOf(
private fun initPasswordValidatorMapping() {
pswrdValidatorMapping = mapOf(
LOW_PASSWORD_LENGTH to binding.lengthValidation,
LESS_SPECIAL_CHAR_COUNT to binding.specialCharValidation,
NOT_MIX_CASE to binding.caseValidation,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.andryoga.safebox.ui.view.chooseMasterPswrd

enum class PasswordValidationFailureCode {
enum class ChooseMasterPswrdValidationFailureCode {
LOW_PASSWORD_LENGTH,
LESS_SPECIAL_CHAR_COUNT,
NOT_MIX_CASE,
LESS_NUMERIC_COUNT,
ALTERNATE_CHAR_FOUND,
PASSWORD_DO_NOT_MATCH
PASSWORD_DO_NOT_MATCH,
HINT_IS_SUBSET
}
Loading

0 comments on commit 3a35079

Please sign in to comment.