Improve password protection
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
@@ -17,6 +18,7 @@ import org.koitharu.kotatsu.base.ui.dialog.TextInputDialog
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
import java.io.File
|
||||
|
||||
@@ -77,6 +79,10 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
||||
?: getString(R.string.not_available)
|
||||
}
|
||||
}
|
||||
AppSettings.KEY_APP_PASSWORD -> {
|
||||
findPreference<SwitchPreference>(AppSettings.KEY_PROTECT_APP)
|
||||
?.isChecked = !settings.appPassword.isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,8 +108,10 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
||||
true
|
||||
}
|
||||
AppSettings.KEY_PROTECT_APP -> {
|
||||
if ((preference as? SwitchPreference ?: return false).isChecked) {
|
||||
enableAppProtection(preference)
|
||||
val pref = (preference as? SwitchPreference ?: return false)
|
||||
if (pref.isChecked) {
|
||||
pref.isChecked = false
|
||||
startActivity(Intent(preference.context, ProtectSetupActivity::class.java))
|
||||
} else {
|
||||
settings.appPassword = null
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.koitharu.kotatsu.core.backup.RestoreRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.settings.backup.BackupViewModel
|
||||
import org.koitharu.kotatsu.settings.backup.RestoreViewModel
|
||||
import org.koitharu.kotatsu.settings.protect.ProtectSetupViewModel
|
||||
|
||||
val settingsModule
|
||||
get() = module {
|
||||
@@ -19,4 +20,5 @@ val settingsModule
|
||||
|
||||
viewModel { BackupViewModel(get(), androidContext()) }
|
||||
viewModel { (uri: Uri?) -> RestoreViewModel(uri, get(), androidContext()) }
|
||||
viewModel { ProtectSetupViewModel(get()) }
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.koitharu.kotatsu.settings.protect
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.TextView
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isGone
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.databinding.ActivitySetupProtectBinding
|
||||
|
||||
class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWatcher,
|
||||
View.OnClickListener, TextView.OnEditorActionListener {
|
||||
|
||||
private val viewModel by viewModel<ProtectSetupViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivitySetupProtectBinding.inflate(layoutInflater))
|
||||
binding.editPassword.addTextChangedListener(this)
|
||||
binding.editPassword.setOnEditorActionListener(this)
|
||||
binding.buttonNext.setOnClickListener(this)
|
||||
binding.buttonCancel.setOnClickListener(this)
|
||||
|
||||
viewModel.isSecondStep.observe(this, this::onStepChanged)
|
||||
viewModel.onPasswordSet.observe(this) {
|
||||
finishAfterTransition()
|
||||
}
|
||||
viewModel.onPasswordMismatch.observe(this) {
|
||||
binding.editPassword.error = getString(R.string.passwords_mismatch)
|
||||
}
|
||||
viewModel.onClearText.observe(this) {
|
||||
binding.editPassword.text?.clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
val basePadding = resources.getDimensionPixelOffset(R.dimen.screen_padding)
|
||||
binding.root.setPadding(
|
||||
basePadding + insets.left,
|
||||
basePadding + insets.top,
|
||||
basePadding + insets.right,
|
||||
basePadding + insets.bottom
|
||||
)
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_cancel -> finish()
|
||||
R.id.button_next -> viewModel.onNextClick(
|
||||
password = binding.editPassword.text?.toString() ?: return
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
|
||||
return if (actionId == EditorInfo.IME_ACTION_DONE && binding.buttonNext.isEnabled) {
|
||||
binding.buttonNext.performClick()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
binding.editPassword.error = null
|
||||
val isEnoughLength = (s?.length ?: 0) >= MIN_PASSWORD_LENGTH
|
||||
binding.buttonNext.isEnabled = isEnoughLength
|
||||
binding.layoutPassword.isHelperTextEnabled =
|
||||
!isEnoughLength || viewModel.isSecondStep.value == true
|
||||
}
|
||||
|
||||
private fun onStepChanged(isSecondStep: Boolean) {
|
||||
binding.buttonCancel.isGone = isSecondStep
|
||||
if (isSecondStep) {
|
||||
binding.layoutPassword.helperText = getString(R.string.repeat_password)
|
||||
binding.buttonNext.setText(R.string.confirm)
|
||||
} else {
|
||||
binding.layoutPassword.helperText = getString(R.string.password_length_hint)
|
||||
binding.buttonNext.setText(R.string.next)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
const val MIN_PASSWORD_LENGTH = 4
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.koitharu.kotatsu.settings.protect
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.md5
|
||||
|
||||
class ProtectSetupViewModel(
|
||||
private val settings: AppSettings
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val firstPassword = MutableStateFlow<String?>(null)
|
||||
|
||||
val isSecondStep = firstPassword.map {
|
||||
it != null
|
||||
}.asLiveDataDistinct(viewModelScope.coroutineContext)
|
||||
val onPasswordSet = SingleLiveEvent<Unit>()
|
||||
val onPasswordMismatch = SingleLiveEvent<Unit>()
|
||||
val onClearText = SingleLiveEvent<Unit>()
|
||||
|
||||
fun onNextClick(password: String) {
|
||||
if (firstPassword.value == null) {
|
||||
firstPassword.value = password
|
||||
onClearText.call(Unit)
|
||||
} else {
|
||||
if (firstPassword.value == password) {
|
||||
settings.appPassword = password.md5()
|
||||
onPasswordSet.call(Unit)
|
||||
} else {
|
||||
onPasswordMismatch.call(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user