Pay any bill

This commit is contained in:
2025-07-26 03:14:44 +05:00
parent 0ef9db8759
commit 77558ab0ee
8 changed files with 976 additions and 9 deletions

View File

@@ -67,6 +67,11 @@
android:exported="false"
android:label="Bill Details"
android:theme="@style/Theme.GridFlow.NoActionBar" />
<activity
android:name=".PayWithoutAccountActivity"
android:exported="false"
android:label="Pay Without Account"
android:theme="@style/Theme.GridFlow.NoActionBar" />
<provider
android:name="androidx.core.content.FileProvider"

View File

@@ -201,8 +201,9 @@ class LoginActivity : AppCompatActivity() {
}
private fun handlePayWithoutAccount() {
Toast.makeText(this, "Guest payment functionality coming soon", Toast.LENGTH_SHORT).show()
// TODO: Navigate to guest payment flow
Log.d(TAG, "Pay without account button clicked")
val intent = Intent(this, PayWithoutAccountActivity::class.java)
startActivity(intent)
}
private fun validateInput(mobileNumber: String, password: String): Boolean {

View File

@@ -75,7 +75,7 @@ class MainActivity : AppCompatActivity() {
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
// Handle special menu items (Terms of Service and Privacy Policy)
// Handle special menu items (Terms of Service, Privacy Policy, and Pay Any Bill)
navView.setNavigationItemSelectedListener { menuItem ->
when (menuItem.itemId) {
R.id.nav_terms_of_service -> {
@@ -88,6 +88,12 @@ class MainActivity : AppCompatActivity() {
drawerLayout.closeDrawers()
true
}
R.id.nav_pay_any_bill -> {
val intent = Intent(this, PayWithoutAccountActivity::class.java)
startActivity(intent)
drawerLayout.closeDrawers()
true
}
else -> {
// Let the default navigation handle other items
try {

View File

@@ -0,0 +1,258 @@
package sh.sar.gridflow
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 kotlinx.coroutines.launch
import sh.sar.gridflow.data.BillLookupResponse
import sh.sar.gridflow.databinding.ActivityPayWithoutAccountBinding
import sh.sar.gridflow.network.ApiResult
import sh.sar.gridflow.network.FenakaApiService
import java.text.SimpleDateFormat
import java.util.*
class PayWithoutAccountActivity : AppCompatActivity() {
private lateinit var binding: ActivityPayWithoutAccountBinding
private lateinit var apiService: FenakaApiService
private var currentBill: BillLookupResponse? = null
companion object {
private const val TAG = "PayWithoutAccountActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Force system theme (follows device dark mode setting)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
Log.d(TAG, "PayWithoutAccountActivity onCreate")
binding = ActivityPayWithoutAccountBinding.inflate(layoutInflater)
setContentView(binding.root)
apiService = FenakaApiService()
setupClickListeners()
}
private fun setupClickListeners() {
binding.btnContinue.setOnClickListener {
handleContinue()
}
binding.btnPay.setOnClickListener {
handlePayment()
}
}
private fun handleContinue() {
// Check if we're showing the card - if so, hide it and show input fields
if (binding.billDetailsCard.visibility == View.VISIBLE) {
Log.d(TAG, "Hiding bill details card and showing input fields")
showInputFields()
return
}
// Otherwise, perform bill search
val billNumber = binding.etBillNumber.text.toString().trim()
val subscriptionNumber = binding.etSubscriptionNumber.text.toString().trim()
Log.d(TAG, "handleContinue called with bill: $billNumber, subscription: $subscriptionNumber")
if (validateInput(billNumber, subscriptionNumber)) {
Log.d(TAG, "Input validation passed, fetching bill details")
fetchBillDetails(billNumber, subscriptionNumber)
} else {
Log.d(TAG, "Input validation failed")
}
}
private fun showInputFields() {
binding.inputFieldsLayout.visibility = View.VISIBLE
binding.billDetailsCard.visibility = View.GONE
currentBill = null
// Update button text to Continue when showing input fields
binding.btnContinue.text = "Continue"
}
private fun fetchBillDetails(billNumber: String, subscriptionNumber: String) {
setLoading(true)
lifecycleScope.launch {
try {
when (val result = apiService.findBill(billNumber, subscriptionNumber)) {
is ApiResult.Success -> {
Log.d(TAG, "Bill fetch successful: ${result.data.billNumber}")
currentBill = result.data
setLoading(false)
showBillDetails(result.data)
}
is ApiResult.Error -> {
if (result.code == 404) {
Log.d(TAG, "404 received, trying with swapped parameters")
// Try again with swapped parameters
when (val retryResult = apiService.findBill(subscriptionNumber, billNumber)) {
is ApiResult.Success -> {
Log.d(TAG, "Retry successful with swapped parameters: ${retryResult.data.billNumber}")
currentBill = retryResult.data
setLoading(false)
showBillDetails(retryResult.data)
}
is ApiResult.Error -> {
Log.d(TAG, "Retry also failed: ${retryResult.message} (code: ${retryResult.code})")
setLoading(false)
showError(retryResult.message)
}
}
} else {
Log.d(TAG, "Bill fetch failed: ${result.message} (code: ${result.code})")
setLoading(false)
showError(result.message)
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Exception in fetchBillDetails", e)
setLoading(false)
showError("Failed to fetch bill details: ${e.message}")
}
}
}
private fun handlePayment() {
currentBill?.let { bill ->
if (bill.status == "paid") {
Toast.makeText(this, "This bill has already been paid", Toast.LENGTH_SHORT).show()
} else {
// TODO: Navigate to payment flow
Toast.makeText(this, "Payment flow coming soon", Toast.LENGTH_SHORT).show()
}
}
}
private fun showError(message: String) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
// Show input fields and hide bill details
showInputFields()
// Don't clear input fields - user might have made a small mistake
}
private fun showBillDetails(bill: BillLookupResponse) {
// Hide input fields and show bill details
binding.inputFieldsLayout.visibility = View.GONE
binding.billDetailsCard.visibility = View.VISIBLE
// Update button text to Search Another Bill when showing card
binding.btnContinue.text = "Search Another Bill"
// Set bill information
binding.tvBillNumber.text = bill.billNumber
binding.tvBillAmount.text = "MVR ${bill.billAmount}"
binding.tvBillStatus.text = bill.status.uppercase()
// Set customer information
binding.tvCustomerName.text = bill.customer.name
binding.tvAccountNumber.text = bill.customer.accountNumber
binding.tvPhoneNumber.text = bill.customer.phone
// Set address information
val address = "${bill.subscriptionAddress.property.name}, ${bill.subscriptionAddress.property.street.name}"
binding.tvAddress.text = address
// Set subscription information
binding.tvSubscriptionNumber.text = bill.subscription.subscriptionNumber
binding.tvServiceType.text = if (bill.subscription.serviceId == 1) "Electricity" else "Water"
// Format and set dates
val dueDateFormatted = formatDate(bill.dueDate)
val billDateFormatted = formatDate(bill.billDate)
binding.tvDueDate.text = dueDateFormatted
binding.tvBillDate.text = billDateFormatted
// Set payment status and button
val isPaid = bill.status == "paid"
binding.btnPay.isEnabled = !isPaid
binding.btnPay.alpha = if (isPaid) 0.5f else 1.0f
binding.btnPay.text = if (isPaid) "Already Paid" else "Pay MVR ${bill.billAmount}"
// Set status color
when (bill.status.lowercase()) {
"paid" -> {
binding.tvBillStatus.setTextColor(getColor(android.R.color.holo_green_dark))
}
"unpaid" -> {
binding.tvBillStatus.setTextColor(getColor(android.R.color.holo_red_dark))
}
else -> {
binding.tvBillStatus.setTextColor(getColor(android.R.color.holo_orange_dark))
}
}
// Show additional payment details if available
bill.billPaymentDetails?.let { paymentDetails ->
if (paymentDetails.paidAmount.toDoubleOrNull() ?: 0.0 > 0.0) {
binding.tvPaidAmount.visibility = View.VISIBLE
binding.tvPaidAmount.text = "Paid: MVR ${paymentDetails.paidAmount}"
if (paymentDetails.pendingAmount.toDoubleOrNull() ?: 0.0 > 0.0) {
binding.tvPendingAmount.visibility = View.VISIBLE
binding.tvPendingAmount.text = "Pending: MVR ${paymentDetails.pendingAmount}"
}
}
}
}
private fun formatDate(dateString: String): String {
return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
val outputFormat = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault())
val date = inputFormat.parse(dateString)
outputFormat.format(date ?: Date())
} catch (e: Exception) {
Log.e(TAG, "Error formatting date: $dateString", e)
dateString.substringBefore("T")
}
}
private fun validateInput(billNumber: String, subscriptionNumber: String): Boolean {
if (billNumber.isEmpty()) {
binding.etBillNumber.error = "Bill number is required"
return false
}
if (subscriptionNumber.isEmpty()) {
binding.etSubscriptionNumber.error = "Subscription number is required"
return false
}
// Basic validation - you can add more specific validation rules here
if (billNumber.length < 3) {
binding.etBillNumber.error = "Bill number must be at least 3 characters"
return false
}
if (subscriptionNumber.length < 3) {
binding.etSubscriptionNumber.error = "Subscription number must be at least 3 characters"
return false
}
return true
}
private fun setLoading(isLoading: Boolean) {
binding.btnContinue.isEnabled = !isLoading
binding.btnContinue.text = if (isLoading) "Searching Bill..." else "Continue"
binding.etBillNumber.isEnabled = !isLoading
binding.etSubscriptionNumber.isEnabled = !isLoading
if (isLoading) {
binding.billDetailsCard.visibility = View.GONE
binding.inputFieldsLayout.visibility = View.VISIBLE
}
}
}

View File

@@ -413,3 +413,169 @@ data class BillPaymentDetails(
val miscFine: String,
val pendingAmount: String
) : Serializable
// Bill lookup response for guest payment
data class BillLookupResponse(
val id: Long,
val billNumber: String,
val billAmount: String,
val carryForwardBalance: String,
val discount: String?,
val tax: String?,
val billedUnits: String,
val dueDate: String,
val billDate: String,
val billYear: Int,
val billMonth: Int,
val deliveredDate: String?,
val warnDate: String?,
val warnDueDate: String?,
val status: String,
val type: String,
val isDelivered: Boolean,
val oldId: String?,
val oldBranchId: String?,
val billDuration: Int,
val fineStatus: String,
val referenceNo: Int,
val details: String?,
val groupUuid: String,
val publicId: String,
val createdAt: String,
val updatedAt: String,
val customerId: Long,
val categoryId: Int,
val subscriptionAddressId: Long,
val billingAddressId: Long,
val subscriptionId: Long,
val consumerId: Long,
val readingEventId: Long?,
val branchId: Int,
val subscriptionAddress: BillLookupSubscriptionAddress,
val subscription: BillLookupSubscription,
val customer: BillLookupCustomer,
val billPayments: List<BillLookupPayment>?,
val billPaymentDetails: BillLookupPaymentDetails?
) : Serializable
data class BillLookupSubscriptionAddress(
val id: Long,
val type: String,
val startAt: String,
val endAt: String?,
val oldId: String?,
val oldServiceId: String?,
val oldBranchId: String?,
val createdAt: String,
val updatedAt: String,
val propertyId: Long,
val subscriptionId: Long,
val apartmentId: String?,
val property: BillLookupProperty
) : Serializable
data class BillLookupProperty(
val id: Long,
val name: String,
val oldId: String?,
val oldServiceId: String?,
val oldBranchId: String?,
val createdAt: String,
val updatedAt: String,
val areaId: Int,
val geographyId: Int,
val streetId: Int,
val street: BillLookupStreet
) : Serializable
data class BillLookupStreet(
val id: Int,
val name: String,
val oldId: String?,
val oldServiceId: String?,
val oldBranchId: String?,
val createdAt: String,
val updatedAt: String,
val geographyId: Int
) : Serializable
data class BillLookupSubscription(
val id: Long,
val subscriptionNumber: String,
val isTemporary: Boolean,
val readingOrder: Int,
val deliveryOrder: Int,
val groupUuid: String,
val email: String?,
val status: String,
val oldId: String?,
val oldServiceId: String?,
val oldBranchId: String?,
val carryForwardBalance: String?,
val depositBalance: String?,
val hasSmartMeter: Boolean,
val subscribedAt: String,
val createdAt: String,
val updatedAt: String,
val areaId: Int,
val branchId: Int,
val categoryId: Int,
val customerId: Long,
val requestId: Long?,
val serviceId: Int
) : Serializable
data class BillLookupCustomer(
val id: Long,
val type: String,
val name: String,
val accountNumber: String,
val phone: String,
val oldAccountNo: String?,
val oldId: String?,
val oldServiceId: String?,
val oldBranchId: String?,
val registrationNumber: String?,
val agreementNumber: String?,
val discount: String,
val status: String,
val createdAt: String,
val updatedAt: String,
val personId: Long?
) : Serializable
data class BillLookupPayment(
val id: Long,
val amount: String,
val type: String,
val createdAt: String,
val updatedAt: String,
val deletedAt: String?,
val billId: Long,
val paymentId: Long,
val payment: BillLookupPaymentDetail
) : Serializable
data class BillLookupPaymentDetail(
val id: Long,
val tenderedAmount: String,
val balanceAmount: String,
val carryForwardAmount: String,
val status: String,
val date: String,
val mode: String,
val createdAt: String,
val updatedAt: String,
val deletedAt: String?,
val mainJournalId: String?,
val branchId: String?,
val userId: Long?
) : Serializable
data class BillLookupPaymentDetails(
val billId: Long,
val advancePaid: String,
val paidAmount: String,
val miscFine: String,
val pendingAmount: String
) : Serializable

View File

@@ -10,6 +10,7 @@ import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.logging.HttpLoggingInterceptor
import sh.sar.gridflow.data.BandRatesResponse
import sh.sar.gridflow.data.Bill
import sh.sar.gridflow.data.BillLookupResponse
import sh.sar.gridflow.data.CustomerSubscription
import sh.sar.gridflow.data.ErrorResponse
import sh.sar.gridflow.data.ForgotPasswordRequest
@@ -453,6 +454,59 @@ class FenakaApiService {
ApiResult.Error("Unexpected error: ${e.message}", -1)
}
}
suspend fun findBill(billNumber: String, subscriptionNumber: String): ApiResult<BillLookupResponse> = withContext(Dispatchers.IO) {
Log.d(TAG, "Attempting to find bill: $billNumber, subscription: $subscriptionNumber")
val filterQuery = "billNumber+eq+$billNumber|subscription.subscription_number+eq+$subscriptionNumber"
val includeQuery = "subscriptionAddress.property.street,subscription,customer"
val url = "$BASE_URL/saiph/find-bill/?filter=$filterQuery&include=$includeQuery"
val request = Request.Builder()
.url(url)
.get()
.header("Authorization", "Bearer $BEARER_TOKEN")
.header("Host", "api.fenaka.mv")
.build()
Log.d(TAG, "Making bill lookup request to: ${request.url}")
try {
val response = client.newCall(request).execute()
Log.d(TAG, "Bill lookup response code: ${response.code}")
Log.d(TAG, "Bill lookup response headers: ${response.headers}")
val responseBody = response.body?.string()
Log.d(TAG, "Bill lookup response body: $responseBody")
when (response.code) {
200 -> {
val billResponse = gson.fromJson(responseBody, BillLookupResponse::class.java)
Log.d(TAG, "Bill lookup successful for bill: ${billResponse.billNumber}")
ApiResult.Success(billResponse, null)
}
404 -> {
Log.d(TAG, "Bill lookup failed: 404 Bill not found")
ApiResult.Error("Bill not found with provided details", 404)
}
400 -> {
Log.d(TAG, "Bill lookup failed: 400 Bad request")
ApiResult.Error("Invalid bill number or subscription number", 400)
}
else -> {
Log.d(TAG, "Bill lookup failed: Unknown error ${response.code}")
ApiResult.Error("Unknown error occurred", response.code)
}
}
} catch (e: IOException) {
Log.e(TAG, "Network error during bill lookup", e)
ApiResult.Error("Network error: ${e.message}", -1)
} catch (e: Exception) {
Log.e(TAG, "Unexpected error during bill lookup", e)
ApiResult.Error("Unexpected error: ${e.message}", -1)
}
}
}
sealed class ApiResult<T> {

View File

@@ -4,7 +4,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:background="@android:color/white">
android:background="?android:attr/colorBackground">
<LinearLayout
android:layout_width="match_parent"
@@ -35,7 +35,7 @@
android:text="GridFlow"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="#000000"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginBottom="8dp" />
<TextView
@@ -43,7 +43,7 @@
android:layout_height="wrap_content"
android:text="Your Personal Fenaka Client"
android:textSize="14sp"
android:textColor="#666666"
android:textColor="?android:attr/textColorSecondary"
android:alpha="0.7" />
</LinearLayout>
@@ -150,7 +150,7 @@
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:background="#CCCCCC"
android:background="?android:attr/textColorSecondary"
android:alpha="0.3" />
<TextView
@@ -158,7 +158,7 @@
android:layout_height="wrap_content"
android:text="OR"
android:textSize="14sp"
android:textColor="#666666"
android:textColor="?android:attr/textColorSecondary"
android:alpha="0.7"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" />
@@ -167,7 +167,7 @@
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:background="#CCCCCC"
android:background="?android:attr/textColorSecondary"
android:alpha="0.3" />
</LinearLayout>

View File

@@ -0,0 +1,477 @@
<?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"
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="32dp"
android:gravity="center">
<!-- Logo and Title Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
<ImageView
android:id="@+id/iv_app_logo"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginBottom="24dp"
android:src="@mipmap/ic_launcher"
android:contentDescription="GridFlow Logo" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pay Without Account"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginBottom="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enter your bill and subscription details"
android:textSize="14sp"
android:textColor="?android:attr/textColorSecondary"
android:alpha="0.7"
android:layout_marginBottom="32dp" />
</LinearLayout>
<!-- Payment Form -->
<LinearLayout
android:id="@+id/input_form_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="32dp">
<!-- Input Fields Container -->
<LinearLayout
android:id="@+id/input_fields_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Bill Number Input -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="Bill Number"
app:boxStrokeColor="@color/design_default_color_primary"
app:hintTextColor="@color/design_default_color_primary"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_bill_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:textSize="16sp" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Subscription Number Input -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:hint="Subscription Number"
app:boxStrokeColor="@color/design_default_color_primary"
app:hintTextColor="@color/design_default_color_primary"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_subscription_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:textSize="16sp" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<!-- Bill Details Card -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/bill_details_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:visibility="gone"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
app:strokeWidth="1dp"
app:strokeColor="?android:attr/textColorSecondary">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<!-- Bill Header -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<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="Bill Number"
android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary"
android:alpha="0.7" />
<TextView
android:id="@+id/tv_bill_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="5-5879"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorPrimary" />
</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="Status"
android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary"
android:alpha="0.7" />
<TextView
android:id="@+id/tv_bill_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PAID"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<!-- Bill Amount -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amount"
android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary"
android:alpha="0.7" />
<TextView
android:id="@+id/tv_bill_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MVR 1,176.23"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginBottom="16dp" />
<!-- Customer Details -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Customer Details"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginBottom="8dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="4dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Name:"
android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/tv_customer_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Ibrahim Shifaah Abdul Rahman"
android:textSize="12sp"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="4dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Account:"
android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/tv_account_number"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="72265"
android:textSize="12sp"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="4dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Phone:"
android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/tv_phone_number"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="7650525"
android:textSize="12sp"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="12dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Address:"
android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/tv_address"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Janavareemaage Apartment 02, Ameenee Magu"
android:textSize="12sp"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
<!-- Service Details -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Service Details"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginBottom="8dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="4dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Subscription:"
android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/tv_subscription_number"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="93607"
android:textSize="12sp"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="4dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Service:"
android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/tv_service_type"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Electricity"
android:textSize="12sp"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="4dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Due Date:"
android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/tv_due_date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Jul 09, 2025"
android:textSize="12sp"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Bill Date:"
android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" />
<TextView
android:id="@+id/tv_bill_date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Jun 29, 2025"
android:textSize="12sp"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
<!-- Payment Details (if applicable) -->
<TextView
android:id="@+id/tv_paid_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Paid: MVR 1,176.23"
android:textSize="12sp"
android:textColor="@android:color/holo_green_dark"
android:visibility="gone"
android:layout_marginBottom="4dp" />
<TextView
android:id="@+id/tv_pending_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pending: MVR 0.00"
android:textSize="12sp"
android:textColor="@android:color/holo_orange_dark"
android:visibility="gone"
android:layout_marginBottom="16dp" />
<!-- Pay Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_pay"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="Pay MVR 1,176.23"
android:textSize="16sp"
android:textAllCaps="false"
app:cornerRadius="8dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Continue/Search Another Bill Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_continue"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="Continue"
android:textSize="16sp"
android:textAllCaps="false"
app:cornerRadius="8dp"
android:layout_marginBottom="24dp" />
</LinearLayout>
<!-- Spacer for bottom -->
<View
android:layout_width="match_parent"
android:layout_height="32dp" />
</LinearLayout>
</ScrollView>