|
|
|
@@ -30,6 +30,7 @@ import sh.sar.basedbank.BasedBankApp
|
|
|
|
|
import sh.sar.basedbank.R
|
|
|
|
|
import sh.sar.basedbank.api.bml.BmlProfile
|
|
|
|
|
import sh.sar.basedbank.api.mib.MibProfile
|
|
|
|
|
import sh.sar.basedbank.api.models.BankAccount
|
|
|
|
|
import sh.sar.basedbank.api.mib.TransactionCache
|
|
|
|
|
import sh.sar.basedbank.databinding.FragmentSettingsLoginsBinding
|
|
|
|
|
import sh.sar.basedbank.ui.login.LoginActivity
|
|
|
|
@@ -486,6 +487,98 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** A single account row with a visibility toggle; [indent] nests it under a parent profile row (tree view). */
|
|
|
|
|
private fun addAccountRow(
|
|
|
|
|
ctx: Context,
|
|
|
|
|
container: LinearLayout,
|
|
|
|
|
dp: Float,
|
|
|
|
|
acc: BankAccount,
|
|
|
|
|
hiddenAccounts: MutableSet<String>,
|
|
|
|
|
indent: Boolean
|
|
|
|
|
): Pair<BankAccount, MaterialSwitch> {
|
|
|
|
|
val row = LinearLayout(ctx).apply {
|
|
|
|
|
orientation = LinearLayout.HORIZONTAL
|
|
|
|
|
gravity = Gravity.CENTER_VERTICAL
|
|
|
|
|
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).also {
|
|
|
|
|
it.bottomMargin = (4 * dp).toInt()
|
|
|
|
|
if (indent) it.marginStart = (28 * dp).toInt()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
val textCol = LinearLayout(ctx).apply {
|
|
|
|
|
orientation = LinearLayout.VERTICAL
|
|
|
|
|
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
|
|
|
|
}
|
|
|
|
|
val nameAppearance = if (indent) com.google.android.material.R.style.TextAppearance_Material3_BodySmall
|
|
|
|
|
else com.google.android.material.R.style.TextAppearance_Material3_BodyMedium
|
|
|
|
|
textCol.addView(TextView(ctx).apply {
|
|
|
|
|
text = acc.accountBriefName.ifBlank { acc.accountTypeName.ifBlank { acc.accountNumber } }
|
|
|
|
|
setTextAppearance(nameAppearance)
|
|
|
|
|
})
|
|
|
|
|
val typeLabel = sh.sar.basedbank.util.AccountListParser.from(acc)?.typeLabel
|
|
|
|
|
?: if (acc.bank == "BML") sh.sar.basedbank.util.bmlapi.BmlDashboardParser.productLabel(acc.accountTypeName)
|
|
|
|
|
else acc.accountTypeName.trim()
|
|
|
|
|
textCol.addView(TextView(ctx).apply {
|
|
|
|
|
text = listOfNotNull(acc.accountNumber, typeLabel.takeIf { it.isNotBlank() }, acc.currencyName.takeIf { it.isNotBlank() })
|
|
|
|
|
.joinToString(" · ")
|
|
|
|
|
setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodySmall)
|
|
|
|
|
alpha = 0.6f
|
|
|
|
|
})
|
|
|
|
|
val toggle = MaterialSwitch(ctx).apply {
|
|
|
|
|
isChecked = acc.accountNumber !in hiddenAccounts
|
|
|
|
|
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
|
|
|
|
|
marginStart = (4 * dp).toInt()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
row.addView(textCol)
|
|
|
|
|
row.addView(toggle)
|
|
|
|
|
container.addView(row)
|
|
|
|
|
return acc to toggle
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Nests [accounts] directly under their parent profile row, without a header — the tree's leaves. */
|
|
|
|
|
private fun addNestedAccountRows(
|
|
|
|
|
ctx: Context,
|
|
|
|
|
container: LinearLayout,
|
|
|
|
|
dp: Float,
|
|
|
|
|
accounts: List<BankAccount>,
|
|
|
|
|
hiddenAccounts: MutableSet<String>
|
|
|
|
|
): List<Pair<BankAccount, MaterialSwitch>> =
|
|
|
|
|
accounts.map { addAccountRow(ctx, container, dp, it, hiddenAccounts, indent = true) }
|
|
|
|
|
|
|
|
|
|
/** Builds a headered, flat "Accounts" section for accounts with no profile to nest under. */
|
|
|
|
|
private fun addAccountsSection(
|
|
|
|
|
ctx: Context,
|
|
|
|
|
container: LinearLayout,
|
|
|
|
|
dp: Float,
|
|
|
|
|
accounts: List<BankAccount>,
|
|
|
|
|
hiddenAccounts: MutableSet<String>,
|
|
|
|
|
showDivider: Boolean
|
|
|
|
|
): List<Pair<BankAccount, MaterialSwitch>> {
|
|
|
|
|
if (accounts.isEmpty()) return emptyList()
|
|
|
|
|
if (showDivider) {
|
|
|
|
|
container.addView(View(ctx).apply {
|
|
|
|
|
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (1 * dp).toInt()).also {
|
|
|
|
|
it.topMargin = (12 * dp).toInt(); it.bottomMargin = (12 * dp).toInt()
|
|
|
|
|
}
|
|
|
|
|
setBackgroundColor(0x1F000000)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
container.addView(TextView(ctx).apply {
|
|
|
|
|
text = getString(R.string.accounts)
|
|
|
|
|
setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelMedium)
|
|
|
|
|
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).also {
|
|
|
|
|
it.bottomMargin = (8 * dp).toInt()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return accounts.map { addAccountRow(ctx, container, dp, it, hiddenAccounts, indent = false) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Merges this login's account-hide choices into the global hidden-accounts set, leaving other logins untouched. */
|
|
|
|
|
private fun persistHiddenAccounts(store: CredentialStore, scopedAccounts: List<BankAccount>, hiddenAccounts: Set<String>) {
|
|
|
|
|
val scopedNumbers = scopedAccounts.map { it.accountNumber }.toSet()
|
|
|
|
|
store.setHiddenAccountNumbers((store.getHiddenAccountNumbers() - scopedNumbers) + hiddenAccounts)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun showMibLoginDetails(
|
|
|
|
|
store: CredentialStore,
|
|
|
|
|
loginId: String,
|
|
|
|
@@ -496,6 +589,10 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
val dp = ctx.resources.displayMetrics.density
|
|
|
|
|
val originalHidden = store.getHiddenMibProfileIds(loginId)
|
|
|
|
|
val hidden = originalHidden.toMutableSet()
|
|
|
|
|
val app = requireActivity().application as BasedBankApp
|
|
|
|
|
val loginAccounts = app.mibAccounts.filter { it.loginTag == "mib_$loginId" }
|
|
|
|
|
val originalHiddenAccounts = loginAccounts.map { it.accountNumber }.filter { it in store.getHiddenAccountNumbers() }.toSet()
|
|
|
|
|
val hiddenAccounts = originalHiddenAccounts.toMutableSet()
|
|
|
|
|
|
|
|
|
|
val scroll = android.widget.ScrollView(ctx)
|
|
|
|
|
val container = LinearLayout(ctx).apply {
|
|
|
|
@@ -538,7 +635,10 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build toggle rows — wired up after dialog.show() so we can reference the Save button
|
|
|
|
|
// Build toggle rows — wired up after dialog.show() so we can reference the Save button.
|
|
|
|
|
// Each profile's own accounts nest directly beneath it (tree view), since one profile
|
|
|
|
|
// can have multiple accounts.
|
|
|
|
|
val accountRows = mutableListOf<Pair<BankAccount, MaterialSwitch>>()
|
|
|
|
|
val toggleRows = mibProfiles.map { p ->
|
|
|
|
|
val row = LinearLayout(ctx).apply {
|
|
|
|
|
orientation = LinearLayout.HORIZONTAL
|
|
|
|
@@ -579,16 +679,24 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
row.addView(pencil)
|
|
|
|
|
row.addView(toggle)
|
|
|
|
|
container.addView(row)
|
|
|
|
|
accountRows += addNestedAccountRows(ctx, container, dp, loginAccounts.filter { it.profileId == p.profileId }, hiddenAccounts)
|
|
|
|
|
p to toggle
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Accounts that don't belong to any known profile still need to be reachable.
|
|
|
|
|
val unassignedAccounts = loginAccounts.filter { acc -> mibProfiles.none { it.profileId == acc.profileId } }
|
|
|
|
|
accountRows += addAccountsSection(
|
|
|
|
|
ctx, container, dp, unassignedAccounts, hiddenAccounts,
|
|
|
|
|
showDivider = mibProfiles.isNotEmpty() || profile != null
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
fun updateToggleStates(saveBtn: android.widget.Button) {
|
|
|
|
|
val visibleCount = mibProfiles.count { it.profileId !in hidden }
|
|
|
|
|
toggleRows.forEach { (p, toggle) ->
|
|
|
|
|
// Disable the sole remaining visible toggle so it can't be turned off
|
|
|
|
|
toggle.isEnabled = !(toggle.isChecked && visibleCount == 1)
|
|
|
|
|
}
|
|
|
|
|
saveBtn.isEnabled = hidden != originalHidden && visibleCount >= 1
|
|
|
|
|
saveBtn.isEnabled = (hidden != originalHidden || hiddenAccounts != originalHiddenAccounts) && visibleCount >= 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val dialog = MaterialAlertDialogBuilder(ctx)
|
|
|
|
@@ -612,8 +720,16 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
accountRows.forEach { (acc, toggle) ->
|
|
|
|
|
toggle.setOnCheckedChangeListener { _, checked ->
|
|
|
|
|
if (checked) hiddenAccounts.remove(acc.accountNumber) else hiddenAccounts.add(acc.accountNumber)
|
|
|
|
|
updateToggleStates(saveBtn)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saveBtn.setOnClickListener {
|
|
|
|
|
store.setHiddenMibProfileIds(loginId, hidden)
|
|
|
|
|
persistHiddenAccounts(store, loginAccounts, hiddenAccounts)
|
|
|
|
|
clearAllCaches(ctx)
|
|
|
|
|
dialog.dismiss()
|
|
|
|
|
(activity as? HomeActivity)?.relogin()
|
|
|
|
@@ -638,6 +754,10 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
if (hidden.add(id)) store.setHiddenBmlProfileIds(loginId, hidden)
|
|
|
|
|
}
|
|
|
|
|
val originalHidden = hidden.toSet()
|
|
|
|
|
val app = requireActivity().application as BasedBankApp
|
|
|
|
|
val loginAccounts = app.bmlAccounts.filter { it.loginTag == "bml_$loginId" }
|
|
|
|
|
val originalHiddenAccounts = loginAccounts.map { it.accountNumber }.filter { it in store.getHiddenAccountNumbers() }.toSet()
|
|
|
|
|
val hiddenAccounts = originalHiddenAccounts.toMutableSet()
|
|
|
|
|
|
|
|
|
|
val scroll = android.widget.ScrollView(ctx)
|
|
|
|
|
val container = LinearLayout(ctx).apply {
|
|
|
|
@@ -683,6 +803,9 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Each profile's own accounts nest directly beneath it (tree view), since one profile
|
|
|
|
|
// can have multiple accounts.
|
|
|
|
|
val accountRows = mutableListOf<Pair<BankAccount, MaterialSwitch>>()
|
|
|
|
|
val toggleRows = bmlProfiles.map { p ->
|
|
|
|
|
val avatarIv = makeCircleAvatarView(ctx, 36)
|
|
|
|
|
val currentBitmap = ProfileImageStore.load(ctx, ProfileImageStore.bmlKey(p.profileId))
|
|
|
|
@@ -730,15 +853,23 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
row.addView(pencil)
|
|
|
|
|
row.addView(toggle)
|
|
|
|
|
container.addView(row)
|
|
|
|
|
accountRows += addNestedAccountRows(ctx, container, dp, loginAccounts.filter { it.profileId == p.profileId }, hiddenAccounts)
|
|
|
|
|
p to toggle
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Accounts that don't belong to any known profile still need to be reachable.
|
|
|
|
|
val unassignedAccounts = loginAccounts.filter { acc -> bmlProfiles.none { it.profileId == acc.profileId } }
|
|
|
|
|
accountRows += addAccountsSection(
|
|
|
|
|
ctx, container, dp, unassignedAccounts, hiddenAccounts,
|
|
|
|
|
showDivider = bmlProfiles.isNotEmpty() || profile != null
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
fun updateToggleStates(saveBtn: android.widget.Button) {
|
|
|
|
|
val visibleCount = bmlProfiles.count { it.profileId !in hidden }
|
|
|
|
|
toggleRows.forEach { (_, toggle) ->
|
|
|
|
|
toggle.isEnabled = !(toggle.isChecked && visibleCount == 1)
|
|
|
|
|
}
|
|
|
|
|
saveBtn.isEnabled = hidden != originalHidden && visibleCount >= 1
|
|
|
|
|
saveBtn.isEnabled = (hidden != originalHidden || hiddenAccounts != originalHiddenAccounts) && visibleCount >= 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val dialog = MaterialAlertDialogBuilder(ctx)
|
|
|
|
@@ -773,8 +904,16 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
accountRows.forEach { (acc, toggle) ->
|
|
|
|
|
toggle.setOnCheckedChangeListener { _, checked ->
|
|
|
|
|
if (checked) hiddenAccounts.remove(acc.accountNumber) else hiddenAccounts.add(acc.accountNumber)
|
|
|
|
|
updateToggleStates(saveBtn)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saveBtn.setOnClickListener {
|
|
|
|
|
store.setHiddenBmlProfileIds(loginId, hidden)
|
|
|
|
|
persistHiddenAccounts(store, loginAccounts, hiddenAccounts)
|
|
|
|
|
clearAllCaches(ctx)
|
|
|
|
|
dialog.dismiss()
|
|
|
|
|
(activity as? HomeActivity)?.relogin()
|
|
|
|
@@ -1000,6 +1139,10 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
val dp = ctx.resources.displayMetrics.density
|
|
|
|
|
val hide = viewModel.hideAmounts.value ?: false
|
|
|
|
|
val masked = "••••••"
|
|
|
|
|
val app = requireActivity().application as BasedBankApp
|
|
|
|
|
val loginAccounts = app.fahipayAccounts.filter { it.loginTag == "fahipay_$loginId" }
|
|
|
|
|
val originalHiddenAccounts = loginAccounts.map { it.accountNumber }.filter { it in store.getHiddenAccountNumbers() }.toSet()
|
|
|
|
|
val hiddenAccounts = originalHiddenAccounts.toMutableSet()
|
|
|
|
|
|
|
|
|
|
val scroll = android.widget.ScrollView(ctx)
|
|
|
|
|
val container = LinearLayout(ctx).apply {
|
|
|
|
@@ -1050,14 +1193,38 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MaterialAlertDialogBuilder(ctx)
|
|
|
|
|
val accountRows = addAccountsSection(ctx, container, dp, loginAccounts, hiddenAccounts, showDivider = true)
|
|
|
|
|
|
|
|
|
|
val dialog = MaterialAlertDialogBuilder(ctx)
|
|
|
|
|
.setTitle(getString(R.string.fahipay_name))
|
|
|
|
|
.setView(scroll)
|
|
|
|
|
.setPositiveButton(R.string.close, null)
|
|
|
|
|
.setNegativeButton(R.string.settings_logout) { _, _ ->
|
|
|
|
|
.apply {
|
|
|
|
|
if (loginAccounts.isNotEmpty()) setPositiveButton(R.string.save, null)
|
|
|
|
|
setNeutralButton(R.string.close, null)
|
|
|
|
|
setNegativeButton(R.string.settings_logout) { _, _ ->
|
|
|
|
|
confirmLogout(getString(R.string.fahipay_name)) { logoutFahipay(store, loginId) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.show()
|
|
|
|
|
|
|
|
|
|
if (loginAccounts.isNotEmpty()) {
|
|
|
|
|
val saveBtn = dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE)
|
|
|
|
|
saveBtn.isEnabled = false
|
|
|
|
|
|
|
|
|
|
accountRows.forEach { (acc, toggle) ->
|
|
|
|
|
toggle.setOnCheckedChangeListener { _, checked ->
|
|
|
|
|
if (checked) hiddenAccounts.remove(acc.accountNumber) else hiddenAccounts.add(acc.accountNumber)
|
|
|
|
|
saveBtn.isEnabled = hiddenAccounts != originalHiddenAccounts
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saveBtn.setOnClickListener {
|
|
|
|
|
persistHiddenAccounts(store, loginAccounts, hiddenAccounts)
|
|
|
|
|
clearAllCaches(ctx)
|
|
|
|
|
dialog.dismiss()
|
|
|
|
|
(activity as? HomeActivity)?.relogin()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun showLoginDetails(title: String, details: String, onLogout: () -> Unit) {
|
|
|
|
@@ -1140,6 +1307,8 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
val pockets = sh.sar.basedbank.util.AccountCache.loadMfaisa(ctx, loginId)
|
|
|
|
|
val hidden = store.getHiddenMfaisaPocketIds(loginId).toMutableSet()
|
|
|
|
|
val originalHidden = hidden.toSet()
|
|
|
|
|
val originalHiddenAccounts = pockets.map { it.accountNumber }.filter { it in store.getHiddenAccountNumbers() }.toSet()
|
|
|
|
|
val hiddenAccounts = originalHiddenAccounts.toMutableSet()
|
|
|
|
|
|
|
|
|
|
// The user-visible "profiles" are: M-Faisa (every non-PayPal pocket) and PayPal (if linked).
|
|
|
|
|
// Each toggle covers the set of pocket account numbers that belong to that profile.
|
|
|
|
@@ -1195,6 +1364,9 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Each group's own pockets nest directly beneath it (tree view), since a group
|
|
|
|
|
// ("M-Faisa" / "PayPal") can hold multiple pocket accounts.
|
|
|
|
|
val accountRows = mutableListOf<Pair<BankAccount, MaterialSwitch>>()
|
|
|
|
|
val toggleRows = profileRows.map { row ->
|
|
|
|
|
val v = LinearLayout(ctx).apply {
|
|
|
|
|
orientation = LinearLayout.HORIZONTAL
|
|
|
|
@@ -1217,6 +1389,7 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
v.addView(label)
|
|
|
|
|
v.addView(toggle)
|
|
|
|
|
container.addView(v)
|
|
|
|
|
accountRows += addNestedAccountRows(ctx, container, dp, pockets.filter { it.accountNumber in row.pocketIds }, hiddenAccounts)
|
|
|
|
|
row to toggle
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1225,7 +1398,7 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
toggleRows.forEach { (_, toggle) ->
|
|
|
|
|
toggle.isEnabled = !(toggle.isChecked && visibleCount == 1)
|
|
|
|
|
}
|
|
|
|
|
saveBtn.isEnabled = hidden != originalHidden && visibleCount >= 1
|
|
|
|
|
saveBtn.isEnabled = (hidden != originalHidden || hiddenAccounts != originalHiddenAccounts) && visibleCount >= 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val dialog = MaterialAlertDialogBuilder(ctx)
|
|
|
|
@@ -1252,8 +1425,16 @@ class SettingsLoginsFragment : Fragment() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
accountRows.forEach { (acc, toggle) ->
|
|
|
|
|
toggle.setOnCheckedChangeListener { _, checked ->
|
|
|
|
|
if (checked) hiddenAccounts.remove(acc.accountNumber) else hiddenAccounts.add(acc.accountNumber)
|
|
|
|
|
updateToggleStates(saveBtn)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saveBtn.setOnClickListener {
|
|
|
|
|
store.setHiddenMfaisaPocketIds(loginId, hidden)
|
|
|
|
|
persistHiddenAccounts(store, pockets, hiddenAccounts)
|
|
|
|
|
clearAllCaches(ctx)
|
|
|
|
|
dialog.dismiss()
|
|
|
|
|
(activity as? HomeActivity)?.relogin()
|
|
|
|
|