lazy loading
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
app/src/main/java/sh/sar/basedbank/util/AccountCache.kt
Normal file
59
app/src/main/java/sh/sar/basedbank/util/AccountCache.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user