diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..aa2b40b --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Woka \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..63241c0 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/other.xml b/.idea/other.xml new file mode 100644 index 0000000..0d3a1fb --- /dev/null +++ b/.idea/other.xml @@ -0,0 +1,263 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c75c7c6..cf07133 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,10 +17,13 @@ android:supportsRtl="true" android:theme="@style/Theme.Woka" tools:targetApi="31"> + + android:screenOrientation="portrait" /> ( + DIFF_CONFIG + ) { + + companion object { + private val DIFF_UTIL = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: BaseCategory, newItem: BaseCategory): Boolean = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: BaseCategory, newItem: BaseCategory): Boolean = + oldItem == newItem + } + + private val DIFF_CONFIG = AsyncDifferConfig.Builder(DIFF_UTIL) + .setBackgroundThreadExecutor(Executors.newSingleThreadExecutor()) + .build() + } + + inner class CategoryViewHolder(val binding: CategoryViweHolderBinding) : + ViewHolder(binding.root) + + var onCategoryClickListener: ((BaseCategory) -> Unit)? = null + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder { + return CategoryViewHolder( + CategoryViweHolderBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) { + val category = getItem(holder.absoluteAdapterPosition) + + holder.binding.apply { + image.loadImage(category.imageUrl) + title.text = category.title + + holder.binding.root.setOnClickListener { + onCategoryClickListener?.invoke(category) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/shop/models/BaseCategory.kt b/app/src/main/java/com/woka/shop/models/BaseCategory.kt new file mode 100644 index 0000000..3440619 --- /dev/null +++ b/app/src/main/java/com/woka/shop/models/BaseCategory.kt @@ -0,0 +1,7 @@ +package com.woka.shop.models + +data class BaseCategory( + val id: Int?, + val imageUrl: String?, + val title: String? +) diff --git a/app/src/main/java/com/woka/shop/models/subcategorylisting/SubCategoryResponse.kt b/app/src/main/java/com/woka/shop/models/subcategorylisting/SubCategoryResponse.kt index e3f1a08..80f1552 100644 --- a/app/src/main/java/com/woka/shop/models/subcategorylisting/SubCategoryResponse.kt +++ b/app/src/main/java/com/woka/shop/models/subcategorylisting/SubCategoryResponse.kt @@ -1,6 +1,6 @@ package com.woka.shop.models.subcategorylisting data class SubCategoryResponse( - val subCategory: List?, + val result: List?, val total_records: Int? ) \ No newline at end of file diff --git a/app/src/main/java/com/woka/shop/viewmodels/ShopViewModel.kt b/app/src/main/java/com/woka/shop/viewmodels/ShopViewModel.kt index 0d891d7..5897d39 100644 --- a/app/src/main/java/com/woka/shop/viewmodels/ShopViewModel.kt +++ b/app/src/main/java/com/woka/shop/viewmodels/ShopViewModel.kt @@ -63,6 +63,10 @@ class ShopViewModel: ViewModel() { } } + fun clearCategoryLiveData(){ + _categoryLiveData.postValue(ApiResult.Loading()) + } + // sub category listing private val _subcategoryLiveData = MutableLiveData>>() val subcategoryLiveData: LiveData>> @@ -82,7 +86,7 @@ class ShopViewModel: ViewModel() { is ApiResult.Error -> _subcategoryLiveData.postValue(ApiResult.Error(response.errorMessage, response.error)) is ApiResult.Loading -> {} is ApiResult.Success -> { - response.data?.subCategory?.filterNotNull()?.toMutableList()?.let { + response.data?.result?.filterNotNull()?.toMutableList()?.let { subcategoryDataMap[categoryId] = it _subcategoryLiveData.postValue(ApiResult.Success(it)) } @@ -90,4 +94,8 @@ class ShopViewModel: ViewModel() { } } } + + fun clearSubCategoryLiveData(){ + _subcategoryLiveData.postValue(ApiResult.Loading()) + } } \ No newline at end of file diff --git a/app/src/main/java/com/woka/shop/views/ShopActivity.kt b/app/src/main/java/com/woka/shop/views/ShopActivity.kt new file mode 100644 index 0000000..9eafc58 --- /dev/null +++ b/app/src/main/java/com/woka/shop/views/ShopActivity.kt @@ -0,0 +1,51 @@ +package com.woka.shop.views + +import android.os.Bundle +import androidx.activity.enableEdgeToEdge +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import com.woka.R +import com.woka.databinding.ActivityShopBinding +import com.woka.shop.views.fragments.ShopFragment1 +import com.woka.utils.WokaBaseActivity + +class ShopActivity : WokaBaseActivity() { + + private lateinit var binding: ActivityShopBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + binding = ActivityShopBinding.inflate(layoutInflater) + setContentView(binding.root) + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) + insets + } + + window.navigationBarColor = getColor(R.color.orders_bg) + + supportFragmentManager.beginTransaction() + .add(R.id.fcv_shop, ShopFragment1.newInstance()) + .commit() + + initViews() + + clickEvents() + } + + private fun initViews(){ + binding.apply { + title.text = getString(R.string.shop) + } + } + + private fun clickEvents(){ + binding.apply { + backBtn.setOnClickListener { + onBackPressedDispatcher.onBackPressed() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/shop/views/fragments/ShopFragment1.kt b/app/src/main/java/com/woka/shop/views/fragments/ShopFragment1.kt new file mode 100644 index 0000000..e298567 --- /dev/null +++ b/app/src/main/java/com/woka/shop/views/fragments/ShopFragment1.kt @@ -0,0 +1,113 @@ +package com.woka.shop.views.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import com.woka.R +import com.woka.databinding.FragmentShop1Binding +import com.woka.networking.ApiResult +import com.woka.shop.adapters.CategoryAdapter +import com.woka.shop.models.BaseCategory +import com.woka.shop.viewmodels.ShopViewModel +import com.woka.utils.hide +import com.woka.utils.show + +class ShopFragment1 private constructor(): Fragment() { + + private lateinit var binding: FragmentShop1Binding + private lateinit var viewModel: ShopViewModel + private lateinit var adapter: CategoryAdapter + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentShop1Binding.inflate(inflater, container, false) + viewModel = ViewModelProvider(requireActivity())[ShopViewModel::class.java] + adapter = CategoryAdapter() + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + initViews() + + clickEvents() + + setObservers() + + if (!viewModel.superCategoryLiveData.isInitialized || + viewModel.superCategoryLiveData.value !is ApiResult.Success) { + + viewModel.loadSuperCategories() + } + } + + private fun initViews() { + binding.apply { + rvSuperCategory.adapter = adapter + } + } + + private fun clickEvents() { + binding.apply { + retryButton.setOnClickListener { + viewModel.loadSuperCategories() + } + + adapter.onCategoryClickListener = { + parentFragmentManager.beginTransaction() + .replace(R.id.fcv_shop, ShopFragment2.newInstance("${it.id}")) + .addToBackStack(null) + .commitAllowingStateLoss() + } + } + } + + private fun setObservers() { + with(binding) { + viewModel.superCategoryLiveData.observe(viewLifecycleOwner) { + when (it) { + is ApiResult.Error -> { + rvSuperCategory.hide() + shimmer.hide() + errorView.show() + } + + is ApiResult.Loading -> { + rvSuperCategory.hide() + shimmer.show() + errorView.hide() + } + + is ApiResult.Success -> { + it.data?.let { categoryList -> + adapter.submitList(categoryList.map { category -> + BaseCategory( + category.id, + category.super_category_thumbnail, + category.super_category_name + ) + } + ) { + rvSuperCategory.show() + + shimmer.hide() + errorView.hide() + } + } + } + } + } + } + } + + companion object { + @JvmStatic + fun newInstance() = ShopFragment1() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/shop/views/fragments/ShopFragment2.kt b/app/src/main/java/com/woka/shop/views/fragments/ShopFragment2.kt new file mode 100644 index 0000000..a140d85 --- /dev/null +++ b/app/src/main/java/com/woka/shop/views/fragments/ShopFragment2.kt @@ -0,0 +1,114 @@ +package com.woka.shop.views.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import com.woka.R +import com.woka.databinding.FragmentShop2Binding +import com.woka.networking.ApiResult +import com.woka.shop.adapters.CategoryAdapter +import com.woka.shop.models.BaseCategory +import com.woka.shop.viewmodels.ShopViewModel +import com.woka.utils.hide +import com.woka.utils.show + +class ShopFragment2 private constructor(private val superCategoryId: String): Fragment() { + + private lateinit var binding: FragmentShop2Binding + private lateinit var viewModel: ShopViewModel + private lateinit var adapter: CategoryAdapter + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentShop2Binding.inflate(inflater, container, false) + viewModel = ViewModelProvider(requireActivity())[ShopViewModel::class.java] + adapter = CategoryAdapter() + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + initViews() + + clickEvents() + + setObservers() + + viewModel.loadCategories(superCategoryId) + } + + override fun onDestroyView() { + super.onDestroyView() + viewModel.clearCategoryLiveData() + } + + private fun initViews() { + binding.apply { + rvCategory.adapter = adapter + } + } + + private fun clickEvents() { + binding.apply { + retryButton.setOnClickListener { + viewModel.loadCategories(superCategoryId) + } + + adapter.onCategoryClickListener = { + parentFragmentManager.beginTransaction() + .replace(R.id.fcv_shop, ShopFragment3.newInstance("${it.id}")) + .addToBackStack(null) + .commitAllowingStateLoss() + } + } + } + + private fun setObservers() { + with(binding) { + viewModel.categoryLiveData.observe(viewLifecycleOwner) { + when (it) { + is ApiResult.Error -> { + rvCategory.hide() + shimmer.hide() + errorView.show() + } + + is ApiResult.Loading -> { + rvCategory.hide() + shimmer.show() + errorView.hide() + } + + is ApiResult.Success -> { + it.data?.let { categoryList -> + adapter.submitList(categoryList.map { category -> + BaseCategory( + category.id, + category.category_thumbnail, + category.category_name + ) + } + ) { + rvCategory.show() + + shimmer.hide() + errorView.hide() + } + } + } + } + } + } + } + + companion object { + @JvmStatic + fun newInstance(superCategoryId: String) = ShopFragment2(superCategoryId) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/shop/views/fragments/ShopFragment3.kt b/app/src/main/java/com/woka/shop/views/fragments/ShopFragment3.kt new file mode 100644 index 0000000..b98d998 --- /dev/null +++ b/app/src/main/java/com/woka/shop/views/fragments/ShopFragment3.kt @@ -0,0 +1,110 @@ +package com.woka.shop.views.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import com.woka.databinding.FragmentShop3Binding +import com.woka.networking.ApiResult +import com.woka.shop.adapters.CategoryAdapter +import com.woka.shop.models.BaseCategory +import com.woka.shop.viewmodels.ShopViewModel +import com.woka.utils.hide +import com.woka.utils.show + +class ShopFragment3 private constructor(private val categoryId: String): Fragment() { + + private lateinit var binding: FragmentShop3Binding + private lateinit var viewModel: ShopViewModel + private lateinit var adapter: CategoryAdapter + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentShop3Binding.inflate(inflater, container, false) + viewModel = ViewModelProvider(requireActivity())[ShopViewModel::class.java] + adapter = CategoryAdapter() + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + initViews() + + clickEvents() + + setObservers() + + viewModel.loadSubCategories(categoryId) + } + + override fun onDestroyView() { + super.onDestroyView() + viewModel.clearSubCategoryLiveData() + } + + private fun initViews() { + binding.apply { + rvCategory.adapter = adapter + } + } + + private fun clickEvents() { + binding.apply { + retryButton.setOnClickListener { + viewModel.loadSubCategories(categoryId) + } + + adapter.onCategoryClickListener = { + + } + } + } + + private fun setObservers() { + with(binding) { + viewModel.subcategoryLiveData.observe(viewLifecycleOwner) { + when (it) { + is ApiResult.Error -> { + rvCategory.hide() + shimmer.hide() + errorView.show() + } + + is ApiResult.Loading -> { + rvCategory.hide() + shimmer.show() + errorView.hide() + } + + is ApiResult.Success -> { + it.data?.let { categoryList -> + adapter.submitList(categoryList.map { category -> + BaseCategory( + category.id, + category.sub_category_thumbnail, + category.sub_category_name + ) + } + ) { + rvCategory.show() + + shimmer.hide() + errorView.hide() + } + } + } + } + } + } + } + + companion object { + @JvmStatic + fun newInstance(categoryId: String) = ShopFragment3(categoryId) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_shop.xml b/app/src/main/res/layout/activity_shop.xml new file mode 100644 index 0000000..0055282 --- /dev/null +++ b/app/src/main/res/layout/activity_shop.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/category_viwe_holder.xml b/app/src/main/res/layout/category_viwe_holder.xml new file mode 100644 index 0000000..65375c3 --- /dev/null +++ b/app/src/main/res/layout/category_viwe_holder.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_shop1.xml b/app/src/main/res/layout/fragment_shop1.xml new file mode 100644 index 0000000..343090e --- /dev/null +++ b/app/src/main/res/layout/fragment_shop1.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +