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