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 ae20a0b..5150822 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
@@ -87,7 +87,6 @@ class HomeActivity : AppCompatActivity() {
binding.drawerLayout.closeDrawers()
when (item.itemId) {
R.id.nav_dashboard -> show(DashboardFragment())
- R.id.nav_add_account -> startActivity(Intent(this, LoginActivity::class.java))
R.id.nav_accounts -> show(AccountsFragment())
R.id.nav_contacts -> show(ContactsFragment())
R.id.nav_transfer_history -> show(TransferHistoryFragment())
@@ -225,6 +224,7 @@ class HomeActivity : AppCompatActivity() {
private fun showAutolockWarning() {
if (warningDialog?.isShowing == true) return
+ if (isFinishing || isDestroyed) return
val dialog = MaterialAlertDialogBuilder(this)
.setTitle(R.string.autolock_warning_title)
.setMessage(getString(R.string.autolock_warning_message, 10))
@@ -268,6 +268,25 @@ class HomeActivity : AppCompatActivity() {
}
+ fun relogin() {
+ val store = CredentialStore(this)
+ val hasMib = store.hasMibCredentials()
+ val hasBml = store.hasBmlCredentials()
+ if (!hasMib && !hasBml) {
+ startActivity(Intent(this, LoginActivity::class.java))
+ finish()
+ return
+ }
+ // Immediately drop accounts for logged-out banks from the displayed list
+ val current = viewModel.accounts.value ?: emptyList()
+ viewModel.accounts.value = current.filter { acc ->
+ if (!hasMib && !acc.profileType.startsWith("BML")) return@filter false
+ if (!hasBml && acc.profileType.startsWith("BML")) return@filter false
+ true
+ }
+ autoRefresh(store.loadMibCredentials(), store.loadBmlCredentials(), store)
+ }
+
private fun autoRefresh(
mibCreds: CredentialStore.MibCredentials?,
bmlCreds: CredentialStore.BmlCredentials?,
diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/SettingsFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/SettingsFragment.kt
index 31534d7..5841373 100644
--- a/app/src/main/java/sh/sar/basedbank/ui/home/SettingsFragment.kt
+++ b/app/src/main/java/sh/sar/basedbank/ui/home/SettingsFragment.kt
@@ -2,15 +2,22 @@ package sh.sar.basedbank.ui.home
import android.content.Context
import android.os.Bundle
+import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.setApplicationLocales
import androidx.biometric.BiometricManager
import androidx.core.os.LocaleListCompat
import androidx.fragment.app.Fragment
+import android.content.Intent
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import sh.sar.basedbank.BasedBankApp
import sh.sar.basedbank.R
import sh.sar.basedbank.api.mib.TransactionCache
import sh.sar.basedbank.databinding.FragmentSettingsBinding
@@ -18,6 +25,8 @@ import sh.sar.basedbank.ui.onboarding.SecuritySetupFragment
import sh.sar.basedbank.util.AccountCache
import sh.sar.basedbank.util.ContactImageCache
import sh.sar.basedbank.util.ContactsCache
+import sh.sar.basedbank.util.CredentialStore
+import sh.sar.basedbank.ui.login.LoginActivity
import sh.sar.basedbank.util.FinancingCache
import sh.sar.basedbank.util.ForeignLimitsCache
import sh.sar.basedbank.util.RecentsCache
@@ -90,19 +99,6 @@ class SettingsFragment : Fragment() {
(activity as? HomeActivity)?.resetAutolockTimer()
}
- // Clear cache
- binding.btnClearCache.setOnClickListener {
- val ctx = requireContext()
- AccountCache.clear(ctx)
- ContactsCache.clear(ctx)
- FinancingCache.clear(ctx)
- ForeignLimitsCache.clear(ctx)
- RecentsCache.clear(ctx)
- TransactionCache.clearAll(ctx)
- ContactImageCache.clearAll(ctx)
- Toast.makeText(ctx, R.string.settings_cache_cleared, Toast.LENGTH_SHORT).show()
- }
-
// Change lock
binding.btnChangeLock.setOnClickListener {
(requireActivity() as HomeActivity).showWithBackStack(
@@ -119,20 +115,187 @@ class SettingsFragment : Fragment() {
prefs.edit().putBoolean("biometrics_enabled", isChecked).apply()
}
}
+
+ // Add account
+ binding.btnAddAccount.setOnClickListener {
+ startActivity(Intent(requireContext(), LoginActivity::class.java))
+ }
+
+ // Clear cache
+ binding.btnClearCache.setOnClickListener {
+ val ctx = requireContext()
+ clearAllCaches(ctx)
+ Toast.makeText(ctx, R.string.settings_cache_cleared, Toast.LENGTH_SHORT).show()
+ }
}
override fun onResume() {
super.onResume()
requireActivity().title = getString(R.string.nav_settings)
- // Re-read biometrics pref in case it was changed by SecuritySetupFragment
val prefs = requireContext().getSharedPreferences("prefs", Context.MODE_PRIVATE)
if (binding.rowBiometrics.visibility == View.VISIBLE) {
binding.switchBiometrics.isChecked = prefs.getBoolean("biometrics_enabled", false)
}
+ buildLoginsSection()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
+
+ // ── Logins section ───────────────────────────────────────────────────────
+
+ private fun buildLoginsSection() {
+ val ctx = requireContext()
+ val store = CredentialStore(ctx)
+ val container = binding.loginsContainer
+ container.removeAllViews()
+
+ val hasMib = store.hasMibCredentials()
+ val hasBml = store.hasBmlCredentials()
+
+ binding.tvLoginsTitle.visibility = if (hasMib || hasBml) View.VISIBLE else View.GONE
+
+ if (hasMib) {
+ val profile = store.loadMibUserProfile()
+ val displayName = profile?.fullName?.takeIf { it.isNotBlank() } ?: getString(R.string.mib_name)
+ val profileNames = AccountCache.load(ctx)
+ .map { it.profileName }.filter { it.isNotBlank() }.distinct()
+ addLoginRow(container, R.drawable.mib_faisanet_logo, displayName) {
+ showLoginDetails(
+ title = getString(R.string.mib_name),
+ details = buildString {
+ if (!profile?.fullName.isNullOrBlank()) appendLine("${getString(R.string.login_detail_name)}: ${profile!!.fullName}")
+ if (!profile?.email.isNullOrBlank()) appendLine("${getString(R.string.login_detail_email)}: ${profile!!.email}")
+ if (!profile?.mobile.isNullOrBlank()) appendLine("${getString(R.string.login_detail_mobile)}: ${profile!!.mobile}")
+ if (profileNames.isNotEmpty()) {
+ appendLine()
+ appendLine(getString(R.string.login_detail_profiles))
+ profileNames.forEach { appendLine(" • $it") }
+ }
+ }.trim(),
+ onLogout = { confirmLogout(getString(R.string.mib_name)) { logoutMib(store) } }
+ )
+ }
+ }
+
+ if (hasBml) {
+ val profile = store.loadBmlUserProfile()
+ val displayName = profile?.fullName?.takeIf { it.isNotBlank() } ?: getString(R.string.bml_name)
+ val profileNames = AccountCache.loadBml(ctx)
+ .map { it.profileName }.filter { it.isNotBlank() }.distinct()
+ addLoginRow(container, R.drawable.bml_logo_vector, displayName) {
+ showLoginDetails(
+ title = getString(R.string.bml_name),
+ details = buildString {
+ if (!profile?.fullName.isNullOrBlank()) appendLine("${getString(R.string.login_detail_name)}: ${profile!!.fullName}")
+ if (!profile?.email.isNullOrBlank()) appendLine("${getString(R.string.login_detail_email)}: ${profile!!.email}")
+ if (!profile?.mobile.isNullOrBlank()) appendLine("${getString(R.string.login_detail_mobile)}: ${profile!!.mobile}")
+ if (!profile?.customerId.isNullOrBlank()) appendLine("${getString(R.string.login_detail_customer_id)}: ${profile!!.customerId}")
+ if (!profile?.idCard.isNullOrBlank()) appendLine("${getString(R.string.login_detail_id_card)}: ${profile!!.idCard}")
+ if (profileNames.isNotEmpty()) {
+ appendLine()
+ appendLine(getString(R.string.login_detail_profiles))
+ profileNames.forEach { appendLine(" • $it") }
+ }
+ }.trim(),
+ onLogout = { confirmLogout(getString(R.string.bml_name)) { logoutBml(store) } }
+ )
+ }
+ }
+ }
+
+ private fun addLoginRow(
+ container: LinearLayout,
+ logoRes: Int,
+ displayName: String,
+ onClick: () -> Unit
+ ) {
+ val ctx = requireContext()
+ val dp = ctx.resources.displayMetrics.density
+
+ val row = LinearLayout(ctx).apply {
+ orientation = LinearLayout.HORIZONTAL
+ gravity = Gravity.CENTER_VERTICAL
+ setPadding(0, (12 * dp).toInt(), 0, (12 * dp).toInt())
+ isClickable = true
+ isFocusable = true
+ val ta = ctx.obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackground))
+ background = ta.getDrawable(0)
+ ta.recycle()
+ setOnClickListener { onClick() }
+ }
+
+ val logo = ImageView(ctx).apply {
+ setImageResource(logoRes)
+ scaleType = ImageView.ScaleType.CENTER_INSIDE
+ layoutParams = LinearLayout.LayoutParams((36 * dp).toInt(), (36 * dp).toInt()).apply {
+ marginEnd = (12 * dp).toInt()
+ }
+ }
+
+ val tvName = TextView(ctx).apply {
+ text = displayName
+ setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyLarge)
+ layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
+ }
+
+ row.addView(logo)
+ row.addView(tvName)
+ container.addView(row)
+ }
+
+ private fun showLoginDetails(title: String, details: String, onLogout: () -> Unit) {
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(title)
+ .apply { if (details.isNotBlank()) setMessage(details) }
+ .setPositiveButton(R.string.close, null)
+ .setNegativeButton(R.string.settings_logout) { _, _ -> onLogout() }
+ .show()
+ }
+
+ private fun confirmLogout(bankName: String, onConfirm: () -> Unit) {
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(getString(R.string.settings_logout_confirm_title, bankName))
+ .setMessage(R.string.settings_logout_confirm_message)
+ .setPositiveButton(R.string.settings_logout) { _, _ -> onConfirm() }
+ .setNegativeButton(R.string.cancel, null)
+ .show()
+ }
+
+ private fun logoutMib(store: CredentialStore) {
+ val ctx = requireContext()
+ store.clearMibCredentials()
+ ctx.getSharedPreferences("mib_prefs", Context.MODE_PRIVATE).edit().clear().apply()
+ val app = requireActivity().application as BasedBankApp
+ app.accounts = emptyList()
+ app.mibSession = null
+ app.mibProfiles = emptyList()
+ clearAllCaches(ctx)
+ (activity as HomeActivity).relogin()
+ buildLoginsSection()
+ }
+
+ private fun logoutBml(store: CredentialStore) {
+ val ctx = requireContext()
+ store.clearBmlCredentials()
+ store.clearBmlSession()
+ val app = requireActivity().application as BasedBankApp
+ app.bmlSession = null
+ app.bmlAccounts = emptyList()
+ clearAllCaches(ctx)
+ (activity as HomeActivity).relogin()
+ buildLoginsSection()
+ }
+
+ private fun clearAllCaches(ctx: Context) {
+ AccountCache.clear(ctx)
+ ContactsCache.clear(ctx)
+ FinancingCache.clear(ctx)
+ ForeignLimitsCache.clear(ctx)
+ RecentsCache.clear(ctx)
+ TransactionCache.clearAll(ctx)
+ ContactImageCache.clearAll(ctx)
+ }
}
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
index 177ee4e..ad1ceae 100644
--- a/app/src/main/res/layout/fragment_settings.xml
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -12,6 +12,30 @@
android:orientation="vertical"
android:padding="16dp">
+
+
+
+
+
+
-
diff --git a/app/src/main/res/values-b+dv/strings.xml b/app/src/main/res/values-b+dv/strings.xml
index 2c3a626..3bc2537 100644
--- a/app/src/main/res/values-b+dv/strings.xml
+++ b/app/src/main/res/values-b+dv/strings.xml
@@ -92,6 +92,19 @@
ކޭޝް
ކޭޝް ސާފުކުރޭ
ކޭޝް ސާފުކުރެވިއްޖެ
+ ލޮގިންތައް
+ ލޮގްއައުޓް
+ %s އިން ލޮގްއައުޓް ވަންތަ؟
+ ހުރިހާ ކޭޝް ޑޭޓާ ސާފުވެ، ބާކީ ހުރި އެކައުންޓްތައް އަލުން ލޯޑްވާނެ.
+ ނަން
+ ޔޫޒަރ ނޭމް
+ އީމެއިލް
+ މޮބައިލް
+ ކަސްޓަމަ ID
+ ID ކާޑް
+ ޕްރޮފައިލްތައް
+ ބަންދު
+ ކެންސަލް
އެކައުންޓްތައް
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6d5470a..e37f886 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -110,6 +110,19 @@
Cache
Clear Cache
Cache cleared
+ Logins
+ Log out
+ Log out of %s?
+ All cached data will be cleared and remaining accounts will be refreshed.
+ Name
+ Username
+ Email
+ Mobile
+ Customer ID
+ ID Card
+ Profiles
+ Close
+ Cancel
This is your source account