Added the options to change the Security Lock option between PIN and None, and force the UI to show the correct one when it is changed.
This commit is contained in:
parent
b4965d8a01
commit
e8e1259314
6 changed files with 150 additions and 26 deletions
|
@ -18,8 +18,9 @@ abstract class BaseSecurityLockDialog : DialogFragment() {
|
||||||
/** Used to denote that the user is in the step of creating the preferred security lock option */
|
/** Used to denote that the user is in the step of creating the preferred security lock option */
|
||||||
const val STEP_SECURITY_LOCK_CREATE = 1
|
const val STEP_SECURITY_LOCK_CREATE = 1
|
||||||
|
|
||||||
/** Used to denote that the user is in the step of confirming the newly created security lock option */
|
/** Used to denote that the user is in the step of confirming the newly created security lock option,
|
||||||
private const val STEP_SECURITY_LOCK_CONFIRM = 2
|
* this option should only be used internally */
|
||||||
|
const val STEP_SECURITY_LOCK_CONFIRM = 2
|
||||||
|
|
||||||
/** Used to denote that the user is in the step of verifying the current security lock option, to give
|
/** Used to denote that the user is in the step of verifying the current security lock option, to give
|
||||||
* permission to do a security constrained action like sending a transaction or trying to change the
|
* permission to do a security constrained action like sending a transaction or trying to change the
|
||||||
|
@ -45,10 +46,10 @@ abstract class BaseSecurityLockDialog : DialogFragment() {
|
||||||
protected var mCallback: OnPINPatternEnteredListener? = null
|
protected var mCallback: OnPINPatternEnteredListener? = null
|
||||||
|
|
||||||
/** Keeps track of the current step, can be create, confirm of verify */
|
/** Keeps track of the current step, can be create, confirm of verify */
|
||||||
protected var currentStep: Int = 1
|
protected var currentStep: Int = -1
|
||||||
|
|
||||||
/** Used so the calling object knows what was the intention to ask for the Security Lock */
|
/** Used so the calling object knows what was the intention to ask for the Security Lock */
|
||||||
protected var actionIdentifier: Int = 0
|
protected var actionIdentifier: Int = -1
|
||||||
|
|
||||||
/** Keeps track of all RxJava disposables, to make sure they are all disposed when the fragment is destroyed */
|
/** Keeps track of all RxJava disposables, to make sure they are all disposed when the fragment is destroyed */
|
||||||
protected var mDisposables = CompositeDisposable()
|
protected var mDisposables = CompositeDisposable()
|
||||||
|
@ -64,9 +65,9 @@ abstract class BaseSecurityLockDialog : DialogFragment() {
|
||||||
currentEncryptedPINPattern = PreferenceManager.getDefaultSharedPreferences(context)
|
currentEncryptedPINPattern = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
.getString(Constants.KEY_ENCRYPTED_PIN, "")?.trim()
|
.getString(Constants.KEY_ENCRYPTED_PIN, "")?.trim()
|
||||||
|
|
||||||
currentStep = arguments?.getInt(KEY_STEP_SECURITY_LOCK) ?: 0
|
currentStep = arguments?.getInt(KEY_STEP_SECURITY_LOCK) ?: -1
|
||||||
|
|
||||||
actionIdentifier = arguments?.getInt(KEY_ACTION_IDENTIFIER) ?: 0
|
actionIdentifier = arguments?.getInt(KEY_ACTION_IDENTIFIER) ?: -1
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package cy.agorise.bitsybitshareswallet.fragments
|
package cy.agorise.bitsybitshareswallet.fragments
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.preference.PreferenceManager
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
import cy.agorise.bitsybitshareswallet.R
|
import cy.agorise.bitsybitshareswallet.R
|
||||||
|
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||||
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
|
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.android.synthetic.main.dialog_pin_security_lock.*
|
import kotlinx.android.synthetic.main.dialog_pin_security_lock.*
|
||||||
|
@ -25,23 +27,50 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
|
||||||
return inflater.inflate(R.layout.dialog_pin_security_lock, container, false)
|
return inflater.inflate(R.layout.dialog_pin_security_lock, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var newPIN = ""
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
setupScreen()
|
||||||
|
|
||||||
// Listens to the event when the user clicks the 'Enter' button in the keyboard and acts accordingly
|
// Listens to the event when the user clicks the 'Enter' button in the keyboard and acts accordingly
|
||||||
tietPIN.setOnEditorActionListener { v, actionId, _ ->
|
tietPIN.setOnEditorActionListener { v, actionId, _ ->
|
||||||
var handled = false
|
var handled = false
|
||||||
if (actionId == EditorInfo.IME_ACTION_GO) {
|
if (actionId == EditorInfo.IME_ACTION_GO) {
|
||||||
val encryptedPIN = CryptoUtils.encrypt(v.context, v.text.toString()).trim()
|
|
||||||
|
|
||||||
if (currentStep == STEP_SECURITY_LOCK_VERIFY) {
|
if (currentStep == STEP_SECURITY_LOCK_VERIFY) {
|
||||||
// The user just wants to verify the current encrypted PIN/Pattern
|
// The user just wants to verify the current encrypted PIN/Pattern
|
||||||
|
val encryptedPIN = CryptoUtils.encrypt(v.context, v.text.toString()).trim()
|
||||||
|
|
||||||
if (encryptedPIN == currentEncryptedPINPattern) {
|
if (encryptedPIN == currentEncryptedPINPattern) {
|
||||||
// PIN is correct, proceed
|
// PIN is correct, proceed
|
||||||
dismiss()
|
dismiss()
|
||||||
mCallback?.onPINPatternEntered(actionIdentifier)
|
mCallback?.onPINPatternEntered(actionIdentifier)
|
||||||
} else {
|
} else {
|
||||||
tilPIN.error = "Wrong PIN"
|
tilPIN.error = getString(R.string.error__wrong_pin)
|
||||||
|
}
|
||||||
|
} else if (currentStep == STEP_SECURITY_LOCK_CREATE) {
|
||||||
|
// The user is trying to create a new PIN
|
||||||
|
if (v.text.toString().trim().length >= Constants.MIN_PIN_LENGTH) {
|
||||||
|
// Proceed to the next step only if the PIN has the min length
|
||||||
|
newPIN = v.text.toString().trim()
|
||||||
|
currentStep = STEP_SECURITY_LOCK_CONFIRM
|
||||||
|
setupScreen()
|
||||||
|
}
|
||||||
|
} else if (currentStep == STEP_SECURITY_LOCK_CONFIRM) {
|
||||||
|
val pinConfirm = v.text.toString().trim()
|
||||||
|
if (pinConfirm != newPIN) {
|
||||||
|
tvTitle.text = getString(R.string.title__pins_dont_match)
|
||||||
|
} else {
|
||||||
|
val encryptedPIN = CryptoUtils.encrypt(v.context, v.text.toString()).trim()
|
||||||
|
|
||||||
|
// Stores the newly selected PIN, encrypted
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(v.context).edit()
|
||||||
|
.putString(Constants.KEY_ENCRYPTED_PIN, encryptedPIN)
|
||||||
|
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0).apply() // 0 -> PIN
|
||||||
|
|
||||||
|
dismiss()
|
||||||
|
mCallback?.onPINPatternChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,11 +79,47 @@ class PINSecurityLockDialog : BaseSecurityLockDialog() {
|
||||||
handled
|
handled
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use RxBindings to clear the error when the user edits the PIN
|
|
||||||
mDisposables.add(
|
mDisposables.add(
|
||||||
tietPIN.textChanges()
|
tietPIN.textChanges()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { tilPIN.isErrorEnabled = false }
|
.subscribe {
|
||||||
|
if (currentStep == STEP_SECURITY_LOCK_VERIFY) {
|
||||||
|
// Make sure the error is removed when the user types again
|
||||||
|
tilPIN.isErrorEnabled = false
|
||||||
|
} else if (currentStep == STEP_SECURITY_LOCK_CREATE) {
|
||||||
|
// Show the min length requirement for the PIN only when it has not been fulfilled
|
||||||
|
if (it.trim().length >= Constants.MIN_PIN_LENGTH) {
|
||||||
|
tilPIN.helperText = ""
|
||||||
|
} else {
|
||||||
|
tilPIN.helperText = getString(R.string.msg__min_pin_length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupScreen() {
|
||||||
|
when (currentStep) {
|
||||||
|
STEP_SECURITY_LOCK_VERIFY -> {
|
||||||
|
tvTitle.text = getString(R.string.title__re_enter_your_pin)
|
||||||
|
tvSubTitle.text = getString(R.string.msg__enter_your_pin)
|
||||||
|
tilPIN.helperText = ""
|
||||||
|
tilPIN.isErrorEnabled = false
|
||||||
|
}
|
||||||
|
STEP_SECURITY_LOCK_CREATE -> {
|
||||||
|
tvTitle.text = getString(R.string.title__set_bitsy_screen_lock)
|
||||||
|
tvSubTitle.text = getString(R.string.msg__set_bitsy_pin)
|
||||||
|
tilPIN.helperText = getString(R.string.msg__min_pin_length)
|
||||||
|
tilPIN.isErrorEnabled = false
|
||||||
|
}
|
||||||
|
STEP_SECURITY_LOCK_CONFIRM -> {
|
||||||
|
tvTitle.text = getString(R.string.title__re_enter_your_pin)
|
||||||
|
tvSubTitle.text = ""
|
||||||
|
tvSubTitle.visibility = View.GONE
|
||||||
|
tietPIN.setText("")
|
||||||
|
tilPIN.helperText = ""
|
||||||
|
tilPIN.isErrorEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -21,7 +21,6 @@ import cy.agorise.bitsybitshareswallet.adapters.FullNodesAdapter
|
||||||
import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository
|
import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository
|
||||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||||
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
|
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
|
||||||
import cy.agorise.bitsybitshareswallet.utils.toast
|
|
||||||
import cy.agorise.graphenej.BrainKey
|
import cy.agorise.graphenej.BrainKey
|
||||||
import cy.agorise.graphenej.api.android.NetworkService
|
import cy.agorise.graphenej.api.android.NetworkService
|
||||||
import cy.agorise.graphenej.api.android.RxBus
|
import cy.agorise.graphenej.api.android.RxBus
|
||||||
|
@ -108,15 +107,12 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
|
||||||
// 1 -> Pattern
|
// 1 -> Pattern
|
||||||
// 2 -> None
|
// 2 -> None
|
||||||
|
|
||||||
btnViewBrainKey.setOnClickListener {
|
|
||||||
if (!verifySecurityLock(securityLockSelected, ACTION_SHOW_BRAINKEY))
|
|
||||||
getBrainkey()
|
|
||||||
}
|
|
||||||
|
|
||||||
tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected]
|
tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected]
|
||||||
|
|
||||||
tvSecurityLock.setOnClickListener { onSecurityLockTextSelected(securityLockSelected) }
|
tvSecurityLock.setOnClickListener { onSecurityLockTextSelected() }
|
||||||
tvSecurityLockSelected.setOnClickListener { onSecurityLockTextSelected(securityLockSelected) }
|
tvSecurityLockSelected.setOnClickListener { onSecurityLockTextSelected() }
|
||||||
|
|
||||||
|
btnViewBrainKey.setOnClickListener { onShowBrainKeyButtonSelected() }
|
||||||
|
|
||||||
// Connect to the RxBus, which receives events from the NetworkService
|
// Connect to the RxBus, which receives events from the NetworkService
|
||||||
mDisposables.add(
|
mDisposables.add(
|
||||||
|
@ -216,7 +212,14 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSecurityLockTextSelected(securityLockSelected: Int) {
|
private fun onSecurityLockTextSelected() {
|
||||||
|
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
|
||||||
|
// Security Lock Options
|
||||||
|
// 0 -> PIN
|
||||||
|
// 1 -> Pattern
|
||||||
|
// 2 -> None
|
||||||
|
|
||||||
if (!verifySecurityLock(securityLockSelected, ACTION_CHANGE_SECURITY_LOCK))
|
if (!verifySecurityLock(securityLockSelected, ACTION_CHANGE_SECURITY_LOCK))
|
||||||
showChooseSecurityLockDialog()
|
showChooseSecurityLockDialog()
|
||||||
}
|
}
|
||||||
|
@ -231,7 +234,7 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
|
||||||
*/
|
*/
|
||||||
private fun verifySecurityLock(securityLockSelected: Int, actionIdentifier: Int): Boolean {
|
private fun verifySecurityLock(securityLockSelected: Int, actionIdentifier: Int): Boolean {
|
||||||
return when (securityLockSelected) {
|
return when (securityLockSelected) {
|
||||||
0 /* PIN */ -> {
|
0 -> { /* PIN */
|
||||||
val pinFrag = PINSecurityLockDialog()
|
val pinFrag = PINSecurityLockDialog()
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK,
|
args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK,
|
||||||
|
@ -241,7 +244,7 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
|
||||||
pinFrag.show(childFragmentManager, "pin_security_lock_tag")
|
pinFrag.show(childFragmentManager, "pin_security_lock_tag")
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
1 /* Pattern */ -> {
|
1 -> { /* Pattern */
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -260,7 +263,15 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPINPatternChanged() {
|
override fun onPINPatternChanged() {
|
||||||
|
// Obtain the new Security Lock Option selected and display it in the screen
|
||||||
|
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
|
||||||
|
// Security Lock Options
|
||||||
|
// 0 -> PIN
|
||||||
|
// 1 -> Pattern
|
||||||
|
// 2 -> None
|
||||||
|
|
||||||
|
tvSecurityLockSelected.text = resources.getStringArray(R.array.security_lock_options)[securityLockSelected]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -271,11 +282,43 @@ class SettingsFragment : Fragment(), ServiceConnection, BaseSecurityLockDialog.O
|
||||||
MaterialDialog(it).show {
|
MaterialDialog(it).show {
|
||||||
title(R.string.title__security_dialog)
|
title(R.string.title__security_dialog)
|
||||||
listItems(R.array.security_lock_options) {dialog, index, text ->
|
listItems(R.array.security_lock_options) {dialog, index, text ->
|
||||||
dialog.context.toast("$text selected!")
|
when (index) {
|
||||||
|
0 -> { /* PIN */
|
||||||
|
val pinFrag = PINSecurityLockDialog()
|
||||||
|
val args = Bundle()
|
||||||
|
args.putInt(BaseSecurityLockDialog.KEY_STEP_SECURITY_LOCK,
|
||||||
|
BaseSecurityLockDialog.STEP_SECURITY_LOCK_CREATE)
|
||||||
|
args.putInt(BaseSecurityLockDialog.KEY_ACTION_IDENTIFIER, -1)
|
||||||
|
pinFrag.arguments = args
|
||||||
|
pinFrag.show(childFragmentManager, "pin_security_lock_tag")
|
||||||
|
}
|
||||||
|
1 -> { /* Pattern */
|
||||||
|
|
||||||
|
}
|
||||||
|
else -> { /* None */
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||||
|
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 2).apply() // 2 -> None
|
||||||
|
|
||||||
|
// Call this function to update the UI
|
||||||
|
onPINPatternChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onShowBrainKeyButtonSelected() {
|
||||||
|
val securityLockSelected = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getInt(Constants.KEY_SECURITY_LOCK_SELECTED, 0)
|
||||||
|
// Security Lock Options
|
||||||
|
// 0 -> PIN
|
||||||
|
// 1 -> Pattern
|
||||||
|
// 2 -> None
|
||||||
|
|
||||||
|
if (!verifySecurityLock(securityLockSelected, ACTION_SHOW_BRAINKEY))
|
||||||
|
getBrainkey()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the brainKey from the authorities db table for the current user account and if it is not null it passes
|
* Obtains the brainKey from the authorities db table for the current user account and if it is not null it passes
|
||||||
|
|
|
@ -19,14 +19,15 @@
|
||||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||||
android:src="@drawable/ic_lock"
|
android:src="@drawable/ic_lock"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"/>
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:contentDescription="@string/title__re_enter_your_pin" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvTitle"
|
android:id="@+id/tvTitle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||||
android:text="Enter your PIN"
|
tools:text="Enter your PIN"
|
||||||
android:textAppearance="@style/TextAppearance.Bitsy.Headline5"
|
android:textAppearance="@style/TextAppearance.Bitsy.Headline5"
|
||||||
app:layout_constraintTop_toBottomOf="@id/ivLock" />
|
app:layout_constraintTop_toBottomOf="@id/ivLock" />
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:text="Enter your BiTSy PIN to continue"
|
tools:text="Enter your BiTSy PIN to continue"
|
||||||
android:textAppearance="@style/TextAppearance.Bitsy.Body1"
|
android:textAppearance="@style/TextAppearance.Bitsy.Body1"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvTitle"/>
|
app:layout_constraintTop_toBottomOf="@id/tvTitle"/>
|
||||||
|
|
||||||
|
|
|
@ -144,5 +144,12 @@
|
||||||
<string name="text__pin">PIN</string>
|
<string name="text__pin">PIN</string>
|
||||||
<string name="text__pattern">Patrón</string>
|
<string name="text__pattern">Patrón</string>
|
||||||
<string name="text__none">Ninguno</string>
|
<string name="text__none">Ninguno</string>
|
||||||
|
<string name="title__re_enter_your_pin">Reingresa tu PIN</string>
|
||||||
|
<string name="msg__enter_your_pin">Digita tu PIN de BiTSy para continuar</string>
|
||||||
|
<string name="error__wrong_pin">PIN incorrecto</string>
|
||||||
|
<string name="title__set_bitsy_screen_lock">Crea un bloqueo de seguridad para BiTSy</string>
|
||||||
|
<string name="msg__set_bitsy_pin">Por seguridad, crea un PIN para BiTSy</string>
|
||||||
|
<string name="msg__min_pin_length">El PIN debe tener al menos 6 dígitos</string>
|
||||||
|
<string name="title__pins_dont_match">El PIN no concuerda</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -145,5 +145,12 @@
|
||||||
<string name="text__pin">PIN</string>
|
<string name="text__pin">PIN</string>
|
||||||
<string name="text__pattern">Pattern</string>
|
<string name="text__pattern">Pattern</string>
|
||||||
<string name="text__none">None</string>
|
<string name="text__none">None</string>
|
||||||
|
<string name="title__re_enter_your_pin">Re-enter your PIN</string>
|
||||||
|
<string name="msg__enter_your_pin">Enter your BiTSy PIN to continue</string>
|
||||||
|
<string name="error__wrong_pin">Wrong PIN</string>
|
||||||
|
<string name="title__set_bitsy_screen_lock">Set BiTSy screen lock</string>
|
||||||
|
<string name="msg__set_bitsy_pin">For security, set BiTSy PIN</string>
|
||||||
|
<string name="msg__min_pin_length">PIN must be at least 6 digits</string>
|
||||||
|
<string name="title__pins_dont_match">PINs don\'t match</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue