Last active
December 4, 2019 21:56
-
-
Save Ajimi/6505df8a63a9cc2e951ac043b6e278b0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.esprit.core.util | |
| open class Event<out T>(private val content: T) { | |
| var consumed = false | |
| private set // Allow external read but not write | |
| /** | |
| * Consumes the content if it's not been consumed yet. | |
| * @return The unconsumed content or `null` if it was consumed already. | |
| */ | |
| fun consume(): T? { | |
| return if (consumed) { | |
| null | |
| } else { | |
| consumed = true | |
| content | |
| } | |
| } | |
| /** | |
| * @return The content whether it's been handled or not. | |
| */ | |
| fun peek(): T = content | |
| override fun equals(other: Any?): Boolean { | |
| if (this === other) return true | |
| if (javaClass != other?.javaClass) return false | |
| other as Event<*> | |
| if (content != other.content) return false | |
| if (consumed != other.consumed) return false | |
| return true | |
| } | |
| override fun hashCode(): Int { | |
| var result = content?.hashCode() ?: 0 | |
| result = 31 * result + consumed.hashCode() | |
| return result | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.esprit.core.util | |
| import androidx.lifecycle.Observer | |
| class EventObserver<T>(private val onEventUnconsumedContent: (T) -> Unit) : Observer<Event<T>> { | |
| override fun onChanged(event: Event<T>?) { | |
| event?.consume()?.run(onEventUnconsumedContent) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.esprit.core.extensions | |
| import androidx.appcompat.app.AppCompatActivity | |
| import androidx.fragment.app.Fragment | |
| import androidx.lifecycle.ViewModel | |
| import com.esprit.core.util.Event | |
| fun <T> ViewModel.emitUiState( | |
| showProgress: Boolean = false, | |
| showError: Event<String>? = null, | |
| showSuccess: Event<T>? = null | |
| ): UiState<T>? = UiState<T>( | |
| showProgress = showProgress, | |
| showError = showError, | |
| showSuccess = showSuccess | |
| ) | |
| data class UiState<T>( | |
| var showProgress: Boolean, | |
| var showError: Event<String>?, | |
| var showSuccess: Event<T>? | |
| ) | |
| fun <T> Fragment.observeUIState( | |
| uiModel: UiState<T>, | |
| progress: (Boolean) -> Unit, | |
| error: (String) -> Unit, | |
| success: (T) -> Unit | |
| ) { | |
| progress(uiModel.showProgress) | |
| if (uiModel.showError != null && !uiModel.showError!!.consumed) { | |
| uiModel.showError!!.consume()?.let { | |
| error(it) | |
| } | |
| } | |
| if (uiModel.showSuccess != null && !uiModel.showSuccess!!.consumed) { | |
| uiModel.showSuccess!!.consume()?.let { | |
| success(it) | |
| } | |
| } | |
| } | |
| fun <T> AppCompatActivity.observeUIState( | |
| uiModel: UiState<T>, | |
| progress: (Boolean) -> Unit, | |
| error: (String) -> Unit, | |
| success: (T) -> Unit | |
| ) { | |
| progress(uiModel.showProgress) | |
| if (uiModel.showError != null && !uiModel.showError!!.consumed) { | |
| uiModel.showError!!.consume()?.let { | |
| error(it) | |
| } | |
| } | |
| if (uiModel.showSuccess != null && !uiModel.showSuccess!!.consumed) { | |
| uiModel.showSuccess!!.consume()?.let { | |
| success(it) | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.esprit.marketo.ui.home | |
| import android.content.BroadcastReceiver | |
| import android.content.Context | |
| import android.content.Intent | |
| import android.content.IntentFilter | |
| import android.net.ConnectivityManager | |
| import android.os.Bundle | |
| import android.view.LayoutInflater | |
| import android.view.View | |
| import android.view.ViewGroup | |
| import androidx.fragment.app.Fragment | |
| import androidx.lifecycle.Observer | |
| import androidx.recyclerview.widget.LinearLayoutManager | |
| import com.esprit.core.data.pavilion.model.Pavilion | |
| import com.esprit.core.data.products.model.Product | |
| import com.esprit.core.extensions.observeUIState | |
| import com.esprit.core.util.EventObserver | |
| import com.esprit.core.util.gone | |
| import com.esprit.core.util.visible | |
| import com.esprit.marketo.GlideApp | |
| import com.esprit.marketo.R | |
| import com.esprit.marketo.ui.MainActivity | |
| import com.esprit.marketo.ui.MainNavigationFragment | |
| import com.esprit.marketo.ui.MainViewModel | |
| import com.esprit.marketo.ui.category.CategoryActivity | |
| import com.esprit.marketo.ui.home.adapters.ProductDiscountItem | |
| import com.esprit.marketo.ui.home.adapters.ProductTrendingItem | |
| import com.esprit.marketo.ui.pavilion.adapters.PavilionItem | |
| import com.esprit.marketo.ui.productdetail.ProductDetailActivity | |
| import com.google.android.material.snackbar.Snackbar | |
| import com.synnapps.carouselview.ImageListener | |
| import com.xwray.groupie.GroupAdapter | |
| import com.xwray.groupie.ViewHolder | |
| import kotlinx.android.synthetic.main.error_network_state.* | |
| import kotlinx.android.synthetic.main.fragment_home.* | |
| import kotlinx.android.synthetic.main.include_category_home.* | |
| import kotlinx.android.synthetic.main.include_discount_product_home.* | |
| import kotlinx.android.synthetic.main.include_image_carousel.* | |
| import kotlinx.android.synthetic.main.include_trending_product_home.* | |
| import org.koin.androidx.viewmodel.ext.android.sharedViewModel | |
| import org.koin.androidx.viewmodel.ext.android.viewModel | |
| class HomeFragment : Fragment(), MainNavigationFragment { | |
| val homeViewModel: HomeViewModel by viewModel() | |
| val mainViewModel: MainViewModel by sharedViewModel() | |
| private val trendingProductAdapter = GroupAdapter<ViewHolder>() | |
| private val discountedProductAdapter = GroupAdapter<ViewHolder>() | |
| private val pavilionAdapter = GroupAdapter<ViewHolder>() | |
| private var connectionStateReceiver: BroadcastReceiver = object : BroadcastReceiver() { | |
| override fun onReceive(context: Context, intent: Intent) { | |
| val notConnected = intent.getBooleanExtra( | |
| ConnectivityManager | |
| .EXTRA_NO_CONNECTIVITY, false | |
| ) | |
| if (notConnected) { | |
| // showErrorState() | |
| } else { | |
| homeViewModel.updateUI() | |
| hideErrorState() | |
| } | |
| } | |
| } | |
| override fun onCreateView( | |
| inflater: LayoutInflater, container: ViewGroup?, | |
| savedInstanceState: Bundle? | |
| ): View? { | |
| return inflater.inflate(R.layout.fragment_home, container, false) | |
| } | |
| override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | |
| super.onViewCreated(view, savedInstanceState) | |
| setupAppbar() | |
| initUI() | |
| homeViewModel.uiDiscountedState.observe(this, Observer { | |
| val uiModel = it ?: return@Observer | |
| observeUIState(uiModel, { | |
| if (it) { | |
| discount_product_home_recycler_view.gone() | |
| discount_shimmer.apply { | |
| visible() | |
| startShimmer() | |
| } | |
| } else { | |
| discount_product_home_recycler_view.visible() | |
| discount_shimmer.apply { | |
| gone() | |
| stopShimmer() | |
| } | |
| swipe_refresh_home.isRefreshing = false | |
| } | |
| }, { | |
| swipe_refresh_home.isRefreshing = false | |
| }, { | |
| swipe_refresh_home.isRefreshing = false | |
| discountedProductAdapter.clear() | |
| val discountedProductsItems = | |
| it.map { | |
| ProductDiscountItem( | |
| requireContext(), | |
| it | |
| ) { isChecked, product -> | |
| homeViewModel.toggleFavorite(isChecked, product) | |
| } | |
| } | |
| discountedProductAdapter.addAll(discountedProductsItems) | |
| }) | |
| }) | |
| homeViewModel.uiTrendingState.observe(this, Observer { | |
| val uiModel = it ?: return@Observer | |
| observeUIState(uiModel, { | |
| if (it) { | |
| trending_product_home_recycler_view.gone() | |
| trending_shimmer.apply { | |
| visible() | |
| startShimmer() | |
| } | |
| } else { | |
| trending_product_home_recycler_view.visible() | |
| trending_shimmer.apply { | |
| gone() | |
| stopShimmer() | |
| } | |
| swipe_refresh_home.isRefreshing = false | |
| } | |
| }, { | |
| swipe_refresh_home.isRefreshing = false | |
| }, { products -> | |
| trendingProductAdapter.clear() | |
| if (!products.isEmpty()) { | |
| val trendingProductsItems = (1..5).map { index -> | |
| val productTrendingItem = ProductTrendingItem( | |
| requireContext(), | |
| products[index] | |
| ) { isChecked, product -> | |
| homeViewModel.toggleFavorite(isChecked, product) | |
| } | |
| productTrendingItem | |
| }.toList() | |
| trendingProductAdapter.addAll(trendingProductsItems) | |
| } | |
| }) | |
| }) | |
| homeViewModel.uiProductState.observe(this, Observer { | |
| val uiModel = it ?: return@Observer | |
| observeUIState(uiModel, { | |
| }, { | |
| }, { | |
| }) | |
| }) | |
| homeViewModel.uiPavilionsState.observe(this, Observer { | |
| val uiModel = it ?: return@Observer | |
| observeUIState(uiModel, { | |
| if (it) { | |
| pavilion_recycler_view.gone() | |
| pavilion_shimmer.apply { | |
| visible() | |
| startShimmer() | |
| } | |
| } else { | |
| pavilion_recycler_view.visible() | |
| pavilion_shimmer.apply { | |
| gone() | |
| stopShimmer() | |
| } | |
| swipe_refresh_home.isRefreshing = false | |
| } | |
| }, { | |
| swipe_refresh_home.isRefreshing = false | |
| }, { | |
| pavilionAdapter.clear() | |
| it.forEach { | |
| pavilionAdapter.add(PavilionItem(it, requireContext())) | |
| } | |
| swipe_refresh_home.isRefreshing = false | |
| }) | |
| }) | |
| homeViewModel.shouldDisplayError.observe(this, EventObserver { | |
| if (it) { | |
| showErrorState() | |
| } else { | |
| hideErrorState() | |
| } | |
| }) | |
| layout_no_connection.setOnClickListener { | |
| layout_no_connection.gone() | |
| progress_bar.visible() | |
| homeViewModel.updateUI() | |
| } | |
| swipe_refresh_home.setOnRefreshListener { | |
| homeViewModel.updateUI() | |
| } | |
| trendingProductAdapter.setOnItemClickListener { item, _ -> | |
| homeViewModel.onTrendingProductClicked((item as ProductTrendingItem).product) | |
| } | |
| discountedProductAdapter.setOnItemClickListener { item, _ -> | |
| homeViewModel.onDiscountedProductClicked((item as ProductDiscountItem).product) | |
| } | |
| pavilionAdapter.setOnItemClickListener { item, _ -> | |
| homeViewModel.onCategoryClicked((item as PavilionItem).pavilion) | |
| } | |
| homeViewModel.navigateToProductDetailAction.observe(this, EventObserver { product -> | |
| openProductActivity(product) | |
| }) | |
| homeViewModel.navigateToCategoryAction.observe(this, EventObserver { pavilion -> | |
| openCategoryActivity(pavilion) | |
| }) | |
| homeViewModel.uiFavoriteSnackBar.observe(this, EventObserver { | |
| val snackBar = Snackbar.make( | |
| (requireActivity() as MainActivity).findViewById(R.id.navigation), | |
| it, | |
| Snackbar.LENGTH_LONG | |
| ) | |
| snackBar.show() | |
| }) | |
| view_all_categories.setOnClickListener { | |
| mainViewModel.onViewAllPavilionClicked() | |
| } | |
| } | |
| private fun showErrorState() { | |
| swipe_refresh_home.gone() | |
| progress_bar.gone() | |
| network_errorstate.visible() | |
| layout_no_connection.visible() | |
| swipe_refresh_home.isRefreshing = false | |
| } | |
| private fun hideErrorState() { | |
| network_errorstate.gone() | |
| layout_no_connection.gone() | |
| swipe_refresh_home.visible() | |
| swipe_refresh_home.isRefreshing = false | |
| } | |
| private fun openProductActivity(product: Product) { | |
| requireActivity().run { | |
| startActivity(ProductDetailActivity.starterIntent(this, product)) | |
| } | |
| } | |
| private fun openCategoryActivity(pavilion: Pavilion) { | |
| requireActivity().run { | |
| startActivity(CategoryActivity.starterIntent(this, pavilion)) | |
| } | |
| } | |
| private fun initUI() { | |
| initCarousel() | |
| initTrendingProducts() | |
| initDiscountedProducts() | |
| initPavilion() | |
| } | |
| private fun initCarousel() { | |
| val list = listOf(R.drawable.grocery1, R.drawable.grocery2, R.drawable.grocory3) | |
| val imageListener = ImageListener { position, imageView -> | |
| GlideApp | |
| .with(requireContext()) | |
| .load(list[position]) | |
| .into(imageView) | |
| } | |
| carouselView.setImageListener(imageListener) | |
| carouselView.pageCount = list.size | |
| } | |
| private fun initTrendingProducts() { | |
| trending_product_home_recycler_view.apply { | |
| layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) | |
| adapter = trendingProductAdapter | |
| } | |
| } | |
| private fun initDiscountedProducts() { | |
| discount_product_home_recycler_view.apply { | |
| layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) | |
| adapter = discountedProductAdapter | |
| } | |
| } | |
| private fun initPavilion() { | |
| pavilion_recycler_view.apply { | |
| layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) | |
| adapter = pavilionAdapter | |
| } | |
| } | |
| private fun setupAppbar() { | |
| home_toolbar.title = "HOME" | |
| } | |
| override fun onStart() { | |
| super.onStart() | |
| requireContext().registerReceiver( | |
| connectionStateReceiver, | |
| IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) | |
| ) | |
| homeViewModel.updateUI() | |
| } | |
| override fun onPause() { | |
| super.onPause() | |
| carouselView.pauseCarousel() | |
| } | |
| override fun onResume() { | |
| super.onResume() | |
| carouselView.playCarousel() | |
| } | |
| override fun onStop() { | |
| super.onStop() | |
| requireContext().unregisterReceiver(connectionStateReceiver) | |
| } | |
| companion object { | |
| fun newInstance() = HomeFragment() | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.esprit.marketo.ui.home | |
| import androidx.lifecycle.LiveData | |
| import androidx.lifecycle.MutableLiveData | |
| import androidx.lifecycle.ViewModel | |
| import com.esprit.core.data.Result | |
| import com.esprit.core.data.favorite.local.FavoriteProduct | |
| import com.esprit.core.data.pavilion.model.Pavilion | |
| import com.esprit.core.data.products.model.Product | |
| import com.esprit.core.domain.usecases.favorite.AddFavoriteProductUseCase | |
| import com.esprit.core.domain.usecases.favorite.IsFavoriteProductExistUseCase | |
| import com.esprit.core.domain.usecases.favorite.LoadFavoriteProductsUseCase | |
| import com.esprit.core.domain.usecases.favorite.RemoveFavoriteProductUseCase | |
| import com.esprit.core.domain.usecases.pavilion.LoadPavilionUseCase | |
| import com.esprit.core.domain.usecases.products.LoadDiscountedProductUseCase | |
| import com.esprit.core.domain.usecases.products.LoadProductsUseCase | |
| import com.esprit.core.domain.usecases.products.LoadTrendingProductUseCase | |
| import com.esprit.core.extensions.UiState | |
| import com.esprit.core.extensions.emitUiState | |
| import com.esprit.core.util.Event | |
| import kotlinx.coroutines.* | |
| class HomeViewModel( | |
| private val loadProductsUseCase: LoadProductsUseCase, | |
| private val loadTrendingProductUseCase: LoadTrendingProductUseCase, | |
| private val loadDiscountedProductUseCase: LoadDiscountedProductUseCase, | |
| private val loadPavilionUseCase: LoadPavilionUseCase, | |
| private val addFavoriteProductUseCase: AddFavoriteProductUseCase, | |
| private val removeFavoriteProductUseCase: RemoveFavoriteProductUseCase, | |
| private val loadFavoriteProductsUseCase: LoadFavoriteProductsUseCase, | |
| private val isFavoriteProductExistUseCase: IsFavoriteProductExistUseCase | |
| ) : ViewModel(), HomeEventListener { | |
| private val viewModelJob = Job() | |
| private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) | |
| private val _navigateToProductDetailAction = MutableLiveData<Event<Product>>() | |
| val navigateToProductDetailAction: LiveData<Event<Product>> | |
| get() = _navigateToProductDetailAction | |
| private val _navigateToCategoryAction = MutableLiveData<Event<Pavilion>>() | |
| val navigateToCategoryAction: LiveData<Event<Pavilion>> | |
| get() = _navigateToCategoryAction | |
| private val _uiProductState = MutableLiveData<UiState<List<Product>>>() | |
| val uiProductState: LiveData<UiState<List<Product>>> | |
| get() = _uiProductState | |
| private val _uiPavilionsState = MutableLiveData<UiState<List<Pavilion>>>() | |
| val uiPavilionsState: LiveData<UiState<List<Pavilion>>> | |
| get() = _uiPavilionsState | |
| private val _uiTrendingState = MutableLiveData<UiState<List<Product>>>() | |
| val uiTrendingState: LiveData<UiState<List<Product>>> | |
| get() = _uiTrendingState | |
| private val _uiDiscountedState = MutableLiveData<UiState<List<Product>>>() | |
| val uiDiscountedState: LiveData<UiState<List<Product>>> | |
| get() = _uiDiscountedState | |
| private val _favoritesProducts = MutableLiveData<List<FavoriteProduct>>() | |
| val favoritesProducts: LiveData<List<FavoriteProduct>> | |
| get() = _favoritesProducts | |
| private val _displayError = MutableLiveData<Event<Boolean>>() | |
| val shouldDisplayError: LiveData<Event<Boolean>> | |
| get() = _displayError | |
| private val _uiFavoriteSnackBar = MutableLiveData<Event<String>>() | |
| val uiFavoriteSnackBar: LiveData<Event<String>> | |
| get() = _uiFavoriteSnackBar | |
| fun updateUI() { | |
| loadAllPavilions() | |
| loadAllProducts() | |
| loadTrendingProducts() | |
| loadDiscountedProducts() | |
| } | |
| private fun loadFavoriteProducts() = uiScope.launch(Dispatchers.IO) { | |
| // val favoriteProducts = loadFavoriteProductsUseCase() | |
| withContext(Dispatchers.Main) { | |
| // _favoriteProducts.value = favoriteProducts.value | |
| } | |
| } | |
| private fun loadDiscountedProducts() = uiScope.launch(Dispatchers.IO) { | |
| withContext(Dispatchers.Main) { | |
| _uiDiscountedState.value = emitUiState(showProgress = true) | |
| } | |
| val result = loadDiscountedProductUseCase() | |
| when (result) { | |
| is Result.Success -> { | |
| val checkedFavorite = async { | |
| result.data.map { | |
| it.isFavorite = isFavoriteProductExistUseCase(it) | |
| return@map it | |
| }.toList() | |
| } | |
| withContext(Dispatchers.Main) { | |
| _uiDiscountedState.value = emitUiState(showSuccess = Event(checkedFavorite.await())) | |
| delay(300) | |
| _displayError.value = Event(false) | |
| } | |
| } | |
| is Result.Error -> withContext(Dispatchers.Main) { | |
| _uiDiscountedState.value = emitUiState(showError = Event(result.toString())) | |
| delay(300) | |
| _displayError.value = Event(true) | |
| } | |
| } | |
| } | |
| private fun loadTrendingProducts() = uiScope.launch(Dispatchers.IO) { | |
| withContext(Dispatchers.Main) { | |
| _uiTrendingState.value = emitUiState(showProgress = true) | |
| } | |
| val result = loadTrendingProductUseCase() | |
| when (result) { | |
| is Result.Success -> { | |
| val checkedFavorite = async { | |
| result.data.map { | |
| it.isFavorite = isFavoriteProductExistUseCase(it) | |
| return@map it | |
| }.toList() | |
| } | |
| withContext(Dispatchers.Main) { | |
| _uiTrendingState.value = emitUiState(showSuccess = Event(checkedFavorite.await())) | |
| delay(300) | |
| _displayError.value = Event(false) | |
| } | |
| } | |
| is Result.Error -> withContext(Dispatchers.Main) { | |
| _uiTrendingState.value = emitUiState(showError = Event(result.toString())) | |
| delay(300) | |
| _displayError.value = Event(true) | |
| } | |
| } | |
| } | |
| private fun loadAllProducts() = uiScope.launch(Dispatchers.IO) { | |
| withContext(Dispatchers.Main) { | |
| _uiProductState.value = emitUiState(showProgress = true) | |
| } | |
| val result = loadProductsUseCase() | |
| when (result) { | |
| is Result.Success -> { | |
| withContext(Dispatchers.Main) { | |
| _uiProductState.value = emitUiState(showSuccess = Event(result.data)) | |
| delay(300) | |
| _displayError.value = Event(false) | |
| } | |
| } | |
| is Result.Error -> withContext(Dispatchers.Main) { | |
| _uiProductState.value = emitUiState(showError = Event(result.toString())) | |
| delay(300) | |
| _displayError.value = Event(true) | |
| } | |
| } | |
| } | |
| private fun loadAllPavilions() = uiScope.launch(Dispatchers.IO) { | |
| withContext(Dispatchers.Main) { | |
| _uiPavilionsState.value = emitUiState(showProgress = true) | |
| } | |
| val result = loadPavilionUseCase() | |
| when (result) { | |
| is Result.Success -> { | |
| withContext(Dispatchers.Main) { | |
| _uiPavilionsState.value = emitUiState(showSuccess = Event(result.data)) | |
| delay(300) | |
| _displayError.value = Event(false) | |
| } | |
| } | |
| is Result.Error -> withContext(Dispatchers.Main) { | |
| _uiPavilionsState.value = emitUiState(showError = Event(result.toString())) | |
| delay(300) | |
| _displayError.value = Event(true) | |
| } | |
| } | |
| } | |
| override fun onCategoryClicked(pavilion: Pavilion) { | |
| _navigateToCategoryAction.postValue(Event(pavilion)) | |
| } | |
| override fun onTrendingProductClicked(product: Product) { | |
| _navigateToProductDetailAction.postValue(Event(product)) | |
| } | |
| override fun onDiscountedProductClicked(product: Product) { | |
| _navigateToProductDetailAction.postValue(Event(product)) | |
| } | |
| override fun onCleared() { | |
| super.onCleared() | |
| viewModelJob.cancel() | |
| } | |
| fun toggleFavorite(isChecked: Boolean, product: Product) { | |
| val message = if (isChecked) { | |
| addToFavorite(product) | |
| "Added successfully" | |
| } else { | |
| removeFromFavorite(product) | |
| "Removed from products" | |
| } | |
| _uiFavoriteSnackBar.value = Event(message) | |
| } | |
| fun addToFavorite(product: Product) { | |
| val favoriteProduct = | |
| FavoriteProduct(product.id, product.name, product.image) | |
| uiScope.launch(Dispatchers.IO) { | |
| addFavoriteProductUseCase(favoriteProduct) | |
| } | |
| } | |
| fun removeFromFavorite(product: Product) { | |
| uiScope.launch(Dispatchers.IO) { | |
| removeFavoriteProductUseCase( | |
| FavoriteProduct( | |
| product.id, | |
| product.name, | |
| product.image | |
| ) | |
| ) | |
| } | |
| } | |
| } | |
| interface HomeEventListener { | |
| fun onTrendingProductClicked(product: Product) | |
| fun onCategoryClicked(pavilion: Pavilion) | |
| fun onDiscountedProductClicked(product: Product) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment