add bml login, accounts and contacts
This commit is contained in:
@@ -61,7 +61,11 @@ class AccountsAdapter(accounts: List<MibAccount>) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: Item.Header) {
|
||||
binding.tvProfileName.text = item.profileName
|
||||
binding.tvProfileType.text = if (item.profileType == "0") "Personal" else "Business"
|
||||
binding.tvProfileType.text = when (item.profileType) {
|
||||
"BML" -> "Bank of Maldives"
|
||||
"0" -> "Personal"
|
||||
else -> "Business"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import sh.sar.basedbank.BasedBankApp
|
||||
import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.api.bml.BmlLoginFlow
|
||||
import sh.sar.basedbank.api.mib.MibAccount
|
||||
import sh.sar.basedbank.api.mib.MibLoginFlow
|
||||
import sh.sar.basedbank.databinding.ActivityHomeBinding
|
||||
import sh.sar.basedbank.ui.login.LoginActivity
|
||||
@@ -63,30 +65,42 @@ class HomeActivity : AppCompatActivity() {
|
||||
|
||||
// Load data
|
||||
val app = application as BasedBankApp
|
||||
if (app.accounts.isNotEmpty()) {
|
||||
if (app.accounts.isNotEmpty() || app.bmlAccounts.isNotEmpty()) {
|
||||
// Came from fresh manual login — accounts ready, rest fetched in background
|
||||
viewModel.accounts.value = app.accounts
|
||||
AccountCache.save(this, app.accounts)
|
||||
val cached = FinancingCache.load(this)
|
||||
if (cached.isNotEmpty()) viewModel.financing.value = cached
|
||||
val cachedContacts = ContactsCache.loadContacts(this)
|
||||
if (cachedContacts.isNotEmpty()) viewModel.contacts.value = cachedContacts
|
||||
val cachedCats = ContactsCache.loadCategories(this)
|
||||
if (cachedCats.isNotEmpty()) viewModel.contactCategories.value = cachedCats
|
||||
refreshFinancing(app.mibSession, app.mibProfiles)
|
||||
refreshContacts(app.mibSession, app.mibProfiles)
|
||||
} else {
|
||||
// Came from lock screen — show caches immediately, refresh everything in background
|
||||
val cached = AccountCache.load(this)
|
||||
if (cached.isNotEmpty()) viewModel.accounts.value = cached
|
||||
val mibAccounts = app.accounts.filter { it.profileType != "BML" }
|
||||
val merged = mibAccounts + app.bmlAccounts
|
||||
viewModel.accounts.value = merged
|
||||
if (mibAccounts.isNotEmpty()) AccountCache.save(this, mibAccounts)
|
||||
if (app.bmlAccounts.isNotEmpty()) AccountCache.saveBml(this, app.bmlAccounts)
|
||||
|
||||
val cachedFinancing = FinancingCache.load(this)
|
||||
if (cachedFinancing.isNotEmpty()) viewModel.financing.value = cachedFinancing
|
||||
val cachedContacts = ContactsCache.loadContacts(this)
|
||||
val cachedContacts = mergeContacts(ContactsCache.loadContacts(this), ContactsCache.loadBml(this))
|
||||
if (cachedContacts.isNotEmpty()) viewModel.contacts.value = cachedContacts
|
||||
val cachedCats = ContactsCache.loadCategories(this)
|
||||
if (cachedCats.isNotEmpty()) viewModel.contactCategories.value = cachedCats
|
||||
val creds = CredentialStore(this).loadMibCredentials()
|
||||
if (creds != null) autoRefresh(creds)
|
||||
|
||||
refreshFinancing(app.mibSession, app.mibProfiles)
|
||||
refreshContacts(app.mibSession, app.mibProfiles)
|
||||
if (app.bmlSession != null) refreshBmlContacts(app)
|
||||
} else {
|
||||
// Came from lock screen — show caches immediately, refresh everything in background
|
||||
val cachedMib = AccountCache.load(this)
|
||||
val cachedBml = AccountCache.loadBml(this)
|
||||
val merged = cachedMib + cachedBml
|
||||
if (merged.isNotEmpty()) viewModel.accounts.value = merged
|
||||
val cachedFinancing = FinancingCache.load(this)
|
||||
if (cachedFinancing.isNotEmpty()) viewModel.financing.value = cachedFinancing
|
||||
val cachedContacts = mergeContacts(ContactsCache.loadContacts(this), ContactsCache.loadBml(this))
|
||||
if (cachedContacts.isNotEmpty()) viewModel.contacts.value = cachedContacts
|
||||
val cachedCats = ContactsCache.loadCategories(this)
|
||||
if (cachedCats.isNotEmpty()) viewModel.contactCategories.value = cachedCats
|
||||
|
||||
val store = CredentialStore(this)
|
||||
val mibCreds = store.loadMibCredentials()
|
||||
val bmlCreds = store.loadBmlCredentials()
|
||||
if (mibCreds != null) autoRefreshMib(mibCreds, bmlCreds)
|
||||
else if (bmlCreds != null) autoRefreshBml(bmlCreds)
|
||||
}
|
||||
|
||||
// Show dashboard on first create
|
||||
@@ -109,32 +123,96 @@ class HomeActivity : AppCompatActivity() {
|
||||
.commit()
|
||||
}
|
||||
|
||||
private fun autoRefresh(creds: CredentialStore.MibCredentials) {
|
||||
private fun autoRefreshMib(
|
||||
mibCreds: CredentialStore.MibCredentials,
|
||||
bmlCreds: CredentialStore.BmlCredentials?
|
||||
) {
|
||||
binding.refreshIndicator.visibility = View.VISIBLE
|
||||
val prefs = getSharedPreferences("mib_prefs", MODE_PRIVATE)
|
||||
val flow = MibLoginFlow(prefs)
|
||||
lifecycleScope.launch {
|
||||
var mibAccounts: List<MibAccount> = AccountCache.load(this@HomeActivity)
|
||||
try {
|
||||
val accounts = withContext(Dispatchers.IO) {
|
||||
flow.login(creds.username, creds.passwordHash, creds.otpSeed)
|
||||
mibAccounts = withContext(Dispatchers.IO) {
|
||||
flow.login(mibCreds.username, mibCreds.passwordHash, mibCreds.otpSeed)
|
||||
}
|
||||
val app = application as BasedBankApp
|
||||
app.accounts = accounts
|
||||
app.accounts = mibAccounts
|
||||
app.mibSession = flow.lastSession
|
||||
app.mibProfiles = flow.lastProfiles
|
||||
AccountCache.save(this@HomeActivity, accounts)
|
||||
viewModel.accounts.postValue(accounts)
|
||||
} catch (_: Exception) {
|
||||
// Keep cached data silently
|
||||
} finally {
|
||||
binding.refreshIndicator.visibility = View.GONE
|
||||
AccountCache.save(this@HomeActivity, mibAccounts)
|
||||
} catch (_: Exception) { /* keep cached */ }
|
||||
finally { binding.refreshIndicator.visibility = View.GONE }
|
||||
|
||||
val bmlAccounts = AccountCache.loadBml(this@HomeActivity).toMutableList()
|
||||
if (bmlCreds != null) {
|
||||
try {
|
||||
val bmlFlow = BmlLoginFlow()
|
||||
val (session, accounts) = withContext(Dispatchers.IO) {
|
||||
bmlFlow.login(bmlCreds.username, bmlCreds.password, bmlCreds.otpSeed)
|
||||
}
|
||||
val app = application as BasedBankApp
|
||||
app.bmlSession = session
|
||||
app.bmlAccounts = accounts
|
||||
AccountCache.saveBml(this@HomeActivity, accounts)
|
||||
bmlAccounts.clear()
|
||||
bmlAccounts.addAll(accounts)
|
||||
refreshBmlContacts(app)
|
||||
} catch (_: Exception) { /* keep cached */ }
|
||||
}
|
||||
viewModel.accounts.postValue(mibAccounts + bmlAccounts)
|
||||
val app = application as BasedBankApp
|
||||
refreshFinancing(app.mibSession, app.mibProfiles)
|
||||
refreshContacts(app.mibSession, app.mibProfiles)
|
||||
}
|
||||
}
|
||||
|
||||
private fun autoRefreshBml(bmlCreds: CredentialStore.BmlCredentials) {
|
||||
binding.refreshIndicator.visibility = View.VISIBLE
|
||||
lifecycleScope.launch {
|
||||
val cachedMib = AccountCache.load(this@HomeActivity)
|
||||
try {
|
||||
val bmlFlow = BmlLoginFlow()
|
||||
val (session, accounts) = withContext(Dispatchers.IO) {
|
||||
bmlFlow.login(bmlCreds.username, bmlCreds.password, bmlCreds.otpSeed)
|
||||
}
|
||||
val app = application as BasedBankApp
|
||||
app.bmlSession = session
|
||||
app.bmlAccounts = accounts
|
||||
AccountCache.saveBml(this@HomeActivity, accounts)
|
||||
viewModel.accounts.postValue(cachedMib + accounts)
|
||||
refreshBmlContacts(app)
|
||||
} catch (_: Exception) { /* keep cached */ }
|
||||
finally { binding.refreshIndicator.visibility = View.GONE }
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshBmlContacts(app: BasedBankApp) {
|
||||
val session = app.bmlSession ?: return
|
||||
val bmlFlow = BmlLoginFlow()
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val bmlContacts = withContext(Dispatchers.IO) { bmlFlow.fetchContacts(session) }
|
||||
if (bmlContacts.isNotEmpty()) {
|
||||
ContactsCache.saveBml(this@HomeActivity, bmlContacts)
|
||||
val mibContacts = viewModel.contacts.value ?: ContactsCache.loadContacts(this@HomeActivity)
|
||||
viewModel.contacts.postValue(mergeContacts(mibContacts, bmlContacts))
|
||||
}
|
||||
} catch (_: Exception) { /* keep cached */ }
|
||||
}
|
||||
}
|
||||
|
||||
private fun mergeContacts(
|
||||
mib: List<MibBeneficiary>,
|
||||
bml: List<MibBeneficiary>
|
||||
): List<MibBeneficiary> {
|
||||
val seen = mutableSetOf<String>()
|
||||
val result = mutableListOf<MibBeneficiary>()
|
||||
for (c in mib) if (seen.add(c.benefNo)) result.add(c)
|
||||
for (c in bml) if (seen.add(c.benefNo)) result.add(c)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun refreshContacts(session: MibSession?, profiles: List<MibProfile>) {
|
||||
if (session == null || profiles.isEmpty()) return
|
||||
val prefs = getSharedPreferences("mib_prefs", MODE_PRIVATE)
|
||||
@@ -162,7 +240,8 @@ class HomeActivity : AppCompatActivity() {
|
||||
}
|
||||
if (allContacts.isNotEmpty()) {
|
||||
ContactsCache.save(this@HomeActivity, allContacts, allCategories)
|
||||
viewModel.contacts.postValue(allContacts)
|
||||
val bmlContacts = ContactsCache.loadBml(this@HomeActivity)
|
||||
viewModel.contacts.postValue(mergeContacts(allContacts, bmlContacts))
|
||||
viewModel.contactCategories.postValue(allCategories)
|
||||
}
|
||||
} catch (_: Exception) { /* keep cached data */ }
|
||||
|
||||
@@ -23,6 +23,10 @@ class BankSelectionFragment : Fragment() {
|
||||
binding.cardMib.setOnClickListener {
|
||||
findNavController().navigate(R.id.action_bankSelection_to_credentials)
|
||||
}
|
||||
binding.cardBml.setOnClickListener {
|
||||
val args = android.os.Bundle().apply { putString("bankType", "BML") }
|
||||
findNavController().navigate(R.id.action_bankSelection_to_credentials_bml, args)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -16,6 +17,8 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import sh.sar.basedbank.util.Totp
|
||||
import sh.sar.basedbank.BasedBankApp
|
||||
import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.api.bml.BmlLoginFlow
|
||||
import sh.sar.basedbank.api.mib.MibLoginFlow
|
||||
import sh.sar.basedbank.util.AccountCache
|
||||
import sh.sar.basedbank.util.CredentialStore
|
||||
@@ -24,7 +27,8 @@ import sh.sar.basedbank.ui.home.HomeActivity
|
||||
|
||||
class CredentialsFragment : Fragment() {
|
||||
|
||||
private val TAG = "CredentialsFragment"
|
||||
private val bankType: String get() = arguments?.getString("bankType") ?: "MIB"
|
||||
|
||||
private val otpHandler = Handler(Looper.getMainLooper())
|
||||
private val otpRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
@@ -42,6 +46,11 @@ class CredentialsFragment : Fragment() {
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
if (bankType == "BML") {
|
||||
binding.ivBankLogo.setImageResource(R.drawable.bml_logo_vector)
|
||||
binding.tvSignInDesc.setText(R.string.bml_sign_in_desc)
|
||||
}
|
||||
|
||||
binding.btnLogin.setOnClickListener { attemptLogin() }
|
||||
|
||||
binding.etOtpSeed.addTextChangedListener(object : TextWatcher {
|
||||
@@ -82,11 +91,15 @@ class CredentialsFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun attemptLogin() {
|
||||
if (bankType == "BML") {
|
||||
attemptBmlLogin()
|
||||
return
|
||||
}
|
||||
|
||||
val username = binding.etUsername.text.toString().trim()
|
||||
val password = binding.etPassword.text.toString()
|
||||
val otpSeed = binding.etOtpSeed.text.toString().trim()
|
||||
|
||||
|
||||
if (username.isEmpty() || password.isEmpty() || otpSeed.isEmpty()) {
|
||||
binding.tvError.text = "Please fill in all fields"
|
||||
binding.tvError.visibility = View.VISIBLE
|
||||
@@ -125,6 +138,47 @@ class CredentialsFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun attemptBmlLogin() {
|
||||
val username = binding.etUsername.text.toString().trim()
|
||||
val password = binding.etPassword.text.toString()
|
||||
val otpSeed = binding.etOtpSeed.text.toString().trim()
|
||||
|
||||
if (username.isEmpty() || password.isEmpty() || otpSeed.isEmpty()) {
|
||||
binding.tvError.text = "Please fill in all fields"
|
||||
binding.tvError.visibility = View.VISIBLE
|
||||
return
|
||||
}
|
||||
|
||||
binding.tvError.visibility = View.GONE
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
binding.btnLogin.isEnabled = false
|
||||
|
||||
val flow = BmlLoginFlow()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
try {
|
||||
val (session, accounts) = withContext(Dispatchers.IO) {
|
||||
flow.login(username, password, otpSeed)
|
||||
}
|
||||
CredentialStore(requireContext()).saveBmlCredentials(username, password, otpSeed)
|
||||
AccountCache.saveBml(requireContext(), accounts)
|
||||
val app = requireActivity().application as BasedBankApp
|
||||
app.bmlSession = session
|
||||
app.bmlAccounts = accounts
|
||||
// Merge with any existing MIB accounts already in app
|
||||
app.accounts = app.accounts + accounts
|
||||
val intent = Intent(requireContext(), HomeActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
binding.tvError.text = e.message ?: "Login failed"
|
||||
binding.tvError.visibility = View.VISIBLE
|
||||
} finally {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.btnLogin.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
|
||||
Reference in New Issue
Block a user