add TOTP code view
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 3s
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 3s
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
91
app/src/main/java/sh/sar/basedbank/ui/home/OtpFragment.kt
Normal file
91
app/src/main/java/sh/sar/basedbank/ui/home/OtpFragment.kt
Normal 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
|
||||
}
|
||||
}
|
||||
15
app/src/main/res/layout/fragment_otp.xml
Normal file
15
app/src/main/res/layout/fragment_otp.xml
Normal 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>
|
||||
55
app/src/main/res/layout/item_otp_card.xml
Normal file
55
app/src/main/res/layout/item_otp_card.xml
Normal 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>
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user