added bill history
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -60,6 +62,21 @@
|
||||
android:exported="false"
|
||||
android:label="Reset Password OTP"
|
||||
android:theme="@style/Theme.GridFlow.NoActionBar" />
|
||||
<activity
|
||||
android:name=".ui.billhistory.BillDetailsActivity"
|
||||
android:exported="false"
|
||||
android:label="Bill Details"
|
||||
android:theme="@style/Theme.GridFlow.NoActionBar" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@@ -1,5 +1,7 @@
|
||||
package sh.sar.gridflow.data
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
data class LoginRequest(
|
||||
val mobile: String,
|
||||
val password: String
|
||||
@@ -80,7 +82,7 @@ data class MainCategory(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val code: String
|
||||
)
|
||||
) : Serializable
|
||||
|
||||
data class Customer(
|
||||
val id: Long,
|
||||
@@ -88,24 +90,24 @@ data class Customer(
|
||||
val accountNumber: String,
|
||||
val phone: String,
|
||||
val type: String
|
||||
)
|
||||
) : Serializable
|
||||
|
||||
data class SubscriptionAddress(
|
||||
val id: Long,
|
||||
val type: String,
|
||||
val property: Property?
|
||||
)
|
||||
) : Serializable
|
||||
|
||||
data class Property(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val street: Street?
|
||||
)
|
||||
) : Serializable
|
||||
|
||||
data class Street(
|
||||
val id: Long,
|
||||
val name: String
|
||||
)
|
||||
) : Serializable
|
||||
|
||||
data class LastBill(
|
||||
val id: Long,
|
||||
@@ -158,3 +160,256 @@ data class BandRate(
|
||||
val band: String,
|
||||
val rate: String
|
||||
)
|
||||
|
||||
// Bill History Models
|
||||
data class Bill(
|
||||
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, // "paid" or "unpaid"
|
||||
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 activeBillCancel: String?,
|
||||
val billingAddress: BillingAddress,
|
||||
val subscription: BillSubscription,
|
||||
val subscriptionAddress: SubscriptionAddress,
|
||||
val customer: Customer,
|
||||
val consumer: Customer,
|
||||
val category: BillCategory,
|
||||
val miscCustomerDetail: String?,
|
||||
val billUnits: List<BillUnit>,
|
||||
val billCharges: List<BillCharge>,
|
||||
val billedReadings: List<BilledReading>,
|
||||
val outstandingBillAmounts: List<Any>,
|
||||
val billFineViews: List<Any>,
|
||||
val billables: List<Billable>,
|
||||
val outstandingBills: List<Any>,
|
||||
val outstandingBillTotal: Double,
|
||||
val meter: BilledReading,
|
||||
val measurementUnit: String,
|
||||
val billableFuelDiscount: Billable?,
|
||||
val billableFuelSurcharge: Billable?,
|
||||
val billableTariff: Billable?,
|
||||
val billableItem: List<Billable>,
|
||||
val billableMisc: List<Any>,
|
||||
val billDiscount: List<Any>,
|
||||
val otherBillables: List<Any>,
|
||||
val billableFine: List<Any>,
|
||||
val unitMeterUnits: List<Any>,
|
||||
val normalMeterUnits: List<BillUnit>,
|
||||
val billPaymentDetails: BillPaymentDetails?,
|
||||
val paidDate: String?,
|
||||
val alias: String
|
||||
) : Serializable
|
||||
|
||||
data class BillingAddress(
|
||||
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: Property
|
||||
) : Serializable
|
||||
|
||||
data class BillSubscription(
|
||||
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: String?,
|
||||
val serviceId: Int,
|
||||
val branch: Branch,
|
||||
val service: Service
|
||||
) : Serializable
|
||||
|
||||
data class Branch(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val code: String,
|
||||
val phone: String,
|
||||
val inquiryContact: String,
|
||||
val powerFailureContact: String,
|
||||
val oldId: String?,
|
||||
val oldServiceId: String?,
|
||||
val oldBranchId: String?,
|
||||
val createdAt: String,
|
||||
val updatedAt: String,
|
||||
val propertyId: String?,
|
||||
val apartmentId: String?,
|
||||
val branchGroupId: Int
|
||||
) : Serializable
|
||||
|
||||
data class Service(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val connectionFee: String,
|
||||
val oldId: String?,
|
||||
val oldServiceId: String?,
|
||||
val oldBranchId: String?,
|
||||
val createdAt: String,
|
||||
val updatedAt: String
|
||||
) : Serializable
|
||||
|
||||
data class BillCategory(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val code: String,
|
||||
val gracePeriod: Int,
|
||||
val noticeDuration: Int,
|
||||
val periodicalChargeType: String,
|
||||
val periodicalCharge: String,
|
||||
val isFuelSurchargeSubsidized: Boolean,
|
||||
val isFuelSurchargeApplied: Boolean,
|
||||
val fineMethod: String,
|
||||
val fineValue: String,
|
||||
val fineMovement: String,
|
||||
val fineInterval: Int,
|
||||
val fineTo: String,
|
||||
val oldId: String?,
|
||||
val oldServiceId: String?,
|
||||
val oldBranchId: String?,
|
||||
val mainCategoryId: Int,
|
||||
val serviceId: Int,
|
||||
val isTemporary: String?,
|
||||
val createdAt: String,
|
||||
val updatedAt: String,
|
||||
val mainCategory: MainCategory
|
||||
) : Serializable
|
||||
|
||||
data class BillUnit(
|
||||
val id: Long,
|
||||
val type: String,
|
||||
val isDeduct: Boolean,
|
||||
val units: String,
|
||||
val oldId: String?,
|
||||
val oldBranchId: String?,
|
||||
val createdAt: String,
|
||||
val updatedAt: String,
|
||||
val billId: Long,
|
||||
val billableTariffId: Long,
|
||||
val meterUnitId: Long
|
||||
) : Serializable
|
||||
|
||||
data class BillCharge(
|
||||
val id: Long,
|
||||
val bandMin: String,
|
||||
val bandMax: String,
|
||||
val duration: Int,
|
||||
val rate: String,
|
||||
val units: String,
|
||||
val oldId: String?,
|
||||
val oldBranchId: String?,
|
||||
val createdAt: String,
|
||||
val updatedAt: String,
|
||||
val deletedAt: String?,
|
||||
val billId: Long,
|
||||
val billableTariffId: Long,
|
||||
val tariffId: Long
|
||||
) : Serializable
|
||||
|
||||
data class BilledReading(
|
||||
val id: Long,
|
||||
val currentReading: String,
|
||||
val previousReading: String,
|
||||
val currentUnits: String,
|
||||
val previousUnits: String,
|
||||
val meterType: String,
|
||||
val readingType: String,
|
||||
val meterNo: String,
|
||||
val currentReadingDate: String,
|
||||
val previousReadingDate: String,
|
||||
val measurementUnit: String,
|
||||
val createdAt: String,
|
||||
val updatedAt: String,
|
||||
val billId: Long,
|
||||
val currentReadingId: Long,
|
||||
val previousReadingId: Long,
|
||||
val subscriptionItemId: Long
|
||||
) : Serializable
|
||||
|
||||
data class Billable(
|
||||
val id: Long,
|
||||
val type: String,
|
||||
val amount: String,
|
||||
val oldId: String?,
|
||||
val oldBranchId: String?,
|
||||
val createdAt: String,
|
||||
val updatedAt: String,
|
||||
val billId: Long,
|
||||
val subscriptionId: Long,
|
||||
val billableItems: List<BillableItem>,
|
||||
val billableMiscs: List<Any>
|
||||
) : Serializable
|
||||
|
||||
data class BillableItem(
|
||||
val id: Long,
|
||||
val quantity: String,
|
||||
val description: String,
|
||||
val oldId: String?,
|
||||
val oldBranchId: String?,
|
||||
val createdAt: String,
|
||||
val updatedAt: String,
|
||||
val billableId: Long,
|
||||
val subscriptionItemId: Long
|
||||
) : Serializable
|
||||
|
||||
data class BillPaymentDetails(
|
||||
val billId: Long,
|
||||
val advancePaid: String,
|
||||
val paidAmount: String,
|
||||
val miscFine: String,
|
||||
val pendingAmount: String
|
||||
) : Serializable
|
||||
|
@@ -9,6 +9,7 @@ import okhttp3.MediaType.Companion.toMediaType
|
||||
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.CustomerSubscription
|
||||
import sh.sar.gridflow.data.ErrorResponse
|
||||
import sh.sar.gridflow.data.ForgotPasswordRequest
|
||||
@@ -371,6 +372,87 @@ class FenakaApiService {
|
||||
ApiResult.Error("Unexpected error: ${e.message}", -1)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getBillHistory(cookie: String): ApiResult<List<Bill>> = withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "Fetching bill history")
|
||||
|
||||
val cookieHeader = "connect.sid=$cookie"
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$BASE_URL/api/customer-bills-detail")
|
||||
.get()
|
||||
.header("Authorization", "Bearer $BEARER_TOKEN")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Cookie", cookieHeader)
|
||||
.header("Host", "api.fenaka.mv")
|
||||
.header("User-Agent", "Dart/3.5 (dart:io)")
|
||||
.build()
|
||||
|
||||
try {
|
||||
val response = client.newCall(request).execute()
|
||||
Log.d(TAG, "Bill history response code: ${response.code}")
|
||||
|
||||
when (response.code) {
|
||||
200 -> {
|
||||
val responseBody = response.body?.string() ?: "[]"
|
||||
Log.d(TAG, "Bill history response length: ${responseBody.length}")
|
||||
val bills = gson.fromJson(responseBody, Array<Bill>::class.java).toList()
|
||||
Log.d(TAG, "Parsed ${bills.size} bills successfully")
|
||||
ApiResult.Success(bills, null)
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Failed to fetch bill history: ${response.code}")
|
||||
ApiResult.Error("Failed to fetch bill history", response.code)
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Network error during bill history fetch", e)
|
||||
ApiResult.Error("Network error: ${e.message}", -1)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unexpected error during bill history fetch", e)
|
||||
ApiResult.Error("Unexpected error: ${e.message}", -1)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun downloadBillPdf(billId: Long, cookie: String): ApiResult<String> = withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "Downloading PDF for bill: $billId")
|
||||
|
||||
val cookieHeader = "connect.sid=$cookie"
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$BASE_URL/saiph/bills/$billId/pdf")
|
||||
.get()
|
||||
.header("Authorization", "Bearer $BEARER_TOKEN")
|
||||
.header("Cookie", cookieHeader)
|
||||
.header("Host", "api.fenaka.mv")
|
||||
.header("User-Agent", "Dart/3.5 (dart:io)")
|
||||
.build()
|
||||
|
||||
try {
|
||||
val response = client.newCall(request).execute()
|
||||
Log.d(TAG, "PDF download response code: ${response.code}")
|
||||
|
||||
when (response.code) {
|
||||
200 -> {
|
||||
val responseBody = response.body?.string() ?: ""
|
||||
Log.d(TAG, "PDF download response length: ${responseBody.length}")
|
||||
// Remove quotes from beginning and end if present
|
||||
val base64Content = responseBody.trim().removeSurrounding("\"")
|
||||
ApiResult.Success(base64Content, null)
|
||||
}
|
||||
else -> {
|
||||
Log.d(TAG, "Failed to download PDF: ${response.code}")
|
||||
ApiResult.Error("Failed to download PDF", response.code)
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Network error during PDF download", e)
|
||||
ApiResult.Error("Network error: ${e.message}", -1)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unexpected error during PDF download", e)
|
||||
ApiResult.Error("Unexpected error: ${e.message}", -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ApiResult<T> {
|
||||
|
@@ -0,0 +1,106 @@
|
||||
package sh.sar.gridflow.ui.billhistory
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import sh.sar.gridflow.R
|
||||
import sh.sar.gridflow.data.Bill
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
class BillCardAdapter(
|
||||
private val onBillClick: (Bill) -> Unit
|
||||
) : RecyclerView.Adapter<BillCardAdapter.BillViewHolder>() {
|
||||
|
||||
private var bills: List<Bill> = emptyList()
|
||||
|
||||
fun updateBills(newBills: List<Bill>) {
|
||||
bills = newBills
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BillViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_bill_card, parent, false)
|
||||
return BillViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: BillViewHolder, position: Int) {
|
||||
holder.bind(bills[position])
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = bills.size
|
||||
|
||||
inner class BillViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val billDate: TextView = itemView.findViewById(R.id.billDate)
|
||||
private val billAmount: TextView = itemView.findViewById(R.id.billAmount)
|
||||
private val billNumber: TextView = itemView.findViewById(R.id.billNumber)
|
||||
private val paymentStatus: TextView = itemView.findViewById(R.id.paymentStatus)
|
||||
private val payNowButton: MaterialButton = itemView.findViewById(R.id.payNowButton)
|
||||
|
||||
fun bind(bill: Bill) {
|
||||
// Format bill date
|
||||
billDate.text = formatBillDate(bill)
|
||||
|
||||
// Set bill amount
|
||||
billAmount.text = "MVR ${bill.billAmount}"
|
||||
|
||||
// Set bill number
|
||||
billNumber.text = "Bill #${bill.billNumber}"
|
||||
|
||||
// Set payment status
|
||||
if (bill.status.equals("paid", ignoreCase = true)) {
|
||||
paymentStatus.visibility = View.VISIBLE
|
||||
payNowButton.visibility = View.GONE
|
||||
|
||||
val paidDate = formatPaidDate(bill.paidDate)
|
||||
paymentStatus.text = "Paid on $paidDate"
|
||||
paymentStatus.setTextColor(ContextCompat.getColor(itemView.context, android.R.color.holo_green_dark))
|
||||
} else {
|
||||
paymentStatus.visibility = View.GONE
|
||||
payNowButton.visibility = View.VISIBLE
|
||||
|
||||
payNowButton.setOnClickListener {
|
||||
Toast.makeText(itemView.context, "Coming soon", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
// Set click listener for the card
|
||||
itemView.setOnClickListener {
|
||||
onBillClick(bill)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatBillDate(bill: Bill): String {
|
||||
return try {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.set(bill.billYear, bill.billMonth - 1, 1) // Month is 0-based in Calendar
|
||||
val monthName = calendar.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()) ?: ""
|
||||
"$monthName ${bill.billYear}"
|
||||
} catch (e: Exception) {
|
||||
"${bill.billMonth}/${bill.billYear}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatPaidDate(paidDate: String?): String {
|
||||
return if (paidDate != null) {
|
||||
try {
|
||||
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||
val outputFormat = SimpleDateFormat("MMM d, yyyy", Locale.getDefault())
|
||||
val date = inputFormat.parse(paidDate)
|
||||
date?.let { outputFormat.format(it) } ?: paidDate
|
||||
} catch (e: Exception) {
|
||||
paidDate
|
||||
}
|
||||
} else {
|
||||
"Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,393 @@
|
||||
package sh.sar.gridflow.ui.billhistory
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import sh.sar.gridflow.R
|
||||
import sh.sar.gridflow.data.Bill
|
||||
import sh.sar.gridflow.databinding.ActivityBillDetailsBinding
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class BillDetailsActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "BillDetailsActivity"
|
||||
const val EXTRA_BILL_ID = "bill_id"
|
||||
const val EXTRA_BILL_DATA = "bill_data"
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityBillDetailsBinding
|
||||
private lateinit var viewModel: BillHistoryViewModel
|
||||
private var currentBill: Bill? = null
|
||||
private var currentPdfFile: File? = null
|
||||
private var currentPdfAction: PdfAction? = null
|
||||
|
||||
enum class PdfAction {
|
||||
SHARE, DOWNLOAD, OPEN
|
||||
}
|
||||
|
||||
private val requestStoragePermission = registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { isGranted ->
|
||||
if (isGranted) {
|
||||
currentBill?.let { bill ->
|
||||
if (currentPdfFile != null) {
|
||||
downloadPdfToDownloads()
|
||||
} else {
|
||||
viewModel.downloadPdf(bill.id)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, "Storage permission required to download files", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityBillDetailsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
viewModel = ViewModelProvider(this)[BillHistoryViewModel::class.java]
|
||||
|
||||
setupToolbar()
|
||||
setupObservers()
|
||||
setupClickListeners()
|
||||
|
||||
// Get bill data from intent
|
||||
val billData = intent.getSerializableExtra(EXTRA_BILL_DATA) as? Bill
|
||||
val billId = intent.getLongExtra(EXTRA_BILL_ID, -1L)
|
||||
|
||||
if (billData != null) {
|
||||
currentBill = billData
|
||||
// Show main content immediately since we have bill data
|
||||
binding.mainContent.visibility = View.VISIBLE
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.errorMessage.visibility = View.GONE
|
||||
populateBillDetails(billData)
|
||||
} else if (billId != -1L) {
|
||||
// Load bill data by ID if needed (fallback, shouldn't happen normally)
|
||||
Log.d(TAG, "Loading bill details for ID: $billId")
|
||||
} else {
|
||||
Toast.makeText(this, "No bill data found", Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
// Only observe PDF data for download/share functionality
|
||||
viewModel.pdfBase64.observe(this) { base64Data ->
|
||||
if (base64Data != null) {
|
||||
handlePdfData(base64Data)
|
||||
viewModel.clearPdf()
|
||||
}
|
||||
}
|
||||
|
||||
// Observe loading state for PDF downloads
|
||||
viewModel.isLoading.observe(this) { isLoading ->
|
||||
if (isLoading) {
|
||||
showPdfLoading()
|
||||
} else {
|
||||
hidePdfLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupClickListeners() {
|
||||
binding.shareButton.setOnClickListener {
|
||||
currentBill?.let { bill ->
|
||||
currentPdfAction = PdfAction.SHARE
|
||||
if (currentPdfFile != null) {
|
||||
sharePdf(currentPdfFile!!)
|
||||
} else {
|
||||
viewModel.downloadPdf(bill.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.downloadButton.setOnClickListener {
|
||||
currentBill?.let { bill ->
|
||||
currentPdfAction = PdfAction.DOWNLOAD
|
||||
if (currentPdfFile != null) {
|
||||
downloadPdfToDownloads()
|
||||
} else {
|
||||
if (hasStoragePermission()) {
|
||||
viewModel.downloadPdf(bill.id)
|
||||
} else {
|
||||
requestStoragePermission.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.openButton.setOnClickListener {
|
||||
currentBill?.let { bill ->
|
||||
currentPdfAction = PdfAction.OPEN
|
||||
if (currentPdfFile != null) {
|
||||
openPdf(currentPdfFile!!)
|
||||
} else {
|
||||
viewModel.downloadPdf(bill.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.payNowButton.setOnClickListener {
|
||||
Toast.makeText(this, "Coming soon", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun populateBillDetails(bill: Bill) {
|
||||
// Card 1: Bill Information
|
||||
binding.billNumber.text = bill.billNumber
|
||||
binding.billTotal.text = "MVR ${bill.billAmount}"
|
||||
|
||||
// Set payment status
|
||||
if (bill.status.equals("paid", ignoreCase = true)) {
|
||||
binding.paidDate.visibility = View.VISIBLE
|
||||
binding.payNowButton.visibility = View.GONE
|
||||
binding.paidDate.text = formatPaidDate(bill.paidDate)
|
||||
binding.paidDate.setTextColor(ContextCompat.getColor(this, android.R.color.holo_green_dark))
|
||||
|
||||
// Set card color based on service type
|
||||
val colorRes = when (bill.subscription.serviceId) {
|
||||
1 -> android.R.color.holo_orange_light
|
||||
2 -> android.R.color.holo_blue_light
|
||||
else -> android.R.color.holo_orange_light
|
||||
}
|
||||
binding.billInfoCard.setCardBackgroundColor(ContextCompat.getColor(this, colorRes))
|
||||
} else {
|
||||
binding.paidDate.visibility = View.GONE
|
||||
binding.payNowButton.visibility = View.VISIBLE
|
||||
binding.billInfoCard.setCardBackgroundColor(ContextCompat.getColor(this, android.R.color.holo_red_light))
|
||||
}
|
||||
|
||||
// Card 2: Subscription Details
|
||||
binding.subscriptionNo.text = bill.subscription.subscriptionNumber
|
||||
binding.accountNo.text = bill.customer.accountNumber
|
||||
binding.branch.text = bill.subscription.branch.name
|
||||
binding.meterNo.text = bill.meter.meterNo
|
||||
binding.tariffCode.text = bill.category.code
|
||||
binding.billDate.text = formatBillDate(bill.billDate)
|
||||
|
||||
// Card 3: Meter Readings
|
||||
// Current month row
|
||||
binding.readingValue.text = bill.meter.currentReading
|
||||
binding.usageValue.text = "${bill.meter.currentUnits} ${bill.measurementUnit}"
|
||||
|
||||
// Previous month row
|
||||
binding.previousReadingValue.text = bill.meter.previousReading
|
||||
binding.previousUsageValue.text = "${bill.meter.previousUnits} ${bill.measurementUnit}"
|
||||
|
||||
// Card 4: Monthly Statement (Bill Charges)
|
||||
populateCharges(bill)
|
||||
}
|
||||
|
||||
private fun populateCharges(bill: Bill) {
|
||||
val container = binding.chargesContainer
|
||||
container.removeAllViews()
|
||||
|
||||
// Get the primary text color from theme
|
||||
val textColor = getThemeTextColor()
|
||||
|
||||
bill.billCharges.forEach { charge ->
|
||||
val chargeRow = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
bottomMargin = 8
|
||||
}
|
||||
setPadding(8, 8, 8, 8)
|
||||
}
|
||||
|
||||
val qtyText = TextView(this).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
text = charge.units
|
||||
textSize = 12f
|
||||
setTextColor(textColor)
|
||||
}
|
||||
|
||||
val rateText = TextView(this).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
text = "MVR ${charge.rate}"
|
||||
textSize = 12f
|
||||
gravity = android.view.Gravity.CENTER
|
||||
setTextColor(textColor)
|
||||
}
|
||||
|
||||
val amountText = TextView(this).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
val amount = charge.units.toFloatOrNull()?.times(charge.rate.toFloatOrNull() ?: 0f) ?: 0f
|
||||
text = "MVR %.2f".format(amount)
|
||||
textSize = 12f
|
||||
gravity = android.view.Gravity.END
|
||||
setTextColor(textColor)
|
||||
}
|
||||
|
||||
chargeRow.addView(qtyText)
|
||||
chargeRow.addView(rateText)
|
||||
chargeRow.addView(amountText)
|
||||
|
||||
container.addView(chargeRow)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getThemeTextColor(): Int {
|
||||
val typedValue = android.util.TypedValue()
|
||||
theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true)
|
||||
return ContextCompat.getColor(this, typedValue.resourceId)
|
||||
}
|
||||
|
||||
private fun showPdfLoading() {
|
||||
binding.pdfLoadingSpinner.visibility = View.VISIBLE
|
||||
binding.shareButton.visibility = View.GONE
|
||||
binding.downloadButton.visibility = View.GONE
|
||||
binding.openButton.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun hidePdfLoading() {
|
||||
binding.pdfLoadingSpinner.visibility = View.GONE
|
||||
binding.shareButton.visibility = View.VISIBLE
|
||||
binding.downloadButton.visibility = View.VISIBLE
|
||||
binding.openButton.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun handlePdfData(base64Data: String) {
|
||||
try {
|
||||
val pdfBytes = Base64.decode(base64Data, Base64.DEFAULT)
|
||||
val fileName = "${currentBill?.billNumber ?: "bill"}.pdf"
|
||||
|
||||
// Save to app's internal cache first
|
||||
val cacheFile = File(cacheDir, fileName)
|
||||
FileOutputStream(cacheFile).use { it.write(pdfBytes) }
|
||||
currentPdfFile = cacheFile
|
||||
|
||||
// Perform the appropriate action based on what button was pressed
|
||||
when (currentPdfAction) {
|
||||
PdfAction.SHARE -> sharePdf(cacheFile)
|
||||
PdfAction.DOWNLOAD -> downloadPdfToDownloads()
|
||||
PdfAction.OPEN -> openPdf(cacheFile)
|
||||
null -> {
|
||||
// Fallback behavior (shouldn't happen)
|
||||
openPdf(cacheFile)
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error handling PDF data", e)
|
||||
Toast.makeText(this, "Error processing PDF: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadPdfToDownloads() {
|
||||
currentPdfFile?.let { cacheFile ->
|
||||
try {
|
||||
val downloadsDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "GridFlow")
|
||||
if (!downloadsDir.exists()) {
|
||||
downloadsDir.mkdirs()
|
||||
}
|
||||
|
||||
val fileName = "${currentBill?.billNumber ?: "bill"}.pdf"
|
||||
val downloadFile = File(downloadsDir, fileName)
|
||||
|
||||
cacheFile.copyTo(downloadFile, overwrite = true)
|
||||
Toast.makeText(this, "PDF saved to Downloads/GridFlow/$fileName", Toast.LENGTH_LONG).show()
|
||||
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Error downloading PDF", e)
|
||||
Toast.makeText(this, "Error downloading PDF: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sharePdf(file: File) {
|
||||
try {
|
||||
val uri = FileProvider.getUriForFile(this, "${packageName}.fileprovider", file)
|
||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = "application/pdf"
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
startActivity(Intent.createChooser(intent, "Share PDF"))
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error sharing PDF", e)
|
||||
Toast.makeText(this, "Error sharing PDF: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openPdf(file: File) {
|
||||
try {
|
||||
val uri = FileProvider.getUriForFile(this, "${packageName}.fileprovider", file)
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(uri, "application/pdf")
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent)
|
||||
} else {
|
||||
Toast.makeText(this, "No PDF viewer app found", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error opening PDF", e)
|
||||
Toast.makeText(this, "Error opening PDF: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasStoragePermission(): Boolean {
|
||||
return ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private fun formatPaidDate(paidDate: String?): String {
|
||||
return if (paidDate != null) {
|
||||
try {
|
||||
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||
val outputFormat = SimpleDateFormat("MMM d, yyyy", Locale.getDefault())
|
||||
val date = inputFormat.parse(paidDate)
|
||||
date?.let { outputFormat.format(it) } ?: paidDate
|
||||
} catch (e: Exception) {
|
||||
paidDate
|
||||
}
|
||||
} else {
|
||||
"Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatBillDate(billDate: String): String {
|
||||
return try {
|
||||
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||
val outputFormat = SimpleDateFormat("MMM d, yyyy", Locale.getDefault())
|
||||
val date = inputFormat.parse(billDate)
|
||||
date?.let { outputFormat.format(it) } ?: billDate
|
||||
} catch (e: Exception) {
|
||||
billDate
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,36 +1,146 @@
|
||||
package sh.sar.gridflow.ui.billhistory
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import sh.sar.gridflow.databinding.FragmentBillHistoryBinding
|
||||
import sh.sar.gridflow.data.Bill
|
||||
|
||||
class BillHistoryFragment : Fragment() {
|
||||
|
||||
private var _binding: FragmentBillHistoryBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var billHistoryViewModel: BillHistoryViewModel
|
||||
private lateinit var subscriptionAdapter: SubscriptionCardAdapter
|
||||
private lateinit var billAdapter: BillCardAdapter
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val billHistoryViewModel =
|
||||
ViewModelProvider(this).get(BillHistoryViewModel::class.java)
|
||||
|
||||
billHistoryViewModel = ViewModelProvider(this)[BillHistoryViewModel::class.java]
|
||||
_binding = FragmentBillHistoryBinding.inflate(inflater, container, false)
|
||||
val root: View = binding.root
|
||||
|
||||
val textView = binding.textBillHistory
|
||||
billHistoryViewModel.text.observe(viewLifecycleOwner) {
|
||||
textView.text = it
|
||||
}
|
||||
setupRecyclerViews()
|
||||
setupObservers()
|
||||
setupClickListeners()
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
private fun setupRecyclerViews() {
|
||||
// Setup subscription RecyclerView
|
||||
subscriptionAdapter = SubscriptionCardAdapter { subscription ->
|
||||
billHistoryViewModel.selectSubscription(subscription)
|
||||
subscriptionAdapter.setSelectedSubscription(subscription.id)
|
||||
showBillsForSubscription(subscription.subscription.id)
|
||||
}
|
||||
|
||||
binding.subscriptionsRecyclerView.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = subscriptionAdapter
|
||||
}
|
||||
|
||||
// Setup bills RecyclerView
|
||||
billAdapter = BillCardAdapter { bill ->
|
||||
billHistoryViewModel.selectBill(bill)
|
||||
navigateToBillDetails(bill)
|
||||
}
|
||||
|
||||
binding.billsRecyclerView.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = billAdapter
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
// Loading state
|
||||
billHistoryViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||
binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
|
||||
binding.subscriptionsContent.visibility = if (isLoading) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
// Error handling
|
||||
billHistoryViewModel.errorMessage.observe(viewLifecycleOwner) { error ->
|
||||
if (error != null) {
|
||||
binding.errorMessage.text = error
|
||||
binding.errorMessage.visibility = View.VISIBLE
|
||||
binding.cardSubscriptions.visibility = View.GONE
|
||||
binding.cardBills.visibility = View.GONE
|
||||
} else {
|
||||
binding.errorMessage.visibility = View.GONE
|
||||
binding.cardSubscriptions.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
// Subscriptions
|
||||
billHistoryViewModel.subscriptions.observe(viewLifecycleOwner) { subscriptions ->
|
||||
subscriptionAdapter.updateSubscriptions(subscriptions)
|
||||
|
||||
// Auto-select first subscription if available and none is currently selected
|
||||
if (subscriptions.isNotEmpty() && billHistoryViewModel.selectedSubscription.value == null) {
|
||||
val firstSubscription = subscriptions.first()
|
||||
billHistoryViewModel.selectSubscription(firstSubscription)
|
||||
subscriptionAdapter.setSelectedSubscription(firstSubscription.id)
|
||||
showBillsForSubscription(firstSubscription.subscription.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Bills
|
||||
billHistoryViewModel.bills.observe(viewLifecycleOwner) { bills ->
|
||||
// Bills will be filtered per subscription when needed
|
||||
}
|
||||
|
||||
// Selected subscription
|
||||
billHistoryViewModel.selectedSubscription.observe(viewLifecycleOwner) { subscription ->
|
||||
if (subscription != null) {
|
||||
binding.billsTitle.text = "Bills"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupClickListeners() {
|
||||
binding.errorMessage.setOnClickListener {
|
||||
billHistoryViewModel.clearError()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showBillsForSubscription(subscriptionId: Long) {
|
||||
val billsForSubscription = billHistoryViewModel.getBillsForSubscription(subscriptionId)
|
||||
|
||||
if (billsForSubscription.isEmpty()) {
|
||||
binding.billsRecyclerView.visibility = View.GONE
|
||||
binding.noBillsMessage.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.billsRecyclerView.visibility = View.VISIBLE
|
||||
binding.noBillsMessage.visibility = View.GONE
|
||||
billAdapter.updateBills(billsForSubscription)
|
||||
}
|
||||
|
||||
// Show bills card below subscriptions
|
||||
binding.cardBills.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun showSubscriptionSelection() {
|
||||
// Hide bills card when no subscription selected
|
||||
binding.cardBills.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun navigateToBillDetails(bill: Bill) {
|
||||
val intent = Intent(context, BillDetailsActivity::class.java).apply {
|
||||
putExtra(BillDetailsActivity.EXTRA_BILL_DATA, bill)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
|
@@ -1,13 +1,198 @@
|
||||
package sh.sar.gridflow.ui.billhistory
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.launch
|
||||
import sh.sar.gridflow.data.Bill
|
||||
import sh.sar.gridflow.data.CustomerSubscription
|
||||
import sh.sar.gridflow.data.ServiceCategory
|
||||
import sh.sar.gridflow.data.SubscriptionDetails
|
||||
import sh.sar.gridflow.network.ApiResult
|
||||
import sh.sar.gridflow.network.FenakaApiService
|
||||
import sh.sar.gridflow.utils.SecureStorage
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class BillHistoryViewModel : ViewModel() {
|
||||
|
||||
private val _text = MutableLiveData<String>().apply {
|
||||
value = "Bill History\n\nComing soon..."
|
||||
class BillHistoryViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "BillHistoryViewModel"
|
||||
}
|
||||
|
||||
private val secureStorage = SecureStorage(application)
|
||||
private val apiService = FenakaApiService()
|
||||
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
val isLoading: LiveData<Boolean> = _isLoading
|
||||
|
||||
private val _errorMessage = MutableLiveData<String?>()
|
||||
val errorMessage: LiveData<String?> = _errorMessage
|
||||
|
||||
private val _subscriptions = MutableLiveData<List<CustomerSubscription>>()
|
||||
val subscriptions: LiveData<List<CustomerSubscription>> = _subscriptions
|
||||
|
||||
private val _bills = MutableLiveData<List<Bill>>()
|
||||
val bills: LiveData<List<Bill>> = _bills
|
||||
|
||||
private val _selectedSubscription = MutableLiveData<CustomerSubscription?>()
|
||||
val selectedSubscription: LiveData<CustomerSubscription?> = _selectedSubscription
|
||||
|
||||
private val _selectedBill = MutableLiveData<Bill?>()
|
||||
val selectedBill: LiveData<Bill?> = _selectedBill
|
||||
|
||||
private val _pdfBase64 = MutableLiveData<String?>()
|
||||
val pdfBase64: LiveData<String?> = _pdfBase64
|
||||
|
||||
init {
|
||||
loadBills()
|
||||
}
|
||||
|
||||
|
||||
private fun loadBills() {
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
_errorMessage.value = null
|
||||
|
||||
val cookie = secureStorage.getCookie()
|
||||
if (cookie == null) {
|
||||
_errorMessage.value = "Please login first"
|
||||
_isLoading.value = false
|
||||
return@launch
|
||||
}
|
||||
|
||||
when (val result = apiService.getBillHistory(cookie)) {
|
||||
is ApiResult.Success -> {
|
||||
Log.d(TAG, "Loaded ${result.data.size} bills")
|
||||
// Sort bills by date (latest first)
|
||||
val sortedBills = result.data.sortedByDescending {
|
||||
try {
|
||||
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||
format.parse(it.billDate)?.time ?: 0L
|
||||
} catch (e: Exception) {
|
||||
// Fallback to year/month sorting
|
||||
(it.billYear * 12 + it.billMonth).toLong()
|
||||
}
|
||||
}
|
||||
_bills.value = sortedBills
|
||||
|
||||
// Extract unique subscriptions from bills
|
||||
val uniqueSubscriptions = sortedBills.groupBy { it.subscriptionId }.map { (_, bills) ->
|
||||
val firstBill = bills.first()
|
||||
// Create a CustomerSubscription-like object from bill data
|
||||
CustomerSubscription(
|
||||
id = firstBill.subscriptionId,
|
||||
name = firstBill.alias,
|
||||
subscriptionId = firstBill.subscriptionId,
|
||||
createdAt = firstBill.createdAt,
|
||||
deletedAt = null,
|
||||
customerId = firstBill.customerId,
|
||||
subscription = SubscriptionDetails(
|
||||
id = firstBill.subscription.id,
|
||||
subscriptionNumber = firstBill.subscription.subscriptionNumber,
|
||||
status = firstBill.subscription.status,
|
||||
serviceId = firstBill.subscription.serviceId,
|
||||
hasSmartMeter = firstBill.subscription.hasSmartMeter,
|
||||
category = ServiceCategory(
|
||||
id = firstBill.category.id,
|
||||
name = firstBill.category.name,
|
||||
code = firstBill.category.code,
|
||||
mainCategory = firstBill.category.mainCategory
|
||||
),
|
||||
customer = firstBill.customer,
|
||||
lastBill = null,
|
||||
subscriptionAddress = firstBill.subscriptionAddress
|
||||
)
|
||||
)
|
||||
}
|
||||
_subscriptions.value = uniqueSubscriptions
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
Log.e(TAG, "Failed to load bills: ${result.message}")
|
||||
_errorMessage.value = result.message
|
||||
}
|
||||
}
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
fun selectSubscription(subscription: CustomerSubscription) {
|
||||
Log.d(TAG, "Selected subscription: ${subscription.subscription.subscriptionNumber}")
|
||||
_selectedSubscription.value = subscription
|
||||
}
|
||||
|
||||
fun selectBill(bill: Bill) {
|
||||
Log.d(TAG, "Selected bill: ${bill.billNumber}")
|
||||
_selectedBill.value = bill
|
||||
}
|
||||
|
||||
fun downloadPdf(billId: Long) {
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
_errorMessage.value = null
|
||||
|
||||
val cookie = secureStorage.getCookie()
|
||||
if (cookie == null) {
|
||||
_errorMessage.value = "Please login first"
|
||||
_isLoading.value = false
|
||||
return@launch
|
||||
}
|
||||
|
||||
when (val result = apiService.downloadBillPdf(billId, cookie)) {
|
||||
is ApiResult.Success -> {
|
||||
Log.d(TAG, "Downloaded PDF for bill: $billId")
|
||||
_pdfBase64.value = result.data
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
Log.e(TAG, "Failed to download PDF: ${result.message}")
|
||||
_errorMessage.value = result.message
|
||||
}
|
||||
}
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
fun clearError() {
|
||||
_errorMessage.value = null
|
||||
}
|
||||
|
||||
fun clearPdf() {
|
||||
_pdfBase64.value = null
|
||||
}
|
||||
|
||||
fun getBillsForSubscription(subscriptionId: Long): List<Bill> {
|
||||
return _bills.value?.filter { it.subscriptionId == subscriptionId } ?: emptyList()
|
||||
}
|
||||
|
||||
fun formatBillDate(bill: Bill): String {
|
||||
return try {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.set(bill.billYear, bill.billMonth - 1, 1) // Month is 0-based in Calendar
|
||||
val monthName = calendar.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()) ?: ""
|
||||
"$monthName ${bill.billYear}"
|
||||
} catch (e: Exception) {
|
||||
"${bill.billMonth}/${bill.billYear}"
|
||||
}
|
||||
}
|
||||
|
||||
fun getServiceIcon(serviceId: Int): Int {
|
||||
return when (serviceId) {
|
||||
1 -> sh.sar.gridflow.R.drawable.ic_electric_24 // Electricity
|
||||
2 -> sh.sar.gridflow.R.drawable.ic_water_24 // Water
|
||||
else -> sh.sar.gridflow.R.drawable.ic_electric_24
|
||||
}
|
||||
}
|
||||
|
||||
fun getServiceColor(serviceId: Int): Int {
|
||||
return when (serviceId) {
|
||||
1 -> android.R.color.holo_orange_light // Orange for electricity
|
||||
2 -> android.R.color.holo_blue_light // Blue for water
|
||||
else -> android.R.color.holo_orange_light
|
||||
}
|
||||
}
|
||||
val text: LiveData<String> = _text
|
||||
}
|
||||
|
@@ -0,0 +1,128 @@
|
||||
package sh.sar.gridflow.ui.billhistory
|
||||
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import sh.sar.gridflow.R
|
||||
import sh.sar.gridflow.data.CustomerSubscription
|
||||
|
||||
class SubscriptionCardAdapter(
|
||||
private val onSubscriptionClick: (CustomerSubscription) -> Unit
|
||||
) : RecyclerView.Adapter<SubscriptionCardAdapter.SubscriptionViewHolder>() {
|
||||
|
||||
private var subscriptions: List<CustomerSubscription> = emptyList()
|
||||
private var selectedSubscriptionId: Long? = null
|
||||
|
||||
fun updateSubscriptions(newSubscriptions: List<CustomerSubscription>) {
|
||||
subscriptions = newSubscriptions
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun setSelectedSubscription(subscriptionId: Long?) {
|
||||
val previousIndex = selectedSubscriptionId?.let { id ->
|
||||
subscriptions.indexOfFirst { it.id == id }
|
||||
}?.takeIf { it >= 0 }
|
||||
|
||||
val newIndex = subscriptionId?.let { id ->
|
||||
subscriptions.indexOfFirst { it.id == id }
|
||||
}?.takeIf { it >= 0 }
|
||||
|
||||
selectedSubscriptionId = subscriptionId
|
||||
|
||||
previousIndex?.let { notifyItemChanged(it) }
|
||||
newIndex?.let { notifyItemChanged(it) }
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_subscription_card, parent, false)
|
||||
return SubscriptionViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SubscriptionViewHolder, position: Int) {
|
||||
val subscription = subscriptions[position]
|
||||
val isSelected = subscription.id == selectedSubscriptionId
|
||||
holder.bind(subscription, isSelected)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = subscriptions.size
|
||||
|
||||
inner class SubscriptionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val serviceIcon: ImageView = itemView.findViewById(R.id.serviceIcon)
|
||||
private val subscriptionName: TextView = itemView.findViewById(R.id.subscriptionName)
|
||||
private val subscriptionDetails: TextView = itemView.findViewById(R.id.subscriptionDetails)
|
||||
|
||||
fun bind(subscription: CustomerSubscription, isSelected: Boolean) {
|
||||
val subscriptionInfo = subscription.subscription
|
||||
|
||||
// Set subscription name from alias or property name
|
||||
subscriptionName.text = subscription.name
|
||||
|
||||
// Set subscription details
|
||||
val serviceName = when (subscriptionInfo.serviceId) {
|
||||
1 -> "Electricity"
|
||||
2 -> "Water"
|
||||
else -> "Unknown Service"
|
||||
}
|
||||
val subscriptionNumber = subscriptionInfo.subscriptionNumber
|
||||
subscriptionDetails.text = "$serviceName • #$subscriptionNumber"
|
||||
|
||||
// Set service icon
|
||||
val iconRes = when (subscriptionInfo.serviceId) {
|
||||
1 -> R.drawable.ic_electric_24 // Electricity
|
||||
2 -> R.drawable.ic_water_24 // Water
|
||||
else -> R.drawable.ic_electric_24
|
||||
}
|
||||
serviceIcon.setImageResource(iconRes)
|
||||
|
||||
// Set colors based on selection state and service type
|
||||
if (isSelected) {
|
||||
// Selected state - use service-specific colors
|
||||
val backgroundColorRes = when (subscriptionInfo.serviceId) {
|
||||
1 -> R.color.electricity_selection_background // Light orange for electricity
|
||||
2 -> R.color.water_selection_background // Light blue for water
|
||||
else -> R.color.electricity_selection_background
|
||||
}
|
||||
val iconColorRes = when (subscriptionInfo.serviceId) {
|
||||
1 -> R.color.electricity_selection_border // Dark orange for electricity
|
||||
2 -> R.color.water_selection_border // Dark blue for water
|
||||
else -> R.color.electricity_selection_border
|
||||
}
|
||||
|
||||
itemView.backgroundTintList = ContextCompat.getColorStateList(itemView.context, backgroundColorRes)
|
||||
serviceIcon.setColorFilter(ContextCompat.getColor(itemView.context, iconColorRes))
|
||||
subscriptionName.setTextColor(ContextCompat.getColor(itemView.context, iconColorRes))
|
||||
subscriptionDetails.setTextColor(ContextCompat.getColor(itemView.context, iconColorRes))
|
||||
} else {
|
||||
// Unselected state - use default colors
|
||||
itemView.backgroundTintList = null
|
||||
val iconColorRes = when (subscriptionInfo.serviceId) {
|
||||
1 -> android.R.color.holo_orange_light // Orange for electricity
|
||||
2 -> android.R.color.holo_blue_light // Blue for water
|
||||
else -> android.R.color.holo_orange_light
|
||||
}
|
||||
serviceIcon.setColorFilter(ContextCompat.getColor(itemView.context, iconColorRes))
|
||||
|
||||
// Resolve theme attribute for text color
|
||||
val typedValue = TypedValue()
|
||||
itemView.context.theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true)
|
||||
subscriptionName.setTextColor(ContextCompat.getColor(itemView.context, typedValue.resourceId))
|
||||
|
||||
// Set subscription details to secondary text color
|
||||
val secondaryTypedValue = TypedValue()
|
||||
itemView.context.theme.resolveAttribute(android.R.attr.textColorSecondary, secondaryTypedValue, true)
|
||||
subscriptionDetails.setTextColor(ContextCompat.getColor(itemView.context, secondaryTypedValue.resourceId))
|
||||
}
|
||||
|
||||
// Set click listener
|
||||
itemView.setOnClickListener {
|
||||
onSubscriptionClick(subscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Safe system background color -->
|
||||
<item android:color="@android:color/background_light"/>
|
||||
<!-- Theme-aware background color for table headers -->
|
||||
<item android:color="?android:attr/colorControlHighlight"/>
|
||||
</selector>
|
||||
|
5
app/src/main/res/drawable/ic_download_24.xml
Normal file
5
app/src/main/res/drawable/ic_download_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_electric_24.xml
Normal file
5
app/src/main/res/drawable/ic_electric_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M11,21h-1l1,-7H7.5c-0.58,0 -0.57,-0.32 -0.38,-0.66 0.19,-0.34 0.05,-0.08 0.07,-0.12C8.48,10.94 10.42,7.54 13,3h1l-1,7h3.5c0.49,0 0.56,0.33 0.47,0.51l-0.07,0.15C12.96,17.55 11,21 11,21z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_open_24.xml
Normal file
5
app/src/main/res/drawable/ic_open_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_share_24.xml
Normal file
5
app/src/main/res/drawable/ic_share_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_water_24.xml
Normal file
5
app/src/main/res/drawable/ic_water_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,2c1.1,0 2,0.9 2,2c0,0.78 -0.99,2.44 -2,4.5c-1.01,-2.06 -2,-3.72 -2,-4.5C10,2.9 10.9,2 12,2zM21,9c0,-7.11 -9,-7.11 -9,0v1.09C7.93,8.84 4,12.31 4,17c0,4.42 4.03,8 9,8s9,-3.58 9,-8C22,12.31 18.07,8.84 14,10.09V9H21z"/>
|
||||
</vector>
|
633
app/src/main/res/layout/activity_bill_details.xml
Normal file
633
app/src/main/res/layout/activity_bill_details.xml
Normal file
@@ -0,0 +1,633 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="?android:attr/colorBackground">
|
||||
|
||||
<!-- Toolbar -->
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="4dp"
|
||||
app:title="Bill Details"
|
||||
app:titleTextColor="@android:color/white"
|
||||
app:navigationIcon="@drawable/ic_arrow_back"
|
||||
app:navigationIconTint="@android:color/white" />
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="20dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Error message -->
|
||||
<TextView
|
||||
android:id="@+id/errorMessage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:padding="16dp"
|
||||
android:text="Error loading bill details"
|
||||
android:textColor="@android:color/holo_red_dark"
|
||||
android:background="@drawable/card_background"
|
||||
android:gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Main content -->
|
||||
<ScrollView
|
||||
android:id="@+id/mainContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Card 1: Bill Information -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/billInfoCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/card_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Bill Information"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/shareButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetBottom="0dp"
|
||||
app:icon="@drawable/ic_share_24"
|
||||
app:iconTint="@android:color/white"
|
||||
app:iconPadding="0dp"
|
||||
style="@style/Widget.Material3.Button.IconButton" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/downloadButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetBottom="0dp"
|
||||
app:icon="@drawable/ic_download_24"
|
||||
app:iconTint="@android:color/white"
|
||||
app:iconPadding="0dp"
|
||||
style="@style/Widget.Material3.Button.IconButton" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/openButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetBottom="0dp"
|
||||
app:icon="@drawable/ic_open_24"
|
||||
app:iconTint="@android:color/white"
|
||||
app:iconPadding="0dp"
|
||||
android:visibility="visible"
|
||||
style="@style/Widget.Material3.Button.IconButton" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pdfLoadingSpinner"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@android:color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Bill Number:"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/billNumber"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="17-120072"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Bill Total:"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/billTotal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="MVR 320.83"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Paid Date:"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/paidDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="July 1, 2025"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@android:color/holo_green_dark" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/payNowButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:text="Pay Now"
|
||||
android:textSize="12sp"
|
||||
android:backgroundTint="@android:color/holo_red_light"
|
||||
android:textColor="@android:color/white"
|
||||
android:visibility="gone"
|
||||
style="@style/Widget.Material3.Button.TextButton" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Bill Date:"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/billDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="June 29, 2025"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Card 2: Subscription Details -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/card_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<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="Subscription Details"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Subscription No:"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subscriptionNo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="8584"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Account No:"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountNo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="16656"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Branch:"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/branch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="HDH.Kulhudhufushi"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Meter No:"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/meterNo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="6211897"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Tariff Code:"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tariffCode"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="D"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Card 3: Meter Readings -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/card_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<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="Meter Readings"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
|
||||
<!-- Table header -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/account_dropdown_background"
|
||||
android:padding="8dp"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Month"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Reading"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Usage"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="end" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Current Month Table row -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Current"
|
||||
android:textSize="12sp"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/readingValue"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="37992"
|
||||
android:textSize="12sp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/usageValue"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="323 kwh"
|
||||
android:textSize="12sp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="end" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Previous Month Table row -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
android:background="@color/account_dropdown_background">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Previous"
|
||||
android:textSize="12sp"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/previousReadingValue"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="37669"
|
||||
android:textSize="12sp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/previousUsageValue"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="123 kWh"
|
||||
android:textSize="12sp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="end" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Card 4: Monthly Statement -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/card_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<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="Monthly Statement"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- Table header -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/account_dropdown_background"
|
||||
android:padding="8dp"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Qty"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Rate"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Amount"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="end" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Dynamic charges will be added here -->
|
||||
<LinearLayout
|
||||
android:id="@+id/chargesContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
@@ -1,29 +1,145 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout 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:orientation="vertical"
|
||||
android:background="?android:attr/colorBackground"
|
||||
tools:context=".ui.billhistory.BillHistoryFragment">
|
||||
|
||||
<LinearLayout
|
||||
<!-- Error message -->
|
||||
<TextView
|
||||
android:id="@+id/errorMessage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp"
|
||||
android:gravity="center">
|
||||
android:layout_margin="16dp"
|
||||
android:padding="16dp"
|
||||
android:text="Error loading data"
|
||||
android:textColor="@android:color/holo_red_dark"
|
||||
android:background="@drawable/card_background"
|
||||
android:gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_bill_history"
|
||||
<!-- Fixed Subscriptions Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card_subscriptions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18sp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:gravity="center"
|
||||
android:layout_marginTop="100dp"
|
||||
tools:text="Bill History\n\nComing soon..." />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/subscriptions_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
</ScrollView>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Subscriptions"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/subscriptionsRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Loading indicator inside subscription card -->
|
||||
<LinearLayout
|
||||
android:id="@+id/progressBar"
|
||||
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 subscriptions..."
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Fixed Bills Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card_bills"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_margin="16dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/billsTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Bills"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<!-- Scrollable bills content -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/billsRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noBillsMessage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="No bills found for this subscription"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
87
app/src/main/res/layout/item_bill_card.xml
Normal file
87
app/src/main/res/layout/item_bill_card.xml
Normal file
@@ -0,0 +1,87 @@
|
||||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/card_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="4dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/billDate"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="June 2025"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/billAmount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="MVR 320.83"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/billNumber"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Bill #17-120072"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/paymentStatus"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Paid on July 1, 2025"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/holo_green_dark" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/payNowButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:text="Pay Now"
|
||||
android:textSize="12sp"
|
||||
android:backgroundTint="@android:color/holo_red_light"
|
||||
android:textColor="@android:color/white"
|
||||
android:visibility="gone"
|
||||
style="@style/Widget.Material3.Button.TextButton" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
60
app/src/main/res/layout/item_subscription_card.xml
Normal file
60
app/src/main/res/layout/item_subscription_card.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@drawable/card_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="4dp"
|
||||
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/serviceIcon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_electric_24"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
app:tint="?android:attr/textColorPrimary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subscriptionName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Subscription Name"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subscriptionDetails"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Subscription Details"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
7
app/src/main/res/xml/file_paths.xml
Normal file
7
app/src/main/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<cache-path name="cache" path="." />
|
||||
<external-path name="external" path="." />
|
||||
<external-files-path name="external_files" path="." />
|
||||
<external-cache-path name="external_cache" path="." />
|
||||
</paths>
|
Reference in New Issue
Block a user