From eecd4b0f1db7efdca5dfb7aeb0b6371645f98f56 Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Fri, 25 Jul 2025 03:12:12 +0500 Subject: [PATCH] render subscriptions --- .../main/java/sh/sar/gridflow/data/Models.kt | 23 ++- .../DetailedSubscriptionsAdapter.kt | 77 ++++++++ .../ui/subscriptions/SubscriptionsFragment.kt | 94 ++++++++- .../subscriptions/SubscriptionsViewModel.kt | 66 ++++++- app/src/main/res/drawable/card_background.xml | 9 + .../res/drawable/category_tag_background.xml | 6 + .../res/layout/fragment_subscriptions.xml | 89 +++++++-- .../res/layout/item_subscription_detailed.xml | 182 ++++++++++++++++++ app/src/main/res/values-night/colors.xml | 3 + app/src/main/res/values/colors.xml | 3 + 10 files changed, 515 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/sh/sar/gridflow/ui/subscriptions/DetailedSubscriptionsAdapter.kt create mode 100644 app/src/main/res/drawable/card_background.xml create mode 100644 app/src/main/res/drawable/category_tag_background.xml create mode 100644 app/src/main/res/layout/item_subscription_detailed.xml diff --git a/app/src/main/java/sh/sar/gridflow/data/Models.kt b/app/src/main/java/sh/sar/gridflow/data/Models.kt index b348260..dff4efb 100644 --- a/app/src/main/java/sh/sar/gridflow/data/Models.kt +++ b/app/src/main/java/sh/sar/gridflow/data/Models.kt @@ -65,7 +65,8 @@ data class SubscriptionDetails( val hasSmartMeter: Boolean, val category: ServiceCategory, val customer: Customer, - val lastBill: LastBill? + val lastBill: LastBill?, + val subscriptionAddress: SubscriptionAddress? ) data class ServiceCategory( @@ -85,7 +86,25 @@ data class Customer( val id: Long, val name: String, val accountNumber: String, - val phone: String + val phone: String, + val type: String +) + +data class SubscriptionAddress( + val id: Long, + val type: String, + val property: Property? +) + +data class Property( + val id: Long, + val name: String, + val street: Street? +) + +data class Street( + val id: Long, + val name: String ) data class LastBill( diff --git a/app/src/main/java/sh/sar/gridflow/ui/subscriptions/DetailedSubscriptionsAdapter.kt b/app/src/main/java/sh/sar/gridflow/ui/subscriptions/DetailedSubscriptionsAdapter.kt new file mode 100644 index 0000000..308d394 --- /dev/null +++ b/app/src/main/java/sh/sar/gridflow/ui/subscriptions/DetailedSubscriptionsAdapter.kt @@ -0,0 +1,77 @@ +package sh.sar.gridflow.ui.subscriptions + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.Toast +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import sh.sar.gridflow.data.CustomerSubscription +import sh.sar.gridflow.databinding.ItemSubscriptionDetailedBinding + +class DetailedSubscriptionsAdapter : ListAdapter(SubscriptionDiffCallback()) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionViewHolder { + val binding = ItemSubscriptionDetailedBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return SubscriptionViewHolder(binding) + } + + override fun onBindViewHolder(holder: SubscriptionViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + class SubscriptionViewHolder( + private val binding: ItemSubscriptionDetailedBinding + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(subscription: CustomerSubscription) { + with(binding) { + // Service icon and category + val serviceIcon = when (subscription.subscription.serviceId) { + 1 -> "⚡" // Electrical + 2 -> "💧" // Water + else -> "🔌" // Unknown + } + textServiceIcon.text = serviceIcon + + // Subscription info + textSubscriptionName.text = subscription.name + textSubscriptionNumber.text = "Sub #${subscription.subscription.subscriptionNumber}" + textServiceCategory.text = subscription.subscription.category.name.uppercase() + + // Property information + val propertyName = subscription.subscription.subscriptionAddress?.property?.name ?: "Unknown Property" + val streetName = subscription.subscription.subscriptionAddress?.property?.street?.name ?: "Unknown Street" + textPropertyName.text = propertyName + textPropertyStreet.text = streetName + + // Customer information + textCustomerName.text = subscription.subscription.customer.name + textCustomerPhone.text = subscription.subscription.customer.phone + + // Action buttons - show coming soon toasts + btnEdit.setOnClickListener { + Toast.makeText(it.context, "Edit subscription coming soon", Toast.LENGTH_SHORT).show() + } + + btnDelete.setOnClickListener { + Toast.makeText(it.context, "Delete subscription coming soon", Toast.LENGTH_SHORT).show() + } + } + } + } + + private class SubscriptionDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: CustomerSubscription, newItem: CustomerSubscription): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: CustomerSubscription, newItem: CustomerSubscription): Boolean { + return oldItem == newItem + } + } +} diff --git a/app/src/main/java/sh/sar/gridflow/ui/subscriptions/SubscriptionsFragment.kt b/app/src/main/java/sh/sar/gridflow/ui/subscriptions/SubscriptionsFragment.kt index 7bb868b..4bc048a 100644 --- a/app/src/main/java/sh/sar/gridflow/ui/subscriptions/SubscriptionsFragment.kt +++ b/app/src/main/java/sh/sar/gridflow/ui/subscriptions/SubscriptionsFragment.kt @@ -7,35 +7,109 @@ 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.FragmentSubscriptionsBinding class SubscriptionsFragment : Fragment() { private var _binding: FragmentSubscriptionsBinding? = null private val binding get() = _binding!! + + private lateinit var subscriptionsViewModel: SubscriptionsViewModel + private lateinit var subscriptionsAdapter: DetailedSubscriptionsAdapter override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - val subscriptionsViewModel = - ViewModelProvider(this).get(SubscriptionsViewModel::class.java) + subscriptionsViewModel = ViewModelProvider( + this, + ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().application) + ).get(SubscriptionsViewModel::class.java) _binding = FragmentSubscriptionsBinding.inflate(inflater, container, false) - val root: View = binding.root - - val textView = binding.textSubscriptions - subscriptionsViewModel.text.observe(viewLifecycleOwner) { - textView.text = it + + setupViews() + setupObservers() + + return binding.root + } + + private fun setupViews() { + // Setup RecyclerView + subscriptionsAdapter = DetailedSubscriptionsAdapter() + binding.recyclerSubscriptions.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = subscriptionsAdapter } - + // Handle FAB click binding.fabAddSubscription.setOnClickListener { Toast.makeText(context, "Add subscription coming soon", Toast.LENGTH_SHORT).show() } - - return root + + // Handle retry button + binding.btnRetry.setOnClickListener { + subscriptionsViewModel.refreshSubscriptions() + } + } + + private fun setupObservers() { + // Subscriptions data + subscriptionsViewModel.subscriptions.observe(viewLifecycleOwner) { subscriptions -> + subscriptionsAdapter.submitList(subscriptions) + + // Show appropriate state + if (subscriptions.isEmpty()) { + showEmptyState() + } else { + showSubscriptionsList() + } + } + + // Loading state + subscriptionsViewModel.isLoading.observe(viewLifecycleOwner) { isLoading -> + if (isLoading) { + showLoadingState() + } + } + + // Error state + subscriptionsViewModel.error.observe(viewLifecycleOwner) { error -> + if (error != null) { + showErrorState(error) + } + } + } + + private fun showLoadingState() { + binding.progressLoading.visibility = View.VISIBLE + binding.recyclerSubscriptions.visibility = View.GONE + binding.layoutError.visibility = View.GONE + binding.layoutEmpty.visibility = View.GONE + } + + private fun showSubscriptionsList() { + binding.progressLoading.visibility = View.GONE + binding.recyclerSubscriptions.visibility = View.VISIBLE + binding.layoutError.visibility = View.GONE + binding.layoutEmpty.visibility = View.GONE + } + + private fun showEmptyState() { + binding.progressLoading.visibility = View.GONE + binding.recyclerSubscriptions.visibility = View.GONE + binding.layoutError.visibility = View.GONE + binding.layoutEmpty.visibility = View.VISIBLE + } + + private fun showErrorState(error: String) { + binding.progressLoading.visibility = View.GONE + binding.recyclerSubscriptions.visibility = View.GONE + binding.layoutError.visibility = View.VISIBLE + binding.layoutEmpty.visibility = View.GONE + binding.textError.text = error } override fun onDestroyView() { diff --git a/app/src/main/java/sh/sar/gridflow/ui/subscriptions/SubscriptionsViewModel.kt b/app/src/main/java/sh/sar/gridflow/ui/subscriptions/SubscriptionsViewModel.kt index 5e8458a..0aac8e7 100644 --- a/app/src/main/java/sh/sar/gridflow/ui/subscriptions/SubscriptionsViewModel.kt +++ b/app/src/main/java/sh/sar/gridflow/ui/subscriptions/SubscriptionsViewModel.kt @@ -1,13 +1,69 @@ package sh.sar.gridflow.ui.subscriptions +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.CustomerSubscription +import sh.sar.gridflow.network.ApiResult +import sh.sar.gridflow.network.FenakaApiService +import sh.sar.gridflow.utils.SecureStorage -class SubscriptionsViewModel : ViewModel() { +class SubscriptionsViewModel(application: Application) : AndroidViewModel(application) { - private val _text = MutableLiveData().apply { - value = "Subscriptions\n\nComing soon..." + private val secureStorage = SecureStorage(application) + private val apiService = FenakaApiService() + + companion object { + private const val TAG = "SubscriptionsViewModel" + } + + private val _subscriptions = MutableLiveData>() + val subscriptions: LiveData> = _subscriptions + + private val _isLoading = MutableLiveData() + val isLoading: LiveData = _isLoading + + private val _error = MutableLiveData() + val error: LiveData = _error + + init { + loadSubscriptions() + } + + fun loadSubscriptions() { + val cookie = secureStorage.getCookie() + if (cookie == null) { + Log.d(TAG, "No cookie found, cannot load subscriptions") + _error.value = "Authentication required" + return + } + + _isLoading.value = true + _error.value = null + + viewModelScope.launch { + Log.d(TAG, "Loading customer subscriptions") + when (val result = apiService.getCustomerSubscriptions("connect.sid=$cookie")) { + is ApiResult.Success -> { + Log.d(TAG, "Subscriptions loaded successfully: ${result.data.size} found") + _isLoading.value = false + _subscriptions.value = result.data + + } is ApiResult.Error -> { + Log.d(TAG, "Failed to load subscriptions: ${result.message}") + _isLoading.value = false + _error.value = result.message + } + } + } + } + + fun refreshSubscriptions() { + Log.d(TAG, "Refreshing subscriptions") + loadSubscriptions() } - val text: LiveData = _text } diff --git a/app/src/main/res/drawable/card_background.xml b/app/src/main/res/drawable/card_background.xml new file mode 100644 index 0000000..d567bf6 --- /dev/null +++ b/app/src/main/res/drawable/card_background.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/drawable/category_tag_background.xml b/app/src/main/res/drawable/category_tag_background.xml new file mode 100644 index 0000000..df82755 --- /dev/null +++ b/app/src/main/res/drawable/category_tag_background.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_subscriptions.xml b/app/src/main/res/layout/fragment_subscriptions.xml index f2fc3d4..23174b4 100644 --- a/app/src/main/res/layout/fragment_subscriptions.xml +++ b/app/src/main/res/layout/fragment_subscriptions.xml @@ -1,37 +1,86 @@ - + + + + + android:orientation="vertical" + android:gravity="center" + android:padding="32dp" + android:visibility="gone"> - + android:textSize="16sp" + android:textColor="?android:attr/textColorPrimary" + android:gravity="center" + android:layout_marginBottom="16dp" + tools:text="Failed to load subscriptions" /> - + - + - + + + + + + + + + + + + android:tint="?android:attr/colorBackground" /> diff --git a/app/src/main/res/layout/item_subscription_detailed.xml b/app/src/main/res/layout/item_subscription_detailed.xml new file mode 100644 index 0000000..04a2715 --- /dev/null +++ b/app/src/main/res/layout/item_subscription_detailed.xml @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +