diff --git a/.idea/vcs.xml b/.idea/vcs.xml index f8b7ff5..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7ae31a6..b30fad2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> + @@ -54,6 +55,16 @@ android:exported="false" android:screenOrientation="portrait" /> + + + + diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt index 69d089b..df0d414 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt @@ -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 { - val sess = session ?: return Pair(false, getString(R.string.transfer_session_unavailable)) + remarks: String, + bankName: String + ): Triple { + 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, allContacts: List - ): Pair { - val sess = bmlSession ?: return Pair(false, getString(R.string.transfer_session_unavailable)) + ): Triple { + 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 diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/TransferReceiptData.kt b/app/src/main/java/sh/sar/basedbank/ui/home/TransferReceiptData.kt new file mode 100644 index 0000000..b7829c6 --- /dev/null +++ b/app/src/main/java/sh/sar/basedbank/ui/home/TransferReceiptData.kt @@ -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 = "", +) diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/TransferReceiptFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/TransferReceiptFragment.kt new file mode 100644 index 0000000..a76d46b --- /dev/null +++ b/app/src/main/java/sh/sar/basedbank/ui/home/TransferReceiptFragment.kt @@ -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(R.id.btnDone).setOnClickListener { + parentFragmentManager.popBackStack() + } + view.findViewById(R.id.btnShare).setOnClickListener { + shareReceipt() + } + view.findViewById(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 + } +} diff --git a/app/src/main/res/drawable/bg_mib_receipt_header.xml b/app/src/main/res/drawable/bg_mib_receipt_header.xml new file mode 100644 index 0000000..261ae7b --- /dev/null +++ b/app/src/main/res/drawable/bg_mib_receipt_header.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable/bml_icon.jpg b/app/src/main/res/drawable/bml_icon.jpg new file mode 100644 index 0000000..e35d50d Binary files /dev/null and b/app/src/main/res/drawable/bml_icon.jpg differ diff --git a/app/src/main/res/drawable/bottom_receipt_wave.jpg b/app/src/main/res/drawable/bottom_receipt_wave.jpg new file mode 100644 index 0000000..06e069c Binary files /dev/null and b/app/src/main/res/drawable/bottom_receipt_wave.jpg differ diff --git a/app/src/main/res/drawable/ic_receipt_check.xml b/app/src/main/res/drawable/ic_receipt_check.xml new file mode 100644 index 0000000..17f117b --- /dev/null +++ b/app/src/main/res/drawable/ic_receipt_check.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/receiptwave.png b/app/src/main/res/drawable/receiptwave.png new file mode 100644 index 0000000..d386802 Binary files /dev/null and b/app/src/main/res/drawable/receiptwave.png differ diff --git a/app/src/main/res/drawable/trx_success_bg.png b/app/src/main/res/drawable/trx_success_bg.png new file mode 100644 index 0000000..35db358 Binary files /dev/null and b/app/src/main/res/drawable/trx_success_bg.png differ diff --git a/app/src/main/res/font/nunito_sans.ttf b/app/src/main/res/font/nunito_sans.ttf new file mode 100644 index 0000000..89ee8de Binary files /dev/null and b/app/src/main/res/font/nunito_sans.ttf differ diff --git a/app/src/main/res/font/sofia_pro.ttf b/app/src/main/res/font/sofia_pro.ttf new file mode 100644 index 0000000..e079693 Binary files /dev/null and b/app/src/main/res/font/sofia_pro.ttf differ diff --git a/app/src/main/res/layout/fragment_receipt_bml.xml b/app/src/main/res/layout/fragment_receipt_bml.xml new file mode 100644 index 0000000..8c9b043 --- /dev/null +++ b/app/src/main/res/layout/fragment_receipt_bml.xml @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_receipt_mib.xml b/app/src/main/res/layout/fragment_receipt_mib.xml new file mode 100644 index 0000000..f8a0da1 --- /dev/null +++ b/app/src/main/res/layout/fragment_receipt_mib.xml @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..bf2868b --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,4 @@ + + + + diff --git a/docs/bmlapi/bottom-receipt-wave.jpg b/docs/bmlapi/bottom-receipt-wave.jpg new file mode 100644 index 0000000..06e069c Binary files /dev/null and b/docs/bmlapi/bottom-receipt-wave.jpg differ diff --git a/docs/bmlapi/receiptwave.png b/docs/bmlapi/receiptwave.png new file mode 100644 index 0000000..d386802 Binary files /dev/null and b/docs/bmlapi/receiptwave.png differ