forked from LibreMV/GridFlow
fix dashboard bills display and add payment support
This commit is contained in:
@@ -72,6 +72,11 @@
|
||||
android:exported="false"
|
||||
android:label="Pay Without Account"
|
||||
android:theme="@style/Theme.GridFlow.NoActionBar" />
|
||||
<activity
|
||||
android:name=".PaymentReviewActivity"
|
||||
android:exported="false"
|
||||
android:label="Payment Review"
|
||||
android:theme="@style/Theme.GridFlow.NoActionBar" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
|
250
app/src/main/java/sh/sar/gridflow/PaymentReviewActivity.kt
Normal file
250
app/src/main/java/sh/sar/gridflow/PaymentReviewActivity.kt
Normal file
@@ -0,0 +1,250 @@
|
||||
package sh.sar.gridflow
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.coroutines.launch
|
||||
import sh.sar.gridflow.data.OutstandingBill
|
||||
import sh.sar.gridflow.data.PaymentDetails
|
||||
import sh.sar.gridflow.data.PaymentGateway
|
||||
import sh.sar.gridflow.data.PaymentItem
|
||||
import sh.sar.gridflow.data.PaymentRequest
|
||||
import sh.sar.gridflow.databinding.ActivityPaymentReviewBinding
|
||||
import sh.sar.gridflow.network.ApiResult
|
||||
import sh.sar.gridflow.network.FenakaApiService
|
||||
import sh.sar.gridflow.ui.payment.BillSummaryAdapter
|
||||
import sh.sar.gridflow.ui.payment.PaymentConfirmationDialog
|
||||
import sh.sar.gridflow.ui.payment.PaymentMethodAdapter
|
||||
import sh.sar.gridflow.utils.SecureStorage
|
||||
|
||||
class PaymentReviewActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityPaymentReviewBinding
|
||||
private lateinit var secureStorage: SecureStorage
|
||||
private lateinit var apiService: FenakaApiService
|
||||
private lateinit var billSummaryAdapter: BillSummaryAdapter
|
||||
private lateinit var paymentMethodAdapter: PaymentMethodAdapter
|
||||
|
||||
private var outstandingBills: List<OutstandingBill> = emptyList()
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PaymentReviewActivity"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Force system theme (follows device dark mode setting)
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
|
||||
binding = ActivityPaymentReviewBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
secureStorage = SecureStorage(this)
|
||||
apiService = FenakaApiService()
|
||||
|
||||
setupViews()
|
||||
loadData()
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
// Setup bills RecyclerView
|
||||
billSummaryAdapter = BillSummaryAdapter()
|
||||
binding.recyclerBills.apply {
|
||||
layoutManager = LinearLayoutManager(this@PaymentReviewActivity)
|
||||
adapter = billSummaryAdapter
|
||||
}
|
||||
|
||||
// Setup payment methods RecyclerView
|
||||
paymentMethodAdapter = PaymentMethodAdapter { paymentMethod ->
|
||||
onPaymentMethodSelected(paymentMethod)
|
||||
}
|
||||
binding.recyclerPaymentMethods.apply {
|
||||
layoutManager = LinearLayoutManager(this@PaymentReviewActivity)
|
||||
adapter = paymentMethodAdapter
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
val cookie = secureStorage.getCookie()
|
||||
if (cookie == null) {
|
||||
Toast.makeText(this, "Authentication required", Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
showLoading(true)
|
||||
|
||||
lifecycleScope.launch {
|
||||
// Load outstanding bills first
|
||||
when (val billsResult = apiService.getOutstandingBills("connect.sid=$cookie")) {
|
||||
is ApiResult.Success -> {
|
||||
outstandingBills = billsResult.data
|
||||
updateBillsUI()
|
||||
|
||||
// Then load payment methods
|
||||
when (val gatewaysResult = apiService.getPaymentGateways("connect.sid=$cookie")) {
|
||||
is ApiResult.Success -> {
|
||||
paymentMethodAdapter.updatePaymentMethods(gatewaysResult.data)
|
||||
showLoading(false)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
Log.e(TAG, "Failed to load payment gateways: ${gatewaysResult.message}")
|
||||
showLoading(false)
|
||||
Toast.makeText(this@PaymentReviewActivity, "Failed to load payment methods", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
Log.e(TAG, "Failed to load outstanding bills: ${billsResult.message}")
|
||||
showLoading(false)
|
||||
Toast.makeText(this@PaymentReviewActivity, "Failed to load bills", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBillsUI() {
|
||||
billSummaryAdapter.updateBills(outstandingBills)
|
||||
|
||||
// Calculate total bills count
|
||||
val totalBillCount = outstandingBills.sumOf { bill ->
|
||||
try {
|
||||
bill.outstandingBillCount.toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total amount
|
||||
val totalAmount = outstandingBills.sumOf { bill ->
|
||||
try {
|
||||
bill.outstandingAmount.toDouble()
|
||||
} catch (e: NumberFormatException) {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
binding.textBillCount.text = "You are paying a total of $totalBillCount bills"
|
||||
binding.textTotalAmount.text = "MVR ${String.format("%.2f", totalAmount)}"
|
||||
}
|
||||
|
||||
private fun showLoading(show: Boolean) {
|
||||
binding.layoutLoading.visibility = if (show) View.VISIBLE else View.GONE
|
||||
binding.recyclerPaymentMethods.visibility = if (show) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
private fun onPaymentMethodSelected(paymentMethod: PaymentGateway) {
|
||||
Log.d(TAG, "Payment method selected: ${paymentMethod.name} (${paymentMethod.code})")
|
||||
|
||||
// Show payment confirmation dialog
|
||||
PaymentConfirmationDialog.show(
|
||||
context = this,
|
||||
paymentGateway = paymentMethod,
|
||||
onContinue = { dialog ->
|
||||
if (paymentMethod.code == "bml") {
|
||||
processBmlPayment(dialog)
|
||||
} else {
|
||||
Toast.makeText(this, "Not yet implemented.. use BML", Toast.LENGTH_SHORT).show()
|
||||
dialog.dismiss()
|
||||
}
|
||||
},
|
||||
onCancel = {
|
||||
Log.d(TAG, "Payment cancelled by user")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun processBmlPayment(dialog: PaymentConfirmationDialog) {
|
||||
val cookie = secureStorage.getCookie()
|
||||
if (cookie == null) {
|
||||
Toast.makeText(this, "Authentication required", Toast.LENGTH_SHORT).show()
|
||||
dialog.dismiss()
|
||||
return
|
||||
}
|
||||
|
||||
Log.d(TAG, "Processing BML payment...")
|
||||
dialog.showLoading(true)
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
// Create payment items from outstanding bills
|
||||
val paymentItems = outstandingBills.map { bill ->
|
||||
PaymentItem(
|
||||
id = bill.subscriptionId,
|
||||
type = "subscription",
|
||||
amount = bill.outstandingAmount.toDoubleOrNull() ?: 0.0
|
||||
)
|
||||
}
|
||||
|
||||
val paymentRequest = PaymentRequest(
|
||||
gateway = "bml",
|
||||
details = PaymentDetails(items = paymentItems)
|
||||
)
|
||||
|
||||
// Step 1: Initiate payment
|
||||
when (val paymentResult = apiService.initiatePayment(paymentRequest, "connect.sid=$cookie")) {
|
||||
is ApiResult.Success -> {
|
||||
val paymentResponse = paymentResult.data
|
||||
Log.d(TAG, "Payment initiated successfully. ID: ${paymentResponse.id}")
|
||||
|
||||
// Calculate total amount
|
||||
val totalAmount = paymentItems.sumOf { it.amount }
|
||||
|
||||
// Step 2: Get redirect URL
|
||||
when (val redirectResult = apiService.getPaymentRedirectUrl(
|
||||
gateway = "bml",
|
||||
reference = paymentResponse.id,
|
||||
amount = totalAmount,
|
||||
cookie = "connect.sid=$cookie"
|
||||
)) {
|
||||
is ApiResult.Success -> {
|
||||
val redirectUrl = redirectResult.data
|
||||
Log.d(TAG, "Got redirect URL: $redirectUrl")
|
||||
|
||||
// Open browser with redirect URL
|
||||
openBrowser(redirectUrl)
|
||||
dialog.dismiss()
|
||||
|
||||
// Take user back (finish activity)
|
||||
finish()
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
Log.e(TAG, "Failed to get redirect URL: ${redirectResult.message}")
|
||||
dialog.showLoading(false)
|
||||
Toast.makeText(this@PaymentReviewActivity, "Failed to get payment URL", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
Log.e(TAG, "Failed to initiate payment: ${paymentResult.message}")
|
||||
dialog.showLoading(false)
|
||||
Toast.makeText(this@PaymentReviewActivity, "Failed to initiate payment", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error processing BML payment", e)
|
||||
dialog.showLoading(false)
|
||||
Toast.makeText(this@PaymentReviewActivity, "Payment processing error", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openBrowser(url: String) {
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
startActivity(intent)
|
||||
Log.d(TAG, "Opened browser with URL: $url")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to open browser", e)
|
||||
Toast.makeText(this, "Failed to open browser", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
@@ -133,11 +133,48 @@ data class MeterReading(
|
||||
// Outstanding bills data model
|
||||
data class OutstandingBill(
|
||||
val id: Long,
|
||||
val billNumber: String,
|
||||
val billAmount: String,
|
||||
val dueDate: String,
|
||||
val branchName: String,
|
||||
val subscriptionId: Long,
|
||||
val serviceId: Int
|
||||
val subscriptionStatus: String,
|
||||
val subscriptionNumber: String,
|
||||
val outstandingBillCount: String,
|
||||
val billAmount: String,
|
||||
val paidAmount: String,
|
||||
val outstandingAmount: String
|
||||
)
|
||||
|
||||
// Payment gateway data models
|
||||
data class PaymentGateway(
|
||||
val code: String,
|
||||
val name: String,
|
||||
val image: String
|
||||
)
|
||||
|
||||
// Payment request/response models
|
||||
data class PaymentItem(
|
||||
val id: Long,
|
||||
val type: String,
|
||||
val amount: Double
|
||||
)
|
||||
|
||||
data class PaymentDetails(
|
||||
val items: List<PaymentItem>
|
||||
)
|
||||
|
||||
data class PaymentRequest(
|
||||
val gateway: String,
|
||||
val details: PaymentDetails
|
||||
)
|
||||
|
||||
data class PaymentResponse(
|
||||
val id: Long,
|
||||
val customerId: Long,
|
||||
val gateway: String,
|
||||
val details: PaymentDetails,
|
||||
val status: String,
|
||||
val updatedAt: String,
|
||||
val createdAt: String,
|
||||
val deletedAt: String?
|
||||
)
|
||||
|
||||
// Band rates data models
|
||||
|
@@ -18,6 +18,9 @@ import sh.sar.gridflow.data.ForgotPasswordResponse
|
||||
import sh.sar.gridflow.data.LoginRequest
|
||||
import sh.sar.gridflow.data.LoginResponse
|
||||
import sh.sar.gridflow.data.OutstandingBill
|
||||
import sh.sar.gridflow.data.PaymentGateway
|
||||
import sh.sar.gridflow.data.PaymentRequest
|
||||
import sh.sar.gridflow.data.PaymentResponse
|
||||
import sh.sar.gridflow.data.SignupRequest
|
||||
import sh.sar.gridflow.data.SignupResponse
|
||||
import sh.sar.gridflow.data.UsageReading
|
||||
@@ -335,6 +338,44 @@ class FenakaApiService {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getPaymentGateways(cookie: String): ApiResult<List<PaymentGateway>> = withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "Fetching payment gateways")
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$BASE_URL/api/gateway-list/")
|
||||
.get()
|
||||
.header("Authorization", "Bearer $BEARER_TOKEN")
|
||||
.header("Cookie", cookie)
|
||||
.header("Host", "api.fenaka.mv")
|
||||
.header("User-Agent", "Dart/3.3 (dart:io)")
|
||||
.build()
|
||||
|
||||
try {
|
||||
val response = client.newCall(request).execute()
|
||||
Log.d(TAG, "Payment gateways response code: ${response.code}")
|
||||
|
||||
when (response.code) {
|
||||
200 -> {
|
||||
val responseBody = response.body?.string() ?: "[]"
|
||||
Log.d(TAG, "Payment gateways response: $responseBody")
|
||||
val gateways = gson.fromJson(responseBody, Array<PaymentGateway>::class.java).toList()
|
||||
Log.d(TAG, "Found ${gateways.size} payment gateways")
|
||||
ApiResult.Success(gateways, null)
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Failed to fetch payment gateways: ${response.code}")
|
||||
ApiResult.Error("Failed to fetch payment gateways", response.code)
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Network error during payment gateways fetch", e)
|
||||
ApiResult.Error("Network error: ${e.message}", -1)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unexpected error during payment gateways fetch", e)
|
||||
ApiResult.Error("Unexpected error: ${e.message}", -1)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getBandRates(cookie: String): ApiResult<BandRatesResponse> = withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "Fetching band rates")
|
||||
|
||||
@@ -507,6 +548,98 @@ class FenakaApiService {
|
||||
ApiResult.Error("Unexpected error: ${e.message}", -1)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun initiatePayment(paymentRequest: PaymentRequest, cookie: String): ApiResult<PaymentResponse> = withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "Initiating payment for gateway: ${paymentRequest.gateway}")
|
||||
|
||||
val requestBody = gson.toJson(paymentRequest).toRequestBody(JSON_MEDIA_TYPE.toMediaType())
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$BASE_URL/api/payments")
|
||||
.post(requestBody)
|
||||
.header("Authorization", "Bearer $BEARER_TOKEN")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Cookie", cookie)
|
||||
.header("Host", "api.fenaka.mv")
|
||||
.header("User-Agent", "Dart/3.3 (dart:io)")
|
||||
.build()
|
||||
|
||||
try {
|
||||
val response = client.newCall(request).execute()
|
||||
Log.d(TAG, "Payment initiation response code: ${response.code}")
|
||||
|
||||
when (response.code) {
|
||||
200, 201 -> {
|
||||
val responseBody = response.body?.string() ?: "{}"
|
||||
Log.d(TAG, "Payment initiation response: $responseBody")
|
||||
val paymentResponse = gson.fromJson(responseBody, PaymentResponse::class.java)
|
||||
ApiResult.Success(paymentResponse, null)
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Failed to initiate payment: ${response.code}")
|
||||
ApiResult.Error("Failed to initiate payment", response.code)
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Network error during payment initiation", e)
|
||||
ApiResult.Error("Network error: ${e.message}", -1)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unexpected error during payment initiation", e)
|
||||
ApiResult.Error("Unexpected error: ${e.message}", -1)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getPaymentRedirectUrl(gateway: String, reference: Long, amount: Double, cookie: String): ApiResult<String> = withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "Getting payment redirect URL for gateway: $gateway, reference: $reference")
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$BASE_URL/pay/home?gateway=$gateway&reference=$reference&amount=$amount")
|
||||
.get()
|
||||
.header("Authorization", "Bearer $BEARER_TOKEN")
|
||||
.header("Cookie", cookie)
|
||||
.header("Host", "api.fenaka.mv")
|
||||
.header("User-Agent", "Dart/3.3 (dart:io)")
|
||||
.build()
|
||||
|
||||
try {
|
||||
val response = client.newCall(request).execute()
|
||||
Log.d(TAG, "Payment redirect response code: ${response.code}")
|
||||
|
||||
when (response.code) {
|
||||
200 -> {
|
||||
val responseBody = response.body?.string() ?: ""
|
||||
Log.d(TAG, "Payment redirect HTML length: ${responseBody.length}")
|
||||
|
||||
// Parse HTML to extract redirect URL from JavaScript
|
||||
val redirectUrl = extractRedirectUrlFromHtml(responseBody)
|
||||
if (redirectUrl != null) {
|
||||
Log.d(TAG, "Extracted redirect URL: $redirectUrl")
|
||||
ApiResult.Success(redirectUrl, null)
|
||||
} else {
|
||||
Log.e(TAG, "Failed to extract redirect URL from HTML")
|
||||
ApiResult.Error("Failed to extract redirect URL", -1)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Failed to get payment redirect: ${response.code}")
|
||||
ApiResult.Error("Failed to get payment redirect", response.code)
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Network error during payment redirect fetch", e)
|
||||
ApiResult.Error("Network error: ${e.message}", -1)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unexpected error during payment redirect fetch", e)
|
||||
ApiResult.Error("Unexpected error: ${e.message}", -1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractRedirectUrlFromHtml(html: String): String? {
|
||||
// Extract URL from JavaScript: document.location = 'URL';
|
||||
val regex = Regex("document\\.location\\s*=\\s*['\"]([^'\"]+)['\"]")
|
||||
val matchResult = regex.find(html)
|
||||
return matchResult?.groupValues?.get(1)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ApiResult<T> {
|
||||
|
@@ -51,6 +51,13 @@ class HomeFragment : Fragment() {
|
||||
layoutManager = LinearLayoutManager(requireContext())
|
||||
adapter = subscriptionsAdapter
|
||||
}
|
||||
|
||||
// Setup Pay now button click listener
|
||||
binding.btnPayNow.setOnClickListener {
|
||||
// Navigate to payment review activity
|
||||
val intent = android.content.Intent(requireContext(), sh.sar.gridflow.PaymentReviewActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
@@ -71,6 +78,33 @@ class HomeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
// Outstanding amount
|
||||
homeViewModel.totalOutstandingAmount.observe(viewLifecycleOwner) { amount ->
|
||||
if (amount > 0) {
|
||||
binding.textOutstandingAmount.text = "Outstanding MVR %.2f".format(amount)
|
||||
binding.textOutstandingAmount.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textOutstandingAmount.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
// Pending bills styling
|
||||
homeViewModel.hasPendingBills.observe(viewLifecycleOwner) { hasPendingBills ->
|
||||
if (hasPendingBills) {
|
||||
// Red styling for pending bills
|
||||
binding.layoutBillsContent.setBackgroundColor(android.graphics.Color.parseColor("#FFEBEE"))
|
||||
binding.textBillsStatus.setTextColor(android.graphics.Color.parseColor("#C62828"))
|
||||
binding.textOutstandingAmount.setTextColor(android.graphics.Color.parseColor("#C62828"))
|
||||
binding.btnPayNow.visibility = View.VISIBLE
|
||||
} else {
|
||||
// Green styling for no pending bills
|
||||
binding.layoutBillsContent.setBackgroundColor(android.graphics.Color.parseColor("#E8F5E8"))
|
||||
binding.textBillsStatus.setTextColor(android.graphics.Color.parseColor("#2E7D32"))
|
||||
binding.textOutstandingAmount.setTextColor(android.graphics.Color.parseColor("#2E7D32"))
|
||||
binding.btnPayNow.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
// Subscriptions
|
||||
homeViewModel.subscriptions.observe(viewLifecycleOwner) { subscriptions ->
|
||||
subscriptionsAdapter.updateSubscriptions(subscriptions)
|
||||
|
@@ -34,6 +34,12 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private val _isLoadingBills = MutableLiveData<Boolean>()
|
||||
val isLoadingBills: LiveData<Boolean> = _isLoadingBills
|
||||
|
||||
private val _totalOutstandingAmount = MutableLiveData<Double>()
|
||||
val totalOutstandingAmount: LiveData<Double> = _totalOutstandingAmount
|
||||
|
||||
private val _hasPendingBills = MutableLiveData<Boolean>()
|
||||
val hasPendingBills: LiveData<Boolean> = _hasPendingBills
|
||||
|
||||
// Subscriptions
|
||||
private val _subscriptions = MutableLiveData<List<CustomerSubscription>>()
|
||||
val subscriptions: LiveData<List<CustomerSubscription>> = _subscriptions
|
||||
@@ -97,8 +103,32 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||
_isLoadingBills.value = false
|
||||
if (result.data.isEmpty()) {
|
||||
_billsStatus.value = "You don't have any pending bills left"
|
||||
_hasPendingBills.value = false
|
||||
_totalOutstandingAmount.value = 0.0
|
||||
} else {
|
||||
_billsStatus.value = "You have ${result.data.size} pending bills"
|
||||
// Calculate total outstanding amount
|
||||
val totalAmount = result.data.sumOf { bill ->
|
||||
try {
|
||||
bill.outstandingAmount.toDouble()
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.w(TAG, "Invalid outstanding amount: ${bill.outstandingAmount}")
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total pending bill count
|
||||
val totalBillCount = result.data.sumOf { bill ->
|
||||
try {
|
||||
bill.outstandingBillCount.toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.w(TAG, "Invalid bill count: ${bill.outstandingBillCount}")
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
_billsStatus.value = "You have $totalBillCount pending bills"
|
||||
_hasPendingBills.value = true
|
||||
_totalOutstandingAmount.value = totalAmount
|
||||
}
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
|
@@ -0,0 +1,35 @@
|
||||
package sh.sar.gridflow.ui.payment
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import sh.sar.gridflow.data.OutstandingBill
|
||||
import sh.sar.gridflow.databinding.ItemBillSummaryBinding
|
||||
|
||||
class BillSummaryAdapter(
|
||||
private var bills: List<OutstandingBill> = emptyList()
|
||||
) : RecyclerView.Adapter<BillSummaryAdapter.BillViewHolder>() {
|
||||
|
||||
inner class BillViewHolder(private val binding: ItemBillSummaryBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(bill: OutstandingBill) {
|
||||
binding.textSubscriptionNumber.text = bill.subscriptionNumber
|
||||
binding.textDueAmount.text = "MVR ${String.format("%.2f", bill.outstandingAmount.toDoubleOrNull() ?: 0.0)}"
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BillViewHolder {
|
||||
val binding = ItemBillSummaryBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return BillViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: BillViewHolder, position: Int) {
|
||||
holder.bind(bills[position])
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = bills.size
|
||||
|
||||
fun updateBills(newBills: List<OutstandingBill>) {
|
||||
bills = newBills
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
package sh.sar.gridflow.ui.payment
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import sh.sar.gridflow.data.PaymentGateway
|
||||
import sh.sar.gridflow.databinding.DialogPaymentConfirmationBinding
|
||||
|
||||
class PaymentConfirmationDialog private constructor(
|
||||
private val context: Context,
|
||||
private val paymentGateway: PaymentGateway,
|
||||
private val onContinue: (dialog: PaymentConfirmationDialog) -> Unit,
|
||||
private val onCancel: () -> Unit
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PaymentConfirmationDialog"
|
||||
|
||||
fun show(
|
||||
context: Context,
|
||||
paymentGateway: PaymentGateway,
|
||||
onContinue: (dialog: PaymentConfirmationDialog) -> Unit,
|
||||
onCancel: () -> Unit
|
||||
): PaymentConfirmationDialog {
|
||||
val dialog = PaymentConfirmationDialog(context, paymentGateway, onContinue, onCancel)
|
||||
dialog.show()
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
|
||||
private val binding = DialogPaymentConfirmationBinding.inflate(LayoutInflater.from(context))
|
||||
private val alertDialog: AlertDialog
|
||||
|
||||
init {
|
||||
// Set gateway name
|
||||
binding.textGatewayName.text = paymentGateway.name
|
||||
|
||||
// Decode and set gateway logo
|
||||
if (paymentGateway.image.isNotEmpty()) {
|
||||
try {
|
||||
val imageBytes = Base64.decode(paymentGateway.image, Base64.DEFAULT)
|
||||
val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||
binding.imageGatewayLogo.setImageBitmap(bitmap)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to decode base64 image for ${paymentGateway.name}", e)
|
||||
// Keep default background if image fails to decode
|
||||
}
|
||||
}
|
||||
|
||||
alertDialog = MaterialAlertDialogBuilder(context)
|
||||
.setView(binding.root)
|
||||
.setCancelable(true)
|
||||
.create()
|
||||
|
||||
// Set button listeners
|
||||
binding.btnContinue.setOnClickListener {
|
||||
onContinue(this)
|
||||
}
|
||||
|
||||
binding.btnCancel.setOnClickListener {
|
||||
onCancel()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
alertDialog.setOnCancelListener {
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
fun show() {
|
||||
alertDialog.show()
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
|
||||
fun showLoading(show: Boolean) {
|
||||
if (show) {
|
||||
binding.layoutLoading.visibility = View.VISIBLE
|
||||
binding.layoutButtons.visibility = View.GONE
|
||||
alertDialog.setCancelable(false)
|
||||
} else {
|
||||
binding.layoutLoading.visibility = View.GONE
|
||||
binding.layoutButtons.visibility = View.VISIBLE
|
||||
alertDialog.setCancelable(true)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
package sh.sar.gridflow.ui.payment
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import sh.sar.gridflow.data.PaymentGateway
|
||||
import sh.sar.gridflow.databinding.ItemPaymentMethodBinding
|
||||
|
||||
class PaymentMethodAdapter(
|
||||
private var paymentMethods: List<PaymentGateway> = emptyList(),
|
||||
private val onMethodClick: (PaymentGateway) -> Unit
|
||||
) : RecyclerView.Adapter<PaymentMethodAdapter.PaymentMethodViewHolder>() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PaymentMethodAdapter"
|
||||
}
|
||||
|
||||
inner class PaymentMethodViewHolder(private val binding: ItemPaymentMethodBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(paymentMethod: PaymentGateway) {
|
||||
binding.textPaymentName.text = paymentMethod.name
|
||||
|
||||
// Decode base64 image if available
|
||||
if (paymentMethod.image.isNotEmpty()) {
|
||||
try {
|
||||
val imageBytes = Base64.decode(paymentMethod.image, Base64.DEFAULT)
|
||||
val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||
binding.imagePaymentLogo.setImageBitmap(bitmap)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to decode base64 image for ${paymentMethod.name}", e)
|
||||
// Keep default background if image fails to decode
|
||||
}
|
||||
}
|
||||
|
||||
binding.root.setOnClickListener {
|
||||
onMethodClick(paymentMethod)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PaymentMethodViewHolder {
|
||||
val binding = ItemPaymentMethodBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return PaymentMethodViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PaymentMethodViewHolder, position: Int) {
|
||||
holder.bind(paymentMethods[position])
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = paymentMethods.size
|
||||
|
||||
fun updatePaymentMethods(newMethods: List<PaymentGateway>) {
|
||||
paymentMethods = newMethods
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
9
app/src/main/res/drawable/cancel_button_background.xml
Normal file
9
app/src/main/res/drawable/cancel_button_background.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<corners android:radius="8dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="?android:attr/textColorSecondary" />
|
||||
</shape>
|
6
app/src/main/res/drawable/continue_button_background.xml
Normal file
6
app/src/main/res/drawable/continue_button_background.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#2196F3" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
6
app/src/main/res/drawable/dialog_background.xml
Normal file
6
app/src/main/res/drawable/dialog_background.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="?android:attr/colorBackground" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
9
app/src/main/res/drawable/payment_logo_background.xml
Normal file
9
app/src/main/res/drawable/payment_logo_background.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#F5F5F5" />
|
||||
<corners android:radius="8dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#E0E0E0" />
|
||||
</shape>
|
6
app/src/main/res/drawable/red_button_background.xml
Normal file
6
app/src/main/res/drawable/red_button_background.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#D32F2F" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
123
app/src/main/res/layout/activity_payment_review.xml
Normal file
123
app/src/main/res/layout/activity_payment_review.xml
Normal file
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:background="?android:attr/colorBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Header -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Review your payment"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="32dp" />
|
||||
|
||||
<!-- Bills List -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_bills"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
tools:listitem="@layout/item_bill_summary" />
|
||||
|
||||
<!-- Bill Count Summary -->
|
||||
<TextView
|
||||
android:id="@+id/text_bill_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_marginBottom="16dp"
|
||||
tools:text="You are paying a total of 2 bills" />
|
||||
|
||||
<!-- Total Amount Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Total"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_total_amount"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
tools:text="MVR 3,578.30" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Payment Methods Section -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Select a payment method"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- Payment Methods RecyclerView -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_payment_methods"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_payment_method" />
|
||||
|
||||
<!-- Loading State -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_loading"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="32dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Loading payment methods..."
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
126
app/src/main/res/layout/dialog_payment_confirmation.xml
Normal file
126
app/src/main/res/layout/dialog_payment_confirmation.xml
Normal file
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp"
|
||||
android:minWidth="300dp">
|
||||
|
||||
<!-- Header -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Proceed to Payment Gateway"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="20dp" />
|
||||
|
||||
<!-- Gateway Logo and Name -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="20dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_gateway_logo"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@drawable/payment_logo_background"
|
||||
tools:src="@drawable/ic_placeholder" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_gateway_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
tools:text="Bank of Maldives" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Instructions -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Your browser will open to complete the payment. Please finish the payment process and return to this app."
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:lineSpacingExtra="2dp" />
|
||||
|
||||
<!-- Loading State -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_loading"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="12dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Loading payment gateway..."
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Buttons -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_cancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Cancel"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
android:background="@drawable/cancel_button_background"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:minWidth="80dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_continue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Continue"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:background="@drawable/continue_button_background"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:minWidth="80dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
@@ -36,29 +36,68 @@
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_bills_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:background="#E8F5E8"
|
||||
android:gravity="center_vertical">
|
||||
android:background="#E8F5E8">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_bills_status"
|
||||
android:layout_width="0dp"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="16sp"
|
||||
android:textColor="#2E7D32"
|
||||
android:textStyle="bold"
|
||||
tools:text="You don't have any pending bills left" />
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bills"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="#2E7D32" />
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_bills_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="#2E7D32"
|
||||
android:textStyle="bold"
|
||||
tools:text="You don't have any pending bills left" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_outstanding_amount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#2E7D32"
|
||||
android:visibility="gone"
|
||||
tools:text="Outstanding MVR 3578.30" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bills"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="#2E7D32" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_pay_now"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="Pay now"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:background="@drawable/red_button_background"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:visibility="gone"
|
||||
android:minWidth="80dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
58
app/src/main/res/layout/item_bill_summary.xml
Normal file
58
app/src/main/res/layout/item_bill_summary.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Subscription no:"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_subscription_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
tools:text="4995" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="end">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Due amount:"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_due_amount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
tools:text="MVR 1,945.36" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
41
app/src/main/res/layout/item_payment_method.xml
Normal file
41
app/src/main/res/layout/item_payment_method.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_payment_logo"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@drawable/payment_logo_background"
|
||||
tools:src="@drawable/ic_placeholder" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_payment_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
tools:text="Bank of Maldives" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
Reference in New Issue
Block a user