add transfer recipt for bml and mib
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 4s

This commit is contained in:
2026-05-15 16:38:15 +05:00
parent 00a5edf539
commit c4379c42c8
18 changed files with 1002 additions and 30 deletions

1
.idea/vcs.xml generated
View File

@@ -2,6 +2,5 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/BinaryEye" vcs="Git" />
</component>
</project>

View File

@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -54,6 +55,16 @@
android:exported="false"
android:screenOrientation="portrait" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

View File

@@ -59,6 +59,7 @@ class TransferFragment : Fragment() {
// Resolved recipient info — set after successful lookup or prefill
private var resolvedAccountNumber = ""
private var resolvedRecipientName = ""
private var resolvedBankName = ""
private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
@@ -268,6 +269,7 @@ class TransferFragment : Fragment() {
resolvedAccountNumber = info.accountNumber
resolvedRecipientName = info.accountName
resolvedBankName = info.bankId
binding.tvToAccountName.text = displayName
binding.tvToBankBic.text = "${info.accountNumber} · ${info.bankId}"
@@ -299,6 +301,8 @@ class TransferFragment : Fragment() {
) {
resolvedAccountNumber = accountNumber
resolvedRecipientName = displayName
val contacts = viewModel.contacts.value ?: emptyList()
resolvedBankName = contacts.firstOrNull { it.benefAccount == accountNumber }?.benefBankName ?: ""
binding.tvToAccountName.text = displayName
binding.tvToBankBic.text = subtitle
@@ -308,7 +312,6 @@ class TransferFragment : Fragment() {
binding.btnScanQr.visibility = View.GONE
binding.cardToInfo.visibility = View.VISIBLE
val contacts = viewModel.contacts.value ?: emptyList()
val contact = contacts.firstOrNull { it.benefAccount == accountNumber }
if (contact != null) {
RecentsCache.save(requireContext(), RecentPick(
@@ -376,29 +379,28 @@ class TransferFragment : Fragment() {
}
val destDisplay = binding.tvToAccountName.text?.toString() ?: resolvedAccountNumber
val bankNameCapture = resolvedBankName
val capturedToAvatar = (binding.ivToPhoto.drawable as? android.graphics.drawable.BitmapDrawable)?.bitmap
AlertDialog.Builder(requireContext())
.setTitle(R.string.transfer)
.setMessage("Send $currency $amountStr to $destDisplay?\n\nFrom: ${src.accountBriefName} · ${src.accountNumber}")
.setPositiveButton(R.string.transfer_confirm) { _, _ ->
binding.btnTransfer.isEnabled = false
viewLifecycleOwner.lifecycleScope.launch {
val (ok, msg) = withContext(Dispatchers.IO) {
val (ok, msg, receipt) = withContext(Dispatchers.IO) {
if (!isSrcBml) {
doMibTransfer(src, resolvedAccountNumber, resolvedRecipientName, amountStr, remarks)
doMibTransfer(src, resolvedAccountNumber, resolvedRecipientName, destDisplay, amountStr, remarks, bankNameCapture)
} else {
doBmlTransfer(src, resolvedAccountNumber, amount, remarks, isSrcCard, isDestMib, currency, allAccounts, allContacts)
doBmlTransfer(src, resolvedAccountNumber, destDisplay, amount, amountStr, remarks, isSrcCard, isDestMib, currency, allAccounts, allContacts)
}
}
binding.btnTransfer.isEnabled = true
if (ok) {
if (ok && receipt != null) {
clearForm()
(requireActivity() as HomeActivity).refreshBalances(src)
AlertDialog.Builder(requireContext())
.setTitle(R.string.transfer_success)
.setMessage(msg)
.setPositiveButton("OK", null)
.show()
} else {
val activity = requireActivity() as HomeActivity
activity.refreshBalances(src)
activity.showWithBackStack(TransferReceiptFragment.newInstance(receipt, capturedToAvatar))
} else if (!ok) {
Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show()
}
}
@@ -411,10 +413,12 @@ class TransferFragment : Fragment() {
src: MibAccount,
destAccount: String,
destName: String,
destDisplay: String,
amount: String,
remarks: String
): Pair<Boolean, String> {
val sess = session ?: return Pair(false, getString(R.string.transfer_session_unavailable))
remarks: String,
bankName: String
): Triple<Boolean, String, TransferReceiptData?> {
val sess = session ?: return Triple(false, getString(R.string.transfer_session_unavailable), null)
val app = requireActivity().application as BasedBankApp
// Switch to the profile that owns the source account
if (src.profileId.isNotBlank()) {
@@ -423,10 +427,19 @@ class TransferFragment : Fragment() {
}
val otp = CredentialStore(requireContext()).loadMibCredentials()?.otpSeed
?.let { Totp.generate(it) }
?: return Pair(false, "OTP unavailable")
?: return Triple(false, "OTP unavailable", null)
val currencyCode = if (src.currencyName == "USD") "840" else "462"
val currency = if (src.currencyName == "USD") "USD" else "MVR"
val isDestMib = destAccount.matches(Regex("^9\\d{16}$"))
val bankNo = if (isDestMib) 2 else 3
val toBank = when {
isDestMib -> "MIB"
else -> when (bankName.uppercase()) {
"MALBMVMV" -> "BML"
"MADVMVMV" -> "MIB"
else -> bankName.ifBlank { "LOCAL" }
}
}
return try {
val result = MibTransferClient().transfer(
session = sess,
@@ -440,32 +453,48 @@ class TransferFragment : Fragment() {
otp = otp
)
if (result.success) {
Pair(true, "Transaction ID: ${result.trxId}\n${result.date}")
val receipt = TransferReceiptData(
isMib = true,
amount = "%.2f".format(amount.toDoubleOrNull() ?: 0.0),
currency = currency,
fromLabel = src.accountBriefName,
fromColorHex = "#FE860E",
fromProfileImageHash = src.profileImageHash,
toLabel = destDisplay.ifBlank { destName },
toAccount = destAccount,
toBank = toBank,
remarks = remarks,
mibReferenceNo = result.trxId,
mibTransactionDate = result.date
)
Triple(true, "Transaction ID: ${result.trxId}\n${result.date}", receipt)
} else {
Pair(false, result.errorMessage.ifBlank { "Transfer failed" })
Triple(false, result.errorMessage.ifBlank { "Transfer failed" }, null)
}
} catch (e: Exception) {
Pair(false, e.message ?: "Transfer failed")
Triple(false, e.message ?: "Transfer failed", null)
}
}
private fun doBmlTransfer(
src: MibAccount,
destAccount: String,
destDisplay: String,
amount: Double,
amountStr: String,
remarks: String,
isSrcCard: Boolean,
isDestMib: Boolean,
currency: String,
allAccounts: List<MibAccount>,
allContacts: List<MibBeneficiary>
): Pair<Boolean, String> {
val sess = bmlSession ?: return Pair(false, getString(R.string.transfer_session_unavailable))
): Triple<Boolean, String, TransferReceiptData?> {
val sess = bmlSession ?: return Triple(false, getString(R.string.transfer_session_unavailable), null)
val otp = CredentialStore(requireContext()).loadBmlCredentials()?.otpSeed
?.let { Totp.generate(it) }
?: return Pair(false, "OTP unavailable")
?: return Triple(false, "OTP unavailable", null)
val debitAccount = src.internalId.ifBlank {
return Pair(false, getString(R.string.transfer_missing_internal_id))
return Triple(false, getString(R.string.transfer_missing_internal_id), null)
}
// Determine type + credit account
@@ -485,19 +514,20 @@ class TransferFragment : Fragment() {
isDestMib -> {
// USD DOT: requires BML contact numeric ID
val contact = allContacts.firstOrNull { it.benefCategoryId == "BML" && it.benefAccount == destAccount }
?: return Pair(false, "BML contact not found for this account")
?: return Triple(false, "BML contact not found for this account", null)
Triple("DOT", contact.benefNo.removePrefix("bml_"), null as String?)
}
else -> Triple("IAT", destAccount, null as String?)
}
val toBank = bank ?: if (isDestMib) "MIB" else "BML"
val bmlFlow = BmlLoginFlow()
// Step 1: initiate
val initiated = try {
bmlFlow.initiateTransfer(sess, debitAccount, creditAccount, amount, transferType, currency, bank)
} catch (e: Exception) { return Pair(false, e.message ?: "Initiation failed") }
} catch (e: Exception) { return Triple(false, e.message ?: "Initiation failed", null) }
if (!initiated) return Pair(false, "Failed to initiate transfer — check your session")
if (!initiated) return Triple(false, "Failed to initiate transfer — check your session", null)
// Step 2: confirm with fresh OTP
val confirmOtp = CredentialStore(requireContext()).loadBmlCredentials()?.otpSeed
@@ -506,13 +536,27 @@ class TransferFragment : Fragment() {
return try {
val result = bmlFlow.confirmTransfer(sess, debitAccount, creditAccount, amount, transferType, currency, confirmOtp, remarks, bank)
if (result.success) {
val receipt = TransferReceiptData(
isMib = false,
amount = "%.2f".format(amount),
currency = currency,
fromLabel = src.accountBriefName,
fromColorHex = "#0066A1",
toLabel = destDisplay.ifBlank { destAccount },
toAccount = destAccount,
toBank = toBank,
remarks = remarks,
bmlFromName = src.accountBriefName,
bmlReference = result.reference,
bmlTimestamp = result.timestamp
)
val time = result.timestamp.take(19).replace("T", " ")
Pair(true, "Reference: ${result.reference}\n$time")
Triple(true, "Reference: ${result.reference}\n$time", receipt)
} else {
Pair(false, result.errorMessage.ifBlank { "Transfer failed" })
Triple(false, result.errorMessage.ifBlank { "Transfer failed" }, null)
}
} catch (e: Exception) {
Pair(false, e.message ?: "Transfer failed")
Triple(false, e.message ?: "Transfer failed", null)
}
}
@@ -524,6 +568,7 @@ class TransferFragment : Fragment() {
binding.etRemarks.setText("")
resolvedAccountNumber = ""
resolvedRecipientName = ""
resolvedBankName = ""
binding.cardToInfo.visibility = View.GONE
binding.tilTo.visibility = View.VISIBLE
binding.btnPickContact.visibility = View.VISIBLE

View File

@@ -0,0 +1,21 @@
package sh.sar.basedbank.ui.home
data class TransferReceiptData(
val isMib: Boolean,
val amount: String,
val currency: String,
val fromLabel: String,
val fromColorHex: String,
val fromProfileImageHash: String? = null,
val toLabel: String,
val toAccount: String,
val toBank: String,
val remarks: String,
// MIB receipt fields
val mibReferenceNo: String = "",
val mibTransactionDate: String = "",
// BML receipt fields
val bmlFromName: String = "",
val bmlReference: String = "",
val bmlTimestamp: String = "",
)

View File

@@ -0,0 +1,319 @@
package sh.sar.basedbank.ui.home
import android.content.ContentValues
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore
import android.util.Base64
import android.view.LayoutInflater
import android.view.PixelCopy
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.google.android.material.button.MaterialButton
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import sh.sar.basedbank.BasedBankApp
import sh.sar.basedbank.R
import sh.sar.basedbank.api.mib.MibContactsClient
import sh.sar.basedbank.databinding.FragmentReceiptBmlBinding
import sh.sar.basedbank.databinding.FragmentReceiptMibBinding
import java.io.File
import java.io.FileOutputStream
import java.text.NumberFormat
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.util.Locale
class TransferReceiptFragment : Fragment() {
private var _receiptCard: View? = null
private val receiptCard get() = _receiptCard!!
companion object {
private const val ARG_IS_MIB = "is_mib"
private const val ARG_AMOUNT = "amount"
private const val ARG_CURRENCY = "currency"
private const val ARG_FROM_LABEL = "from_label"
private const val ARG_FROM_COLOR = "from_color"
private const val ARG_FROM_PROFILE_HASH = "from_profile_hash"
private const val ARG_TO_LABEL = "to_label"
private const val ARG_TO_ACCOUNT = "to_account"
private const val ARG_TO_BANK = "to_bank"
private const val ARG_REMARKS = "remarks"
private const val ARG_MIB_REF = "mib_ref"
private const val ARG_MIB_DATE = "mib_date"
private const val ARG_BML_FROM_NAME = "bml_from_name"
private const val ARG_BML_REFERENCE = "bml_reference"
private const val ARG_BML_TIMESTAMP = "bml_timestamp"
// Holds the already-rendered to-avatar bitmap from TransferFragment
var pendingToAvatarBitmap: Bitmap? = null
fun newInstance(data: TransferReceiptData, toAvatarBitmap: Bitmap?) = TransferReceiptFragment().apply {
pendingToAvatarBitmap = toAvatarBitmap
arguments = Bundle().apply {
putBoolean(ARG_IS_MIB, data.isMib)
putString(ARG_AMOUNT, data.amount)
putString(ARG_CURRENCY, data.currency)
putString(ARG_FROM_LABEL, data.fromLabel)
putString(ARG_FROM_COLOR, data.fromColorHex)
putString(ARG_FROM_PROFILE_HASH, data.fromProfileImageHash)
putString(ARG_TO_LABEL, data.toLabel)
putString(ARG_TO_ACCOUNT, data.toAccount)
putString(ARG_TO_BANK, data.toBank)
putString(ARG_REMARKS, data.remarks)
putString(ARG_MIB_REF, data.mibReferenceNo)
putString(ARG_MIB_DATE, data.mibTransactionDate)
putString(ARG_BML_FROM_NAME, data.bmlFromName)
putString(ARG_BML_REFERENCE, data.bmlReference)
putString(ARG_BML_TIMESTAMP, data.bmlTimestamp)
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val isMib = arguments?.getBoolean(ARG_IS_MIB, true) ?: true
return if (isMib) {
val binding = FragmentReceiptMibBinding.inflate(inflater, container, false)
bindMib(binding)
_receiptCard = binding.receiptCard
binding.root
} else {
val binding = FragmentReceiptBmlBinding.inflate(inflater, container, false)
bindBml(binding)
_receiptCard = binding.receiptCard
binding.root
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
view.findViewById<MaterialButton>(R.id.btnDone).setOnClickListener {
parentFragmentManager.popBackStack()
}
view.findViewById<MaterialButton>(R.id.btnShare).setOnClickListener {
shareReceipt()
}
view.findViewById<MaterialButton>(R.id.btnSave).setOnClickListener {
saveReceipt()
}
}
// ── Data binding ──────────────────────────────────────────────────────────
private fun bindMib(binding: FragmentReceiptMibBinding) {
val args = requireArguments()
val fromLabel = args.getString(ARG_FROM_LABEL, "")
val fromColor = args.getString(ARG_FROM_COLOR, "#FE860E")
val fromProfileHash = args.getString(ARG_FROM_PROFILE_HASH)
val toLabel = args.getString(ARG_TO_LABEL, "")
val currency = args.getString(ARG_CURRENCY, "MVR")
val amount = args.getString(ARG_AMOUNT, "")
// From avatar: initials first, then load profile image if hash available
binding.ivFromAvatar.setImageBitmap(makeInitialsBitmap(fromLabel, fromColor))
binding.tvFromLabel.text = fromLabel
if (fromProfileHash != null) {
loadProfileImage(fromProfileHash, isProfile = true) { binding.ivFromAvatar.setImageBitmap(it) }
}
// To avatar: use already-rendered bitmap from TransferFragment if available
val toAvatar = pendingToAvatarBitmap
if (toAvatar != null) {
binding.ivToAvatar.setImageBitmap(toAvatar)
} else {
binding.ivToAvatar.setImageBitmap(makeInitialsBitmap(toLabel, "#607D8B"))
}
binding.tvToLabel.text = toLabel
binding.tvAmount.text = "$currency $amount"
binding.tvReferenceNo.text = args.getString(ARG_MIB_REF, "")
binding.tvToAccount.text = args.getString(ARG_TO_ACCOUNT, "")
binding.tvToBank.text = args.getString(ARG_TO_BANK, "")
binding.tvTransactionDate.text = args.getString(ARG_MIB_DATE, "")
binding.tvValueDate.text = args.getString(ARG_MIB_DATE, "")
binding.tvPurpose.text = args.getString(ARG_REMARKS, "").ifBlank { "-" }
}
private fun loadProfileImage(hash: String, isProfile: Boolean, onLoaded: (Bitmap) -> Unit) {
val app = requireActivity().application as BasedBankApp
val sess = app.mibSession ?: return
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
try {
val base64 = if (isProfile) {
app.mibLoginFlow.fetchProfileImage(sess, hash)
} else {
MibContactsClient().fetchProfileImageBase64(sess, hash)
} ?: return@launch
val bytes = Base64.decode(base64, Base64.DEFAULT)
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return@launch
withContext(Dispatchers.Main) { if (_receiptCard != null) onLoaded(bitmap) }
} catch (_: Exception) { }
}
}
private fun bindBml(binding: FragmentReceiptBmlBinding) {
val args = requireArguments()
val currency = args.getString(ARG_CURRENCY, "MVR")
val amountStr = args.getString(ARG_AMOUNT, "")
// Format with thousands separator: "1000.00" → "1,000.00"
val formattedAmount = try {
val d = amountStr.toDouble()
val intFmt = NumberFormat.getNumberInstance(Locale.US).apply { maximumFractionDigits = 0 }
intFmt.format(d.toLong()) + "%.2f".format(d).takeLast(3)
} catch (_: Exception) { amountStr }
binding.tvAmountValue.text = formattedAmount
binding.tvAmountCurrency.text = currency
binding.tvMessageRow.text = "Thank you. Transfer transaction is successful."
binding.tvReference.text = args.getString(ARG_BML_REFERENCE, "")
binding.tvTransactionDate.text = formatBmlTimestamp(args.getString(ARG_BML_TIMESTAMP, ""))
binding.tvFrom.text = args.getString(ARG_BML_FROM_NAME, "").ifBlank {
args.getString(ARG_FROM_LABEL, "")
}.uppercase(Locale.US)
binding.tvToName.text = args.getString(ARG_TO_LABEL, "")
binding.tvToAccount.text = args.getString(ARG_TO_ACCOUNT, "")
binding.tvAmountRow.text = "$currency $formattedAmount"
val remarks = args.getString(ARG_REMARKS, "")
if (!remarks.isNullOrBlank()) {
binding.tvRemarks.text = remarks
binding.remarksDivider.visibility = View.VISIBLE
binding.remarksRow.visibility = View.VISIBLE
}
}
// ── Share / Save ──────────────────────────────────────────────────────────
private fun shareReceipt() {
captureReceiptBitmap { bitmap ->
if (bitmap == null) {
Toast.makeText(requireContext(), "Failed to render receipt", Toast.LENGTH_SHORT).show()
return@captureReceiptBitmap
}
try {
val dir = File(requireContext().cacheDir, "receipts").also { it.mkdirs() }
val file = File(dir, "receipt_${System.currentTimeMillis()}.png")
FileOutputStream(file).use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) }
val uri: Uri = FileProvider.getUriForFile(
requireContext(), "${requireContext().packageName}.fileprovider", file
)
val intent = Intent(Intent.ACTION_SEND).apply {
type = "image/png"
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, "Share Receipt"))
} catch (e: Exception) {
Toast.makeText(requireContext(), "Share failed: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
private fun saveReceipt() {
captureReceiptBitmap { bitmap ->
if (bitmap == null) {
Toast.makeText(requireContext(), "Failed to render receipt", Toast.LENGTH_SHORT).show()
return@captureReceiptBitmap
}
val filename = "receipt_${System.currentTimeMillis()}.png"
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val values = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, filename)
put(MediaStore.Images.Media.MIME_TYPE, "image/png")
put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
}
val resolver = requireContext().contentResolver
val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
?: throw Exception("Could not create media entry")
resolver.openOutputStream(uri)?.use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) }
} else {
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
dir.mkdirs()
val file = File(dir, filename)
FileOutputStream(file).use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) }
android.media.MediaScannerConnection.scanFile(
requireContext(), arrayOf(file.absolutePath), null, null
)
}
Toast.makeText(requireContext(), "Receipt saved to Photos", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Toast.makeText(requireContext(), "Save failed: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
// ── Helpers ───────────────────────────────────────────────────────────────
/**
* Captures the receipt card using PixelCopy, which correctly handles
* hardware-accelerated views (avoids the black-square problem with view.draw()).
*/
private fun captureReceiptBitmap(callback: (Bitmap?) -> Unit) {
val view = _receiptCard ?: run { callback(null); return }
if (view.width == 0 || view.height == 0) { callback(null); return }
val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
val location = IntArray(2)
view.getLocationInWindow(location)
val srcRect = Rect(location[0], location[1], location[0] + view.width, location[1] + view.height)
PixelCopy.request(requireActivity().window, srcRect, bitmap, { result ->
callback(if (result == PixelCopy.SUCCESS) bitmap else null)
}, Handler(Looper.getMainLooper()))
}
private fun formatBmlTimestamp(raw: String): String {
if (raw.isBlank()) return ""
return try {
OffsetDateTime.parse(raw).format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"))
} catch (_: Exception) {
raw.take(16)
}
}
private fun makeInitialsBitmap(name: String, colorHex: String): Bitmap {
val sizePx = (resources.displayMetrics.density * 52).toInt()
val bgColor = try { Color.parseColor(colorHex) } catch (_: Exception) { Color.GRAY }
val bm = Bitmap.createBitmap(sizePx, sizePx, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bm)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.color = bgColor
canvas.drawCircle(sizePx / 2f, sizePx / 2f, sizePx / 2f, paint)
paint.color = Color.WHITE
paint.textSize = sizePx * 0.42f
paint.textAlign = Paint.Align.CENTER
val letter = name.firstOrNull()?.uppercaseChar()?.toString() ?: "?"
val metrics = paint.fontMetrics
canvas.drawText(letter, sizePx / 2f, sizePx / 2f - (metrics.ascent + metrics.descent) / 2f, paint)
return bm
}
override fun onResume() {
super.onResume()
requireActivity().title = "Receipt"
}
override fun onDestroyView() {
super.onDestroyView()
_receiptCard = null
pendingToAvatarBitmap = null
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#2E7D32"
android:centerColor="#43A047"
android:endColor="#66BB6A"
android:angle="315"
android:type="linear" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="100"
android:viewportHeight="100">
<!-- Circle outline -->
<path
android:pathData="M50,50 m-45,0 a45,45 0 1,0 90,0 a45,45 0 1,0 -90,0"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeWidth="4" />
<!-- Checkmark -->
<path
android:pathData="M27,50 L41,64 L73,33"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeWidth="6"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,261 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FFFFFF">
<!-- ══════════════════════════════════════════════════════════════════════ -->
<!-- Renderable receipt card -->
<!-- ══════════════════════════════════════════════════════════════════════ -->
<LinearLayout
android:id="@+id/receiptCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#FFFFFF">
<!-- BML icon (bmlicon.jpg, centered, ~52dp ≈ 146/1080*360dp) -->
<ImageView
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:src="@drawable/bml_icon"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:contentDescription="@null" />
<!-- Success message — NunitoSans 27.5px → 14sp, #2D2D2D -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="14dp"
android:layout_marginBottom="20dp"
android:id="@+id/tvMessage"
android:text="Thank you. Transfer transaction is successful."
android:textSize="14sp"
android:textColor="#2D2D2D"
android:fontFamily="@font/nunito_sans"
android:gravity="center" />
<!-- ══════════════════════════════════════════════════════════════════ -->
<!-- Amount / wave section -->
<!-- receiptwave.png fills this FrameLayout; text overlays center. -->
<!-- Matches: drawImage(receiptwave, 0, 368, 1080, 155) clipped to -->
<!-- y=379488 visible; amount at y=355 (above wave), ccy at y=438. -->
<!-- ══════════════════════════════════════════════════════════════════ -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/receiptwave"
android:scaleType="fitXY"
android:contentDescription="@null" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:paddingVertical="14dp">
<!-- Amount — SofiaPro 68px → 42sp, #242424 -->
<TextView
android:id="@+id/tvAmountValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="42sp"
android:textColor="#242424"
android:fontFamily="@font/sofia_pro"
android:gravity="center" />
<!-- Currency — SofiaPro 40px → 20sp, #7E7E7E -->
<TextView
android:id="@+id/tvAmountCurrency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textSize="20sp"
android:textColor="#7E7E7E"
android:fontFamily="@font/sofia_pro"
android:gravity="center" />
</LinearLayout>
</FrameLayout>
<!-- ══════════════════════════════════════════════════════════════════ -->
<!-- Body rows -->
<!-- Labels: SofiaPro 28px → 13sp, #000000, left-aligned -->
<!-- Values: SofiaPro 35px → 15sp, #808080, right-aligned -->
<!-- Divider: #E9E9E9, 1dp, margins 20dp each side -->
<!-- ══════════════════════════════════════════════════════════════════ -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginHorizontal="20dp"
android:paddingTop="6dp">
<!-- Status (value = green #8BC155) -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:paddingVertical="13dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Status" android:textSize="13sp" android:textColor="#000000" android:fontFamily="@font/sofia_pro" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvStatus" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="SUCCESS" android:textSize="15sp" android:textColor="#8BC155" android:fontFamily="@font/sofia_pro" />
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#E9E9E9" />
<!-- Message (value wraps if long) -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:paddingVertical="13dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Message" android:textSize="13sp" android:textColor="#000000" android:fontFamily="@font/sofia_pro" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvMessageRow" android:layout_width="0dp" android:layout_weight="1.4" android:layout_height="wrap_content" android:textSize="15sp" android:textColor="#808080" android:fontFamily="@font/sofia_pro" android:gravity="end" />
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#E9E9E9" />
<!-- Reference -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:paddingVertical="13dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Reference" android:textSize="13sp" android:textColor="#000000" android:fontFamily="@font/sofia_pro" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvReference" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="15sp" android:textColor="#808080" android:fontFamily="@font/sofia_pro" />
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#E9E9E9" />
<!-- Transaction date -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:paddingVertical="13dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Transaction date" android:textSize="13sp" android:textColor="#000000" android:fontFamily="@font/sofia_pro" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvTransactionDate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="15sp" android:textColor="#808080" android:fontFamily="@font/sofia_pro" />
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#E9E9E9" />
<!-- From (value uppercase) -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:paddingVertical="13dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="From" android:textSize="13sp" android:textColor="#000000" android:fontFamily="@font/sofia_pro" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvFrom" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="15sp" android:textColor="#808080" android:fontFamily="@font/sofia_pro" android:textAllCaps="false" />
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#E9E9E9" />
<!-- To (stacked name + account) -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:paddingVertical="13dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="To" android:textSize="13sp" android:textColor="#000000" android:fontFamily="@font/sofia_pro" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:gravity="end">
<TextView android:id="@+id/tvToName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="15sp" android:textColor="#808080" android:fontFamily="@font/sofia_pro" android:gravity="end" />
<TextView android:id="@+id/tvToAccount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="2dp" android:textSize="13sp" android:textColor="#808080" android:fontFamily="@font/sofia_pro" android:gravity="end" />
</LinearLayout>
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#E9E9E9" />
<!-- Amount (value = green #8BC155) -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:paddingVertical="13dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Amount" android:textSize="13sp" android:textColor="#000000" android:fontFamily="@font/sofia_pro" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvAmountRow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="15sp" android:textColor="#8BC155" android:fontFamily="@font/sofia_pro" />
</LinearLayout>
<!-- Remarks (hidden when empty) -->
<View android:id="@+id/remarksDivider" android:layout_width="match_parent" android:layout_height="1dp" android:background="#E9E9E9" android:visibility="gone" />
<LinearLayout android:id="@+id/remarksRow" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:paddingVertical="13dp" android:visibility="gone">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Remarks" android:textSize="13sp" android:textColor="#000000" android:fontFamily="@font/sofia_pro" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvRemarks" android:layout_width="0dp" android:layout_weight="1.4" android:layout_height="wrap_content" android:textSize="15sp" android:textColor="#808080" android:fontFamily="@font/sofia_pro" android:gravity="end" />
</LinearLayout>
</LinearLayout>
<!-- ══════════════════════════════════════════════════════════════════ -->
<!-- Footer — bottom wave + #F5F5F5 bg + "Bank of Maldives" -->
<!-- 113px gap ≈ 38dp, then wave overlaps, then footer rect 157px≈52dp-->
<!-- ══════════════════════════════════════════════════════════════════ -->
<!-- Gap between last row and footer wave (113px / 3 ≈ 38dp) -->
<View android:layout_width="match_parent" android:layout_height="38dp" />
<!-- Bottom wave — sits on top of footer, extends slightly upward -->
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/bottom_receipt_wave"
android:scaleType="fitXY"
android:adjustViewBounds="true"
android:layout_marginTop="-4dp"
android:contentDescription="@null" />
<!-- Footer — #F5F5F5, "Bank of Maldives" centered -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:background="#F5F5F5"
android:paddingVertical="22dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Bank of Maldives"
android:textSize="13sp"
android:textColor="#848484"
android:fontFamily="@font/sofia_pro"
android:gravity="center" />
</LinearLayout>
</LinearLayout>
<!-- Pushes buttons to bottom of screen -->
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<!-- ══════════════════════════════════════════════════════════════════════ -->
<!-- Action buttons — outside renderable area -->
<!-- ══════════════════════════════════════════════════════════════════════ -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#FFFFFF"
android:paddingHorizontal="12dp"
android:paddingTop="8dp"
android:paddingBottom="12dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnShare"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="4dp"
android:text="Share" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSave"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginHorizontal="4dp"
android:text="Save" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDone"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="4dp"
android:text="Done" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,282 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FFFFFF">
<!-- Renderable receipt card (header grows to fill remaining space) -->
<LinearLayout
android:id="@+id/receiptCard"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<!-- Green header — fills all space not taken by body -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@drawable/trx_success_bg">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TRANSACTION RECEIPT"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:letterSpacing="0.08"
android:layout_marginBottom="16dp" />
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/ic_receipt_check"
android:contentDescription="@null" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SUCCESSFUL"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold"
android:letterSpacing="0.05"
android:layout_marginTop="12dp" />
</LinearLayout>
</FrameLayout>
<!-- White body — fixed size content -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#FFFFFF"
android:paddingTop="16dp">
<!-- Avatars row -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:paddingHorizontal="24dp"
android:paddingBottom="14dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/ivFromAvatar"
android:layout_width="52dp"
android:layout_height="52dp"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle" />
<TextView
android:id="@+id/tvFromLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textSize="12sp"
android:textColor="#1C1B1F"
android:gravity="center"
android:maxLines="2" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:text="→"
android:textSize="20sp"
android:textColor="#49454F" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/ivToAvatar"
android:layout_width="52dp"
android:layout_height="52dp"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Circle" />
<TextView
android:id="@+id/tvToLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textSize="12sp"
android:textColor="#1C1B1F"
android:gravity="center"
android:maxLines="2" />
</LinearLayout>
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#CAC4D0" android:layout_marginHorizontal="20dp" />
<!-- Total Amount -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TOTAL AMOUNT"
android:textSize="11sp"
android:textColor="#49454F"
android:letterSpacing="0.08"
android:gravity="center"
android:paddingTop="10dp"
android:paddingBottom="4dp" />
<TextView
android:id="@+id/tvAmount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="#E65100"
android:gravity="center"
android:paddingBottom="10dp" />
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#CAC4D0" android:layout_marginHorizontal="20dp" />
<!-- Reference # -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingHorizontal="20dp" android:paddingVertical="11dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Reference #" android:textSize="13sp" android:textColor="#49454F" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvReferenceNo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="13sp" android:textColor="#1C1B1F" />
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#CAC4D0" android:layout_marginHorizontal="20dp" />
<!-- To Account -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingHorizontal="20dp" android:paddingVertical="11dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="To Account" android:textSize="13sp" android:textColor="#49454F" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvToAccount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="13sp" android:textColor="#1C1B1F" />
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#CAC4D0" android:layout_marginHorizontal="20dp" />
<!-- To Bank -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingHorizontal="20dp" android:paddingVertical="11dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="To Bank" android:textSize="13sp" android:textColor="#49454F" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvToBank" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="13sp" android:textColor="#1C1B1F" />
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#CAC4D0" android:layout_marginHorizontal="20dp" />
<!-- Transaction Date -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingHorizontal="20dp" android:paddingVertical="11dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Transaction Date" android:textSize="13sp" android:textColor="#49454F" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvTransactionDate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="13sp" android:textColor="#1C1B1F" />
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#CAC4D0" android:layout_marginHorizontal="20dp" />
<!-- Value Date -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingHorizontal="20dp" android:paddingVertical="11dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Value Date" android:textSize="13sp" android:textColor="#49454F" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvValueDate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="13sp" android:textColor="#1C1B1F" />
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#CAC4D0" android:layout_marginHorizontal="20dp" />
<!-- Purpose -->
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingHorizontal="20dp" android:paddingVertical="11dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Purpose" android:textSize="13sp" android:textColor="#49454F" />
<View android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" />
<TextView android:id="@+id/tvPurpose" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="13sp" android:textColor="#1C1B1F" />
</LinearLayout>
<!-- MIB footer -->
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#CAC4D0" android:layout_marginHorizontal="20dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:paddingVertical="12dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="28dp"
android:src="@drawable/mib_logo"
android:adjustViewBounds="true"
android:contentDescription="@null" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="MALDIVES ISLAMIC BANK"
android:textSize="11sp"
android:textStyle="bold"
android:textColor="#49454F"
android:letterSpacing="0.05" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- Action buttons — outside renderable area -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#FFFFFF"
android:paddingHorizontal="12dp"
android:paddingTop="8dp"
android:paddingBottom="12dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnShare"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="4dp"
android:text="Share" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSave"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginHorizontal="4dp"
android:text="Save" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDone"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="4dp"
android:text="Done" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="receipt_cache" path="receipts/" />
</paths>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
docs/bmlapi/receiptwave.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB