From b35f44f35b3349d37695e475f5743aed366552f6 Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Mon, 18 May 2026 23:49:42 +0500 Subject: [PATCH] optimize bank login screens, add support for otpauth://totp/ URI --- .../basedbank/ui/login/CredentialsFragment.kt | 51 ++++++++++++++++--- .../main/res/layout/fragment_credentials.xml | 3 +- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt index 9daf33c..74cb95a 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt @@ -67,11 +67,27 @@ class CredentialsFragment : Fragment() { } } + binding.btnLogin.isEnabled = false binding.btnLogin.setOnClickListener { attemptLogin() } + val loginFieldWatcher = object : TextWatcher { + override fun afterTextChanged(s: Editable?) { updateLoginButtonState() } + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + } + binding.etUsername.addTextChangedListener(loginFieldWatcher) + binding.etPassword.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(s: Editable?) { updateLoginButtonState(); updateOtpDisplay() } + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + }) + if (bankType != "FAHIPAY") { binding.etOtpSeed.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) { updateOtpDisplay() } + override fun afterTextChanged(s: Editable?) { + updateOtpDisplay() + updateLoginButtonState() + } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} }) @@ -88,10 +104,33 @@ class CredentialsFragment : Fragment() { otpHandler.removeCallbacks(otpRunnable) } + private fun resolveOtpSeed(input: String): String { + val secret = if (input.startsWith("otpauth://totp/")) + android.net.Uri.parse(input).getQueryParameter("secret") ?: input + else + input + return secret.replace("\\s".toRegex(), "").replace("-", "").uppercase() + } + + private fun updateLoginButtonState() { + val username = binding.etUsername.text.toString().trim() + val password = binding.etPassword.text.toString() + val otpSeed = resolveOtpSeed(binding.etOtpSeed.text.toString().trim()) + binding.btnLogin.isEnabled = when (bankType) { + "FAHIPAY" -> username.isNotEmpty() && password.isNotEmpty() + else -> username.isNotEmpty() && password.isNotEmpty() && otpSeed.isNotEmpty() && password != otpSeed + } + } + private fun updateOtpDisplay() { - val seed = binding.etOtpSeed.text.toString().trim() + val seed = resolveOtpSeed(binding.etOtpSeed.text.toString().trim()) if (seed.isEmpty()) { - binding.cardOtp.visibility = View.GONE + binding.cardOtp.visibility = View.INVISIBLE + return + } + val password = binding.etPassword.text.toString() + if (seed == password || seed.matches(Regex("\\d{6}"))) { + binding.cardOtp.visibility = View.INVISIBLE return } try { @@ -104,7 +143,7 @@ class CredentialsFragment : Fragment() { binding.otpTimer.progress = remaining binding.cardOtp.visibility = View.VISIBLE } catch (e: Exception) { - binding.cardOtp.visibility = View.GONE + binding.cardOtp.visibility = View.INVISIBLE } } @@ -116,7 +155,7 @@ class CredentialsFragment : Fragment() { val username = binding.etUsername.text.toString().trim() val password = binding.etPassword.text.toString() - val otpSeed = binding.etOtpSeed.text.toString().trim() + val otpSeed = resolveOtpSeed(binding.etOtpSeed.text.toString().trim()) if (username.isEmpty() || password.isEmpty() || otpSeed.isEmpty()) { binding.tvError.text = "Please fill in all fields" @@ -173,7 +212,7 @@ class CredentialsFragment : Fragment() { private fun attemptBmlLogin() { val username = binding.etUsername.text.toString().trim() val password = binding.etPassword.text.toString() - val otpSeed = binding.etOtpSeed.text.toString().trim() + val otpSeed = resolveOtpSeed(binding.etOtpSeed.text.toString().trim()) if (username.isEmpty() || password.isEmpty() || otpSeed.isEmpty()) { binding.tvError.text = "Please fill in all fields" diff --git a/app/src/main/res/layout/fragment_credentials.xml b/app/src/main/res/layout/fragment_credentials.xml index 21224c3..55ef8fe 100644 --- a/app/src/main/res/layout/fragment_credentials.xml +++ b/app/src/main/res/layout/fragment_credentials.xml @@ -80,7 +80,6 @@ android:hint="@string/otp_seed" android:layout_marginBottom="8dp" app:endIconMode="password_toggle" - app:helperText="@string/otp_seed_hint" style="@style/Widget.Material3.TextInputLayout.OutlinedBox">