diff --git a/app/src/main/java/com/woka/shop/ShopApiService.kt b/app/src/main/java/com/woka/shop/ShopApiService.kt index 0cfb16b..1274002 100644 --- a/app/src/main/java/com/woka/shop/ShopApiService.kt +++ b/app/src/main/java/com/woka/shop/ShopApiService.kt @@ -42,6 +42,9 @@ interface ShopApiService { @POST("remove_cart") suspend fun removeCartItem(@Body formBody: FormBody): Response> + @POST("add_cart") + suspend fun addToCart(@Body formBody: FormBody): Response> + @GET("coupon_listing") suspend fun couponsListing(): Response> diff --git a/app/src/main/java/com/woka/shop/ShopRepository.kt b/app/src/main/java/com/woka/shop/ShopRepository.kt index d5f3e87..9ed633e 100644 --- a/app/src/main/java/com/woka/shop/ShopRepository.kt +++ b/app/src/main/java/com/woka/shop/ShopRepository.kt @@ -1,5 +1,7 @@ package com.woka.shop +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import com.woka.networking.ApiResult import com.woka.networking.RetrofitHelper import com.woka.networking.RetrofitHelper.handleApiCall @@ -7,6 +9,7 @@ import com.woka.shop.models.addaddress.AddAddressRequestData import com.woka.shop.models.addaddress.AddAddressResponseData import com.woka.shop.models.addresslisting.ParentAddressData import com.woka.shop.models.applycoupon.ApplyCouponResponse +import com.woka.shop.models.cartlisting.CartItem import com.woka.shop.models.cartlisting.CartResponse import com.woka.shop.models.categorylisting.CategoryResponse import com.woka.shop.models.couponlisting.CouponsResponse @@ -14,6 +17,7 @@ import com.woka.shop.models.createorder.CreateOrderRequestData import com.woka.shop.models.createorder.CreateOrderResponse import com.woka.shop.models.edd.EDDResponse import com.woka.shop.models.productlisting.ProductListingResponse +import com.woka.shop.models.productlisting.ShopProduct import com.woka.shop.models.subcategorylisting.SubCategoryResponse import com.woka.shop.models.superlisting.SuperCategoryResponse import okhttp3.FormBody @@ -74,6 +78,10 @@ object ShopRepository { // cart listing with loose caching private var cartResponse: CartResponse? = null + private val _cartCountLiveData = MutableLiveData() + val cartCountLiveData: LiveData + get() = _cartCountLiveData + suspend fun cartListing(): ApiResult { if (cartResponse != null) { return ApiResult.Success(cartResponse) @@ -88,12 +96,50 @@ object ShopRepository { is ApiResult.Loading -> {} is ApiResult.Success -> { cartResponse = response.data + response.data?.result?.let { + _cartCountLiveData.postValue(it.size) + } } } return response } + suspend fun addToCart(shopProduct: ShopProduct): ApiResult{ + val response = handleApiCall { + apiService.addToCart( + FormBody.Builder() + .add("shop_master_id", "${shopProduct.id}") + .build() + ) + } + + when (response) { + is ApiResult.Error -> {} + is ApiResult.Loading -> {} + is ApiResult.Success -> { + if (cartResponse == null){ + cartListing() + }else{ + // removing from cache + cartResponse?.let { cartData -> + cartData.result?.let { cartItems -> + cartItems.add(CartItem(shopProduct)) + shopProduct.product_final_price?.let { + cartData.total_amount = (cartData.total_amount?:0.0) + it + } + + cartItems.let { + _cartCountLiveData.postValue(it.size) + } + } + } + } + } + } + return response + } + suspend fun removeCartItem(id: Int): ApiResult { val response = handleApiCall { apiService.removeCartItem( @@ -120,6 +166,10 @@ object ShopRepository { } cartItems.removeIf { it?.id == id } + + cartItems.let { + _cartCountLiveData.postValue(it.size) + } } } diff --git a/app/src/main/java/com/woka/shop/adapters/CartAdapter.kt b/app/src/main/java/com/woka/shop/adapters/CartAdapter.kt index 143a716..30ea52d 100644 --- a/app/src/main/java/com/woka/shop/adapters/CartAdapter.kt +++ b/app/src/main/java/com/woka/shop/adapters/CartAdapter.kt @@ -26,7 +26,7 @@ class CartAdapter: ListAdapter(ASYNC_DIFF_ inner class CartViewHolder(val binding: CartItemViewHolderBinding): ViewHolder(binding.root) - var onCartItemDeleteListener: ((Int, Int) -> Unit)? = null + var onCartItemDeleteListener: ((CartItem, Int) -> Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartViewHolder { return CartViewHolder( @@ -55,7 +55,7 @@ class CartAdapter: ListAdapter(ASYNC_DIFF_ delete.show() delete.setOnClickListener { - cartItem.id?.let { id -> onCartItemDeleteListener?.invoke(id, holder.absoluteAdapterPosition) } + onCartItemDeleteListener?.invoke(cartItem, holder.absoluteAdapterPosition) } } } diff --git a/app/src/main/java/com/woka/shop/adapters/ShopProductAdapter.kt b/app/src/main/java/com/woka/shop/adapters/ShopProductAdapter.kt index a908668..cff5ae9 100644 --- a/app/src/main/java/com/woka/shop/adapters/ShopProductAdapter.kt +++ b/app/src/main/java/com/woka/shop/adapters/ShopProductAdapter.kt @@ -43,7 +43,12 @@ class ShopProductAdapter: ListAdapter?, @@ -13,6 +19,33 @@ data class ShopProduct( val sku_id: String?, val stock_status: String?, val sub_category_master_id: Int?, - val tax_category: Any?, val tax_value: String? -) \ No newline at end of file +): Parcelable{ + + constructor(cartItem: CartItem, added_to_cart: Boolean?): this( + added_to_cart, + cartItem.category_master_id, + cartItem.id, + cartItem.product_name, + cartItem.product_price, + cartItem.product_final_price, + null, + cartItem.remain_stock_quantity, + cartItem.shop_image, + cartItem.shop_master_detail, + cartItem.sku_id, + cartItem.stock_status, + cartItem.sub_category_master_id, + cartItem.tax_value + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ShopProduct) return false + return other.id == this.id + } + + override fun hashCode(): Int { + return this.id?:0 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/woka/shop/viewmodels/CartViewModel.kt b/app/src/main/java/com/woka/shop/viewmodels/CartViewModel.kt index 151e4ca..8e535f6 100644 --- a/app/src/main/java/com/woka/shop/viewmodels/CartViewModel.kt +++ b/app/src/main/java/com/woka/shop/viewmodels/CartViewModel.kt @@ -12,6 +12,7 @@ import com.woka.shop.models.cartlisting.CartResponse import com.woka.shop.models.couponlisting.CouponsResponse import com.woka.shop.models.createorder.CreateOrderRequestData import com.woka.shop.models.createorder.CreateOrderResponse +import com.woka.shop.models.productlisting.ShopProduct import kotlinx.coroutines.async import kotlinx.coroutines.launch @@ -30,6 +31,8 @@ class CartViewModel: ViewModel() { var selectedAddressId: Int? = null val eddMap = HashMap() + // cart + val removedProducts = ArrayList() // data callbacks private val repository = ShopRepository 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 5709861..6de2fba 100644 --- a/app/src/main/java/com/woka/shop/viewmodels/ShopViewModel.kt +++ b/app/src/main/java/com/woka/shop/viewmodels/ShopViewModel.kt @@ -1,5 +1,6 @@ package com.woka.shop.viewmodels +import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -11,7 +12,9 @@ import com.woka.shop.models.productlisting.ShopProduct import com.woka.shop.models.subcategorylisting.SubCategoryData import com.woka.shop.models.superlisting.SuperCategory import com.woka.utils.PagingData +import com.woka.utils.TAG import kotlinx.coroutines.launch +import kotlin.collections.HashSet class ShopViewModel: ViewModel() { @@ -105,14 +108,14 @@ class ShopViewModel: ViewModel() { } // product listing - private val _productListingLiveData = MutableLiveData>>() - val productListingLiveData: LiveData>> + private val _productListingLiveData = MutableLiveData>>() + val productListingLiveData: LiveData>> get() = _productListingLiveData var productPagingData = HashMap() // product data for every super-category, category and sub-category - private var productDataMap = HashMap>() + private var productDataMap = HashMap>() fun loadProducts(superCategoryId: String, categoryId: String, subCategoryId: String?) { val key = "${superCategoryId}_${categoryId}_$subCategoryId" @@ -159,7 +162,7 @@ class ShopViewModel: ViewModel() { is ApiResult.Success -> { response.data?.let { data -> data.result?.filterNotNull()?.let { newList -> - val currentList = productDataMap.getOrDefault(key, ArrayList()) + val currentList = productDataMap.getOrDefault(key, HashSet()) currentList.addAll(newList) productDataMap[key] = currentList @@ -173,7 +176,39 @@ class ShopViewModel: ViewModel() { } } + fun removeProductsFromCart(products: List){ + val toBeReplaced = mutableSetOf() + + for (productSet in productDataMap.values){ + + toBeReplaced.clear() + + for (product in products){ + if (productSet.contains(product)){ + toBeReplaced.add(product) + } + } + + productSet.removeAll(toBeReplaced) + productSet.addAll(toBeReplaced) + } + + _productListingLiveData.postValue(ApiResult.Success(productDataMap[""])) + } + fun clearProductListingLiveData() { _productListingLiveData.postValue(ApiResult.Loading()) } + + // cart + val cartCountLivedata: LiveData + get() { + viewModelScope.launch { + repository.cartListing() + } + return repository.cartCountLiveData + } + suspend fun addToCart(shopProduct: ShopProduct): ApiResult { + return repository.addToCart(shopProduct) + } } \ No newline at end of file diff --git a/app/src/main/java/com/woka/shop/views/CartActivity.kt b/app/src/main/java/com/woka/shop/views/CartActivity.kt index 1da950c..cabfad3 100644 --- a/app/src/main/java/com/woka/shop/views/CartActivity.kt +++ b/app/src/main/java/com/woka/shop/views/CartActivity.kt @@ -11,6 +11,10 @@ import com.woka.utils.WokaBaseActivity class CartActivity : WokaBaseActivity() { + companion object{ + const val EXTRA_REMOVED_CART_ITEMS = "extra_removed_cart_items" + } + private lateinit var binding: ActivityCartBinding private lateinit var viewModel: CartViewModel diff --git a/app/src/main/java/com/woka/shop/views/ShopActivity.kt b/app/src/main/java/com/woka/shop/views/ShopActivity.kt index 1da9970..ef2f6bf 100644 --- a/app/src/main/java/com/woka/shop/views/ShopActivity.kt +++ b/app/src/main/java/com/woka/shop/views/ShopActivity.kt @@ -5,14 +5,18 @@ import android.os.Bundle import androidx.activity.enableEdgeToEdge import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.lifecycle.ViewModelProvider import com.woka.R import com.woka.databinding.ActivityShopBinding +import com.woka.shop.viewmodels.ShopViewModel import com.woka.shop.views.fragments.shop.ShopFragment1 import com.woka.utils.WokaBaseActivity +import com.woka.utils.setVisibility class ShopActivity : WokaBaseActivity() { private lateinit var binding: ActivityShopBinding + private lateinit var viewModel: ShopViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -25,6 +29,8 @@ class ShopActivity : WokaBaseActivity() { insets } + viewModel = ViewModelProvider(this)[ShopViewModel::class.java] + window.navigationBarColor = getColor(R.color.orders_bg) supportFragmentManager.beginTransaction() @@ -34,6 +40,8 @@ class ShopActivity : WokaBaseActivity() { initViews() clickEvents() + + setObservers() } private fun initViews(){ @@ -55,4 +63,11 @@ class ShopActivity : WokaBaseActivity() { } } } + + private fun setObservers() { + viewModel.cartCountLivedata.observe(this){ + binding.cartCount.text = "$it" + binding.cartCountView.setVisibility(it > 0) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/woka/shop/views/fragments/cart/CartFragment.kt b/app/src/main/java/com/woka/shop/views/fragments/cart/CartFragment.kt index a7ba799..6d0fa07 100644 --- a/app/src/main/java/com/woka/shop/views/fragments/cart/CartFragment.kt +++ b/app/src/main/java/com/woka/shop/views/fragments/cart/CartFragment.kt @@ -1,9 +1,11 @@ package com.woka.shop.views.fragments.cart +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -12,7 +14,9 @@ import com.woka.R import com.woka.databinding.FragmentCartBinding import com.woka.networking.ApiResult import com.woka.shop.adapters.CartAdapter +import com.woka.shop.models.productlisting.ShopProduct import com.woka.shop.viewmodels.CartViewModel +import com.woka.shop.views.CartActivity import com.woka.utils.ProgressView import com.woka.utils.hide import com.woka.utils.show @@ -65,35 +69,42 @@ class CartFragment: Fragment() { private fun clickEvents() { binding.apply { - adapter.onCartItemDeleteListener = {id, position -> + adapter.onCartItemDeleteListener = {cartItem, position -> lifecycleScope.launch { progressDialog.show(getString(R.string.removing_item)) - when (val response = viewModel.removeItem(id)){ - is ApiResult.Error -> { - progressDialog.hide() - toast(response.errorMessage) - } - is ApiResult.Loading -> {} - is ApiResult.Success -> { - progressDialog.hide() - toast(response.message) + cartItem.id?.let { + when (val response = viewModel.removeItem(it)){ + is ApiResult.Error -> { + progressDialog.hide() + toast(response.errorMessage) + } + is ApiResult.Loading -> {} + is ApiResult.Success -> { + progressDialog.hide() + toast(response.message) - try { - adapter.notifyItemRemoved(position) - } finally { - response.data?.let {cartValue -> - if (cartValue > 0){ - val finalAmount = "₹ $cartValue" - totalAmount.text = finalAmount - }else{ - rvCart.hide() - progressView.hide() - checkoutView.hide() + try { + adapter.notifyItemRemoved(position) + } finally { + response.data?.let {cartValue -> + if (cartValue > 0){ + val finalAmount = "₹ $cartValue" + totalAmount.text = finalAmount + }else{ + rvCart.hide() + progressView.hide() + checkoutView.hide() - noDataView.show() + noDataView.show() + } } } + + viewModel.removedProducts.add(ShopProduct(cartItem, false)) + activity?.setResult(AppCompatActivity.RESULT_OK, Intent().apply { + putParcelableArrayListExtra(CartActivity.EXTRA_REMOVED_CART_ITEMS, viewModel.removedProducts) + }) } } } diff --git a/app/src/main/java/com/woka/shop/views/fragments/shop/ProductFragment.kt b/app/src/main/java/com/woka/shop/views/fragments/shop/ProductFragment.kt index 86c9e2e..31a917b 100644 --- a/app/src/main/java/com/woka/shop/views/fragments/shop/ProductFragment.kt +++ b/app/src/main/java/com/woka/shop/views/fragments/shop/ProductFragment.kt @@ -1,16 +1,28 @@ package com.woka.shop.views.fragments.shop +import android.app.Activity.RESULT_OK +import android.content.Intent import android.os.Bundle import android.text.Html -import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import com.google.android.material.tabs.TabLayoutMediator import com.woka.R import com.woka.databinding.FragmentProductBinding +import com.woka.networking.ApiResult import com.woka.shop.adapters.ProductImagesAdapter import com.woka.shop.models.productlisting.ShopProduct +import com.woka.shop.viewmodels.ShopViewModel +import com.woka.shop.views.CartActivity +import com.woka.utils.ProgressView +import com.woka.utils.toast +import kotlinx.coroutines.launch class ProductFragment private constructor( private val shopProduct: ShopProduct, @@ -23,18 +35,26 @@ class ProductFragment private constructor( } private lateinit var binding: FragmentProductBinding + private lateinit var viewModel: ShopViewModel private lateinit var imageAdapter: ProductImagesAdapter + private lateinit var progressView: ProgressView + + private lateinit var cartLauncher: ActivityResultLauncher override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { binding = FragmentProductBinding.inflate(inflater, container, false) + viewModel = ViewModelProvider(requireActivity())[ShopViewModel::class.java] + progressView = ProgressView(requireContext(), getString(R.string.please_wait)) + val imageList = ArrayList() shopProduct.shop_image?.filterNotNull()?.let { imageList.addAll(it) } imageAdapter = ProductImagesAdapter(imageList) + return binding.root } @@ -43,6 +63,10 @@ class ProductFragment private constructor( initViews() + clickEvents() + + registerLaunchers() + } private fun initViews() { @@ -52,15 +76,77 @@ class ProductFragment private constructor( TabLayoutMediator(tabLayout, vpImages){_, _ -> }.attach() - title.text = shopProduct.product_name categoryName.text = category skuId.text = shopProduct.sku_id price.text = shopProduct.product_price - description.text = Html.fromHtml( - shopProduct.shop_master_detail?.description_english, - Html.FROM_HTML_MODE_LEGACY - ) + addToCart.text = if (shopProduct.added_to_cart == true){ + getString(R.string.view_cart) + }else{ + getString(R.string.add_to_cart) + } + + if (shopProduct.sub_category_master_id == 12){ + title.text = shopProduct.shop_master_detail?.product_name_english + description.text = Html.fromHtml( + shopProduct.shop_master_detail?.description_english, + Html.FROM_HTML_MODE_LEGACY + ) + }else{ + title.text = shopProduct.shop_master_detail?.product_name_hindi + description.text = Html.fromHtml( + shopProduct.shop_master_detail?.description_hindi, + Html.FROM_HTML_MODE_LEGACY + ) + } + } + } + + private fun clickEvents(){ + binding.apply { + addToCart.setOnClickListener { + if (shopProduct.added_to_cart == false){ + lifecycleScope.launch { + progressView.show() + when (val response = viewModel.addToCart(shopProduct)){ + is ApiResult.Error -> { + progressView.hide() + toast(response.errorMessage) + } + is ApiResult.Loading -> {} + is ApiResult.Success -> { + progressView.hide() + toast(response.message) + + shopProduct.added_to_cart = true + addToCart.text = getString(R.string.view_cart) + } + } + } + }else{ + activity?.let { + cartLauncher.launch(Intent(it, CartActivity::class.java)) + } + } + } + } + } + + private fun registerLaunchers(){ + cartLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ activityResult -> + if (activityResult.resultCode == RESULT_OK){ + @Suppress("DEPRECATION") + activityResult.data?.getParcelableArrayListExtra(CartActivity.EXTRA_REMOVED_CART_ITEMS)?.let { + viewModel.removeProductsFromCart(it) + if (it.toSet().contains(shopProduct)){ + binding.addToCart.text = if (shopProduct.added_to_cart == true){ + getString(R.string.view_cart) + }else{ + getString(R.string.add_to_cart) + } + } + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/woka/shop/views/fragments/shop/ShopFragment3.kt b/app/src/main/java/com/woka/shop/views/fragments/shop/ShopFragment3.kt index 5efe07d..aed02f7 100644 --- a/app/src/main/java/com/woka/shop/views/fragments/shop/ShopFragment3.kt +++ b/app/src/main/java/com/woka/shop/views/fragments/shop/ShopFragment3.kt @@ -160,7 +160,7 @@ class ShopFragment3 private constructor( } is ApiResult.Success -> { - it.data?.let { productList -> + it.data?.toMutableList()?.let { productList -> binding.rvProducts.show() binding.productShimmer.hide() diff --git a/app/src/main/res/drawable-hdpi/img_cart.png b/app/src/main/res/drawable-hdpi/img_cart.png new file mode 100644 index 0000000..fe9093d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/img_cart.png differ diff --git a/app/src/main/res/drawable-ldpi/img_cart.png b/app/src/main/res/drawable-ldpi/img_cart.png new file mode 100644 index 0000000..e58e385 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/img_cart.png differ diff --git a/app/src/main/res/drawable-mdpi/img_cart.png b/app/src/main/res/drawable-mdpi/img_cart.png new file mode 100644 index 0000000..311214d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/img_cart.png differ diff --git a/app/src/main/res/drawable-xhdpi/img_cart.png b/app/src/main/res/drawable-xhdpi/img_cart.png new file mode 100644 index 0000000..2484ef4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/img_cart.png differ diff --git a/app/src/main/res/drawable-xxhdpi/img_cart.png b/app/src/main/res/drawable-xxhdpi/img_cart.png new file mode 100644 index 0000000..8d4e9ae Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/img_cart.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/img_cart.png b/app/src/main/res/drawable-xxxhdpi/img_cart.png new file mode 100644 index 0000000..6b64df4 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/img_cart.png differ diff --git a/app/src/main/res/drawable/default_shop_dot.xml b/app/src/main/res/drawable/default_shop_dot.xml new file mode 100644 index 0000000..9b222e9 --- /dev/null +++ b/app/src/main/res/drawable/default_shop_dot.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/product_indicator_selector.xml b/app/src/main/res/drawable/product_indicator_selector.xml new file mode 100644 index 0000000..dad2853 --- /dev/null +++ b/app/src/main/res/drawable/product_indicator_selector.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selected_shop_dot.xml b/app/src/main/res/drawable/selected_shop_dot.xml new file mode 100644 index 0000000..fd2343f --- /dev/null +++ b/app/src/main/res/drawable/selected_shop_dot.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ 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 index 0055282..9cc207b 100644 --- a/app/src/main/res/layout/activity_shop.xml +++ b/app/src/main/res/layout/activity_shop.xml @@ -57,11 +57,11 @@ @@ -61,11 +61,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" - app:tabBackground="@drawable/onboard_indicator_selector" + app:tabBackground="@drawable/product_indicator_selector" app:tabGravity="center" app:tabIndicatorHeight="0dp" app:tabPaddingEnd="10dp" app:tabPaddingStart="10dp" + app:tabRippleColor="@android:color/transparent" android:background="@android:color/transparent" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cd86496..2da0fbb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -281,4 +281,5 @@ Category Name : SKU Id : ADD TO CART + view cart \ No newline at end of file