5 Commits
Author SHA1 Message Date
shihaam 87536a339b update download links
Auto Tag on Version Change / check-version (push) Failing after 14m42s
2026-05-29 11:48:10 +05:00
shihaam 32d23a43b3 lmao 2026-05-29 11:47:14 +05:00
shihaam 846ce22245 more astudio bs 2026-05-29 11:40:09 +05:00
shihaam ed5b456e3b Release version 1.0.11
Build and Release APK / build (push) Failing after 15m35s
Auto Tag on Version Change / check-version (push) Successful in 28s
2026-05-28 23:40:19 +05:00
shihaam 9b284cc8d4 BML change payment gateway to use PayMV QR, so added support for it
Auto Tag on Version Change / check-version (push) Successful in 15s
2026-05-28 23:39:39 +05:00
8 changed files with 39 additions and 18 deletions
+2 -2
View File
@@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-05-22T00:11:32.873305232Z"> <DropdownSelection timestamp="2026-05-28T18:41:19.777722821Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=683a9830" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=4254e2f" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>
+3 -2
View File
@@ -14,8 +14,9 @@ A native Android client for Maldivian banking services. It is a pure client: req
- Existing accounts with MIB, BML, or Fahipay - Existing accounts with MIB, BML, or Fahipay
- Your TOTP seed (base32 secret from your authenticator app setup) for each bank - Your TOTP seed (base32 secret from your authenticator app setup) for each bank
## Download ## Download APK
[Download latest APK](https://git.shihaam.dev/shihaam/ISODroid/releases/latest) [Gitea Releases](https://git.shihaam.dev/shihaam/thijooree/releases)
[Telegram Channel](https://t.me/thijooreeapks)
## Privacy ## Privacy
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId = "sh.sar.basedbank" applicationId = "sh.sar.basedbank"
minSdk = 26 minSdk = 26
targetSdk = 36 targetSdk = 36
versionCode = 9 versionCode = 10
versionName = "1.0.10" versionName = "1.0.11"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -25,6 +25,7 @@ import sh.sar.basedbank.api.models.BankAccount
import sh.sar.basedbank.api.mib.MibCard import sh.sar.basedbank.api.mib.MibCard
import sh.sar.basedbank.api.mib.MibFinanceDeal import sh.sar.basedbank.api.mib.MibFinanceDeal
import sh.sar.basedbank.util.bmlapi.BmlCardParser import sh.sar.basedbank.util.bmlapi.BmlCardParser
import sh.sar.basedbank.util.PaymvQrParser
import sh.sar.basedbank.util.CredentialStore import sh.sar.basedbank.util.CredentialStore
import kotlin.math.abs import kotlin.math.abs
import sh.sar.basedbank.databinding.FragmentDashboardBinding import sh.sar.basedbank.databinding.FragmentDashboardBinding
@@ -41,10 +42,10 @@ class DashboardFragment : Fragment() {
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || val bmlUrl = PaymvQrParser.extractBmlGatewayUrl(raw)
raw.startsWith("https://pay.bml.com.mv/app/")) { if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || bmlUrl != null) {
(requireActivity() as HomeActivity).navigateTo( (requireActivity() as HomeActivity).navigateTo(
R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw, pendingQrAccountNumber) R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(bmlUrl ?: raw, pendingQrAccountNumber)
) )
} else { } else {
Toast.makeText(requireContext(), R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show()
@@ -61,9 +61,9 @@ class PayMvQrFragment : Fragment() {
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
// BML card/gateway QR — hand off to dedicated payment screen // BML card/gateway QR — hand off to dedicated payment screen
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || val bmlUrl = PaymvQrParser.extractBmlGatewayUrl(raw)
raw.startsWith("https://pay.bml.com.mv/app/")) { if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || bmlUrl != null) {
(requireActivity() as HomeActivity).navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw)) (requireActivity() as HomeActivity).navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(bmlUrl ?: raw))
return@registerForActivityResult return@registerForActivityResult
} }
@@ -31,6 +31,7 @@ import sh.sar.basedbank.databinding.FragmentCardsBinding
import sh.sar.basedbank.util.CardsCache import sh.sar.basedbank.util.CardsCache
import sh.sar.basedbank.util.CredentialStore import sh.sar.basedbank.util.CredentialStore
import sh.sar.basedbank.util.bmlapi.BmlCardParser import sh.sar.basedbank.util.bmlapi.BmlCardParser
import sh.sar.basedbank.util.PaymvQrParser
import kotlin.math.abs import kotlin.math.abs
class CardsFragment : Fragment() { class CardsFragment : Fragment() {
@@ -60,10 +61,10 @@ class CardsFragment : Fragment() {
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || val bmlUrl = PaymvQrParser.extractBmlGatewayUrl(raw)
raw.startsWith("https://pay.bml.com.mv/app/")) { if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || bmlUrl != null) {
(requireActivity() as HomeActivity).navigateTo( (requireActivity() as HomeActivity).navigateTo(
R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw, pendingQrAccountNumber) R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(bmlUrl ?: raw, pendingQrAccountNumber)
) )
} else { } else {
Toast.makeText(requireContext(), R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), R.string.transfer_qr_invalid, Toast.LENGTH_SHORT).show()
@@ -125,12 +125,12 @@ class TransferFragment : Fragment() {
val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult val raw = result.data?.getStringExtra(QrScannerActivity.EXTRA_QR_CONTENT) ?: return@registerForActivityResult
// BML card/gateway QR — hand off to dedicated payment screen // BML card/gateway QR — hand off to dedicated payment screen
if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || val bmlUrl = PaymvQrParser.extractBmlGatewayUrl(raw)
raw.startsWith("https://pay.bml.com.mv/app/")) { if (raw.startsWith("https://ebanking.bankofmaldives.com.mv/qrpay/") || bmlUrl != null) {
val fromCard = selectedAccount?.takeIf { val fromCard = selectedAccount?.takeIf {
it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT" it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT"
} }
(requireActivity() as HomeActivity).navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(raw, fromCard?.accountNumber)) (requireActivity() as HomeActivity).navigateTo(R.id.nav_transfer, TransferFragment.newInstanceFromBmlQr(bmlUrl ?: raw, fromCard?.accountNumber))
return@registerForActivityResult return@registerForActivityResult
} }
@@ -9,6 +9,24 @@ data class PaymvQrData(
object PaymvQrParser { object PaymvQrParser {
/**
* Returns the BML gateway URL if [raw] is or contains one, otherwise null.
* Handles both plain URL QRs and combined EMV QRs (e.g. Fahipay+BML card QR).
* For combined EMV QRs the URL is parsed from TLV (root tag 35 → sub-tag 20 → sub-sub-tag 01)
* rather than via regex, to avoid greedily consuming subsequent EMV tag bytes.
*/
fun extractBmlGatewayUrl(raw: String): String? {
if (raw.startsWith("https://pay.bml.com.mv/app/")) return raw
return try {
val root = parseTlv(raw)
val bmlMerchantInfo = root["35"]?.let { parseTlv(it) } ?: return null
val inner = bmlMerchantInfo["20"]?.let { parseTlv(it) } ?: return null
inner["01"]?.takeIf { it.startsWith("https://pay.bml.com.mv/app/") }
} catch (_: Exception) {
null
}
}
fun parse(raw: String): PaymvQrData? { fun parse(raw: String): PaymvQrData? {
return try { return try {
val root = parseTlv(raw) val root = parseTlv(raw)