forked from LibreMV/GridFlow
added band rates ui
This commit is contained in:
@@ -53,6 +53,7 @@ dependencies {
|
|||||||
implementation(libs.okhttp.logging)
|
implementation(libs.okhttp.logging)
|
||||||
implementation(libs.gson)
|
implementation(libs.gson)
|
||||||
implementation(libs.security.crypto)
|
implementation(libs.security.crypto)
|
||||||
|
implementation(libs.flexbox)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
@@ -137,3 +137,24 @@ data class OutstandingBill(
|
|||||||
val subscriptionId: Long,
|
val subscriptionId: Long,
|
||||||
val serviceId: Int
|
val serviceId: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Band rates data models
|
||||||
|
data class BandRatesResponse(
|
||||||
|
val waterGroups: List<WaterGroup>,
|
||||||
|
val electricityRates: Map<String, List<ElectricityZoneCategory>>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class WaterGroup(
|
||||||
|
val category: String,
|
||||||
|
val rates: List<BandRate>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ElectricityZoneCategory(
|
||||||
|
val category: String,
|
||||||
|
val rates: List<BandRate>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class BandRate(
|
||||||
|
val band: String,
|
||||||
|
val rate: String
|
||||||
|
)
|
||||||
|
@@ -8,6 +8,7 @@ import okhttp3.*
|
|||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import sh.sar.gridflow.data.BandRatesResponse
|
||||||
import sh.sar.gridflow.data.CustomerSubscription
|
import sh.sar.gridflow.data.CustomerSubscription
|
||||||
import sh.sar.gridflow.data.ErrorResponse
|
import sh.sar.gridflow.data.ErrorResponse
|
||||||
import sh.sar.gridflow.data.ForgotPasswordRequest
|
import sh.sar.gridflow.data.ForgotPasswordRequest
|
||||||
@@ -331,6 +332,45 @@ class FenakaApiService {
|
|||||||
ApiResult.Error("Unexpected error: ${e.message}", -1)
|
ApiResult.Error("Unexpected error: ${e.message}", -1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getBandRates(cookie: String): ApiResult<BandRatesResponse> = withContext(Dispatchers.IO) {
|
||||||
|
Log.d(TAG, "Fetching band rates")
|
||||||
|
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("$BASE_URL/api/band-rates")
|
||||||
|
.get()
|
||||||
|
.header("Authorization", "Bearer $BEARER_TOKEN")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Cookie", cookie)
|
||||||
|
.header("Host", "api.fenaka.mv")
|
||||||
|
.header("User-Agent", "Dart/3.3 (dart:io)")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val response = client.newCall(request).execute()
|
||||||
|
Log.d(TAG, "Band rates response code: ${response.code}")
|
||||||
|
|
||||||
|
when (response.code) {
|
||||||
|
200 -> {
|
||||||
|
val responseBody = response.body?.string() ?: "{}"
|
||||||
|
Log.d(TAG, "Band rates response: $responseBody")
|
||||||
|
val bandRates = gson.fromJson(responseBody, BandRatesResponse::class.java)
|
||||||
|
Log.d(TAG, "Parsed band rates successfully")
|
||||||
|
ApiResult.Success(bandRates, null)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.d(TAG, "Failed to fetch band rates: ${response.code}")
|
||||||
|
ApiResult.Error("Failed to fetch band rates", response.code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "Network error during band rates fetch", e)
|
||||||
|
ApiResult.Error("Network error: ${e.message}", -1)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unexpected error during band rates fetch", e)
|
||||||
|
ApiResult.Error("Unexpected error: ${e.message}", -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class ApiResult<T> {
|
sealed class ApiResult<T> {
|
||||||
|
@@ -0,0 +1,41 @@
|
|||||||
|
package sh.sar.gridflow.ui.bandrates
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import sh.sar.gridflow.data.BandRate
|
||||||
|
import sh.sar.gridflow.databinding.ItemBandRateBinding
|
||||||
|
|
||||||
|
class BandRateAdapter : RecyclerView.Adapter<BandRateAdapter.BandRateViewHolder>() {
|
||||||
|
|
||||||
|
private var rates: List<BandRate> = emptyList()
|
||||||
|
|
||||||
|
fun updateRates(newRates: List<BandRate>) {
|
||||||
|
rates = newRates
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BandRateViewHolder {
|
||||||
|
val binding = ItemBandRateBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
return BandRateViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: BandRateViewHolder, position: Int) {
|
||||||
|
holder.bind(rates[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = rates.size
|
||||||
|
|
||||||
|
class BandRateViewHolder(private val binding: ItemBandRateBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
fun bind(rate: BandRate) {
|
||||||
|
binding.textBand.text = rate.band
|
||||||
|
binding.textRate.text = rate.rate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,19 @@
|
|||||||
package sh.sar.gridflow.ui.bandrates
|
package sh.sar.gridflow.ui.bandrates
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.google.android.flexbox.FlexDirection
|
||||||
|
import com.google.android.flexbox.FlexWrap
|
||||||
|
import com.google.android.flexbox.FlexboxLayoutManager
|
||||||
|
import com.google.android.flexbox.JustifyContent
|
||||||
|
import sh.sar.gridflow.R
|
||||||
import sh.sar.gridflow.databinding.FragmentBandRatesBinding
|
import sh.sar.gridflow.databinding.FragmentBandRatesBinding
|
||||||
|
|
||||||
class BandRatesFragment : Fragment() {
|
class BandRatesFragment : Fragment() {
|
||||||
@@ -13,23 +21,225 @@ class BandRatesFragment : Fragment() {
|
|||||||
private var _binding: FragmentBandRatesBinding? = null
|
private var _binding: FragmentBandRatesBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private lateinit var bandRatesViewModel: BandRatesViewModel
|
||||||
|
|
||||||
|
private lateinit var categoriesAdapter: SelectionChipAdapter
|
||||||
|
private lateinit var zonesAdapter: SelectionChipAdapter
|
||||||
|
private lateinit var ratesAdapter: BandRateAdapter
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
val bandRatesViewModel =
|
bandRatesViewModel = ViewModelProvider(
|
||||||
ViewModelProvider(this).get(BandRatesViewModel::class.java)
|
this,
|
||||||
|
ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().application)
|
||||||
|
).get(BandRatesViewModel::class.java)
|
||||||
|
|
||||||
_binding = FragmentBandRatesBinding.inflate(inflater, container, false)
|
_binding = FragmentBandRatesBinding.inflate(inflater, container, false)
|
||||||
val root: View = binding.root
|
|
||||||
|
|
||||||
val textView = binding.textBandRates
|
setupViews()
|
||||||
bandRatesViewModel.text.observe(viewLifecycleOwner) {
|
setupObservers()
|
||||||
textView.text = it
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupViews() {
|
||||||
|
// Setup adapters - will be updated when tab changes
|
||||||
|
categoriesAdapter = SelectionChipAdapter({ category ->
|
||||||
|
val currentTab = bandRatesViewModel.selectedTab.value ?: 0
|
||||||
|
if (currentTab == 0) {
|
||||||
|
bandRatesViewModel.selectElectricityCategory(category)
|
||||||
|
} else {
|
||||||
|
bandRatesViewModel.selectWaterCategory(category)
|
||||||
|
}
|
||||||
|
}, true) // Default to electricity tab
|
||||||
|
|
||||||
|
zonesAdapter = SelectionChipAdapter({ formattedZone ->
|
||||||
|
// Convert formatted zone name back to original API format
|
||||||
|
val originalZone = bandRatesViewModel.getAvailableZones().find {
|
||||||
|
bandRatesViewModel.formatZoneName(it) == formattedZone
|
||||||
|
}
|
||||||
|
if (originalZone != null) {
|
||||||
|
bandRatesViewModel.selectZone(originalZone)
|
||||||
|
}
|
||||||
|
}, true) // Always electricity tab for zones
|
||||||
|
|
||||||
|
ratesAdapter = BandRateAdapter()
|
||||||
|
|
||||||
|
// Setup RecyclerViews with FlexboxLayoutManager for wrapping
|
||||||
|
binding.recyclerCategories.apply {
|
||||||
|
layoutManager = FlexboxLayoutManager(requireContext()).apply {
|
||||||
|
flexDirection = FlexDirection.ROW
|
||||||
|
flexWrap = FlexWrap.WRAP
|
||||||
|
justifyContent = JustifyContent.FLEX_START
|
||||||
|
}
|
||||||
|
adapter = categoriesAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
return root
|
binding.recyclerZones.apply {
|
||||||
|
layoutManager = FlexboxLayoutManager(requireContext()).apply {
|
||||||
|
flexDirection = FlexDirection.ROW
|
||||||
|
flexWrap = FlexWrap.WRAP
|
||||||
|
justifyContent = JustifyContent.FLEX_START
|
||||||
|
}
|
||||||
|
adapter = zonesAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.recyclerRates.apply {
|
||||||
|
layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
adapter = ratesAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup tab clicks
|
||||||
|
binding.tabElectricity.setOnClickListener {
|
||||||
|
bandRatesViewModel.selectTab(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tabWater.setOnClickListener {
|
||||||
|
bandRatesViewModel.selectTab(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupObservers() {
|
||||||
|
// Loading state
|
||||||
|
bandRatesViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||||
|
binding.layoutLoading.visibility = if (isLoading) View.VISIBLE else View.GONE
|
||||||
|
binding.layoutContent.visibility = if (isLoading) View.GONE else View.VISIBLE
|
||||||
|
binding.layoutError.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error state
|
||||||
|
bandRatesViewModel.error.observe(viewLifecycleOwner) { error ->
|
||||||
|
if (error != null) {
|
||||||
|
binding.layoutError.visibility = View.VISIBLE
|
||||||
|
binding.textError.text = error
|
||||||
|
binding.layoutContent.visibility = View.GONE
|
||||||
|
binding.layoutLoading.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.layoutError.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab selection
|
||||||
|
bandRatesViewModel.selectedTab.observe(viewLifecycleOwner) { tabIndex ->
|
||||||
|
updateTabUI(tabIndex)
|
||||||
|
recreateAdaptersForTab(tabIndex)
|
||||||
|
updateCategoriesAndZones()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data loaded
|
||||||
|
bandRatesViewModel.bandRatesData.observe(viewLifecycleOwner) { data ->
|
||||||
|
if (data != null) {
|
||||||
|
binding.layoutContent.visibility = View.VISIBLE
|
||||||
|
updateCategoriesAndZones()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zone selection (electricity only)
|
||||||
|
bandRatesViewModel.selectedZone.observe(viewLifecycleOwner) { _ ->
|
||||||
|
updateCategoriesAndZones()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Category selections
|
||||||
|
bandRatesViewModel.selectedElectricityCategory.observe(viewLifecycleOwner) { _ ->
|
||||||
|
updateCategoriesAndZones()
|
||||||
|
}
|
||||||
|
|
||||||
|
bandRatesViewModel.selectedWaterCategory.observe(viewLifecycleOwner) { _ ->
|
||||||
|
updateCategoriesAndZones()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current rates
|
||||||
|
bandRatesViewModel.currentRates.observe(viewLifecycleOwner) { rates ->
|
||||||
|
ratesAdapter.updateRates(rates)
|
||||||
|
binding.recyclerRates.visibility = if (rates.isNotEmpty()) View.VISIBLE else View.GONE
|
||||||
|
binding.layoutRatesEmpty.visibility = if (rates.isEmpty()) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recreateAdaptersForTab(tabIndex: Int) {
|
||||||
|
val isElectricityTab = tabIndex == 0
|
||||||
|
|
||||||
|
// Recreate categories adapter with correct tab colors
|
||||||
|
categoriesAdapter = SelectionChipAdapter({ category ->
|
||||||
|
val currentTab = bandRatesViewModel.selectedTab.value ?: 0
|
||||||
|
if (currentTab == 0) {
|
||||||
|
bandRatesViewModel.selectElectricityCategory(category)
|
||||||
|
} else {
|
||||||
|
bandRatesViewModel.selectWaterCategory(category)
|
||||||
|
}
|
||||||
|
}, isElectricityTab)
|
||||||
|
|
||||||
|
// Update RecyclerView with new adapter
|
||||||
|
binding.recyclerCategories.apply {
|
||||||
|
layoutManager = FlexboxLayoutManager(requireContext()).apply {
|
||||||
|
flexDirection = FlexDirection.ROW
|
||||||
|
flexWrap = FlexWrap.WRAP
|
||||||
|
justifyContent = JustifyContent.FLEX_START
|
||||||
|
}
|
||||||
|
adapter = categoriesAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTabUI(selectedTab: Int) {
|
||||||
|
if (selectedTab == 0) {
|
||||||
|
// Electricity tab selected
|
||||||
|
binding.tabElectricity.backgroundTintList = ContextCompat.getColorStateList(
|
||||||
|
requireContext(), R.color.electricity_color
|
||||||
|
)
|
||||||
|
binding.tabElectricity.setTextColor(ContextCompat.getColor(requireContext(), R.color.white))
|
||||||
|
|
||||||
|
binding.tabWater.backgroundTintList = ContextCompat.getColorStateList(
|
||||||
|
requireContext(), R.color.tab_inactive_color
|
||||||
|
)
|
||||||
|
binding.tabWater.setTextColor(ContextCompat.getColor(requireContext(), R.color.tab_inactive_text_color))
|
||||||
|
|
||||||
|
// Show zones card for electricity
|
||||||
|
binding.cardZones.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
// Water tab selected
|
||||||
|
binding.tabWater.backgroundTintList = ContextCompat.getColorStateList(
|
||||||
|
requireContext(), R.color.water_color
|
||||||
|
)
|
||||||
|
binding.tabWater.setTextColor(ContextCompat.getColor(requireContext(), R.color.white))
|
||||||
|
|
||||||
|
binding.tabElectricity.backgroundTintList = ContextCompat.getColorStateList(
|
||||||
|
requireContext(), R.color.tab_inactive_color
|
||||||
|
)
|
||||||
|
binding.tabElectricity.setTextColor(ContextCompat.getColor(requireContext(), R.color.tab_inactive_text_color))
|
||||||
|
|
||||||
|
// Hide zones card for water
|
||||||
|
binding.cardZones.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCategoriesAndZones() {
|
||||||
|
val currentTab = bandRatesViewModel.selectedTab.value ?: 0
|
||||||
|
|
||||||
|
if (currentTab == 0) {
|
||||||
|
// Electricity tab
|
||||||
|
val zones = bandRatesViewModel.getAvailableZones()
|
||||||
|
val selectedZone = bandRatesViewModel.selectedZone.value
|
||||||
|
zonesAdapter.updateItems(
|
||||||
|
zones.map { bandRatesViewModel.formatZoneName(it) },
|
||||||
|
selectedZone?.let { bandRatesViewModel.formatZoneName(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
val categories = bandRatesViewModel.getAvailableElectricityCategories()
|
||||||
|
val selectedCategory = bandRatesViewModel.selectedElectricityCategory.value
|
||||||
|
categoriesAdapter.updateItems(categories, selectedCategory)
|
||||||
|
|
||||||
|
binding.textRatesTitle.text = "Electricity Rate Structure"
|
||||||
|
} else {
|
||||||
|
// Water tab
|
||||||
|
val categories = bandRatesViewModel.getAvailableWaterCategories()
|
||||||
|
val selectedCategory = bandRatesViewModel.selectedWaterCategory.value
|
||||||
|
categoriesAdapter.updateItems(categories, selectedCategory)
|
||||||
|
|
||||||
|
binding.textRatesTitle.text = "Water Rate Structure"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
@@ -1,13 +1,200 @@
|
|||||||
package sh.sar.gridflow.ui.bandrates
|
package sh.sar.gridflow.ui.bandrates
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import sh.sar.gridflow.data.BandRate
|
||||||
|
import sh.sar.gridflow.data.BandRatesResponse
|
||||||
|
import sh.sar.gridflow.network.ApiResult
|
||||||
|
import sh.sar.gridflow.network.FenakaApiService
|
||||||
|
import sh.sar.gridflow.utils.SecureStorage
|
||||||
|
|
||||||
class BandRatesViewModel : ViewModel() {
|
class BandRatesViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
||||||
private val _text = MutableLiveData<String>().apply {
|
private val secureStorage = SecureStorage(application)
|
||||||
value = "Band Rates\n\nComing soon..."
|
private val apiService = FenakaApiService()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "BandRatesViewModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _bandRatesData = MutableLiveData<BandRatesResponse?>()
|
||||||
|
val bandRatesData: LiveData<BandRatesResponse?> = _bandRatesData
|
||||||
|
|
||||||
|
private val _isLoading = MutableLiveData<Boolean>()
|
||||||
|
val isLoading: LiveData<Boolean> = _isLoading
|
||||||
|
|
||||||
|
private val _error = MutableLiveData<String?>()
|
||||||
|
val error: LiveData<String?> = _error
|
||||||
|
|
||||||
|
// Current tab - 0 for electricity, 1 for water
|
||||||
|
private val _selectedTab = MutableLiveData<Int>()
|
||||||
|
val selectedTab: LiveData<Int> = _selectedTab
|
||||||
|
|
||||||
|
// Electricity specific - zone and category selection
|
||||||
|
private val _selectedZone = MutableLiveData<String?>()
|
||||||
|
val selectedZone: LiveData<String?> = _selectedZone
|
||||||
|
|
||||||
|
private val _selectedElectricityCategory = MutableLiveData<String?>()
|
||||||
|
val selectedElectricityCategory: LiveData<String?> = _selectedElectricityCategory
|
||||||
|
|
||||||
|
// Water specific - category selection
|
||||||
|
private val _selectedWaterCategory = MutableLiveData<String?>()
|
||||||
|
val selectedWaterCategory: LiveData<String?> = _selectedWaterCategory
|
||||||
|
|
||||||
|
// Current rates to display
|
||||||
|
private val _currentRates = MutableLiveData<List<BandRate>>()
|
||||||
|
val currentRates: LiveData<List<BandRate>> = _currentRates
|
||||||
|
|
||||||
|
init {
|
||||||
|
Log.d(TAG, "BandRatesViewModel initialized")
|
||||||
|
_selectedTab.value = 0 // Default to electricity tab
|
||||||
|
loadBandRates()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadBandRates() {
|
||||||
|
val cookie = secureStorage.getCookie()
|
||||||
|
if (cookie == null) {
|
||||||
|
Log.d(TAG, "No cookie found, cannot load band rates")
|
||||||
|
_error.value = "Authentication required"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_isLoading.value = true
|
||||||
|
_error.value = null
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
Log.d(TAG, "Loading band rates")
|
||||||
|
when (val result = apiService.getBandRates("connect.sid=$cookie")) {
|
||||||
|
is ApiResult.Success -> {
|
||||||
|
Log.d(TAG, "Band rates loaded successfully")
|
||||||
|
_isLoading.value = false
|
||||||
|
_bandRatesData.value = result.data
|
||||||
|
initializeDefaultSelections()
|
||||||
|
} is ApiResult.Error -> {
|
||||||
|
Log.d(TAG, "Failed to load band rates: ${result.message}")
|
||||||
|
_isLoading.value = false
|
||||||
|
_error.value = result.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeDefaultSelections() {
|
||||||
|
val data = _bandRatesData.value ?: return
|
||||||
|
|
||||||
|
// Initialize electricity selections
|
||||||
|
val firstZone = data.electricityRates.keys.firstOrNull()
|
||||||
|
_selectedZone.value = firstZone
|
||||||
|
|
||||||
|
if (firstZone != null) {
|
||||||
|
val firstCategory = data.electricityRates[firstZone]?.firstOrNull()?.category
|
||||||
|
_selectedElectricityCategory.value = firstCategory
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize water selections
|
||||||
|
val firstWaterCategory = data.waterGroups.firstOrNull()?.category
|
||||||
|
_selectedWaterCategory.value = firstWaterCategory
|
||||||
|
|
||||||
|
// Update current rates based on default tab (electricity)
|
||||||
|
updateCurrentRates()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectTab(tabIndex: Int) {
|
||||||
|
_selectedTab.value = tabIndex
|
||||||
|
updateCurrentRates()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectZone(zone: String) {
|
||||||
|
_selectedZone.value = zone
|
||||||
|
|
||||||
|
// Keep the current category if it exists in the new zone, otherwise pick the first one
|
||||||
|
val data = _bandRatesData.value ?: return
|
||||||
|
val currentCategory = _selectedElectricityCategory.value
|
||||||
|
val availableCategories = data.electricityRates[zone]?.map { it.category } ?: emptyList()
|
||||||
|
|
||||||
|
if (currentCategory != null && availableCategories.contains(currentCategory)) {
|
||||||
|
// Keep the current category since it's available in the new zone
|
||||||
|
_selectedElectricityCategory.value = currentCategory
|
||||||
|
} else {
|
||||||
|
// Fall back to first category if current one is not available
|
||||||
|
val firstCategory = availableCategories.firstOrNull()
|
||||||
|
_selectedElectricityCategory.value = firstCategory
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCurrentRates()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectElectricityCategory(category: String) {
|
||||||
|
_selectedElectricityCategory.value = category
|
||||||
|
updateCurrentRates()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectWaterCategory(category: String) {
|
||||||
|
_selectedWaterCategory.value = category
|
||||||
|
updateCurrentRates()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCurrentRates() {
|
||||||
|
val data = _bandRatesData.value ?: return
|
||||||
|
val currentTab = _selectedTab.value ?: 0
|
||||||
|
|
||||||
|
val rates = if (currentTab == 0) {
|
||||||
|
// Electricity tab
|
||||||
|
val zone = _selectedZone.value
|
||||||
|
val category = _selectedElectricityCategory.value
|
||||||
|
|
||||||
|
if (zone != null && category != null) {
|
||||||
|
data.electricityRates[zone]?.find { it.category == category }?.rates ?: emptyList()
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Water tab
|
||||||
|
val category = _selectedWaterCategory.value
|
||||||
|
if (category != null) {
|
||||||
|
data.waterGroups.find { it.category == category }?.rates ?: emptyList()
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentRates.value = rates
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formatZoneName(zoneName: String): String {
|
||||||
|
return zoneName
|
||||||
|
.replace("-", " ")
|
||||||
|
.split(" ")
|
||||||
|
.joinToString(" ") { word ->
|
||||||
|
if (word.isNotEmpty()) {
|
||||||
|
word.first().uppercaseChar() + word.drop(1).lowercase()
|
||||||
|
} else {
|
||||||
|
word
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAvailableZones(): List<String> {
|
||||||
|
return _bandRatesData.value?.electricityRates?.keys?.toList() ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAvailableElectricityCategories(): List<String> {
|
||||||
|
val data = _bandRatesData.value ?: return emptyList()
|
||||||
|
val zone = _selectedZone.value ?: return emptyList()
|
||||||
|
return data.electricityRates[zone]?.map { it.category } ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAvailableWaterCategories(): List<String> {
|
||||||
|
return _bandRatesData.value?.waterGroups?.map { it.category } ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshData() {
|
||||||
|
Log.d(TAG, "Refreshing band rates data")
|
||||||
|
loadBandRates()
|
||||||
}
|
}
|
||||||
val text: LiveData<String> = _text
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,93 @@
|
|||||||
|
package sh.sar.gridflow.ui.bandrates
|
||||||
|
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import sh.sar.gridflow.R
|
||||||
|
import sh.sar.gridflow.databinding.ItemSelectionChipBinding
|
||||||
|
|
||||||
|
class SelectionChipAdapter(
|
||||||
|
private val onItemClick: (String) -> Unit,
|
||||||
|
private val isElectricityTab: Boolean = false
|
||||||
|
) : RecyclerView.Adapter<SelectionChipAdapter.ChipViewHolder>() {
|
||||||
|
|
||||||
|
private var items: List<String> = emptyList()
|
||||||
|
private var selectedItem: String? = null
|
||||||
|
|
||||||
|
fun updateItems(newItems: List<String>, selected: String? = null) {
|
||||||
|
items = newItems
|
||||||
|
selectedItem = selected
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSelectedItem(item: String?) {
|
||||||
|
val previousIndex = selectedItem?.let { items.indexOf(it) } ?: -1
|
||||||
|
val newIndex = item?.let { items.indexOf(it) } ?: -1
|
||||||
|
|
||||||
|
selectedItem = item
|
||||||
|
|
||||||
|
if (previousIndex >= 0) notifyItemChanged(previousIndex)
|
||||||
|
if (newIndex >= 0) notifyItemChanged(newIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChipViewHolder {
|
||||||
|
val binding = ItemSelectionChipBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
return ChipViewHolder(binding, onItemClick, isElectricityTab)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ChipViewHolder, position: Int) {
|
||||||
|
holder.bind(items[position], items[position] == selectedItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = items.size
|
||||||
|
|
||||||
|
class ChipViewHolder(
|
||||||
|
private val binding: ItemSelectionChipBinding,
|
||||||
|
private val onItemClick: (String) -> Unit,
|
||||||
|
private val isElectricityTab: Boolean
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
fun bind(item: String, isSelected: Boolean) {
|
||||||
|
binding.textChip.text = item
|
||||||
|
|
||||||
|
// Set background and text color based on selection and tab
|
||||||
|
if (isSelected) {
|
||||||
|
if (isElectricityTab) {
|
||||||
|
binding.textChip.backgroundTintList = ContextCompat.getColorStateList(
|
||||||
|
binding.root.context,
|
||||||
|
R.color.electricity_selection_background
|
||||||
|
)
|
||||||
|
binding.textChip.setTextColor(
|
||||||
|
ContextCompat.getColor(binding.root.context, R.color.electricity_selection_border)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
binding.textChip.backgroundTintList = ContextCompat.getColorStateList(
|
||||||
|
binding.root.context,
|
||||||
|
R.color.water_selection_background
|
||||||
|
)
|
||||||
|
binding.textChip.setTextColor(
|
||||||
|
ContextCompat.getColor(binding.root.context, R.color.water_selection_border)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.textChip.backgroundTintList = ContextCompat.getColorStateList(
|
||||||
|
binding.root.context,
|
||||||
|
R.color.category_tag_background
|
||||||
|
)
|
||||||
|
binding.textChip.setTextColor(
|
||||||
|
ContextCompat.getColor(binding.root.context, R.color.tab_inactive_text_color)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.textChip.setOnClickListener {
|
||||||
|
onItemClick(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@@ -11,18 +12,284 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="20dp"
|
android:padding="16dp">
|
||||||
android:gravity="center">
|
|
||||||
|
|
||||||
<TextView
|
<!-- Tab Layout -->
|
||||||
android:id="@+id/text_band_rates"
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="18sp"
|
android:layout_marginBottom="16dp"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
app:cardCornerRadius="12dp"
|
||||||
android:gravity="center"
|
app:cardElevation="2dp">
|
||||||
android:layout_marginTop="100dp"
|
|
||||||
tools:text="Band Rates\n\nComing soon..." />
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="4dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tab_electricity"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Electricity"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:background="@drawable/card_background"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
tools:textColor="@color/white"
|
||||||
|
tools:backgroundTint="@color/electricity_color" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tab_water"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Water"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:background="@drawable/card_background"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
tools:textColor="?android:attr/textColorPrimary"
|
||||||
|
tools:backgroundTint="@color/tab_inactive_color" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<!-- Content Area -->
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_loading"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="48dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Loading band rates..."
|
||||||
|
android:textColor="?android:attr/textColorSecondary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Error State -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_error"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="48dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="⚠️"
|
||||||
|
android:textSize="48sp"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_error"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Unable to load band rates"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:textAlignment="center" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<!-- Category Selection -->
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="2dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Categories"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:layout_marginBottom="12dp" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler_categories"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:listitem="@layout/item_subscription" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<!-- Zone Selection (Electricity Only) -->
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/card_zones"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="2dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<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="Zones"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:layout_marginBottom="12dp" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler_zones"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:listitem="@layout/item_subscription" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<!-- Rates Table -->
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="2dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_rates_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Rate Structure"
|
||||||
|
android:textSize="16sp"
|
||||||
|
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/category_tag_background"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:layout_marginBottom="1dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Allocated Units"
|
||||||
|
android:textSize="14sp"
|
||||||
|
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="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:gravity="end" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Rates List -->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler_rates"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_rates_empty"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="32dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="📊"
|
||||||
|
android:textSize="48sp"
|
||||||
|
android:layout_marginBottom="8dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="No rates available for selected criteria"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:textAlignment="center" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
31
app/src/main/res/layout/item_band_rate.xml
Normal file
31
app/src/main/res/layout/item_band_rate.xml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:layout_marginBottom="1dp"
|
||||||
|
android:background="@color/card_background_color">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_band"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
tools:text="0-100" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_rate"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="end"
|
||||||
|
tools:text="MVR 22.00" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
17
app/src/main/res/layout/item_selection_chip.xml
Normal file
17
app/src/main/res/layout/item_selection_chip.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/text_chip"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:background="@drawable/category_tag_background"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:minWidth="80dp"
|
||||||
|
android:gravity="center"
|
||||||
|
tools:text="Domestic" />
|
@@ -17,4 +17,16 @@
|
|||||||
<color name="category_tag_background">#E0E0E0</color>
|
<color name="category_tag_background">#E0E0E0</color>
|
||||||
<color name="card_stroke_color">#E0E0E0</color>
|
<color name="card_stroke_color">#E0E0E0</color>
|
||||||
<color name="card_background_color">#FFFFFF</color>
|
<color name="card_background_color">#FFFFFF</color>
|
||||||
|
|
||||||
|
<!-- Band rates colors (matching chart colors) -->
|
||||||
|
<color name="electricity_color">#FF9800</color>
|
||||||
|
<color name="water_color">#2196F3</color>
|
||||||
|
<color name="tab_inactive_color">#E0E0E0</color>
|
||||||
|
<color name="tab_inactive_text_color">#666666</color>
|
||||||
|
|
||||||
|
<!-- Selection chip colors -->
|
||||||
|
<color name="electricity_selection_background">#FFF3E0</color>
|
||||||
|
<color name="electricity_selection_border">#FF9800</color>
|
||||||
|
<color name="water_selection_background">#E3F2FD</color>
|
||||||
|
<color name="water_selection_border">#2196F3</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -16,6 +16,7 @@ okhttp = "4.12.0"
|
|||||||
gson = "2.10.1"
|
gson = "2.10.1"
|
||||||
security = "1.1.0-alpha06"
|
security = "1.1.0-alpha06"
|
||||||
coordinatorlayout = "1.2.0"
|
coordinatorlayout = "1.2.0"
|
||||||
|
flexbox = "3.0.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
@@ -34,6 +35,7 @@ okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor",
|
|||||||
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||||
security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "security" }
|
security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "security" }
|
||||||
androidx-coordinatorlayout = { group = "androidx.coordinatorlayout", name = "coordinatorlayout", version.ref = "coordinatorlayout" }
|
androidx-coordinatorlayout = { group = "androidx.coordinatorlayout", name = "coordinatorlayout", version.ref = "coordinatorlayout" }
|
||||||
|
flexbox = { group = "com.google.android.flexbox", name = "flexbox", version.ref = "flexbox" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
Reference in New Issue
Block a user