diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/PayMvQrFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/PayMvQrFragment.kt
index 650ea5f..71eb0f4 100644
--- a/app/src/main/java/sh/sar/basedbank/ui/home/PayMvQrFragment.kt
+++ b/app/src/main/java/sh/sar/basedbank/ui/home/PayMvQrFragment.kt
@@ -7,12 +7,15 @@ import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
+import android.text.TextPaint
+import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.FileProvider
+import androidx.core.content.res.ResourcesCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
@@ -65,7 +68,18 @@ class PayMvQrFragment : Fragment() {
private data class QrTarget(val accountNumber: String, val name: String, val bank: String)
private fun currentTarget(): QrTarget? = contactTarget
- ?: selectedAccount?.let { QrTarget(it.accountNumber, it.accountBriefName, it.bank) }
+ ?: selectedAccount?.let { QrTarget(it.accountNumber, qrHolderName(it), it.bank) }
+
+ /** Name printed on the card and put in the payload (tag 59). */
+ private fun qrHolderName(account: BankAccount): String = when {
+ // Fahipay's brief name is the generic "Fahipay Wallet"; the holder's name is on the profile
+ account.bank == "FAHIPAY" -> account.profileName.takeIf { it.isNotBlank() && it != "Fahipay" }
+ ?: CredentialStore(requireContext())
+ .loadFahipayUserProfile(sh.sar.basedbank.util.ProfileImageStore.loginIdFromTag(account.loginTag))
+ ?.fullName?.takeIf { it.isNotBlank() }
+ ?: account.accountBriefName
+ else -> account.accountBriefName
+ }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
contactTarget = arguments?.let { args ->
@@ -146,11 +160,13 @@ class PayMvQrFragment : Fragment() {
"FAHIPAY" -> "FAHIMVMV"
else -> "MADVMVMV"
}
- val amountFormatted = binding.etAmount.text?.toString()?.trim()
- ?.replace(",", "")
+ val amountRaw = binding.etAmount.text?.toString()?.trim()?.replace(",", "")
+ val amountFormatted = amountRaw
?.toDoubleOrNull()
?.takeIf { it > 0 }
?.let { "%.2f".format(it) }
+ // BML shows the amount on the card as typed (no forced decimals)
+ val amountDisplay = amountRaw?.takeIf { amountFormatted != null }
val ctx = requireContext()
val account = selectedAccount
@@ -165,17 +181,28 @@ class PayMvQrFragment : Fragment() {
when {
m.startsWith("+") -> m
m.length == 7 -> "+960$m"
+ m.length == 10 && m.startsWith("960") -> "+$m" // Fahipay stores 960XXXXXXX
else -> m
}
}
} else null
val purpose = binding.etReference.text?.toString()?.trim()
- ?.takeIf { it.isNotBlank() } ?: getString(R.string.paymvqr_reference_default)
+ ?.takeIf { it.isNotBlank() }
+
+ // The reference (62/05) is also printed vertically beside the QR, as each bank does
+ val reference = when (target.bank) {
+ // BML: base-32 account number followed by the amount as typed
+ "BML" -> ((target.accountNumber.toBigIntegerOrNull()?.toString(32)?.uppercase() ?: "") +
+ (amountDisplay ?: "")).take(25).ifEmpty { generateReference(9) }
+ "FAHIPAY" -> "P" + generateReference(9) // Fahipay's own references are P + 9 chars
+ else -> generateReference(9)
+ }
val bmp = withContext(Dispatchers.Default) {
- val payload = buildQrPayload(target.accountNumber, target.name, acquirer, amountFormatted, mobile, purpose)
- renderQrCard(ctx, target, payload, amountFormatted)
+ val payload = buildQrPayload(target.accountNumber, target.name, acquirer, amountFormatted, mobile, purpose, reference, target.bank)
+ if (target.bank == "FAHIPAY") renderFahipayQrCard(ctx, target, payload, reference)
+ else renderQrCard(ctx, target, payload, reference)
}
if (_binding == null) return
generatedBitmap = bmp
@@ -194,14 +221,19 @@ class PayMvQrFragment : Fragment() {
acquirer: String,
amountStr: String?,
mobile: String?,
- purpose: String
+ purpose: String?,
+ ref: String,
+ bank: String
): String {
fun tlv(tag: String, value: String): String {
val len = value.length
return tag + (if (len < 10) "0$len" else "$len") + value
}
val format = tlv("00", "01")
- val poi = tlv("01", "11")
+ // Fahipay's own QRs are dynamic (12) when they carry an amount and mask the amount
+ // as "***" when they don't; its scanner may reject QRs that differ
+ val fahipay = bank == "FAHIPAY"
+ val poi = tlv("01", if (fahipay && !amountStr.isNullOrBlank()) "12" else "11")
val sub00 = tlv("00", "mv.favara.mpqr")
val sub01 = tlv("01", acquirer)
val sub02 = tlv("02", acquirer) // repeated acquirer, as per official PayMV app
@@ -211,21 +243,28 @@ class PayMvQrFragment : Fragment() {
val merchantAcct = tlv("26", sub00 + sub01 + sub02 + sub03 + sub05 + sub10)
val mcc = tlv("52", "0000")
val currency = tlv("53", "462")
- val amountTLV = if (!amountStr.isNullOrBlank()) tlv("54", amountStr) else ""
+ val amountTLV = when {
+ !amountStr.isNullOrBlank() -> tlv("54", amountStr)
+ fahipay -> tlv("54", "***")
+ else -> ""
+ }
val country = tlv("58", "MV")
- val name = tlv("59", accountName.take(25))
- val ref = generateReference()
- val addlData = tlv("62", tlv("05", ref) + tlv("08", purpose))
+ val name = tlv("59", accountName.uppercase().take(25))
+ // Fahipay's QRs always carry a city ("LD" + 4 digits) and default the purpose to PAYMENT
+ val city = if (fahipay) tlv("60", "LD" + (0..9999).random().toString().padStart(4, '0')) else ""
+ val purposeText = purpose?.takeIf { it.isNotBlank() } ?: if (fahipay) "PAYMENT" else null
+ val purposeTLV = if (purposeText != null) tlv("08", purposeText) else ""
+ val addlData = tlv("62", tlv("05", ref) + purposeTLV)
val timestamp = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.00000"))
val tag80 = tlv("80", tlv("00", "mv.favara.mpqr") + tlv("01", timestamp))
- val prefix = format + poi + merchantAcct + mcc + currency + amountTLV + country + name + addlData + tag80 + "6304"
+ val prefix = format + poi + merchantAcct + mcc + currency + amountTLV + country + name + city + addlData + tag80 + "6304"
return prefix + crc16(prefix)
}
- private fun generateReference(): String {
+ private fun generateReference(length: Int): String {
val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
- return (1..9).map { chars.random() }.joinToString("")
+ return (1..length).map { chars.random() }.joinToString("")
}
private fun crc16(data: String): String {
@@ -242,88 +281,127 @@ class PayMvQrFragment : Fragment() {
// ── QR card rendering ────────────────────────────────────────────────────
+ /**
+ * Replicates the BML app's ReceiveCard (React Native, v2.1.47) 1:1. All measurements are in
+ * dp, as in BML's StyleSheet, laid out for BML's reference screen width and drawn at
+ * [PX_PER_DP] pixels per dp.
+ */
private fun renderQrCard(
ctx: Context,
target: QrTarget,
qrPayload: String,
- amountStr: String?
+ qrId: String
): Bitmap {
- val W = 900
- val H = 1080
- val outerCorner = 48f
- val boxBlue = Color.parseColor("#2272B7")
- val footerBlue = Color.parseColor("#1A5799")
- val boxL = 24f; val boxT = 110f; val boxR = 876f; val boxB = 962f
+ val sw = SCREEN_WIDTH_DP
+ fun px(dp: Float) = dp * PX_PER_DP
+ val mmaBlue = Color.parseColor("#0E5CA4")
- val bm = Bitmap.createBitmap(W, H, Bitmap.Config.ARGB_8888)
+ val cardW = sw - 48f // screen's horizontal margins, spacing[5] each side
+ val qrSize = sw * 0.5f
+ val railW = sw / 1.85f
+
+ val namePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = Color.WHITE
+ textSize = px(14f)
+ typeface = Typeface.DEFAULT
+ textAlign = Paint.Align.CENTER
+ }
+ val footerPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = Color.WHITE
+ textSize = px(sw * 0.046f)
+ typeface = ResourcesCompat.getFont(ctx, R.font.sofia_pro_bold) ?: Typeface.DEFAULT_BOLD
+ letterSpacing = 1.2f / (sw * 0.046f) // RN letterSpacing is in dp, Paint's is in em
+ textAlign = Paint.Align.CENTER
+ }
+ // Android RN Text lines include font padding: line height = bottom - top
+ fun lineHeight(p: Paint) = p.fontMetrics.let { it.bottom - it.top } / PX_PER_DP
+
+ // --- Vertical layout (dp, card-local) ---
+ val topCardTop = 2f
+ val brandTop = topCardTop + 32f // brandRow marginTop spacing[6]
+ val logoBoxW = sw * 0.38f // bml-logo-paymv box: 0.38·sw wide
+ val logoBoxH = logoBoxW * 0.1116751269035533f
+ val payMvBoxW = sw * 0.2f // paymv-logo box: 0.2·sw wide
+ val payMvBoxH = payMvBoxW * 0.17333333333333334f
+ val brandH = maxOf(logoBoxH, payMvBoxH)
+ val nameText = target.name.uppercase()
+ val hasName = nameText.isNotBlank()
+ val qrCardTop = brandTop + brandH + if (hasName) 24f else 32f
+ val nameTop = qrCardTop + 8f + 12f // qrCard paddingTop spacing[2], name marginTop spacing[3]
+ val nameH = if (hasName) lineHeight(namePaint) else 0f
+ val qrTop = if (hasName) nameTop + nameH + 16f else qrCardTop + 37f
+ val qrCardBottom = qrTop + qrSize + 37f // paddingBottom spacing[6] + 5
+ val topCardBottom = qrCardBottom + 24f + 8f // qrCard marginBottom, topCard paddingBottom
+ val footerLineH = lineHeight(footerPaint)
+ val cardH = topCardBottom + 12f + footerLineH + 12f + 2f
+
+ val bm = Bitmap.createBitmap(px(cardW).toInt(), px(cardH).toInt(), Bitmap.Config.ARGB_8888)
val canvas = Canvas(bm)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
- // Clip to outer rounded card shape
- val outerPath = Path()
- outerPath.addRoundRect(RectF(0f, 0f, W.toFloat(), H.toFloat()), outerCorner, outerCorner, Path.Direction.CW)
- canvas.clipPath(outerPath)
- canvas.drawColor(Color.WHITE)
-
- // --- Bank logo top-left ---
- val logoRes = when (target.bank) {
- "BML" -> R.drawable.bml_logo_vector
- "MIB" -> R.drawable.mib_faisanet_logo
- else -> R.drawable.fahipay_logo_long
+ // captureWrapper: mmaBlue, radius 20 — shows as a 2dp border around the white top card
+ val outerPath = Path().apply {
+ addRoundRect(RectF(0f, 0f, px(cardW), px(cardH)), px(20f), px(20f), Path.Direction.CW)
}
+ canvas.clipPath(outerPath)
+ canvas.drawColor(mmaBlue)
+
+ // topCard: white, 2dp inset, top corners 18
+ paint.color = Color.WHITE
+ val r = px(18f)
+ canvas.drawPath(Path().apply {
+ addRoundRect(
+ RectF(px(2f), px(topCardTop), px(cardW - 2f), px(topCardBottom)),
+ floatArrayOf(r, r, r, r, 0f, 0f, 0f, 0f), Path.Direction.CW
+ )
+ }, paint)
+
+ // --- brandRow: "BANK OF MALDIVES" wordmark left, "PayMV QR" right, 40dp side margins ---
+ val rowL = 2f + 40f
+ val rowR = cardW - 2f - 40f
+ val rowCenterY = brandTop + brandH / 2
+ val logoRes = if (target.bank == "BML") R.drawable.bml_logo_paymv else R.drawable.mib_faisanet_logo
AppCompatResources.getDrawable(ctx, logoRes)?.let { d ->
val nW = d.intrinsicWidth.coerceAtLeast(1)
val nH = d.intrinsicHeight.coerceAtLeast(1)
- val maxW = 180f; val maxH = 76f
- val scale = minOf(maxW / nW, maxH / nH)
+ // resizeMode "contain" inside the logo box, left-aligned in the row
+ val scale = minOf(px(logoBoxW) / nW, px(logoBoxH) / nH)
val lW = (nW * scale).toInt()
val lH = (nH * scale).toInt()
- val lTop = ((boxT - lH) / 2).toInt().coerceAtLeast(10)
- d.setBounds(24, lTop, 24 + lW, lTop + lH)
+ val lLeft = (px(rowL) + (px(logoBoxW) - lW) / 2f).toInt()
+ val lTop = (px(rowCenterY) - lH / 2f).toInt()
+ d.setBounds(lLeft, lTop, lLeft + lW, lTop + lH)
d.draw(canvas)
}
-
- // --- "PayMV QR" top-right ---
- paint.color = Color.parseColor("#1A1A2E")
- paint.textSize = 36f
- paint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
+ // BML draws the paymv-logo image (fills its box exactly), nudged down 1dp
+ paint.color = mmaBlue
+ paint.typeface = footerPaint.typeface
paint.textAlign = Paint.Align.RIGHT
- canvas.drawText("PayMV QR", W - 28f, 66f, paint)
+ paint.textSize = px(payMvBoxH)
+ paint.textSize *= px(payMvBoxW) / paint.measureText("PayMV QR")
+ val payMvBounds = Rect().also { paint.getTextBounds("PayMV QR", 0, 8, it) }
+ canvas.drawText(
+ "PayMV QR", px(rowR),
+ px(rowCenterY + 1f) - payMvBounds.exactCenterY(), paint
+ )
- // --- Blue rounded box ---
- paint.color = boxBlue
- paint.textAlign = Paint.Align.LEFT
- canvas.drawRoundRect(RectF(boxL, boxT, boxR, boxB), 36f, 36f, paint)
+ // --- qrCard: mmaBlue, radius 16, 40dp side margins ---
+ val qrCardL = 2f + 40f
+ val qrCardR = cardW - 2f - 40f
+ paint.color = mmaBlue
+ canvas.drawRoundRect(RectF(px(qrCardL), px(qrCardTop), px(qrCardR), px(qrCardBottom)), px(16f), px(16f), paint)
- // Account name (white, bold, uppercase, auto-scaled to fit)
- paint.color = Color.WHITE
- paint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
- paint.textAlign = Paint.Align.CENTER
- val nameText = target.name.uppercase()
- paint.textSize = 36f
- val maxNameW = boxR - boxL - 48f
- if (paint.measureText(nameText) > maxNameW) {
- paint.textSize = 36f * maxNameW / paint.measureText(nameText)
- }
- val nameBaseline = boxT + 68f
- canvas.drawText(nameText, W / 2f, nameBaseline, paint)
-
- // Optional amount below name
- val qrTopY: Float
- if (!amountStr.isNullOrBlank()) {
- paint.textSize = 28f
- paint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
- val amtBaseline = nameBaseline + 42f
- canvas.drawText("MVR $amountStr", W / 2f, amtBaseline, paint)
- qrTopY = amtBaseline + 20f
- } else {
- qrTopY = nameBaseline + 26f
+ if (hasName) {
+ val maxNameW = px(qrCardR - qrCardL)
+ if (namePaint.measureText(nameText) > maxNameW) {
+ namePaint.textSize *= maxNameW / namePaint.measureText(nameText)
+ }
+ canvas.drawText(nameText, px(cardW / 2), px(nameTop) - namePaint.fontMetrics.top, namePaint)
}
- // QR code — white modules on the same blue as the box background
- val availH = boxB - qrTopY - 24f
- val qrPx = minOf(availH, boxR - boxL - 48f).toInt().coerceAtMost(700).coerceAtLeast(200)
- val qrLeft = ((W - qrPx) / 2).toFloat()
+ // QR — white modules on mmaBlue, ECL M, no quiet zone (react-native-qrcode-svg defaults)
+ val qrPx = px(qrSize).toInt()
+ val qrLeft = px(cardW / 2) - qrPx / 2f
try {
val hints = mapOf(
EncodeHintType.MARGIN to 0,
@@ -333,23 +411,150 @@ class PayMvQrFragment : Fragment() {
val pixels = IntArray(qrPx * qrPx)
for (y in 0 until qrPx) {
for (x in 0 until qrPx) {
- pixels[y * qrPx + x] = if (matrix[x, y]) Color.WHITE else boxBlue
+ pixels[y * qrPx + x] = if (matrix[x, y]) Color.WHITE else mmaBlue
}
}
val qrBm = Bitmap.createBitmap(pixels, qrPx, qrPx, Bitmap.Config.ARGB_8888)
- canvas.drawBitmap(qrBm, qrLeft, qrTopY, null)
+ canvas.drawBitmap(qrBm, qrLeft, px(qrTop), null)
qrBm.recycle()
} catch (_: Exception) { /* skip if encoding fails */ }
- // --- Dark blue footer ---
- paint.color = footerBlue
- paint.textAlign = Paint.Align.LEFT
- canvas.drawRect(RectF(0f, 970f, W.toFloat(), H.toFloat()), paint)
+ // qrIdRail: the reference, rotated -90°, beside the QR's right edge
+ if (qrId.isNotEmpty()) {
+ val idPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = Color.BLACK
+ alpha = (255 * 0.8f).toInt()
+ textSize = px(10f)
+ typeface = Typeface.DEFAULT
+ textAlign = Paint.Align.CENTER
+ }
+ val qrRight = cardW / 2 + qrSize / 2
+ val cx = px(qrRight + railW / 1.37f - railW / 2)
+ val cy = px(qrTop + (qrSize + railW / 1.5f) / 2)
+ val idText = TextUtils.ellipsize(qrId, TextPaint(idPaint), px(railW), TextUtils.TruncateAt.END).toString()
+ canvas.save()
+ canvas.rotate(-90f, cx, cy)
+ val fm = idPaint.fontMetrics
+ canvas.drawText(idText, cx, cy - (fm.bottom + fm.top) / 2, idPaint)
+ canvas.restore()
+ }
+
+ // --- footer: SofiaPro-Bold, letterSpacing 1.2 ---
+ canvas.drawText(
+ "MALDIVES NATIONAL QR", px(cardW / 2),
+ px(topCardBottom + 12f) - footerPaint.fontMetrics.top, footerPaint
+ )
+
+ return bm
+ }
+
+ /**
+ * Replicates the card Fahipay's server renders for PayMV QR (api/app/qr/), measured
+ * in pixels on its 1240×1322 image. Fonts follow the BML card (Sofia Pro Bold, Roboto),
+ * except the vertical reference, which is Montserrat as on Fahipay's.
+ */
+ private fun renderFahipayQrCard(
+ ctx: Context,
+ target: QrTarget,
+ qrPayload: String,
+ reference: String
+ ): Bitmap {
+ val w = 1240f
+ val h = 1322f
+ val blue = Color.parseColor("#005DA3")
+ val sofiaBold = ResourcesCompat.getFont(ctx, R.font.sofia_pro_bold) ?: Typeface.DEFAULT_BOLD
+ val montserrat = ResourcesCompat.getFont(ctx, R.font.montserrat_regular) ?: Typeface.DEFAULT
+
+ val bm = Bitmap.createBitmap(w.toInt(), h.toInt(), Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bm)
+ val paint = Paint(Paint.ANTI_ALIAS_FLAG)
+
+ // Blue card with a 6px border around the white area; footer is the blue below it
+ canvas.clipPath(Path().apply {
+ addRoundRect(RectF(0f, 0f, w, h), 50f, 50f, Path.Direction.CW)
+ })
+ canvas.drawColor(blue)
paint.color = Color.WHITE
- paint.textSize = 32f
- paint.typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
- paint.textAlign = Paint.Align.CENTER
- canvas.drawText("MALDIVES NATIONAL QR", W / 2f, 1038f, paint)
+ canvas.drawPath(Path().apply {
+ addRoundRect(
+ RectF(6f, 6f, w - 6f, 1174f),
+ floatArrayOf(44f, 44f, 44f, 44f, 0f, 0f, 0f, 0f), Path.Direction.CW
+ )
+ }, paint)
+
+ // Square app icon, then the "FahiPay" wordmark, in one row
+ AppCompatResources.getDrawable(ctx, R.drawable.fahipay_logo)?.let { d ->
+ d.setBounds(94, 94, 163, 163)
+ d.draw(canvas)
+ }
+ AppCompatResources.getDrawable(ctx, R.drawable.fahipay_logo_long)?.let { d ->
+ val lH = 48
+ val lW = (lH * d.intrinsicWidth.toFloat() / d.intrinsicHeight.coerceAtLeast(1)).toInt()
+ d.setBounds(178, 104, 178 + lW, 104 + lH)
+ d.draw(canvas)
+ }
+
+ // Sized so capitals match the reference's cap heights; positions below are cap tops
+ fun textPaint(tf: Typeface, capH: Float, spacingEm: Float, align: Paint.Align) =
+ Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ typeface = tf
+ letterSpacing = spacingEm
+ textAlign = align
+ textSize = 100f
+ val h = Rect().also { getTextBounds("H", 0, 1, it) }.height().coerceAtLeast(1)
+ textSize = 100f * capH / h
+ }
+ fun Paint.capHeight() = Rect().also { getTextBounds("H", 0, 1, it) }.height()
+
+ // "PayMV QR" top-right
+ val payMvPaint = textPaint(sofiaBold, 30f, 0f, Paint.Align.RIGHT).apply { color = blue }
+ canvas.drawText("PayMV QR", 1147f, 101f + 30f, payMvPaint)
+
+ // Blue QR panel
+ paint.color = blue
+ canvas.drawRoundRect(RectF(166f, 218f, 1074f, 1126f), 55f, 55f, paint)
+
+ // Account name
+ val nameText = target.name.uppercase()
+ if (nameText.isNotBlank()) {
+ val namePaint = textPaint(Typeface.DEFAULT, 30f, 0f, Paint.Align.CENTER).apply { color = Color.WHITE }
+ val maxNameW = 1074f - 166f - 80f
+ if (namePaint.measureText(nameText) > maxNameW) {
+ namePaint.textSize *= maxNameW / namePaint.measureText(nameText)
+ }
+ canvas.drawText(nameText, 619f, 314f + namePaint.capHeight(), namePaint)
+ }
+
+ // QR — white modules on blue, no quiet zone
+ val qrPx = 562
+ try {
+ val hints = mapOf(
+ EncodeHintType.MARGIN to 0,
+ EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.M
+ )
+ val matrix = QRCodeWriter().encode(qrPayload, BarcodeFormat.QR_CODE, qrPx, qrPx, hints)
+ val pixels = IntArray(qrPx * qrPx)
+ for (y in 0 until qrPx) {
+ for (x in 0 until qrPx) {
+ pixels[y * qrPx + x] = if (matrix[x, y]) Color.WHITE else blue
+ }
+ }
+ val qrBm = Bitmap.createBitmap(pixels, qrPx, qrPx, Bitmap.Config.ARGB_8888)
+ canvas.drawBitmap(qrBm, 338f, 417f, null)
+ qrBm.recycle()
+ } catch (_: Exception) { /* skip if encoding fails */ }
+
+ // Reference, blue, reading bottom-to-top in the white margin right of the panel,
+ // starting level with y=1087 and with its baseline at x=1171
+ val refPaint = textPaint(montserrat, 27f, 0.005f, Paint.Align.LEFT).apply { color = blue }
+ canvas.save()
+ canvas.rotate(-90f, 1171f, 1087f)
+ canvas.drawText(reference, 1171f, 1087f, refPaint)
+ canvas.restore()
+
+ // Footer
+ val footerPaint = textPaint(sofiaBold, 55.5f, 1.2f / 25.76f, Paint.Align.CENTER).apply { color = Color.WHITE }
+ canvas.drawText("MALDIVES NATIONAL QR", w / 2, 1220f + 55.5f, footerPaint)
return bm
}
@@ -437,6 +642,10 @@ class PayMvQrFragment : Fragment() {
private const val ARG_ACCOUNT_NAME = "account_name"
private const val ARG_BANK = "bank"
+ /** BML's layout reference: the screen width (dp) its ReceiveCard sizes were captured at. */
+ private const val SCREEN_WIDTH_DP = 560f
+ private const val PX_PER_DP = 2f
+
/** QR for a contact's account. [bank] is "BML" / "MIB" / "FAHIPAY", as on [BankAccount.bank]. */
fun forContact(accountNumber: String, name: String, bank: String) = PayMvQrFragment().apply {
arguments = Bundle().apply {
diff --git a/app/src/main/res/drawable-nodpi/bml_logo_paymv.png b/app/src/main/res/drawable-nodpi/bml_logo_paymv.png
new file mode 100644
index 0000000..a9abad5
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/bml_logo_paymv.png differ
diff --git a/app/src/main/res/font/montserrat_regular.ttf b/app/src/main/res/font/montserrat_regular.ttf
new file mode 100644
index 0000000..2a2b2aa
Binary files /dev/null and b/app/src/main/res/font/montserrat_regular.ttf differ
diff --git a/app/src/main/res/font/sofia_pro_bold.ttf b/app/src/main/res/font/sofia_pro_bold.ttf
new file mode 100644
index 0000000..cd156bf
Binary files /dev/null and b/app/src/main/res/font/sofia_pro_bold.ttf differ
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ee06fdc..6f3cec4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -127,7 +127,6 @@
Failed to save image
Include phone number
Reference (optional)
- PayMV QR Transfer
Lock app