lazy loading

This commit is contained in:
2026-05-12 11:06:08 +05:00
parent 81a2be150f
commit 7026da9ccd
11 changed files with 131 additions and 75 deletions

View File

@@ -11,7 +11,7 @@ import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import com.google.android.material.button.MaterialButton
import sh.sar.basedbank.databinding.ActivityLockBinding
import sh.sar.basedbank.ui.login.LoginActivity
import sh.sar.basedbank.ui.home.HomeActivity
import java.security.MessageDigest
class LockActivity : AppCompatActivity() {
@@ -154,7 +154,7 @@ class LockActivity : AppCompatActivity() {
}
private fun proceed() {
startActivity(Intent(this, LoginActivity::class.java))
startActivity(Intent(this, HomeActivity::class.java))
finish()
}

View File

@@ -3,8 +3,10 @@ package sh.sar.basedbank
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import sh.sar.basedbank.ui.home.HomeActivity
import sh.sar.basedbank.ui.login.LoginActivity
import sh.sar.basedbank.ui.onboarding.OnboardingActivity
import sh.sar.basedbank.util.CredentialStore
class MainActivity : AppCompatActivity() {
@@ -13,10 +15,13 @@ class MainActivity : AppCompatActivity() {
val prefs = getSharedPreferences("prefs", MODE_PRIVATE)
val onboardingDone = prefs.getBoolean("onboarding_done", false)
val securitySet = prefs.getString("security_method", null) != null
val hasCredentials = CredentialStore(this).hasMibCredentials()
val target = when {
!onboardingDone -> OnboardingActivity::class.java
securitySet -> LockActivity::class.java
else -> LoginActivity::class.java
!hasCredentials -> LoginActivity::class.java
securitySet -> LockActivity::class.java // proceed() → HomeActivity
else -> HomeActivity::class.java
}
startActivity(Intent(this, target))
finish()

View File

@@ -7,7 +7,7 @@ import sh.sar.basedbank.api.mib.MibAccount
import sh.sar.basedbank.databinding.ItemAccountBinding
import sh.sar.basedbank.databinding.ItemProfileHeaderBinding
class AccountsAdapter(private val accounts: List<MibAccount>) :
class AccountsAdapter(accounts: List<MibAccount>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private sealed class Item {
@@ -15,7 +15,15 @@ class AccountsAdapter(private val accounts: List<MibAccount>) :
data class Account(val account: MibAccount) : Item()
}
private val items: List<Item> = buildList {
private val items: MutableList<Item> = buildItems(accounts).toMutableList()
fun updateAccounts(accounts: List<MibAccount>) {
items.clear()
items.addAll(buildItems(accounts))
notifyDataSetChanged()
}
private fun buildItems(accounts: List<MibAccount>): List<Item> = buildList {
var lastProfile = ""
for (account in accounts) {
if (account.profileName != lastProfile) {

View File

@@ -1,26 +1,66 @@
package sh.sar.basedbank.ui.home
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import sh.sar.basedbank.BasedBankApp
import sh.sar.basedbank.api.mib.MibLoginFlow
import sh.sar.basedbank.databinding.ActivityHomeBinding
import sh.sar.basedbank.util.AccountCache
import sh.sar.basedbank.util.CredentialStore
class HomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityHomeBinding
private lateinit var adapter: AccountsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
val accounts = (application as BasedBankApp).accounts
val adapter = AccountsAdapter(accounts)
val app = application as BasedBankApp
// If we arrived here from a fresh manual login, accounts are already in memory.
// Otherwise load the last-known cache so the UI is instant.
val initial = if (app.accounts.isNotEmpty()) app.accounts else AccountCache.load(this)
adapter = AccountsAdapter(initial)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter
if (app.accounts.isNotEmpty()) {
// Just logged in — persist the fresh data and we're done
AccountCache.save(this, app.accounts)
} else {
// Came from lock screen — refresh in background
val creds = CredentialStore(this).loadMibCredentials()
if (creds != null) autoRefresh(creds)
}
}
private fun autoRefresh(creds: CredentialStore.MibCredentials) {
binding.refreshIndicator.visibility = View.VISIBLE
val prefs = getSharedPreferences("mib_prefs", MODE_PRIVATE)
val flow = MibLoginFlow(prefs)
lifecycleScope.launch {
try {
val accounts = withContext(Dispatchers.IO) {
flow.login(creds.username, creds.passwordHash, creds.otpSeed)
}
(application as BasedBankApp).accounts = accounts
AccountCache.save(this@HomeActivity, accounts)
adapter.updateAccounts(accounts)
} catch (_: Exception) {
// Keep showing cached data silently
} finally {
binding.refreshIndicator.visibility = View.GONE
}
}
}
}

View File

@@ -18,6 +18,7 @@ import kotlinx.coroutines.withContext
import sh.sar.basedbank.util.Totp
import sh.sar.basedbank.BasedBankApp
import sh.sar.basedbank.api.mib.MibLoginFlow
import sh.sar.basedbank.util.AccountCache
import sh.sar.basedbank.util.CredentialStore
import sh.sar.basedbank.databinding.FragmentCredentialsBinding
import sh.sar.basedbank.ui.home.HomeActivity
@@ -111,6 +112,7 @@ class CredentialsFragment : Fragment() {
}
Log.d(TAG, "Login succeeded, got ${accounts.size} accounts")
CredentialStore(requireContext()).saveMibCredentials(username, passwordHash, otpSeed)
AccountCache.save(requireContext(), accounts)
(requireActivity().application as BasedBankApp).accounts = accounts
startActivity(Intent(requireContext(), HomeActivity::class.java))
requireActivity().finish()

View File

@@ -1,19 +1,8 @@
package sh.sar.basedbank.ui.login
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import sh.sar.basedbank.BasedBankApp
import sh.sar.basedbank.R
import sh.sar.basedbank.api.mib.MibLoginFlow
import sh.sar.basedbank.databinding.ActivityLoginBinding
import sh.sar.basedbank.ui.home.HomeActivity
import sh.sar.basedbank.util.CredentialStore
class LoginActivity : AppCompatActivity() {
@@ -23,31 +12,5 @@ class LoginActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
val creds = CredentialStore(this).loadMibCredentials()
if (creds != null) {
binding.navHostFragment.visibility = View.GONE
binding.autoLoginGroup.visibility = View.VISIBLE
autoLogin(creds)
}
}
private fun autoLogin(creds: CredentialStore.MibCredentials) {
val prefs = getSharedPreferences("mib_prefs", MODE_PRIVATE)
val flow = MibLoginFlow(prefs)
lifecycleScope.launch {
try {
val accounts = withContext(Dispatchers.IO) {
flow.login(creds.username, creds.passwordHash, creds.otpSeed)
}
(application as BasedBankApp).accounts = accounts
startActivity(Intent(this@LoginActivity, HomeActivity::class.java))
finish()
} catch (e: Exception) {
// Auto-login failed — fall back to manual login form
binding.autoLoginGroup.visibility = View.GONE
binding.navHostFragment.visibility = View.VISIBLE
}
}
}
}

View File

@@ -0,0 +1,59 @@
package sh.sar.basedbank.util
import android.content.Context
import org.json.JSONArray
import org.json.JSONObject
import sh.sar.basedbank.api.mib.MibAccount
object AccountCache {
private const val PREFS = "account_cache"
private const val KEY_MIB = "mib_accounts"
fun save(context: Context, accounts: List<MibAccount>) {
val arr = JSONArray()
for (acc in accounts) {
arr.put(JSONObject().apply {
put("profileName", acc.profileName)
put("profileType", acc.profileType)
put("accountNumber", acc.accountNumber)
put("accountBriefName", acc.accountBriefName)
put("currencyName", acc.currencyName)
put("accountTypeName", acc.accountTypeName)
put("availableBalance", acc.availableBalance)
put("currentBalance", acc.currentBalance)
put("blockedAmount", acc.blockedAmount)
put("mvrBalance", acc.mvrBalance)
put("statusDesc", acc.statusDesc)
})
}
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
.edit().putString(KEY_MIB, arr.toString()).apply()
}
fun load(context: Context): List<MibAccount> {
val json = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
.getString(KEY_MIB, null) ?: return emptyList()
return try {
val arr = JSONArray(json)
(0 until arr.length()).map { i ->
val o = arr.getJSONObject(i)
MibAccount(
profileName = o.optString("profileName"),
profileType = o.optString("profileType"),
accountNumber = o.optString("accountNumber"),
accountBriefName = o.optString("accountBriefName"),
currencyName = o.optString("currencyName"),
accountTypeName = o.optString("accountTypeName"),
availableBalance = o.optString("availableBalance"),
currentBalance = o.optString("currentBalance"),
blockedAmount = o.optString("blockedAmount"),
mvrBalance = o.optString("mvrBalance"),
statusDesc = o.optString("statusDesc")
)
}
} catch (e: Exception) {
emptyList()
}
}
}

View File

@@ -19,6 +19,14 @@
app:title="@string/accounts"
app:titleTextAppearance="?attr/textAppearanceTitleLarge" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/refreshIndicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:indeterminate="true"
app:trackCornerRadius="0dp" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView

View File

@@ -18,29 +18,4 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<LinearLayout
android:id="@+id/autoLoginGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ProgressBar
android:layout_width="48dp"
android:layout_height="48dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/signing_in"
android:textAppearance="?attr/textAppearanceBodyMedium" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -60,8 +60,6 @@
<string name="skip_biometrics">ސްކިޕް — PIN/ޕެޓަން ބޭނުން ކުރޭ</string>
<string name="back">ފަހަތަށް</string>
<string name="signing_in">ލޮގިން ވަނީ…</string>
<!-- Home -->
<string name="accounts">އެކައުންޓްތައް</string>
<string name="available_balance">ލިބެން ހުރި ބެލެންސް</string>

View File

@@ -59,8 +59,6 @@
<string name="skip_biometrics">Skip — use PIN/Pattern only</string>
<string name="back">Back</string>
<string name="signing_in">Signing in…</string>
<!-- Home -->
<string name="accounts">Accounts</string>
<string name="available_balance">Available Balance</string>