forked from github/szkolny
[UI/Login] Implement login QR code scanning. (#100)
* Enable Vulcan QR login [WiP] * [UI/Login] Implement login QR scanning. Co-authored-by: Kuba Szczodrzyński <kuba@szczodrzynski.pl>
This commit is contained in:
parent
e2ad3758e0
commit
e629e03b33
@ -36,4 +36,8 @@ class QrScannerDialog(
|
|||||||
}
|
}
|
||||||
root.startCamera()
|
root.startCamera()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDismiss() {
|
||||||
|
root.stopCamera()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,20 +10,28 @@ import android.text.InputType
|
|||||||
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.widget.Toast
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.addTextChangedListener
|
import androidx.core.widget.addTextChangedListener
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.mikepenz.iconics.IconicsDrawable
|
import com.mikepenz.iconics.IconicsDrawable
|
||||||
|
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||||
import com.mikepenz.iconics.utils.sizeDp
|
import com.mikepenz.iconics.utils.sizeDp
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import pl.szczodrzynski.edziennik.*
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.databinding.LoginFormCheckboxItemBinding
|
import pl.szczodrzynski.edziennik.databinding.LoginFormCheckboxItemBinding
|
||||||
import pl.szczodrzynski.edziennik.databinding.LoginFormFieldItemBinding
|
import pl.szczodrzynski.edziennik.databinding.LoginFormFieldItemBinding
|
||||||
import pl.szczodrzynski.edziennik.databinding.LoginFormFragmentBinding
|
import pl.szczodrzynski.edziennik.databinding.LoginFormFragmentBinding
|
||||||
import pl.szczodrzynski.edziennik.ext.*
|
import pl.szczodrzynski.edziennik.ext.*
|
||||||
|
import pl.szczodrzynski.edziennik.ui.dialogs.QrScannerDialog
|
||||||
|
import pl.szczodrzynski.edziennik.ui.login.LoginInfo.BaseCredential
|
||||||
|
import pl.szczodrzynski.edziennik.ui.login.LoginInfo.FormCheckbox
|
||||||
|
import pl.szczodrzynski.edziennik.ui.login.LoginInfo.FormField
|
||||||
import pl.szczodrzynski.navlib.colorAttr
|
import pl.szczodrzynski.navlib.colorAttr
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
@ -31,8 +39,10 @@ import kotlin.coroutines.CoroutineContext
|
|||||||
class LoginFormFragment : Fragment(), CoroutineScope {
|
class LoginFormFragment : Fragment(), CoroutineScope {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "LoginFormFragment"
|
private const val TAG = "LoginFormFragment"
|
||||||
|
|
||||||
// eggs
|
// eggs
|
||||||
var wantEggs = false
|
var wantEggs = false
|
||||||
|
var isEggs = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var app: App
|
private lateinit var app: App
|
||||||
@ -44,9 +54,23 @@ class LoginFormFragment : Fragment(), CoroutineScope {
|
|||||||
override val coroutineContext: CoroutineContext
|
override val coroutineContext: CoroutineContext
|
||||||
get() = job + Dispatchers.Main
|
get() = job + Dispatchers.Main
|
||||||
|
|
||||||
// local/private variables go here
|
private val credentials = mutableMapOf<BaseCredential, ViewBinding>()
|
||||||
|
private val platformName
|
||||||
|
get() = arguments?.getString("platformName")
|
||||||
|
private val platformGuideText
|
||||||
|
get() = arguments?.getString("platformGuideText")
|
||||||
|
private val platformDescription
|
||||||
|
get() = arguments?.getString("platformDescription")
|
||||||
|
private val platformFormFields
|
||||||
|
get() = arguments?.getString("platformFormFields")?.split(";")
|
||||||
|
private val platformRealmData
|
||||||
|
get() = arguments?.getString("platformRealmData")?.toJsonObject()
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?,
|
||||||
|
): View? {
|
||||||
activity = (getActivity() as LoginActivity?) ?: return null
|
activity = (getActivity() as LoginActivity?) ?: return null
|
||||||
context ?: return null
|
context ?: return null
|
||||||
app = activity.application as App
|
app = activity.application as App
|
||||||
@ -54,7 +78,6 @@ class LoginFormFragment : Fragment(), CoroutineScope {
|
|||||||
return b.root
|
return b.root
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ResourceType")
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
if (!isAdded) return
|
if (!isAdded) return
|
||||||
b.backButton.onClick { nav.navigateUp() }
|
b.backButton.onClick { nav.navigateUp() }
|
||||||
@ -67,70 +90,24 @@ class LoginFormFragment : Fragment(), CoroutineScope {
|
|||||||
val loginMode = arguments?.getInt("loginMode") ?: return
|
val loginMode = arguments?.getInt("loginMode") ?: return
|
||||||
val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return
|
val mode = register.loginModes.firstOrNull { it.loginMode == loginMode } ?: return
|
||||||
|
|
||||||
val platformName = arguments?.getString("platformName")
|
|
||||||
val platformGuideText = arguments?.getString("platformGuideText")
|
|
||||||
val platformDescription = arguments?.getString("platformDescription")
|
|
||||||
val platformFormFields = arguments?.getString("platformFormFields")?.split(";")
|
|
||||||
val platformRealmData = arguments?.getString("platformRealmData")?.toJsonObject()
|
|
||||||
|
|
||||||
b.title.setText(R.string.login_form_title_format, app.getString(register.registerName))
|
b.title.setText(R.string.login_form_title_format, app.getString(register.registerName))
|
||||||
b.subTitle.text = platformName ?: app.getString(mode.name)
|
b.subTitle.text = platformName ?: app.getString(mode.name)
|
||||||
b.text.text = platformGuideText ?: app.getString(mode.guideText)
|
b.text.text = platformGuideText ?: app.getString(mode.guideText)
|
||||||
|
|
||||||
val credentials = mutableMapOf<LoginInfo.BaseCredential, Any>()
|
// eggs
|
||||||
|
isEggs = register.internalName == "podlasie"
|
||||||
|
|
||||||
for (credential in mode.credentials) {
|
for (credential in mode.credentials) {
|
||||||
if (platformFormFields?.contains(credential.keyName) == false)
|
if (platformFormFields?.contains(credential.keyName) == false)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if (credential is LoginInfo.FormField) {
|
val b = when (credential) {
|
||||||
val b = LoginFormFieldItemBinding.inflate(layoutInflater)
|
is FormField -> buildFormField(credential)
|
||||||
b.textLayout.hint = app.getString(credential.name)
|
is FormCheckbox -> buildFormCheckbox(credential)
|
||||||
if (credential.isNumber) {
|
else -> continue
|
||||||
b.textEdit.inputType = InputType.TYPE_CLASS_NUMBER
|
|
||||||
}
|
|
||||||
if (credential.hideText) {
|
|
||||||
b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
|
|
||||||
b.textLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
|
||||||
}
|
|
||||||
b.textEdit.addTextChangedListener {
|
|
||||||
b.textLayout.error = null
|
|
||||||
}
|
|
||||||
if (credential.prefix != null)
|
|
||||||
b.textLayout.prefixText = app.getString(credential.prefix)
|
|
||||||
if (credential.suffix != null)
|
|
||||||
b.textLayout.suffixText = app.getString(credential.suffix)
|
|
||||||
|
|
||||||
b.textEdit.id = credential.name
|
|
||||||
|
|
||||||
b.textEdit.setText(arguments?.getString(credential.keyName) ?: "")
|
|
||||||
b.textLayout.startIconDrawable = IconicsDrawable(activity).apply {
|
|
||||||
icon = credential.icon
|
|
||||||
sizeDp = 24
|
|
||||||
colorAttr(activity, R.attr.colorOnBackground)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.b.formContainer.addView(b.root)
|
|
||||||
credentials[credential] = b
|
|
||||||
}
|
|
||||||
if (credential is LoginInfo.FormCheckbox) {
|
|
||||||
val b = LoginFormCheckboxItemBinding.inflate(layoutInflater)
|
|
||||||
b.checkbox.text = app.getString(credential.name)
|
|
||||||
b.checkbox.onChange { _, isChecked ->
|
|
||||||
b.errorText.text = null
|
|
||||||
|
|
||||||
// eggs
|
|
||||||
if (register.internalName == "podlasie") {
|
|
||||||
wantEggs = !isChecked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (arguments?.containsKey(credential.keyName) == true) {
|
|
||||||
b.checkbox.isChecked = arguments?.getBoolean(credential.keyName) == true
|
|
||||||
}
|
|
||||||
|
|
||||||
this.b.formContainer.addView(b.root)
|
|
||||||
credentials[credential] = b
|
|
||||||
}
|
}
|
||||||
|
this.b.formContainer.addView(b.root)
|
||||||
|
credentials[credential] = b
|
||||||
}
|
}
|
||||||
|
|
||||||
activity.lastError?.let { error ->
|
activity.lastError?.let { error ->
|
||||||
@ -156,61 +133,168 @@ class LoginFormFragment : Fragment(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.loginButton.onClick {
|
b.loginButton.onClick {
|
||||||
val payload = Bundle(
|
login(loginType, loginMode)
|
||||||
"loginType" to loginType,
|
|
||||||
"loginMode" to loginMode
|
|
||||||
)
|
|
||||||
|
|
||||||
if (App.debugMode && b.fakeLogin.isChecked) {
|
|
||||||
payload.putBoolean("fakeLogin", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.putBundle("webRealmData", platformRealmData?.toBundle())
|
|
||||||
|
|
||||||
var hasErrors = false
|
|
||||||
credentials.forEach { (credential, b) ->
|
|
||||||
if (credential is LoginInfo.FormField && b is LoginFormFieldItemBinding) {
|
|
||||||
var text = b.textEdit.text?.toString() ?: return@forEach
|
|
||||||
if (!credential.hideText)
|
|
||||||
text = text.trim()
|
|
||||||
|
|
||||||
if (credential.caseMode == LoginInfo.FormField.CaseMode.UPPER_CASE)
|
|
||||||
text = text.uppercase()
|
|
||||||
if (credential.caseMode == LoginInfo.FormField.CaseMode.LOWER_CASE)
|
|
||||||
text = text.lowercase()
|
|
||||||
|
|
||||||
credential.stripTextRegex?.let {
|
|
||||||
text = text.replace(it.toRegex(), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
b.textEdit.setText(text)
|
|
||||||
|
|
||||||
if (credential.isRequired && text.isBlank()) {
|
|
||||||
b.textLayout.error = app.getString(credential.emptyText)
|
|
||||||
hasErrors = true
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!text.matches(credential.validationRegex.toRegex())) {
|
|
||||||
b.textLayout.error = app.getString(credential.invalidText)
|
|
||||||
hasErrors = true
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.putString(credential.keyName, text)
|
|
||||||
arguments?.putString(credential.keyName, text)
|
|
||||||
}
|
|
||||||
if (credential is LoginInfo.FormCheckbox && b is LoginFormCheckboxItemBinding) {
|
|
||||||
val checked = b.checkbox.isChecked
|
|
||||||
payload.putBoolean(credential.keyName, checked)
|
|
||||||
arguments?.putBoolean(credential.keyName, checked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasErrors)
|
|
||||||
return@onClick
|
|
||||||
|
|
||||||
nav.navigate(R.id.loginProgressFragment, payload, activity.navOptions)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun <C : BaseCredential, B : ViewBinding> getCredential(keyName: String): Pair<C, B>? {
|
||||||
|
val (credential, binding) = credentials.entries.firstOrNull {
|
||||||
|
it.key.keyName == keyName
|
||||||
|
} ?: return null
|
||||||
|
val c = credential as? C ?: return null
|
||||||
|
val b = binding as? B ?: return null
|
||||||
|
return c to b
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ResourceType")
|
||||||
|
private fun buildFormField(credential: FormField): LoginFormFieldItemBinding {
|
||||||
|
val b = LoginFormFieldItemBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
if (credential.isNumber) {
|
||||||
|
b.textEdit.inputType = InputType.TYPE_CLASS_NUMBER
|
||||||
|
}
|
||||||
|
|
||||||
|
if (credential.qrDecoderClass != null) {
|
||||||
|
b.textLayout.endIconDrawable = IconicsDrawable(activity).apply {
|
||||||
|
icon = CommunityMaterial.Icon3.cmd_qrcode
|
||||||
|
sizeDp = 24
|
||||||
|
colorAttr(activity, R.attr.colorOnBackground)
|
||||||
|
}
|
||||||
|
b.textLayout.endIconMode = TextInputLayout.END_ICON_CUSTOM
|
||||||
|
b.textLayout.setEndIconOnClickListener {
|
||||||
|
scanQrCode(credential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (credential.hideText) {
|
||||||
|
b.textEdit.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||||
|
b.textLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
||||||
|
}
|
||||||
|
|
||||||
|
b.textEdit.addTextChangedListener {
|
||||||
|
b.textLayout.error = null
|
||||||
|
}
|
||||||
|
|
||||||
|
b.textEdit.id = credential.name
|
||||||
|
b.textEdit.setText(arguments?.getString(credential.keyName))
|
||||||
|
b.textLayout.hint = credential.name.resolveString(app)
|
||||||
|
b.textLayout.prefixText = credential.prefix?.resolveString(app)
|
||||||
|
b.textLayout.suffixText = credential.suffix?.resolveString(app)
|
||||||
|
b.textLayout.tag = credential
|
||||||
|
|
||||||
|
b.textLayout.startIconDrawable = IconicsDrawable(activity).apply {
|
||||||
|
icon = credential.icon
|
||||||
|
sizeDp = 24
|
||||||
|
colorAttr(activity, R.attr.colorOnBackground)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildFormCheckbox(credential: FormCheckbox): LoginFormCheckboxItemBinding {
|
||||||
|
val b = LoginFormCheckboxItemBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
b.checkbox.onChange { _, isChecked ->
|
||||||
|
b.errorText.text = null
|
||||||
|
|
||||||
|
// eggs
|
||||||
|
if (isEggs) {
|
||||||
|
wantEggs = !isChecked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arguments?.containsKey(credential.keyName) == true) {
|
||||||
|
b.checkbox.isChecked = arguments?.getBoolean(credential.keyName) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
b.checkbox.tag = credential
|
||||||
|
b.checkbox.text = credential.name.resolveString(app)
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scanQrCode(credential: FormField) {
|
||||||
|
val qrDecoderClass = credential.qrDecoderClass ?: return
|
||||||
|
app.permissionManager.requestCameraPermission(activity, R.string.permissions_qr_scanner) {
|
||||||
|
QrScannerDialog(activity, onCodeScanned = { code ->
|
||||||
|
val decoder = qrDecoderClass.newInstance()
|
||||||
|
val values = decoder.decode(code)
|
||||||
|
if (values == null) {
|
||||||
|
Toast.makeText(activity, R.string.login_qr_decoding_error, Toast.LENGTH_SHORT).show()
|
||||||
|
return@QrScannerDialog
|
||||||
|
}
|
||||||
|
|
||||||
|
values.forEach { (keyName, fieldText) ->
|
||||||
|
val (_, b) = getCredential<FormField, LoginFormFieldItemBinding>(keyName)
|
||||||
|
?: return@forEach
|
||||||
|
b.textEdit.setText(fieldText)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder.focusFieldName()?.let { keyName ->
|
||||||
|
val (_, b) = getCredential<FormField, LoginFormFieldItemBinding>(keyName)
|
||||||
|
?: return@let
|
||||||
|
b.textEdit.requestFocus()
|
||||||
|
}
|
||||||
|
}).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun login(loginType: Int, loginMode: Int) {
|
||||||
|
val payload = Bundle(
|
||||||
|
"loginType" to loginType,
|
||||||
|
"loginMode" to loginMode
|
||||||
|
)
|
||||||
|
|
||||||
|
if (App.debugMode && b.fakeLogin.isChecked) {
|
||||||
|
payload.putBoolean("fakeLogin", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.putBundle("webRealmData", platformRealmData?.toBundle())
|
||||||
|
|
||||||
|
var hasErrors = false
|
||||||
|
credentials.forEach { (credential, b) ->
|
||||||
|
if (credential is FormField && b is LoginFormFieldItemBinding) {
|
||||||
|
var text = b.textEdit.text?.toString() ?: return@forEach
|
||||||
|
if (!credential.hideText)
|
||||||
|
text = text.trim()
|
||||||
|
|
||||||
|
if (credential.caseMode == FormField.CaseMode.UPPER_CASE)
|
||||||
|
text = text.uppercase()
|
||||||
|
if (credential.caseMode == FormField.CaseMode.LOWER_CASE)
|
||||||
|
text = text.uppercase()
|
||||||
|
|
||||||
|
credential.stripTextRegex?.let {
|
||||||
|
text = text.replace(it.toRegex(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
b.textEdit.setText(text)
|
||||||
|
|
||||||
|
if (credential.isRequired && text.isBlank()) {
|
||||||
|
b.textLayout.error = app.getString(credential.emptyText)
|
||||||
|
hasErrors = true
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!text.matches(credential.validationRegex.toRegex())) {
|
||||||
|
b.textLayout.error = app.getString(credential.invalidText)
|
||||||
|
hasErrors = true
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.putString(credential.keyName, text)
|
||||||
|
arguments?.putString(credential.keyName, text)
|
||||||
|
}
|
||||||
|
if (credential is FormCheckbox && b is LoginFormCheckboxItemBinding) {
|
||||||
|
val checked = b.checkbox.isChecked
|
||||||
|
payload.putBoolean(credential.keyName, checked)
|
||||||
|
arguments?.putBoolean(credential.keyName, checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasErrors)
|
||||||
|
return
|
||||||
|
|
||||||
|
nav.navigate(R.id.loginProgressFragment, payload, activity.navOptions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,9 @@ import com.mikepenz.iconics.typeface.library.community.material.CommunityMateria
|
|||||||
import pl.szczodrzynski.edziennik.R
|
import pl.szczodrzynski.edziennik.R
|
||||||
import pl.szczodrzynski.edziennik.data.api.*
|
import pl.szczodrzynski.edziennik.data.api.*
|
||||||
import pl.szczodrzynski.edziennik.ui.grades.models.ExpandableItemModel
|
import pl.szczodrzynski.edziennik.ui.grades.models.ExpandableItemModel
|
||||||
|
import pl.szczodrzynski.edziennik.ui.login.qr.LoginLibrusQrDecoder
|
||||||
|
import pl.szczodrzynski.edziennik.ui.login.qr.LoginQrDecoder
|
||||||
|
import pl.szczodrzynski.edziennik.ui.login.qr.LoginVulcanQrDecoder
|
||||||
import pl.szczodrzynski.fslogin.realm.RealmData
|
import pl.szczodrzynski.fslogin.realm.RealmData
|
||||||
|
|
||||||
object LoginInfo {
|
object LoginInfo {
|
||||||
@ -105,7 +108,8 @@ object LoginInfo {
|
|||||||
errorCodes = mapOf(),
|
errorCodes = mapOf(),
|
||||||
isRequired = true,
|
isRequired = true,
|
||||||
validationRegex = "[A-Z0-9_]+",
|
validationRegex = "[A-Z0-9_]+",
|
||||||
caseMode = FormField.CaseMode.UPPER_CASE
|
caseMode = FormField.CaseMode.UPPER_CASE,
|
||||||
|
qrDecoderClass = LoginLibrusQrDecoder::class.java
|
||||||
),
|
),
|
||||||
FormField(
|
FormField(
|
||||||
keyName = "accountPin",
|
keyName = "accountPin",
|
||||||
@ -152,7 +156,8 @@ object LoginInfo {
|
|||||||
),
|
),
|
||||||
isRequired = true,
|
isRequired = true,
|
||||||
validationRegex = "[A-Z0-9]{5,12}",
|
validationRegex = "[A-Z0-9]{5,12}",
|
||||||
caseMode = FormField.CaseMode.UPPER_CASE
|
caseMode = FormField.CaseMode.UPPER_CASE,
|
||||||
|
qrDecoderClass = LoginVulcanQrDecoder::class.java
|
||||||
),
|
),
|
||||||
FormField(
|
FormField(
|
||||||
keyName = "symbol",
|
keyName = "symbol",
|
||||||
@ -409,7 +414,8 @@ object LoginInfo {
|
|||||||
val caseMode: CaseMode = CaseMode.UNCHANGED,
|
val caseMode: CaseMode = CaseMode.UNCHANGED,
|
||||||
val hideText: Boolean = false,
|
val hideText: Boolean = false,
|
||||||
val isNumber: Boolean = false,
|
val isNumber: Boolean = false,
|
||||||
val stripTextRegex: String? = null
|
val stripTextRegex: String? = null,
|
||||||
|
val qrDecoderClass: Class<out LoginQrDecoder>? = null,
|
||||||
) : BaseCredential(keyName, name, errorCodes) {
|
) : BaseCredential(keyName, name, errorCodes) {
|
||||||
enum class CaseMode { UNCHANGED, UPPER_CASE, LOWER_CASE }
|
enum class CaseMode { UNCHANGED, UPPER_CASE, LOWER_CASE }
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-10-23.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.login.qr
|
||||||
|
|
||||||
|
class LoginLibrusQrDecoder : LoginQrDecoder {
|
||||||
|
|
||||||
|
private val regex = "[A-Z0-9_]+".toRegex()
|
||||||
|
|
||||||
|
override fun decode(value: String): Map<String, String>? {
|
||||||
|
if (!regex.matches(value) || value.length > 10)
|
||||||
|
return null
|
||||||
|
return mapOf(
|
||||||
|
"accountCode" to value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun focusFieldName() = "accountPin"
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package pl.szczodrzynski.edziennik.ui.login.qr
|
||||||
|
|
||||||
|
interface LoginQrDecoder {
|
||||||
|
|
||||||
|
fun decode(value: String): Map<String, String>?
|
||||||
|
fun focusFieldName(): String?
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2021-10-23.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.ui.login.qr
|
||||||
|
|
||||||
|
import pl.szczodrzynski.edziennik.ext.get
|
||||||
|
import pl.szczodrzynski.edziennik.utils.Utils
|
||||||
|
|
||||||
|
class LoginVulcanQrDecoder : LoginQrDecoder {
|
||||||
|
|
||||||
|
private val regex = "CERT#https?://.+?/([A-z]+)/mobile-api#([A-z0-9]+)#ENDCERT".toRegex()
|
||||||
|
|
||||||
|
override fun decode(value: String): Map<String, String>? {
|
||||||
|
val data = try {
|
||||||
|
Utils.VulcanQrEncryptionUtils.decode(value)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val match = regex.find(data) ?: return null
|
||||||
|
return mapOf(
|
||||||
|
"deviceToken" to match[2],
|
||||||
|
"symbol" to match[1],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun focusFieldName() = "devicePin"
|
||||||
|
}
|
@ -1496,4 +1496,5 @@
|
|||||||
<string name="login_mobidziennik_server_prefix">https://</string>
|
<string name="login_mobidziennik_server_prefix">https://</string>
|
||||||
<string name="login_mobidziennik_server_suffix">.mobidziennik.pl/</string>
|
<string name="login_mobidziennik_server_suffix">.mobidziennik.pl/</string>
|
||||||
<string name="styled_text_dialog_title">Edytuj tekst</string>
|
<string name="styled_text_dialog_title">Edytuj tekst</string>
|
||||||
|
<string name="login_qr_decoding_error">Kod QR nie wygląda na prawidłowy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user