Squashed commit of the following:

commit ff0a46aefcc6a918ec349608f21613c71605eefe
Author: Jonas L <jonas@determapp.de>
Date:   Mon Apr 8 00:00:00 2019 +0000

    Skip asking for password if only one parent and password is empty

commit c2ca7bd177806f99a3db49528a35de3b8a06782e
Author: Jonas L <jonas@determapp.de>
Date:   Mon Apr 8 00:00:00 2019 +0000

    Allow setting password to empty

commit 218f0fc94047593ff9cb083ce1eefa48e69c8e6d
Author: Jonas L <jonas@determapp.de>
Date:   Mon Apr 8 00:00:00 2019 +0000

    Extend set password view
This commit is contained in:
Jonas L 2019-04-08 00:00:00 +00:00
parent 2dd04796b0
commit 82295de5fa
9 changed files with 79 additions and 7 deletions

View file

@ -23,6 +23,7 @@ import io.timelimit.android.coroutines.executeAndWait
import io.timelimit.android.coroutines.runAsync
import io.timelimit.android.crypto.PasswordHashing
import io.timelimit.android.livedata.castDown
import io.timelimit.android.livedata.waitForNonNullValue
import io.timelimit.android.livedata.waitForNullableValue
import io.timelimit.android.logic.AppLogic
import io.timelimit.android.logic.DefaultAppLogic
@ -40,6 +41,25 @@ class LoginPasswordDialogFragmentModel(application: Application): AndroidViewMod
val status = statusInternal.castDown()
fun tryDefaultLogin(model: ActivityViewModel) {
runAsync {
loginLock.withLock {
logic.database.user().getParentUsersLive().waitForNonNullValue().singleOrNull()?.let { user ->
val emptyPasswordValid = Threads.crypto.executeAndWait { PasswordHashing.validateSync("", user.password) }
if (emptyPasswordValid) {
model.setAuthenticatedUser(AuthenticatedUser(
userId = user.id,
passwordHash = user.password
))
statusInternal.value = LoginPasswordDialogFragmentStatus.Success
}
}
}
}
}
fun tryLogin(userId: String, password: String, model: ActivityViewModel) {
runAsync {
loginLock.withLock {

View file

@ -56,6 +56,10 @@ class NewLoginFragment: DialogFragment() {
if (savedInstanceState?.containsKey(SELECTED_USER_ID) == true) {
selectedUserId.value = savedInstanceState.getString(SELECTED_USER_ID)
}
if (savedInstanceState == null) {
model.tryDefaultLogin(getActivityViewModel(activity!!))
}
}
override fun onCreateDialog(savedInstanceState: Bundle?) = object: BottomSheetDialog(context!!, theme) {

View file

@ -67,7 +67,7 @@ class ChangeParentPasswordFragment : Fragment(), FragmentWithCustomTitle {
model.changePassword(
parentUserId = params.parentUserId,
oldPassword = binding.oldPassword.text.toString(),
newPassword = binding.newPassword.password.value!!
newPassword = binding.newPassword.readPassword()
)
}
@ -109,6 +109,8 @@ class ChangeParentPasswordFragment : Fragment(), FragmentWithCustomTitle {
}.let { }
})
binding.newPassword.allowNoPassword.value = true
return binding.root
}

View file

@ -61,10 +61,12 @@ class SetupLocalModeFragment : Fragment() {
binding.nextBtn.setOnClickListener {
model.trySetupWithPassword(
set_password_view.password.value!!
binding.setPasswordView.readPassword()
)
}
binding.setPasswordView.allowNoPassword.value = true
return binding.root
}
}

View file

@ -75,6 +75,8 @@ class AddUserFragment : Fragment() {
val isPasswordOk = binding.password.passwordOk.or(isPasswordRequired.invert())
binding.password.allowNoPassword.value = true
// username
val username = binding.name.getTextLive()
@ -94,7 +96,7 @@ class AddUserFragment : Fragment() {
name = binding.name.text.toString(),
type = userType.value!!,
password = when (userType.value!!) {
UserType.Parent -> binding.password.password.value!!
UserType.Parent -> binding.password.readPassword()
UserType.Child -> ""
},
model = auth

View file

@ -25,6 +25,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import io.timelimit.android.databinding.ViewSetPasswordBinding
import io.timelimit.android.livedata.and
import io.timelimit.android.livedata.or
import io.timelimit.android.util.PasswordValidator
class SetPasswordView(context: Context, attributeSet: AttributeSet): FrameLayout(context, attributeSet) {
@ -36,6 +38,14 @@ class SetPasswordView(context: Context, attributeSet: AttributeSet): FrameLayout
val password = MutableLiveData<String>()
val passwordRepeat = MutableLiveData<String>()
val allowNoPassword = MutableLiveData<Boolean>().apply { value = false }
val noPasswordChecked = MutableLiveData<Boolean>().apply { value = false }
val useEmptyPassword = allowNoPassword.and(noPasswordChecked)
fun readPassword() = if (useEmptyPassword.value!! == true)
""
else
password.value!!
init {
password.value = ""
@ -71,6 +81,20 @@ class SetPasswordView(context: Context, attributeSet: AttributeSet): FrameLayout
password.observeForever { binding.password = it!! }
passwordRepeat.observeForever() { binding.passwordRepeat = it!! }
allowNoPassword.observeForever { binding.allowNoPassword = it }
noPasswordChecked.observeForever {
binding.noPasswordChecked = it
if (binding.noPasswordCheckbox.isChecked != it) {
binding.noPasswordCheckbox.isChecked = it
}
}
binding.noPasswordCheckbox.setOnCheckedChangeListener { _, isChecked ->
if (isChecked != noPasswordChecked.value) {
noPasswordChecked.value = isChecked
}
}
}
private val passwordQualityProblem: LiveData<String?> = Transformations.map(password) {
@ -87,7 +111,7 @@ class SetPasswordView(context: Context, attributeSet: AttributeSet): FrameLayout
(passwordValue.isNotEmpty() && it.isNotEmpty()) && passwordValue != it
}
}
val passwordOk: LiveData<Boolean> = Transformations.switchMap(password) {
val passwordOk: LiveData<Boolean> = useEmptyPassword.or(Transformations.switchMap(password) {
val password1 = it
Transformations.map(passwordRepeat) {
@ -96,7 +120,7 @@ class SetPasswordView(context: Context, attributeSet: AttributeSet): FrameLayout
password1.isNotEmpty() && password2.isNotEmpty() && (password1 == password2) &&
(PasswordValidator.validate(password1, context) == null)
}
}
})
init {
passwordQualityProblem .observeForever { binding.passwordProblem = it }