Skip to content
This repository has been archived by the owner on Aug 4, 2019. It is now read-only.

Neo Login Password #44

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions app/src/main/java/de/netalic/peacock/common/MyApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package de.netalic.peacock.common

import android.app.Application
import de.netalic.peacock.BuildConfig
import de.netalic.peacock.di.apiModule
import de.netalic.peacock.di.repositoryModule
import de.netalic.peacock.di.viewModelModule
import de.netalic.peacock.di.*
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
Expand All @@ -23,7 +21,9 @@ class MyApplication : Application() {
listOf(
repositoryModule,
viewModelModule,
apiModule
apiModule,
validatorModule,
passwordLogonViewModelModule
)
)
}
Expand Down
27 changes: 27 additions & 0 deletions app/src/main/java/de/netalic/peacock/common/Validator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package de.netalic.peacock.common

class Validator {

fun hasMinimumLength(password: String, length: Int): Boolean {
return password.length >= length
}

fun hasSpecialCharacters(password: String): Boolean {
val customCharacters = arrayOf("@", "#", "$", "%", "+", "=", "_", "*", "?")
for (customCharacter in customCharacters) {
if (password.contains(customCharacter)) {
return true
}
}
return false
}

fun hasCapitalLetter(password: String): Boolean {
return password != password.toLowerCase()
}

fun hasDigit(password: String): Boolean {
return password.matches(".*\\d+.*".toRegex())
}

}
11 changes: 10 additions & 1 deletion app/src/main/java/de/netalic/peacock/di/Modules.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package de.netalic.peacock.di

import de.netalic.peacock.common.Validator
import de.netalic.peacock.data.repository.UserRepository
import de.netalic.peacock.data.webservice.ApiClient
import de.netalic.peacock.ui.login.password.PasswordLoginViewModel
import de.netalic.peacock.ui.login.pattern.PatternViewModel
import de.netalic.peacock.ui.registration.RegistrationViewModel
import org.koin.android.viewmodel.dsl.viewModel
import org.koin.dsl.module


