add support for multiple BML accounts, and BML credit cards
Auto Tag on Version Change / check-version (push) Successful in 2s

This commit is contained in:
2026-05-18 00:29:52 +05:00
parent cd4b3fef8b
commit 00e109562b
16 changed files with 289 additions and 200 deletions
@@ -9,9 +9,10 @@ object AccountCache {
private const val PREFS = "account_cache"
private const val KEY_MIB = "mib_accounts"
private const val KEY_BML = "bml_accounts"
private const val KEY_FAHIPAY = "fahipay_accounts"
private fun bmlKey(loginId: String) = "bml_accounts_$loginId"
fun save(context: Context, accounts: List<MibAccount>) {
val arr = JSONArray()
for (acc in accounts) {
@@ -36,7 +37,7 @@ object AccountCache {
.edit().putString(KEY_MIB, CacheEncryption.encrypt(arr.toString())).apply()
}
fun saveBml(context: Context, accounts: List<MibAccount>) {
fun saveBml(context: Context, loginId: String, accounts: List<MibAccount>) {
val arr = JSONArray()
for (acc in accounts) {
arr.put(JSONObject().apply {
@@ -56,15 +57,14 @@ object AccountCache {
})
}
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
.edit().putString(KEY_BML, CacheEncryption.encrypt(arr.toString())).apply()
.edit().putString(bmlKey(loginId), CacheEncryption.encrypt(arr.toString())).apply()
}
fun loadBml(context: Context): List<MibAccount> {
fun loadBml(context: Context, loginId: String): List<MibAccount> {
val raw = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
.getString(KEY_BML, null) ?: return emptyList()
.getString(bmlKey(loginId), null) ?: return emptyList()
return try {
val json = CacheEncryption.decrypt(raw)
val arr = JSONArray(json)
val arr = JSONArray(CacheEncryption.decrypt(raw))
(0 until arr.length()).map { i ->
val o = arr.getJSONObject(i)
MibAccount(
@@ -84,9 +84,12 @@ object AccountCache {
internalId = o.optString("internalId", "")
)
}
} catch (e: Exception) { emptyList() }
} catch (_: Exception) { emptyList() }
}
fun loadBml(context: Context, loginIds: List<String>): List<MibAccount> =
loginIds.flatMap { loadBml(context, it) }
fun saveFahipay(context: Context, accounts: List<MibAccount>) {
val arr = JSONArray()
for (acc in accounts) {
@@ -84,7 +84,9 @@ object ContactsCache {
}
}
fun saveBml(context: Context, contacts: List<MibBeneficiary>) {
private fun bmlKey(loginId: String) = "bml_contacts_$loginId"
fun saveBml(context: Context, loginId: String, contacts: List<MibBeneficiary>) {
val arr = JSONArray()
for (c in contacts) {
arr.put(JSONObject().apply {
@@ -99,18 +101,18 @@ object ContactsCache {
put("benefStatus", c.benefStatus)
put("transferCyDesc", c.transferCyDesc)
put("benefCategoryId", c.benefCategoryId)
put("profileId", c.profileId)
})
}
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
.edit().putString("bml_contacts", CacheEncryption.encrypt(arr.toString())).apply()
.edit().putString(bmlKey(loginId), CacheEncryption.encrypt(arr.toString())).apply()
}
fun loadBml(context: Context): List<MibBeneficiary> {
fun loadBml(context: Context, loginId: String): List<MibBeneficiary> {
val raw = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
.getString("bml_contacts", null) ?: return emptyList()
.getString(bmlKey(loginId), null) ?: return emptyList()
return try {
val json = CacheEncryption.decrypt(raw)
val arr = JSONArray(json)
val arr = JSONArray(CacheEncryption.decrypt(raw))
(0 until arr.length()).map { i ->
val o = arr.getJSONObject(i)
MibBeneficiary(
@@ -125,12 +127,16 @@ object ContactsCache {
benefStatus = o.optString("benefStatus"),
transferCyDesc = o.optString("transferCyDesc", "MVR"),
customerImgHash = null,
benefCategoryId = o.optString("benefCategoryId", "BML")
benefCategoryId = o.optString("benefCategoryId", "BML"),
profileId = o.optString("profileId", "")
)
}
} catch (e: Exception) { emptyList() }
} catch (_: Exception) { emptyList() }
}
fun loadBml(context: Context, loginIds: List<String>): List<MibBeneficiary> =
loginIds.flatMap { loadBml(context, it) }
fun saveFahipay(context: Context, contacts: List<MibBeneficiary>, categories: List<MibBeneficiaryCategory>) {
val prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit()
val arr = JSONArray()
@@ -24,7 +24,6 @@ class CredentialStore(context: Context) {
// ── MIB login credentials ─────────────────────────────────────────────────
fun hasMibCredentials(): Boolean = prefs.contains("mib_enc_username")
fun hasBmlCredentials(): Boolean = prefs.contains("bml_enc_username")
fun hasFahipayCredentials(): Boolean = prefs.contains("fahipay_enc_id_card")
fun saveMibCredentials(username: String, passwordHash: String, otpSeed: String) {
@@ -91,58 +90,106 @@ class CredentialStore(context: Context) {
return try { decrypt(enc, key) } catch (_: Exception) { null }
}
// ── BML login credentials ─────────────────────────────────────────────────
// ── BML login credentials (multi-login, keyed by loginId = username) ────────
fun saveBmlCredentials(username: String, password: String, otpSeed: String) {
fun getBmlLoginIds(): List<String> {
val json = prefs.getString("bml_login_ids", null)
if (json != null) {
return try {
val arr = org.json.JSONArray(json)
(0 until arr.length()).map { arr.getString(it) }
} catch (_: Exception) { emptyList() }
}
// One-time migration from single-slot BML storage
val oldEncUsername = prefs.getString("bml_enc_username", null) ?: return emptyList()
return try {
val key = getOrCreateKey()
val loginId = decrypt(oldEncUsername, key)
val edit = prefs.edit()
prefs.getString("bml_enc_password", null)?.let { edit.putString("bml_${loginId}_enc_password", it) }
prefs.getString("bml_enc_otp_seed", null)?.let { edit.putString("bml_${loginId}_enc_otp_seed", it) }
prefs.getString("bml_enc_token", null)?.let { edit.putString("bml_${loginId}_enc_token", it) }
prefs.getString("bml_enc_device_id", null)?.let { edit.putString("bml_${loginId}_enc_device_id", it) }
prefs.getString("bml_enc_profile", null)?.let { edit.putString("bml_${loginId}_enc_profile", it) }
edit.putString("bml_${loginId}_enc_username", oldEncUsername)
edit.remove("bml_enc_username").remove("bml_enc_password").remove("bml_enc_otp_seed")
.remove("bml_enc_token").remove("bml_enc_device_id")
.remove("bml_enc_profile").remove("bml_enc_full_name")
val ids = org.json.JSONArray(listOf(loginId)).toString()
edit.putString("bml_login_ids", ids)
edit.apply()
listOf(loginId)
} catch (_: Exception) { emptyList() }
}
fun hasBmlCredentials(): Boolean = getBmlLoginIds().isNotEmpty()
private fun addBmlLoginId(loginId: String) {
val ids = getBmlLoginIds().toMutableList()
if (loginId !in ids) {
ids.add(loginId)
prefs.edit().putString("bml_login_ids", org.json.JSONArray(ids).toString()).apply()
}
}
private fun removeBmlLoginId(loginId: String) {
val ids = getBmlLoginIds().toMutableList()
if (ids.remove(loginId))
prefs.edit().putString("bml_login_ids", org.json.JSONArray(ids).toString()).apply()
}
fun saveBmlCredentials(loginId: String, username: String, password: String, otpSeed: String) {
addBmlLoginId(loginId)
val key = getOrCreateKey()
prefs.edit()
.putString("bml_enc_username", encrypt(username, key))
.putString("bml_enc_password", encrypt(password, key))
.putString("bml_enc_otp_seed", encrypt(otpSeed, key))
.putString("bml_${loginId}_enc_username", encrypt(username, key))
.putString("bml_${loginId}_enc_password", encrypt(password, key))
.putString("bml_${loginId}_enc_otp_seed", encrypt(otpSeed, key))
.apply()
}
fun loadBmlCredentials(): BmlCredentials? {
fun loadBmlCredentials(loginId: String): BmlCredentials? {
val key = getOrCreateKey()
val encUsername = prefs.getString("bml_enc_username", null) ?: return null
val encPassword = prefs.getString("bml_enc_password", null) ?: return null
val encSeed = prefs.getString("bml_enc_otp_seed", null) ?: return null
val encUsername = prefs.getString("bml_${loginId}_enc_username", null) ?: return null
val encPassword = prefs.getString("bml_${loginId}_enc_password", null) ?: return null
val encSeed = prefs.getString("bml_${loginId}_enc_otp_seed", null) ?: return null
return try {
BmlCredentials(decrypt(encUsername, key), decrypt(encPassword, key), decrypt(encSeed, key))
} catch (_: Exception) { null }
}
fun clearBmlCredentials() {
fun clearBmlCredentials(loginId: String) {
removeBmlLoginId(loginId)
prefs.edit()
.remove("bml_enc_username")
.remove("bml_enc_password")
.remove("bml_enc_otp_seed")
.remove("bml_${loginId}_enc_username")
.remove("bml_${loginId}_enc_password")
.remove("bml_${loginId}_enc_otp_seed")
.apply()
}
// ── BML session token ─────────────────────────────────────────────────────
// ── BML session token (per loginId) ───────────────────────────────────────
fun saveBmlSession(accessToken: String, deviceId: String) {
fun saveBmlSession(loginId: String, accessToken: String, deviceId: String) {
val key = getOrCreateKey()
prefs.edit()
.putString("bml_enc_token", encrypt(accessToken, key))
.putString("bml_enc_device_id", encrypt(deviceId, key))
.putString("bml_${loginId}_enc_token", encrypt(accessToken, key))
.putString("bml_${loginId}_enc_device_id", encrypt(deviceId, key))
.apply()
}
fun loadBmlSession(): Pair<String, String>? {
fun loadBmlSession(loginId: String): Pair<String, String>? {
val key = getOrCreateKey()
val encToken = prefs.getString("bml_enc_token", null) ?: return null
val encDeviceId = prefs.getString("bml_enc_device_id", null) ?: return null
val encToken = prefs.getString("bml_${loginId}_enc_token", null) ?: return null
val encDeviceId = prefs.getString("bml_${loginId}_enc_device_id", null) ?: return null
return try {
Pair(decrypt(encToken, key), decrypt(encDeviceId, key))
} catch (_: Exception) { null }
}
fun clearBmlSession() {
fun clearBmlSession(loginId: String) {
prefs.edit()
.remove("bml_enc_token")
.remove("bml_enc_device_id")
.remove("bml_${loginId}_enc_token")
.remove("bml_${loginId}_enc_device_id")
.apply()
}
@@ -315,16 +362,6 @@ class CredentialStore(context: Context) {
return try { decrypt(enc, key) } catch (_: Exception) { null }
}
fun saveBmlFullName(name: String) {
val key = getOrCreateKey()
prefs.edit().putString("bml_enc_full_name", encrypt(name, key)).apply()
}
fun loadBmlFullName(): String? {
val key = getOrCreateKey()
val enc = prefs.getString("bml_enc_full_name", null) ?: return null
return try { decrypt(enc, key) } catch (_: Exception) { null }
}
fun saveMibUserProfile(p: MibUserProfile) {
val json = JSONObject().apply {
@@ -355,7 +392,7 @@ class CredentialStore(context: Context) {
} catch (_: Exception) { null }
}
fun saveBmlUserProfile(p: BmlUserProfile) {
fun saveBmlUserProfile(loginId: String, p: BmlUserProfile) {
val json = JSONObject().apply {
put("fullName", p.fullName)
put("email", p.email)
@@ -365,13 +402,12 @@ class CredentialStore(context: Context) {
put("birthdate", p.birthdate)
}.toString()
val key = getOrCreateKey()
prefs.edit().putString("bml_enc_profile", encrypt(json, key)).apply()
prefs.edit().putString("bml_enc_full_name", encrypt(p.fullName, key)).apply()
prefs.edit().putString("bml_${loginId}_enc_profile", encrypt(json, key)).apply()
}
fun loadBmlUserProfile(): BmlUserProfile? {
fun loadBmlUserProfile(loginId: String): BmlUserProfile? {
val key = getOrCreateKey()
val enc = prefs.getString("bml_enc_profile", null) ?: return null
val enc = prefs.getString("bml_${loginId}_enc_profile", null) ?: return null
return try {
val o = JSONObject(decrypt(enc, key))
BmlUserProfile(