add TOTP code view
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 3s

This commit is contained in:
2026-05-15 10:30:05 +05:00
parent 1b5a417196
commit feb5b41f8b
6 changed files with 166 additions and 0 deletions

View File

@@ -92,6 +92,7 @@ class HomeActivity : AppCompatActivity() {
R.id.nav_contacts -> show(ContactsFragment())
R.id.nav_transfer_history -> show(TransferHistoryFragment())
R.id.nav_finances -> show(FinancingFragment())
R.id.nav_otp -> show(OtpFragment())
R.id.nav_settings -> show(SettingsFragment())
else -> Toast.makeText(this, R.string.work_in_progress, Toast.LENGTH_SHORT).show()
}

View File

@@ -0,0 +1,91 @@
package sh.sar.basedbank.ui.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import sh.sar.basedbank.databinding.FragmentOtpBinding
import sh.sar.basedbank.databinding.ItemOtpCardBinding
import sh.sar.basedbank.util.CredentialStore
import sh.sar.basedbank.util.Totp
class OtpFragment : Fragment() {
private var _binding: FragmentOtpBinding? = null
private val binding get() = _binding!!
private data class OtpEntry(val label: String, val seed: String)
private inner class OtpAdapter(private val entries: List<OtpEntry>) :
RecyclerView.Adapter<OtpAdapter.VH>() {
inner class VH(val b: ItemOtpCardBinding) : RecyclerView.ViewHolder(b.root)
override fun getItemCount() = entries.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
VH(ItemOtpCardBinding.inflate(LayoutInflater.from(parent.context), parent, false))
override fun onBindViewHolder(holder: VH, position: Int) {
holder.b.tvOtpLabel.text = entries[position].label
update(holder.b, entries[position].seed)
}
fun tick() {
for (i in entries.indices) {
val vh = (binding.recyclerView.findViewHolderForAdapterPosition(i) as? VH) ?: continue
update(vh.b, entries[i].seed)
}
}
private fun update(b: ItemOtpCardBinding, seed: String) {
val epochSeconds = System.currentTimeMillis() / 1000L
val secondsInPeriod = (epochSeconds % 30).toInt()
val remaining = 30 - secondsInPeriod
val code = try { Totp.generate(seed) } catch (_: Exception) { "------" }
b.tvOtpCode.text = "${code.take(3)} ${code.drop(3)}"
b.otpProgress.progress = remaining
b.tvOtpCountdown.text = "Refreshes in $remaining second${if (remaining == 1) "" else "s"}"
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentOtpBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val store = CredentialStore(requireContext())
val entries = mutableListOf<OtpEntry>()
store.loadMibCredentials()?.let { entries.add(OtpEntry("MIB · ${it.username}", it.otpSeed)) }
store.loadBmlCredentials()?.let { entries.add(OtpEntry("BML · ${it.username}", it.otpSeed)) }
val adapter = OtpAdapter(entries)
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.adapter = adapter
viewLifecycleOwner.lifecycleScope.launch {
while (isActive) {
adapter.tick()
delay(1_000)
}
}
}
override fun onResume() {
super.onResume()
requireActivity().title = "OTP Codes"
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="16dp"
android:clipToPadding="false" />
</LinearLayout>

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="12dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:id="@+id/tvOtpLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceLabelMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/tvOtpCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceDisplaySmall"
android:textColor="?attr/colorPrimary"
android:fontFamily="monospace"
android:letterSpacing="0.15"
android:layout_marginBottom="16dp" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/otpProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="30"
app:trackCornerRadius="4dp"
app:indicatorColor="?attr/colorPrimary"
app:trackColor="?attr/colorSurfaceVariant" />
<TextView
android:id="@+id/tvOtpCountdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textAppearance="?attr/textAppearanceLabelSmall"
android:textColor="?attr/colorOnSurfaceVariant" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -32,6 +32,9 @@
<item android:id="@+id/nav_add_account"
android:icon="@drawable/ic_nav_add_account"
android:title="@string/nav_add_account" />
<item android:id="@+id/nav_otp"
android:icon="@drawable/ic_nav_generic"
android:title="@string/nav_otp" />
<item android:id="@+id/nav_settings"
android:icon="@drawable/ic_nav_settings"
android:title="@string/nav_settings" />

View File

@@ -69,6 +69,7 @@
<string name="nav_transfer_history">Transfer History</string>
<string name="nav_finances">Finances</string>
<string name="nav_card_settings">Card Settings</string>
<string name="nav_otp">OTP Codes</string>
<string name="nav_settings">Settings</string>
<string name="nav_open_drawer">Open navigation</string>
<string name="nav_close_drawer">Close navigation</string>