forked from shihaam/thijooree
add transfer recipt for bml and mib
This commit is contained in:
Generated
-1
@@ -2,6 +2,5 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/BinaryEye" vcs="Git" />
|
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<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.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
@@ -54,6 +55,16 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:screenOrientation="portrait" />
|
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>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ class TransferFragment : Fragment() {
|
|||||||
// Resolved recipient info — set after successful lookup or prefill
|
// Resolved recipient info — set after successful lookup or prefill
|
||||||
private var resolvedAccountNumber = ""
|
private var resolvedAccountNumber = ""
|
||||||
private var resolvedRecipientName = ""
|
private var resolvedRecipientName = ""
|
||||||
|
private var resolvedBankName = ""
|
||||||
|
|
||||||
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
|
||||||
@@ -268,6 +269,7 @@ class TransferFragment : Fragment() {
|
|||||||
|
|
||||||
resolvedAccountNumber = info.accountNumber
|
resolvedAccountNumber = info.accountNumber
|
||||||
resolvedRecipientName = info.accountName
|
resolvedRecipientName = info.accountName
|
||||||
|
resolvedBankName = info.bankId
|
||||||
|
|
||||||
binding.tvToAccountName.text = displayName
|
binding.tvToAccountName.text = displayName
|
||||||
binding.tvToBankBic.text = "${info.accountNumber} · ${info.bankId}"
|
binding.tvToBankBic.text = "${info.accountNumber} · ${info.bankId}"
|
||||||
@@ -299,6 +301,8 @@ class TransferFragment : Fragment() {
|
|||||||
) {
|
) {
|
||||||
resolvedAccountNumber = accountNumber
|
resolvedAccountNumber = accountNumber
|
||||||
resolvedRecipientName = displayName
|
resolvedRecipientName = displayName
|
||||||
|
val contacts = viewModel.contacts.value ?: emptyList()
|
||||||
|
resolvedBankName = contacts.firstOrNull { it.benefAccount == accountNumber }?.benefBankName ?: ""
|
||||||
|
|
||||||
binding.tvToAccountName.text = displayName
|
binding.tvToAccountName.text = displayName
|
||||||
binding.tvToBankBic.text = subtitle
|
binding.tvToBankBic.text = subtitle
|
||||||
@@ -308,7 +312,6 @@ class TransferFragment : Fragment() {
|
|||||||
binding.btnScanQr.visibility = View.GONE
|
binding.btnScanQr.visibility = View.GONE
|
||||||
binding.cardToInfo.visibility = View.VISIBLE
|
binding.cardToInfo.visibility = View.VISIBLE
|
||||||
|
|
||||||
val contacts = viewModel.contacts.value ?: emptyList()
|
|
||||||
val contact = contacts.firstOrNull { it.benefAccount == accountNumber }
|
val contact = contacts.firstOrNull { it.benefAccount == accountNumber }
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
RecentsCache.save(requireContext(), RecentPick(
|
RecentsCache.save(requireContext(), RecentPick(
|
||||||
@@ -376,29 +379,28 @@ class TransferFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val destDisplay = binding.tvToAccountName.text?.toString() ?: resolvedAccountNumber
|
val destDisplay = binding.tvToAccountName.text?.toString() ?: resolvedAccountNumber
|
||||||
|
val bankNameCapture = resolvedBankName
|
||||||
|
val capturedToAvatar = (binding.ivToPhoto.drawable as? android.graphics.drawable.BitmapDrawable)?.bitmap
|
||||||
AlertDialog.Builder(requireContext())
|
AlertDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.transfer)
|
.setTitle(R.string.transfer)
|
||||||
.setMessage("Send $currency $amountStr to $destDisplay?\n\nFrom: ${src.accountBriefName} · ${src.accountNumber}")
|
.setMessage("Send $currency $amountStr to $destDisplay?\n\nFrom: ${src.accountBriefName} · ${src.accountNumber}")
|
||||||
.setPositiveButton(R.string.transfer_confirm) { _, _ ->
|
.setPositiveButton(R.string.transfer_confirm) { _, _ ->
|
||||||
binding.btnTransfer.isEnabled = false
|
binding.btnTransfer.isEnabled = false
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
val (ok, msg) = withContext(Dispatchers.IO) {
|
val (ok, msg, receipt) = withContext(Dispatchers.IO) {
|
||||||
if (!isSrcBml) {
|
if (!isSrcBml) {
|
||||||
doMibTransfer(src, resolvedAccountNumber, resolvedRecipientName, amountStr, remarks)
|
doMibTransfer(src, resolvedAccountNumber, resolvedRecipientName, destDisplay, amountStr, remarks, bankNameCapture)
|
||||||
} else {
|
} 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
|
binding.btnTransfer.isEnabled = true
|
||||||
if (ok) {
|
if (ok && receipt != null) {
|
||||||
clearForm()
|
clearForm()
|
||||||
(requireActivity() as HomeActivity).refreshBalances(src)
|
val activity = requireActivity() as HomeActivity
|
||||||
AlertDialog.Builder(requireContext())
|
activity.refreshBalances(src)
|
||||||
.setTitle(R.string.transfer_success)
|
activity.showWithBackStack(TransferReceiptFragment.newInstance(receipt, capturedToAvatar))
|
||||||
.setMessage(msg)
|
} else if (!ok) {
|
||||||
.setPositiveButton("OK", null)
|
|
||||||
.show()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show()
|
Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,10 +413,12 @@ class TransferFragment : Fragment() {
|
|||||||
src: MibAccount,
|
src: MibAccount,
|
||||||
destAccount: String,
|
destAccount: String,
|
||||||
destName: String,
|
destName: String,
|
||||||
|
destDisplay: String,
|
||||||
amount: String,
|
amount: String,
|
||||||
remarks: String
|
remarks: String,
|
||||||
): Pair<Boolean, String> {
|
bankName: String
|
||||||
val sess = session ?: return Pair(false, getString(R.string.transfer_session_unavailable))
|
): Triple<Boolean, String, TransferReceiptData?> {
|
||||||
|
val sess = session ?: return Triple(false, getString(R.string.transfer_session_unavailable), null)
|
||||||
val app = requireActivity().application as BasedBankApp
|
val app = requireActivity().application as BasedBankApp
|
||||||
// Switch to the profile that owns the source account
|
// Switch to the profile that owns the source account
|
||||||
if (src.profileId.isNotBlank()) {
|
if (src.profileId.isNotBlank()) {
|
||||||
@@ -423,10 +427,19 @@ class TransferFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
val otp = CredentialStore(requireContext()).loadMibCredentials()?.otpSeed
|
val otp = CredentialStore(requireContext()).loadMibCredentials()?.otpSeed
|
||||||
?.let { Totp.generate(it) }
|
?.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 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 isDestMib = destAccount.matches(Regex("^9\\d{16}$"))
|
||||||
val bankNo = if (isDestMib) 2 else 3
|
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 {
|
return try {
|
||||||
val result = MibTransferClient().transfer(
|
val result = MibTransferClient().transfer(
|
||||||
session = sess,
|
session = sess,
|
||||||
@@ -440,32 +453,48 @@ class TransferFragment : Fragment() {
|
|||||||
otp = otp
|
otp = otp
|
||||||
)
|
)
|
||||||
if (result.success) {
|
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 {
|
} else {
|
||||||
Pair(false, result.errorMessage.ifBlank { "Transfer failed" })
|
Triple(false, result.errorMessage.ifBlank { "Transfer failed" }, null)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Pair(false, e.message ?: "Transfer failed")
|
Triple(false, e.message ?: "Transfer failed", null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doBmlTransfer(
|
private fun doBmlTransfer(
|
||||||
src: MibAccount,
|
src: MibAccount,
|
||||||
destAccount: String,
|
destAccount: String,
|
||||||
|
destDisplay: String,
|
||||||
amount: Double,
|
amount: Double,
|
||||||
|
amountStr: String,
|
||||||
remarks: String,
|
remarks: String,
|
||||||
isSrcCard: Boolean,
|
isSrcCard: Boolean,
|
||||||
isDestMib: Boolean,
|
isDestMib: Boolean,
|
||||||
currency: String,
|
currency: String,
|
||||||
allAccounts: List<MibAccount>,
|
allAccounts: List<MibAccount>,
|
||||||
allContacts: List<MibBeneficiary>
|
allContacts: List<MibBeneficiary>
|
||||||
): Pair<Boolean, String> {
|
): Triple<Boolean, String, TransferReceiptData?> {
|
||||||
val sess = bmlSession ?: return Pair(false, getString(R.string.transfer_session_unavailable))
|
val sess = bmlSession ?: return Triple(false, getString(R.string.transfer_session_unavailable), null)
|
||||||
val otp = CredentialStore(requireContext()).loadBmlCredentials()?.otpSeed
|
val otp = CredentialStore(requireContext()).loadBmlCredentials()?.otpSeed
|
||||||
?.let { Totp.generate(it) }
|
?.let { Totp.generate(it) }
|
||||||
?: return Pair(false, "OTP unavailable")
|
?: return Triple(false, "OTP unavailable", null)
|
||||||
val debitAccount = src.internalId.ifBlank {
|
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
|
// Determine type + credit account
|
||||||
@@ -485,19 +514,20 @@ class TransferFragment : Fragment() {
|
|||||||
isDestMib -> {
|
isDestMib -> {
|
||||||
// USD DOT: requires BML contact numeric ID
|
// USD DOT: requires BML contact numeric ID
|
||||||
val contact = allContacts.firstOrNull { it.benefCategoryId == "BML" && it.benefAccount == destAccount }
|
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?)
|
Triple("DOT", contact.benefNo.removePrefix("bml_"), null as String?)
|
||||||
}
|
}
|
||||||
else -> Triple("IAT", destAccount, null as String?)
|
else -> Triple("IAT", destAccount, null as String?)
|
||||||
}
|
}
|
||||||
|
val toBank = bank ?: if (isDestMib) "MIB" else "BML"
|
||||||
|
|
||||||
val bmlFlow = BmlLoginFlow()
|
val bmlFlow = BmlLoginFlow()
|
||||||
// Step 1: initiate
|
// Step 1: initiate
|
||||||
val initiated = try {
|
val initiated = try {
|
||||||
bmlFlow.initiateTransfer(sess, debitAccount, creditAccount, amount, transferType, currency, bank)
|
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
|
// Step 2: confirm with fresh OTP
|
||||||
val confirmOtp = CredentialStore(requireContext()).loadBmlCredentials()?.otpSeed
|
val confirmOtp = CredentialStore(requireContext()).loadBmlCredentials()?.otpSeed
|
||||||
@@ -506,13 +536,27 @@ class TransferFragment : Fragment() {
|
|||||||
return try {
|
return try {
|
||||||
val result = bmlFlow.confirmTransfer(sess, debitAccount, creditAccount, amount, transferType, currency, confirmOtp, remarks, bank)
|
val result = bmlFlow.confirmTransfer(sess, debitAccount, creditAccount, amount, transferType, currency, confirmOtp, remarks, bank)
|
||||||
if (result.success) {
|
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", " ")
|
val time = result.timestamp.take(19).replace("T", " ")
|
||||||
Pair(true, "Reference: ${result.reference}\n$time")
|
Triple(true, "Reference: ${result.reference}\n$time", receipt)
|
||||||
} else {
|
} else {
|
||||||
Pair(false, result.errorMessage.ifBlank { "Transfer failed" })
|
Triple(false, result.errorMessage.ifBlank { "Transfer failed" }, null)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} 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("")
|
binding.etRemarks.setText("")
|
||||||
resolvedAccountNumber = ""
|
resolvedAccountNumber = ""
|
||||||
resolvedRecipientName = ""
|
resolvedRecipientName = ""
|
||||||
|
resolvedBankName = ""
|
||||||
binding.cardToInfo.visibility = View.GONE
|
binding.cardToInfo.visibility = View.GONE
|
||||||
binding.tilTo.visibility = View.VISIBLE
|
binding.tilTo.visibility = View.VISIBLE
|
||||||
binding.btnPickContact.visibility = View.VISIBLE
|
binding.btnPickContact.visibility = View.VISIBLE
|
||||||
|
|||||||
@@ -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 = "",
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 |
@@ -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.
@@ -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=379–488 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>
|
||||||
@@ -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>
|
||||||
@@ -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 |
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
Reference in New Issue
Block a user