diff --git a/app/src/main/java/sh/sar/basedbank/LockActivity.kt b/app/src/main/java/sh/sar/basedbank/LockActivity.kt index ecd0151..fefe21e 100644 --- a/app/src/main/java/sh/sar/basedbank/LockActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/LockActivity.kt @@ -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() } diff --git a/app/src/main/java/sh/sar/basedbank/MainActivity.kt b/app/src/main/java/sh/sar/basedbank/MainActivity.kt index 37b17e8..a35230d 100644 --- a/app/src/main/java/sh/sar/basedbank/MainActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/MainActivity.kt @@ -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() diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/AccountsAdapter.kt b/app/src/main/java/sh/sar/basedbank/ui/home/AccountsAdapter.kt index 16013a3..13ceee0 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/AccountsAdapter.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/AccountsAdapter.kt @@ -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) : +class AccountsAdapter(accounts: List) : RecyclerView.Adapter() { private sealed class Item { @@ -15,7 +15,15 @@ class AccountsAdapter(private val accounts: List) : data class Account(val account: MibAccount) : Item() } - private val items: List = buildList { + private val items: MutableList = buildItems(accounts).toMutableList() + + fun updateAccounts(accounts: List) { + items.clear() + items.addAll(buildItems(accounts)) + notifyDataSetChanged() + } + + private fun buildItems(accounts: List): List = buildList { var lastProfile = "" for (account in accounts) { if (account.profileName != lastProfile) { diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt index 1f1e4e7..321b910 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt @@ -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 + } + } } } diff --git a/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt index 0528a8b..e513344 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/login/CredentialsFragment.kt @@ -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() diff --git a/app/src/main/java/sh/sar/basedbank/ui/login/LoginActivity.kt b/app/src/main/java/sh/sar/basedbank/ui/login/LoginActivity.kt index 0bbffcb..267b433 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/login/LoginActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/login/LoginActivity.kt @@ -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 - } - } } } diff --git a/app/src/main/java/sh/sar/basedbank/util/AccountCache.kt b/app/src/main/java/sh/sar/basedbank/util/AccountCache.kt new file mode 100644 index 0000000..b0b520e --- /dev/null +++ b/app/src/main/java/sh/sar/basedbank/util/AccountCache.kt @@ -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) { + 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 { + 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() + } + } +} diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 0570341..e8a0f10 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -19,6 +19,14 @@ app:title="@string/accounts" app:titleTextAppearance="?attr/textAppearanceTitleLarge" /> + + - - - - - - - - diff --git a/app/src/main/res/values-b+dv/strings.xml b/app/src/main/res/values-b+dv/strings.xml index f7b0b38..62caf73 100644 --- a/app/src/main/res/values-b+dv/strings.xml +++ b/app/src/main/res/values-b+dv/strings.xml @@ -60,8 +60,6 @@ ސްކިޕް — PIN/ޕެޓަން ބޭނުން ކުރޭ ފަހަތަށް - ލޮގިން ވަނީ… - އެކައުންޓްތައް ލިބެން ހުރި ބެލެންސް diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4cc0f66..c85cb25 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -59,8 +59,6 @@ Skip — use PIN/Pattern only Back - Signing in… - Accounts Available Balance