val repositoryModule = module {
single {
UserRepository(get())
Expand All @@ -26,4 +27,12 @@ val apiModule = module {
single {
ApiClient.getService()
}
}

val validatorModule = module {
factory { Validator() }
}

val passwordLogonViewModelModule = module {
viewModel { PasswordLoginViewModel(get()) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package de.netalic.peacock.ui.login.password

import android.os.Bundle
import android.text.Editable
import android.text.Spannable
import android.text.SpannableString
import android.text.TextWatcher
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import de.netalic.peacock.R
import de.netalic.peacock.data.model.Status
import de.netalic.peacock.ui.base.BaseFragment
import de.netalic.peacock.ui.main.MainHostActivity
import kotlinx.android.synthetic.main.fragment_passwordlogin.*
import org.koin.android.viewmodel.ext.android.viewModel

class PasswordLoginFragment : BaseFragment() {

private val mImageViewProfile by lazy { imageView_patternLogin_profile }
private val mTextInputEditTextPassword by lazy { textInputEditText_passwordLogin_password }
private val mTextInputEditTextRepeatPassword by lazy { textInputEditText_passwordLogin_repeatPassword }
private val mTextViewPasswordRules by lazy { textView_passwordLogin_passwordRules }
private val mButtonContinue by lazy { materialButton_passwordLogin_continue }
private val mTextViewSkipToPattern by lazy { textView_patternLogin_skipToPattern }

private val mViewModel: PasswordLoginViewModel by viewModel()

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_passwordlogin, container, false)
}

override fun initUiComponents() {
updateToolbar()
mImageViewProfile.setImageResource(R.drawable.temp)
initObservers()
}

private fun updateToolbar() {
val activity = requireActivity()
if (activity is MainHostActivity) {
activity.updateToolbarTitle(getString(R.string.all_stepNOfFour, "1"))
}
}

private fun initObservers() {
initPasswordObserver()
initPasswordRepeatObserver()
initPasswordEqualityObserver()
}

private fun initPasswordObserver() {
val message = getString(R.string.passwordLogin_passwordRules)
val spannableString = SpannableString(message)
val successColor = ContextCompat.getColor(requireContext(), R.color.success)
val errorColor = ContextCompat.getColor(requireContext(), R.color.error)
val messageParts = message.split(",")
mViewModel.getResponse().observe(this, Observer { response ->

if (response.status == Status.SUCCESS) {

when (response.data) {
ResponseStatus.SUCCESS_MINIMUM_CHARS -> {
val startIndex = message.indexOf(messageParts[0])
tina-t2 marked this conversation as resolved.
Show resolved Hide resolved
val endIndex = startIndex + (messageParts[0].length)
spannableString.setSpan(ForegroundColorSpan(successColor), startIndex, endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
ResponseStatus.SUCCESS_UPPERCASE -> {
val startIndex = message.indexOf(messageParts[1])
val endIndex = startIndex + (messageParts[1].length)
spannableString.setSpan(ForegroundColorSpan(successColor), startIndex, endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
ResponseStatus.SUCCESS_DIGIT -> {
val startIndex = message.indexOf(messageParts[2])
val endIndex = startIndex + (messageParts[2].length)
spannableString.setSpan(ForegroundColorSpan(successColor), startIndex, endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
ResponseStatus.SUCCESS_SPECIAL_CHAR -> {
val startIndex = message.indexOf(messageParts[3])
val endIndex = startIndex + (messageParts[3].length)
spannableString.setSpan(ForegroundColorSpan(successColor), startIndex, endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
else -> {}
}

}

else if (response.status == Status.FAILED) {
val throwableMessage = response.throwable?.message ?: return@Observer
tina-t2 marked this conversation as resolved.
Show resolved Hide resolved
when (throwableMessage) {
PasswordLoginViewModel.FAILED_MINIMUM_CHARS -> {
val startIndex = message.indexOf(messageParts[0])
val endIndex = startIndex + (messageParts[0].length)
spannableString.setSpan(ForegroundColorSpan(errorColor), startIndex, endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
PasswordLoginViewModel.FAILED_UPPERCASE -> {
val startIndex = message.indexOf(messageParts[1])
val endIndex = startIndex + (messageParts[1].length)
spannableString.setSpan(ForegroundColorSpan(errorColor), startIndex, endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
PasswordLoginViewModel.FAILED_DIGIT -> {
val startIndex = message.indexOf(messageParts[2])
val endIndex = startIndex + (messageParts[2].length)
spannableString.setSpan(ForegroundColorSpan(errorColor), startIndex, endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
PasswordLoginViewModel.FAILED_SPECIAL_CHAR -> {
val startIndex = message.indexOf(messageParts[3])
val endIndex = startIndex + (messageParts[3].length)
spannableString.setSpan(ForegroundColorSpan(errorColor), startIndex, endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}
mTextViewPasswordRules.text = spannableString
})
}

private fun initPasswordRepeatObserver() {
mViewModel.getRepeatResponse().observe(this, Observer {})
}

private fun initPasswordEqualityObserver() {
mViewModel.getEqualityResponse().observe(this, Observer {
val status = it.data ?: throw IllegalArgumentException("Data is null")
if (status == ResponseStatus.PASSWORD_MATCH) {
Toast.makeText(context, "Password Match", Toast.LENGTH_LONG).show()
mButtonContinue.isEnabled = true
mButtonContinue.backgroundTintList = ContextCompat.getColorStateList(requireContext(), R.color.colorTertiary)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs a selector to handle the button color.
I have that selector in the registration branch in drawable, you can take it to prevent the conflict.

} else {
mButtonContinue.isEnabled = false
mButtonContinue.backgroundTintList = ContextCompat.getColorStateList(requireContext(), R.color.colorPrimaryDark)
}
})
}

override fun initUiListeners() {
mTextInputEditTextPassword.addTextChangedListener(PasswordListener())
mTextInputEditTextRepeatPassword.addTextChangedListener(PasswordRepeatListener())
mTextViewSkipToPattern.setOnClickListener { findNavController()
.navigate(R.id.action_passwordLoginFragment_to_patternFragment) }
}

inner class PasswordListener : TextWatcher {
override fun afterTextChanged(s: Editable?) {
val input = s?.toString() ?: return
tina-t2 marked this conversation as resolved.
Show resolved Hide resolved
mViewModel.onPasswordEntered(input)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
}

inner class PasswordRepeatListener : TextWatcher {
override fun afterTextChanged(s: Editable?) {
val input = s?.toString() ?: return
mViewModel.onPasswordRepeated(input)
}
override fun beforeTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package de.netalic.peacock.ui.login.password

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import de.netalic.peacock.common.Validator
import de.netalic.peacock.data.model.MyResponse
import de.netalic.peacock.ui.base.BaseViewModel

enum class ResponseStatus {
SUCCESS_MINIMUM_CHARS,
SUCCESS_UPPERCASE,
SUCCESS_DIGIT,
SUCCESS_SPECIAL_CHAR,
PASSWORD_MATCH,
PASSWORD_NOT_MATCH
}

class PasswordLoginViewModel(private val validator: Validator) : BaseViewModel(){
tina-t2 marked this conversation as resolved.
Show resolved Hide resolved

private var mPassword: String? = null
private var mPasswordRepeat: String? = null

private val mPasswordResponse = MutableLiveData<MyResponse<ResponseStatus>>()
private val mRepeatPasswordResponse = MutableLiveData<MyResponse<ResponseStatus>>()
private val mResponseEquality = MutableLiveData<MyResponse<ResponseStatus>>()



fun getResponse(): LiveData<MyResponse<ResponseStatus>> {
return mPasswordResponse
}

fun getRepeatResponse(): LiveData<MyResponse<ResponseStatus>> {
return mRepeatPasswordResponse
}

fun getEqualityResponse(): LiveData<MyResponse<ResponseStatus>> {
return mResponseEquality
}

fun onPasswordEntered(password: String) {

var counter = 0

if (validator.hasMinimumLength(password, PASSWORD_LENGTH)) {
mPasswordResponse.value = MyResponse.success(ResponseStatus.SUCCESS_MINIMUM_CHARS)
++counter
} else {
mPasswordResponse.value = MyResponse.failed(Throwable(FAILED_MINIMUM_CHARS))
}

if (validator.hasCapitalLetter(password)) {
mPasswordResponse.value = MyResponse.success(ResponseStatus.SUCCESS_UPPERCASE)
++counter
} else {
mPasswordResponse.value = MyResponse.failed(Throwable(FAILED_UPPERCASE))
}

if (validator.hasDigit(password)) {
mPasswordResponse.value = MyResponse.success(ResponseStatus.SUCCESS_DIGIT)
++counter
} else {
mPasswordResponse.value = MyResponse.failed(Throwable(FAILED_DIGIT))
}

if (validator.hasSpecialCharacters(password)) {
mPasswordResponse.value = MyResponse.success(ResponseStatus.SUCCESS_SPECIAL_CHAR)
++counter
} else {
mPasswordResponse.value = MyResponse.failed(Throwable(FAILED_SPECIAL_CHAR))
}

if (counter == 4) {
mPassword = password
} else {
mPassword = null
}

isPasswordMatch()
}

fun onPasswordRepeated(password: String) {
mPasswordRepeat = password
isPasswordMatch()
}

private fun isPasswordMatch() {
if (mPassword == null || mPasswordRepeat == null) {
mResponseEquality.value = MyResponse.success(ResponseStatus.PASSWORD_NOT_MATCH)
tina-t2 marked this conversation as resolved.
Show resolved Hide resolved
return
}
if (mPassword == mPasswordRepeat) {
mResponseEquality.value = MyResponse.success(ResponseStatus.PASSWORD_MATCH)
} else {
mResponseEquality.value = MyResponse.success(ResponseStatus.PASSWORD_NOT_MATCH)
}
}

companion object {
const val FAILED_MINIMUM_CHARS = "failed_minimum_chars"
const val FAILED_UPPERCASE = "failed_uppercase"
const val FAILED_DIGIT = "failed_digit"
const val FAILED_SPECIAL_CHAR = "failed_special_char"

private const val PASSWORD_LENGTH = 8
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
import com.andrognito.patternlockview.PatternLockView
import com.andrognito.patternlockview.listener.PatternLockViewListener
import com.andrognito.patternlockview.utils.PatternLockUtils
Expand All @@ -22,6 +24,7 @@ class PatternFragment : BaseFragment(), PatternLockViewListener {
private val mImageViewProfile by lazy { imageView_patternLogin_profile }
private val mPatternLockView by lazy { patternLockView_patternLogin_pattern }
private val mTextViewMessage by lazy { textView_patternLogin_message }
private val mTextViewSkipToPassword by lazy { textView_patternLogin_skipToPassword }

private val mPatternViewModel: PatternViewModel by viewModel()

Expand Down Expand Up @@ -66,6 +69,8 @@ class PatternFragment : BaseFragment(), PatternLockViewListener {

override fun initUiListeners() {
mPatternLockView.addPatternLockListener(this)
mTextViewSkipToPassword.setOnClickListener { findNavController()
.navigate(R.id.action_patternFragment_to_passwordLoginFragment) }
}

override fun onComplete(pattern: MutableList<PatternLockView.Dot>?) {
Expand Down
Loading