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

@@ -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
}
}
}