forked from shihaam/thijooree
lazy loading
This commit is contained in:
@@ -11,7 +11,7 @@ import androidx.biometric.BiometricPrompt
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import sh.sar.basedbank.databinding.ActivityLockBinding
|
import sh.sar.basedbank.databinding.ActivityLockBinding
|
||||||
import sh.sar.basedbank.ui.login.LoginActivity
|
import sh.sar.basedbank.ui.home.HomeActivity
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
class LockActivity : AppCompatActivity() {
|
class LockActivity : AppCompatActivity() {
|
||||||
@@ -154,7 +154,7 @@ class LockActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun proceed() {
|
private fun proceed() {
|
||||||
startActivity(Intent(this, LoginActivity::class.java))
|
startActivity(Intent(this, HomeActivity::class.java))
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package sh.sar.basedbank
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import sh.sar.basedbank.ui.home.HomeActivity
|
||||||
import sh.sar.basedbank.ui.login.LoginActivity
|
import sh.sar.basedbank.ui.login.LoginActivity
|
||||||
import sh.sar.basedbank.ui.onboarding.OnboardingActivity
|
import sh.sar.basedbank.ui.onboarding.OnboardingActivity
|
||||||
|
import sh.sar.basedbank.util.CredentialStore
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -13,10 +15,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val prefs = getSharedPreferences("prefs", MODE_PRIVATE)
|
val prefs = getSharedPreferences("prefs", MODE_PRIVATE)
|
||||||
val onboardingDone = prefs.getBoolean("onboarding_done", false)
|
val onboardingDone = prefs.getBoolean("onboarding_done", false)
|
||||||
val securitySet = prefs.getString("security_method", null) != null
|
val securitySet = prefs.getString("security_method", null) != null
|
||||||
|
val hasCredentials = CredentialStore(this).hasMibCredentials()
|
||||||
|
|
||||||
val target = when {
|
val target = when {
|
||||||
!onboardingDone -> OnboardingActivity::class.java
|
!onboardingDone -> OnboardingActivity::class.java
|
||||||
securitySet -> LockActivity::class.java
|
!hasCredentials -> LoginActivity::class.java
|
||||||
else -> LoginActivity::class.java
|
securitySet -> LockActivity::class.java // proceed() → HomeActivity
|
||||||
|
else -> HomeActivity::class.java
|
||||||
}
|
}
|
||||||
startActivity(Intent(this, target))
|
startActivity(Intent(this, target))
|
||||||
finish()
|
finish()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import sh.sar.basedbank.api.mib.MibAccount
|
|||||||
import sh.sar.basedbank.databinding.ItemAccountBinding
|
import sh.sar.basedbank.databinding.ItemAccountBinding
|
||||||
import sh.sar.basedbank.databinding.ItemProfileHeaderBinding
|
import sh.sar.basedbank.databinding.ItemProfileHeaderBinding
|
||||||
|
|
||||||
class AccountsAdapter(private val accounts: List<MibAccount>) :
|
class AccountsAdapter(accounts: List<MibAccount>) :
|
||||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
private sealed class Item {
|
private sealed class Item {
|
||||||
@@ -15,7 +15,15 @@ class AccountsAdapter(private val accounts: List<MibAccount>) :
|
|||||||
data class Account(val account: MibAccount) : Item()
|
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 = ""
|
var lastProfile = ""
|
||||||
for (account in accounts) {
|
for (account in accounts) {
|
||||||
if (account.profileName != lastProfile) {
|
if (account.profileName != lastProfile) {
|
||||||
|
|||||||
@@ -1,26 +1,66 @@
|
|||||||
package sh.sar.basedbank.ui.home
|
package sh.sar.basedbank.ui.home
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
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.BasedBankApp
|
||||||
|
import sh.sar.basedbank.api.mib.MibLoginFlow
|
||||||
import sh.sar.basedbank.databinding.ActivityHomeBinding
|
import sh.sar.basedbank.databinding.ActivityHomeBinding
|
||||||
|
import sh.sar.basedbank.util.AccountCache
|
||||||
|
import sh.sar.basedbank.util.CredentialStore
|
||||||
|
|
||||||
class HomeActivity : AppCompatActivity() {
|
class HomeActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var binding: ActivityHomeBinding
|
private lateinit var binding: ActivityHomeBinding
|
||||||
|
private lateinit var adapter: AccountsAdapter
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityHomeBinding.inflate(layoutInflater)
|
binding = ActivityHomeBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
setSupportActionBar(binding.toolbar)
|
setSupportActionBar(binding.toolbar)
|
||||||
|
|
||||||
val accounts = (application as BasedBankApp).accounts
|
val app = application as BasedBankApp
|
||||||
val adapter = AccountsAdapter(accounts)
|
|
||||||
|
|
||||||
|
// 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.layoutManager = LinearLayoutManager(this)
|
||||||
binding.recyclerView.adapter = adapter
|
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.util.Totp
|
||||||
import sh.sar.basedbank.BasedBankApp
|
import sh.sar.basedbank.BasedBankApp
|
||||||
import sh.sar.basedbank.api.mib.MibLoginFlow
|
import sh.sar.basedbank.api.mib.MibLoginFlow
|
||||||
|
import sh.sar.basedbank.util.AccountCache
|
||||||
import sh.sar.basedbank.util.CredentialStore
|
import sh.sar.basedbank.util.CredentialStore
|
||||||
import sh.sar.basedbank.databinding.FragmentCredentialsBinding
|
import sh.sar.basedbank.databinding.FragmentCredentialsBinding
|
||||||
import sh.sar.basedbank.ui.home.HomeActivity
|
import sh.sar.basedbank.ui.home.HomeActivity
|
||||||
@@ -111,6 +112,7 @@ class CredentialsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
Log.d(TAG, "Login succeeded, got ${accounts.size} accounts")
|
Log.d(TAG, "Login succeeded, got ${accounts.size} accounts")
|
||||||
CredentialStore(requireContext()).saveMibCredentials(username, passwordHash, otpSeed)
|
CredentialStore(requireContext()).saveMibCredentials(username, passwordHash, otpSeed)
|
||||||
|
AccountCache.save(requireContext(), accounts)
|
||||||
(requireActivity().application as BasedBankApp).accounts = accounts
|
(requireActivity().application as BasedBankApp).accounts = accounts
|
||||||
startActivity(Intent(requireContext(), HomeActivity::class.java))
|
startActivity(Intent(requireContext(), HomeActivity::class.java))
|
||||||
requireActivity().finish()
|
requireActivity().finish()
|
||||||
|
|||||||
@@ -1,19 +1,8 @@
|
|||||||
package sh.sar.basedbank.ui.login
|
package sh.sar.basedbank.ui.login
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
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.databinding.ActivityLoginBinding
|
||||||
import sh.sar.basedbank.ui.home.HomeActivity
|
|
||||||
import sh.sar.basedbank.util.CredentialStore
|
|
||||||
|
|
||||||
class LoginActivity : AppCompatActivity() {
|
class LoginActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -23,31 +12,5 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityLoginBinding.inflate(layoutInflater)
|
binding = ActivityLoginBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:title="@string/accounts"
|
||||||
app:titleTextAppearance="?attr/textAppearanceTitleLarge" />
|
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>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
|||||||
@@ -18,29 +18,4 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -60,8 +60,6 @@
|
|||||||
<string name="skip_biometrics">ސްކިޕް — PIN/ޕެޓަން ބޭނުން ކުރޭ</string>
|
<string name="skip_biometrics">ސްކިޕް — PIN/ޕެޓަން ބޭނުން ކުރޭ</string>
|
||||||
<string name="back">ފަހަތަށް</string>
|
<string name="back">ފަހަތަށް</string>
|
||||||
|
|
||||||
<string name="signing_in">ލޮގިން ވަނީ…</string>
|
|
||||||
|
|
||||||
<!-- Home -->
|
<!-- Home -->
|
||||||
<string name="accounts">އެކައުންޓްތައް</string>
|
<string name="accounts">އެކައުންޓްތައް</string>
|
||||||
<string name="available_balance">ލިބެން ހުރި ބެލެންސް</string>
|
<string name="available_balance">ލިބެން ހުރި ބެލެންސް</string>
|
||||||
|
|||||||
@@ -59,8 +59,6 @@
|
|||||||
<string name="skip_biometrics">Skip — use PIN/Pattern only</string>
|
<string name="skip_biometrics">Skip — use PIN/Pattern only</string>
|
||||||
<string name="back">Back</string>
|
<string name="back">Back</string>
|
||||||
|
|
||||||
<string name="signing_in">Signing in…</string>
|
|
||||||
|
|
||||||
<!-- Home -->
|
<!-- Home -->
|
||||||
<string name="accounts">Accounts</string>
|
<string name="accounts">Accounts</string>
|
||||||
<string name="available_balance">Available Balance</string>
|
<string name="available_balance">Available Balance</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user