From b74d66b613d70f119000b2e290d02fc4e7ca7f89 Mon Sep 17 00:00:00 2001 From: malina Date: Thu, 12 Mar 2026 22:54:15 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=81=20Fragment/View=20=D0=BD=D0=B0=20Compose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 13 ++ .../java/ru/otus/marketsample/MainActivity.kt | 19 +-- .../java/ru/otus/marketsample/MainContent.kt | 112 ++++++++++++++++ .../java/ru/otus/marketsample/MainFragment.kt | 49 ------- .../otus/marketsample/common/DiscountBadge.kt | 73 ++++++++++ .../ru/otus/marketsample/common/Loader.kt | 20 +++ .../ru/otus/marketsample/common/Screen.kt | 12 ++ .../details/feature/DetailsContent.kt | 90 +++++++++++++ .../details/feature/DetailsFragment.kt | 125 ------------------ .../details/feature/DetailsScreen.kt | 45 +++++++ .../details/feature/di/DetailsComponent.kt | 5 +- .../products/feature/ProductItem.kt | 89 +++++++++++++ .../products/feature/ProductListFragment.kt | 117 ---------------- .../products/feature/ProductListViewModel.kt | 2 +- .../products/feature/ProductScreen.kt | 47 +++++++ .../products/feature/ProductsContent.kt | 43 ++++++ .../products/feature/adapter/ProductHolder.kt | 32 ----- .../feature/adapter/ProductsAdapter.kt | 44 ------ .../feature/di/ProductListComponent.kt | 5 +- .../promo/feature/PromoContent.kt | 39 ++++++ .../marketsample/promo/feature/PromoItem.kt | 68 ++++++++++ .../promo/feature/PromoListFragment.kt | 106 --------------- .../promo/feature/PromoListViewModel.kt | 1 + .../marketsample/promo/feature/PromoScreen.kt | 39 ++++++ .../promo/feature/adapter/PromoAdapter.kt | 40 ------ .../promo/feature/adapter/PromoHolder.kt | 17 --- .../promo/feature/di/PromoComponent.kt | 4 +- .../main/res/drawable/price_background.xml | 6 - app/src/main/res/layout/activity_main.xml | 16 --- app/src/main/res/layout/fragment_details.xml | 78 ----------- app/src/main/res/layout/fragment_main.xml | 32 ----- .../main/res/layout/fragment_product_list.xml | 34 ----- .../main/res/layout/fragment_promo_list.xml | 36 ----- app/src/main/res/layout/item_product.xml | 85 ------------ app/src/main/res/layout/item_promo.xml | 48 ------- app/src/main/res/menu/bottom_nav_menu.xml | 14 -- .../navigation/main_activity_navigation.xml | 27 ---- .../navigation/main_fragment_navigation.xml | 20 --- .../src/main/res/drawable/discount_shape.xml | 18 --- common/ui/src/main/res/drawable/gradient.xml | 9 -- .../ui/src/main/res/drawable/price_shape.xml | 13 -- common/ui/src/main/res/values/colors.xml | 1 + gradle/libs.versions.toml | 20 +++ 43 files changed, 723 insertions(+), 990 deletions(-) create mode 100644 app/src/main/java/ru/otus/marketsample/MainContent.kt delete mode 100644 app/src/main/java/ru/otus/marketsample/MainFragment.kt create mode 100644 app/src/main/java/ru/otus/marketsample/common/DiscountBadge.kt create mode 100644 app/src/main/java/ru/otus/marketsample/common/Loader.kt create mode 100644 app/src/main/java/ru/otus/marketsample/common/Screen.kt create mode 100644 app/src/main/java/ru/otus/marketsample/details/feature/DetailsContent.kt delete mode 100644 app/src/main/java/ru/otus/marketsample/details/feature/DetailsFragment.kt create mode 100644 app/src/main/java/ru/otus/marketsample/details/feature/DetailsScreen.kt create mode 100644 app/src/main/java/ru/otus/marketsample/products/feature/ProductItem.kt delete mode 100644 app/src/main/java/ru/otus/marketsample/products/feature/ProductListFragment.kt create mode 100644 app/src/main/java/ru/otus/marketsample/products/feature/ProductScreen.kt create mode 100644 app/src/main/java/ru/otus/marketsample/products/feature/ProductsContent.kt delete mode 100644 app/src/main/java/ru/otus/marketsample/products/feature/adapter/ProductHolder.kt delete mode 100644 app/src/main/java/ru/otus/marketsample/products/feature/adapter/ProductsAdapter.kt create mode 100644 app/src/main/java/ru/otus/marketsample/promo/feature/PromoContent.kt create mode 100644 app/src/main/java/ru/otus/marketsample/promo/feature/PromoItem.kt delete mode 100644 app/src/main/java/ru/otus/marketsample/promo/feature/PromoListFragment.kt create mode 100644 app/src/main/java/ru/otus/marketsample/promo/feature/PromoScreen.kt delete mode 100644 app/src/main/java/ru/otus/marketsample/promo/feature/adapter/PromoAdapter.kt delete mode 100644 app/src/main/java/ru/otus/marketsample/promo/feature/adapter/PromoHolder.kt delete mode 100644 app/src/main/res/drawable/price_background.xml delete mode 100644 app/src/main/res/layout/activity_main.xml delete mode 100644 app/src/main/res/layout/fragment_details.xml delete mode 100644 app/src/main/res/layout/fragment_main.xml delete mode 100644 app/src/main/res/layout/fragment_product_list.xml delete mode 100644 app/src/main/res/layout/fragment_promo_list.xml delete mode 100644 app/src/main/res/layout/item_product.xml delete mode 100644 app/src/main/res/layout/item_promo.xml delete mode 100644 app/src/main/res/menu/bottom_nav_menu.xml delete mode 100644 app/src/main/res/navigation/main_activity_navigation.xml delete mode 100644 app/src/main/res/navigation/main_fragment_navigation.xml delete mode 100644 common/ui/src/main/res/drawable/discount_shape.xml delete mode 100644 common/ui/src/main/res/drawable/gradient.xml delete mode 100644 common/ui/src/main/res/drawable/price_shape.xml diff --git a/app/build.gradle b/app/build.gradle index 1cdd96f..84fae65 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,7 @@ plugins { alias(libs.plugins.kotlinAndroid) alias(libs.plugins.kotlinxSerialization) alias(libs.plugins.kapt) + alias(libs.plugins.compose) } android { @@ -34,6 +35,11 @@ android { } buildFeatures { viewBinding true + compose = true + } + composeCompiler { + reportsDestination = layout.buildDirectory.dir("compose_compiler") + metricsDestination = layout.buildDirectory.dir("compose_compiler") } } @@ -55,6 +61,7 @@ dependencies { implementation libs.lifecycle.runtime.ktx implementation libs.navigation.fragment.ktx implementation libs.navigation.ui.ktx + implementation libs.navigation.compose implementation libs.coil implementation libs.gson implementation libs.bundles.network @@ -62,8 +69,14 @@ dependencies { implementation libs.kotlinx.serializationJson implementation libs.androidx.datastore implementation libs.androidx.datastore.preferences + implementation libs.compose.ui + implementation libs.androidx.material3 + implementation libs.compose.foundation + implementation libs.coil.compose implementation libs.dagger + implementation libs.androidx.runtime + implementation libs.androidx.foundation.layout kapt libs.daggerCompiler testImplementation libs.junit diff --git a/app/src/main/java/ru/otus/marketsample/MainActivity.kt b/app/src/main/java/ru/otus/marketsample/MainActivity.kt index 7e34aaf..ec64621 100644 --- a/app/src/main/java/ru/otus/marketsample/MainActivity.kt +++ b/app/src/main/java/ru/otus/marketsample/MainActivity.kt @@ -1,26 +1,15 @@ package ru.otus.marketsample import android.os.Bundle -import androidx.activity.enableEdgeToEdge +import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import ru.otus.marketsample.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { - - private lateinit var binding: ActivityMainBinding - override fun onCreate(savedInstanceState: Bundle?) { - enableEdgeToEdge() super.onCreate(savedInstanceState) - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - - ViewCompat.setOnApplyWindowInsetsListener(binding.container) { view, insets -> - val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - view.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) - insets + val appComponent = (applicationContext as MarketSampleApp).appComponent + setContent { + MainContent(appComponent) } } } \ No newline at end of file diff --git a/app/src/main/java/ru/otus/marketsample/MainContent.kt b/app/src/main/java/ru/otus/marketsample/MainContent.kt new file mode 100644 index 0000000..f717710 --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/MainContent.kt @@ -0,0 +1,112 @@ +package ru.otus.marketsample + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationBarItemDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import ru.otus.marketsample.common.Screen +import ru.otus.marketsample.details.feature.DetailsScreen +import ru.otus.marketsample.di.AppComponent +import ru.otus.marketsample.products.feature.ProductsScreen +import ru.otus.marketsample.promo.feature.PromoScreen + +@Composable +fun MainContent(appComponent: AppComponent) { + val navController = rememberNavController() + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = navBackStackEntry?.destination?.route + val items = listOf(Screen.Products, Screen.Promo) + Scaffold( + bottomBar = { + if (currentRoute != Screen.Details.route) { + BottomBar( + navController = navController, + items = items, + ) + } + } + ) { innerPadding -> + NavHost( + navController = navController, + startDestination = Screen.Products.route, + modifier = Modifier.padding(innerPadding) + ) { + composable(Screen.Products.route) { + ProductsScreen( + appComponent = appComponent, + navController = navController, + ) + } + composable(Screen.Promo.route) { + PromoScreen(appComponent) + } + composable(Screen.Details.route) { backStackEntry -> + val productId = backStackEntry.arguments?.getString(Screen.Details.PRODUCT_ID) ?: "" + DetailsScreen( + appComponent = appComponent, + productId = productId + ) + } + } + } +} +@Composable +fun BottomBar( + items: List, + navController: NavController, +) { + NavigationBar { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + + items.forEach { screen -> + NavigationBarItem( + icon = { + Icon( + ImageVector.vectorResource( + screen.iconRes + ), + modifier = Modifier.size(24.dp), + contentDescription = null + ) + }, + label = { Text(stringResource(screen.titleRes)) }, + selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true, + colors = NavigationBarItemDefaults.colors( + selectedIconColor = colorResource(id = ru.otus.common.ui.R.color.purple_500), + selectedTextColor = colorResource(id = ru.otus.common.ui.R.color.purple_500), + indicatorColor = Color.Transparent, + unselectedIconColor = Color.Black, + unselectedTextColor = Color.Black, + ), + onClick = { + navController.navigate(screen.route) { + popUpTo(navController.graph.findStartDestination().id) { saveState = true } + launchSingleTop = true + restoreState = true + } + } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/marketsample/MainFragment.kt b/app/src/main/java/ru/otus/marketsample/MainFragment.kt deleted file mode 100644 index d03161e..0000000 --- a/app/src/main/java/ru/otus/marketsample/MainFragment.kt +++ /dev/null @@ -1,49 +0,0 @@ -package ru.otus.marketsample - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.fragment.app.Fragment -import androidx.navigation.Navigation.findNavController -import com.google.android.material.bottomnavigation.BottomNavigationView -import ru.otus.marketsample.databinding.FragmentMainBinding - -class MainFragment : Fragment() { - - private var _binding: FragmentMainBinding? = null - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentMainBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val navView: BottomNavigationView = binding.navView - - navView.setOnItemSelectedListener { - findNavController(binding.navHostFragmentMain).navigate(it.itemId) - true - } - - ViewCompat.setOnApplyWindowInsetsListener(binding.container) { view, insets -> - val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - view.setPadding(systemBars.left, 0, systemBars.right, 0) - insets - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/marketsample/common/DiscountBadge.kt b/app/src/main/java/ru/otus/marketsample/common/DiscountBadge.kt new file mode 100644 index 0000000..8538640 --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/common/DiscountBadge.kt @@ -0,0 +1,73 @@ +package ru.otus.marketsample.common + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.RoundRect +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun DiscountBadge( + modifier: Modifier = Modifier, + discountText: String, +) { + val purple200 = colorResource(id = ru.otus.common.ui.R.color.purple_200) + val purple500 = colorResource(id = ru.otus.common.ui.R.color.purple_500) + Box( + modifier = modifier + .padding(8.dp) + .drawWithCache { + val cornerRadius = 40.dp.toPx() + val topRightRadius = 10.dp.toPx() + val outlinePath = Path().apply { + addRoundRect( + RoundRect( + rect = Rect(Offset.Zero, size), + topLeft = CornerRadius(cornerRadius), + topRight = CornerRadius(topRightRadius), + bottomRight = CornerRadius(cornerRadius), + bottomLeft = CornerRadius(cornerRadius) + ) + ) + } + val gradientBrush = Brush.linearGradient( + colors = listOf(purple200, purple500), + start = Offset(0f, size.height), + end = Offset(size.width, 0f) + ) + + onDrawWithContent { + drawPath(path = outlinePath, brush = gradientBrush) + drawPath( + path = outlinePath, + color = Color.White, + style = Stroke(width = 2.dp.toPx()) + ) + drawContent() + } + }, + contentAlignment = Alignment.Center + ) { + Text( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp), + text = discountText, + color = Color.White, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + ) + } +} diff --git a/app/src/main/java/ru/otus/marketsample/common/Loader.kt b/app/src/main/java/ru/otus/marketsample/common/Loader.kt new file mode 100644 index 0000000..5c33c57 --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/common/Loader.kt @@ -0,0 +1,20 @@ +package ru.otus.marketsample.common + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun Loader( + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/marketsample/common/Screen.kt b/app/src/main/java/ru/otus/marketsample/common/Screen.kt new file mode 100644 index 0000000..b265f86 --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/common/Screen.kt @@ -0,0 +1,12 @@ +package ru.otus.marketsample.common + +import ru.otus.marketsample.R + +sealed class Screen(val route: String, val titleRes: Int, val iconRes: Int) { + object Products : Screen("products_fragment", R.string.title_products, ru.otus.common.ui.R.drawable.ic_list) + object Promo : Screen("promo_fragment", R.string.title_promo, ru.otus.common.ui.R.drawable.ic_discount) + object Details : Screen("details/{productId}", 0, 0) { + const val PRODUCT_ID = "productId" + fun createRoute(productId: String) = "details/$productId" + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/marketsample/details/feature/DetailsContent.kt b/app/src/main/java/ru/otus/marketsample/details/feature/DetailsContent.kt new file mode 100644 index 0000000..1db13f5 --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/details/feature/DetailsContent.kt @@ -0,0 +1,90 @@ +package ru.otus.marketsample.details.feature + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import ru.otus.marketsample.R +import ru.otus.marketsample.common.DiscountBadge + +@Composable +fun DetailsContent( + state: DetailsState, +) { + Box( + modifier = Modifier + .fillMaxSize(), + ) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + ) { + AsyncImage( + modifier = Modifier + .fillMaxWidth() + .height(300.dp), + model = state.image, + contentDescription = null, + contentScale = ContentScale.Crop, + ) + Text( + modifier = Modifier + .align(Alignment.Start), + text = state.name, + fontSize = 24.sp, + color = Color.Black, + ) + if (state.discount.isNotEmpty()) { + DiscountBadge( + modifier = Modifier + .align(Alignment.End) + .padding(horizontal = 16.dp), + discountText = state.discount, + ) + } + Text( + modifier = Modifier + .align(Alignment.End) + .padding(14.dp), + text = stringResource(R.string.price_with_arg, state.price), + fontSize = 18.sp, + color = colorResource(id = ru.otus.common.ui.R.color.purple_500), + ) + Button( + modifier = Modifier + .align(Alignment.End) + .padding(10.dp), + contentPadding = PaddingValues(14.dp), + colors = ButtonDefaults.buttonColors( + containerColor = colorResource(id = ru.otus.common.ui.R.color.purple_500) + ), + shape = RoundedCornerShape(4.dp), + onClick = {}, + ) { + Text( + text = "Add to cart".uppercase(), fontSize = 18.sp + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/marketsample/details/feature/DetailsFragment.kt b/app/src/main/java/ru/otus/marketsample/details/feature/DetailsFragment.kt deleted file mode 100644 index e23c57e..0000000 --- a/app/src/main/java/ru/otus/marketsample/details/feature/DetailsFragment.kt +++ /dev/null @@ -1,125 +0,0 @@ -package ru.otus.marketsample.details.feature - -import android.content.Context -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import coil.load -import kotlinx.coroutines.launch -import ru.otus.common.di.findDependencies -import ru.otus.marketsample.details.feature.di.DaggerDetailsComponent -import ru.otus.marketsample.R -import ru.otus.marketsample.databinding.FragmentDetailsBinding -import javax.inject.Inject - -class DetailsFragment : Fragment() { - - private var _binding: FragmentDetailsBinding? = null - private val binding get() = _binding!! - - @Inject - lateinit var factory: DetailsViewModelFactory - - private val viewModel: DetailsViewModel by viewModels( - factoryProducer = { factory } - ) - - private val productId by lazy { arguments?.getString("productId")!! } - - override fun onAttach(context: Context) { - super.onAttach(context) - - DaggerDetailsComponent.factory() - .create( - dependencies = findDependencies(), - productId = productId, - ) - .inject(this) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentDetailsBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - subscribeUI() - } - - private fun subscribeUI() { - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.state.collect { state -> - when { - state.isLoading -> showLoading() - state.hasError -> { - Toast.makeText( - requireContext(), - "Error wile loading data", - Toast.LENGTH_SHORT - ).show() - - viewModel.errorHasShown() - } - - else -> showProduct(detailsState = state.detailsState) - } - } - } - } - } - } - - private fun showLoading() { - hideAll() - binding.progress.visibility = View.VISIBLE - } - - private fun showProduct(detailsState: DetailsState) { - hideAll() - binding.image.load(detailsState.image) - binding.image.visibility = View.VISIBLE - - binding.name.text = detailsState.name - binding.name.visibility = View.VISIBLE - - binding.price.text = getString(R.string.price_with_arg, detailsState.price) - binding.price.visibility = View.VISIBLE - - if (detailsState.hasDiscount) { - binding.promo.visibility = View.VISIBLE - binding.promo.text = detailsState.discount - } else { - binding.promo.visibility = View.GONE - } - - binding.addToCart.visibility = View.VISIBLE - } - - private fun hideAll() { - binding.progress.visibility = View.GONE - binding.image.visibility = View.GONE - binding.name.visibility = View.GONE - binding.price.visibility = View.GONE - binding.progress.visibility = View.GONE - binding.addToCart.visibility = View.GONE - } -} diff --git a/app/src/main/java/ru/otus/marketsample/details/feature/DetailsScreen.kt b/app/src/main/java/ru/otus/marketsample/details/feature/DetailsScreen.kt new file mode 100644 index 0000000..b07811d --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/details/feature/DetailsScreen.kt @@ -0,0 +1,45 @@ +package ru.otus.marketsample.details.feature + +import android.widget.Toast +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.viewmodel.compose.viewModel +import ru.otus.marketsample.common.Loader +import ru.otus.marketsample.details.feature.di.DaggerDetailsComponent +import ru.otus.marketsample.di.AppComponent + +@Composable +fun DetailsScreen( + appComponent: AppComponent, + productId: String, +) { + val component = remember { + DaggerDetailsComponent.factory() + .create( + dependencies = appComponent, + productId = productId, + ) + } + val context = LocalContext.current + val factory = component.getFactory() + val viewModel: DetailsViewModel = viewModel(factory = factory) + + val state by viewModel.state.collectAsState() + when { + state.isLoading -> Loader() + state.hasError -> { + Toast.makeText( + context, + "Error wile loading data", + Toast.LENGTH_SHORT + ).show() + viewModel.errorHasShown() + } + else -> DetailsContent( + state.detailsState, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/marketsample/details/feature/di/DetailsComponent.kt b/app/src/main/java/ru/otus/marketsample/details/feature/di/DetailsComponent.kt index 8eecd0b..f9cc486 100644 --- a/app/src/main/java/ru/otus/marketsample/details/feature/di/DetailsComponent.kt +++ b/app/src/main/java/ru/otus/marketsample/details/feature/di/DetailsComponent.kt @@ -3,8 +3,8 @@ package ru.otus.marketsample.details.feature.di import dagger.BindsInstance import dagger.Component import ru.otus.common.data.products.ProductRepository -import ru.otus.marketsample.details.feature.DetailsFragment import ru.otus.common.di.FeatureScope +import ru.otus.marketsample.details.feature.DetailsViewModelFactory import javax.inject.Named @FeatureScope @@ -18,8 +18,7 @@ interface DetailsComponent { @BindsInstance @Named("productId") productId: String, ): DetailsComponent } - - fun inject(detailsFragment: DetailsFragment) + fun getFactory(): DetailsViewModelFactory } interface DetailsComponentDependencies { diff --git a/app/src/main/java/ru/otus/marketsample/products/feature/ProductItem.kt b/app/src/main/java/ru/otus/marketsample/products/feature/ProductItem.kt new file mode 100644 index 0000000..70fce08 --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/products/feature/ProductItem.kt @@ -0,0 +1,89 @@ +package ru.otus.marketsample.products.feature + + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import ru.otus.marketsample.R +import ru.otus.marketsample.common.DiscountBadge + +@Composable +fun ProductItem( + modifier: Modifier = Modifier, + state: ProductState, + onItemClicked: (String) -> Unit, +) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 24.dp) + .height(130.dp) + .clickable { onItemClicked(state.id) }, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Box( + modifier = Modifier + .weight(1f) + ) { + AsyncImage( + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(12.dp)), + model = state.image, + contentDescription = null, + contentScale = ContentScale.Crop, + ) + if (state.hasDiscount) { + DiscountBadge( + modifier = Modifier + .align(Alignment.TopEnd), + discountText = state.discount, + ) + } + } + Column( + modifier = Modifier + .weight(1f) + .fillMaxHeight(), + verticalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = state.name, + fontSize = 18.sp, + color = Color.Black, + fontWeight = FontWeight.Medium, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth() + ) + Text( + text = stringResource(id = R.string.price_with_arg, state.price), + fontSize = 16.sp, + color = colorResource(id = ru.otus.common.ui.R.color.purple_500), + fontWeight = FontWeight.Bold, + modifier = Modifier + .background( + color = colorResource(id = ru.otus.common.ui.R.color.price_background), + shape = RoundedCornerShape(8.dp) + ) + .align(Alignment.End) + .padding(horizontal = 12.dp, vertical = 8.dp) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/marketsample/products/feature/ProductListFragment.kt b/app/src/main/java/ru/otus/marketsample/products/feature/ProductListFragment.kt deleted file mode 100644 index 88f7ec0..0000000 --- a/app/src/main/java/ru/otus/marketsample/products/feature/ProductListFragment.kt +++ /dev/null @@ -1,117 +0,0 @@ -package ru.otus.marketsample.products.feature - -import android.content.Context -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.core.os.bundleOf -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.navigation.findNavController -import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.coroutines.launch -import ru.otus.marketsample.MarketSampleApp -import ru.otus.marketsample.R -import ru.otus.marketsample.databinding.FragmentProductListBinding -import ru.otus.marketsample.products.feature.adapter.ProductsAdapter -import ru.otus.marketsample.products.feature.di.DaggerProductListComponent -import javax.inject.Inject - -class ProductListFragment : Fragment() { - - private var _binding: FragmentProductListBinding? = null - private val binding get() = _binding!! - - @Inject - lateinit var factory: ProductListViewModelFactory - - private val viewModel: ProductListViewModel by viewModels { factory } - - override fun onAttach(context: Context) { - super.onAttach(context) - - val appComponent = (activity?.applicationContext as MarketSampleApp).appComponent - - DaggerProductListComponent.factory() - .create(appComponent) - .inject(this) - } - - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentProductListBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.recyclerView.adapter = ProductsAdapter( - onItemClicked = { productId -> - requireActivity().findNavController(R.id.nav_host_activity_main) - .navigate( - resId = R.id.action_main_to_details, - args = bundleOf("productId" to productId), - ) - } - ) - binding.recyclerView.layoutManager = LinearLayoutManager(context) - - binding.swipeRefreshLayout.setOnRefreshListener { - viewModel.refresh() - } - - subscribeUI() - } - - private fun subscribeUI() { - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.state.collect { state -> - when { - state.isLoading -> showLoading() - state.hasError -> { - Toast.makeText( - requireContext(), - "Error wile loading data", - Toast.LENGTH_SHORT - ).show() - - viewModel.errorHasShown() - } - - else -> showProductList(productListState = state.productListState) - } - } - } - } - } - } - - private fun showProductList(productListState: List) { - binding.progress.visibility = View.GONE - binding.recyclerView.visibility = View.VISIBLE - (binding.recyclerView.adapter as ProductsAdapter).submitList(productListState) - binding.swipeRefreshLayout.isRefreshing = false - } - - private fun showLoading() { - binding.progress.visibility = View.VISIBLE - binding.recyclerView.visibility = View.GONE - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} diff --git a/app/src/main/java/ru/otus/marketsample/products/feature/ProductListViewModel.kt b/app/src/main/java/ru/otus/marketsample/products/feature/ProductListViewModel.kt index ce33e63..ea2c580 100644 --- a/app/src/main/java/ru/otus/marketsample/products/feature/ProductListViewModel.kt +++ b/app/src/main/java/ru/otus/marketsample/products/feature/ProductListViewModel.kt @@ -6,7 +6,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -46,6 +45,7 @@ class ProductListViewModel( _state.update { screenState -> screenState.copy( hasError = true, + isLoading = false, errorProvider = { context -> context.getString(R.string.error_wile_loading_data) } ) } diff --git a/app/src/main/java/ru/otus/marketsample/products/feature/ProductScreen.kt b/app/src/main/java/ru/otus/marketsample/products/feature/ProductScreen.kt new file mode 100644 index 0000000..e3bea9a --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/products/feature/ProductScreen.kt @@ -0,0 +1,47 @@ +package ru.otus.marketsample.products.feature + +import android.widget.Toast +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import ru.otus.marketsample.common.Screen +import ru.otus.marketsample.di.AppComponent +import ru.otus.marketsample.products.feature.di.DaggerProductListComponent + +@Composable +fun ProductsScreen( + appComponent: AppComponent, + navController: NavController +) { + val component = remember { + DaggerProductListComponent.factory().create(appComponent) + } + val context = LocalContext.current + val factory = component.getFactory() + val viewModel: ProductListViewModel = viewModel(factory = factory) + val state by viewModel.state.collectAsState() + when { + state.hasError -> { + Toast.makeText( + context, + "Error wile loading data", + Toast.LENGTH_SHORT + ).show() + + viewModel.errorHasShown() + } + + else -> ProductsContent( + isLoading = state.isLoading, + products = state.productListState, + onRefresh = viewModel::refresh, + onItemClicked = { productId -> + navController.navigate(Screen.Details.createRoute(productId)) + }, + ) + } +} diff --git a/app/src/main/java/ru/otus/marketsample/products/feature/ProductsContent.kt b/app/src/main/java/ru/otus/marketsample/products/feature/ProductsContent.kt new file mode 100644 index 0000000..bc11142 --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/products/feature/ProductsContent.kt @@ -0,0 +1,43 @@ +package ru.otus.marketsample.products.feature + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ProductsContent( + isLoading: Boolean, + products: List, + onRefresh: () -> Unit, + onItemClicked: (String) -> Unit, +) { + val pullToRefreshState = rememberPullToRefreshState() + PullToRefreshBox( + modifier = Modifier.fillMaxSize(), + state = pullToRefreshState, + isRefreshing = isLoading, + onRefresh = { + onRefresh() + }, + ) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + ) { + items( + items = products, + key = { it.id }, + ) { product -> + ProductItem( + state = product, + onItemClicked = onItemClicked, + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/marketsample/products/feature/adapter/ProductHolder.kt b/app/src/main/java/ru/otus/marketsample/products/feature/adapter/ProductHolder.kt deleted file mode 100644 index f216b25..0000000 --- a/app/src/main/java/ru/otus/marketsample/products/feature/adapter/ProductHolder.kt +++ /dev/null @@ -1,32 +0,0 @@ -package ru.otus.marketsample.products.feature.adapter - -import android.view.View.GONE -import android.view.View.VISIBLE -import androidx.recyclerview.widget.RecyclerView -import coil.load -import ru.otus.marketsample.R -import ru.otus.marketsample.databinding.ItemProductBinding -import ru.otus.marketsample.products.feature.ProductState - -class ProductHolder( - private val binding: ItemProductBinding, - private val onItemClicked: (String) -> Unit, -) : RecyclerView.ViewHolder(binding.root) { - - fun bind(productState: ProductState) { - binding.image.load(productState.image) - binding.name.text = productState.name - binding.price.text = - binding.root.resources.getString(R.string.price_with_arg, productState.price) - if (productState.hasDiscount) { - binding.promo.visibility = VISIBLE - binding.promo.text = productState.discount - } else { - binding.promo.visibility = GONE - } - - binding.root.setOnClickListener { - onItemClicked(productState.id) - } - } -} diff --git a/app/src/main/java/ru/otus/marketsample/products/feature/adapter/ProductsAdapter.kt b/app/src/main/java/ru/otus/marketsample/products/feature/adapter/ProductsAdapter.kt deleted file mode 100644 index 18354a2..0000000 --- a/app/src/main/java/ru/otus/marketsample/products/feature/adapter/ProductsAdapter.kt +++ /dev/null @@ -1,44 +0,0 @@ -package ru.otus.marketsample.products.feature.adapter - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import ru.otus.common.di.FeatureScope -import ru.otus.marketsample.databinding.ItemProductBinding -import ru.otus.marketsample.products.feature.ProductState -import javax.inject.Inject - -@FeatureScope -class ProductsAdapter @Inject constructor( - private val onItemClicked: (String) -> Unit, -) : - ListAdapter(DiffCallback()) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductHolder { - return ProductHolder( - binding = ItemProductBinding.inflate( - LayoutInflater.from(parent.context), parent, false - ), - onItemClicked = onItemClicked, - ) - } - - override fun onBindViewHolder(holder: ProductHolder, position: Int) { - val entity = getItem(position) - entity?.let { - holder.bind(entity) - } - } -} - -private class DiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ProductState, newItem: ProductState): Boolean { - return oldItem.id == newItem.id - } - - override fun areContentsTheSame(oldItem: ProductState, newItem: ProductState): Boolean { - return oldItem == newItem - } -} diff --git a/app/src/main/java/ru/otus/marketsample/products/feature/di/ProductListComponent.kt b/app/src/main/java/ru/otus/marketsample/products/feature/di/ProductListComponent.kt index 2b4c9fd..a9a06f4 100644 --- a/app/src/main/java/ru/otus/marketsample/products/feature/di/ProductListComponent.kt +++ b/app/src/main/java/ru/otus/marketsample/products/feature/di/ProductListComponent.kt @@ -4,7 +4,7 @@ import dagger.Component import ru.otus.common.data.products.ProductRepository import ru.otus.common.data.promo.PromoRepository import ru.otus.common.di.FeatureScope -import ru.otus.marketsample.products.feature.ProductListFragment +import ru.otus.marketsample.products.feature.ProductListViewModelFactory @FeatureScope @Component(dependencies = [ProductListComponentDependencies::class]) @@ -16,8 +16,7 @@ interface ProductListComponent { dependencies: ProductListComponentDependencies, ): ProductListComponent } - - fun inject(productListFragment: ProductListFragment) + fun getFactory(): ProductListViewModelFactory } interface ProductListComponentDependencies { diff --git a/app/src/main/java/ru/otus/marketsample/promo/feature/PromoContent.kt b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoContent.kt new file mode 100644 index 0000000..4986618 --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoContent.kt @@ -0,0 +1,39 @@ +package ru.otus.marketsample.promo.feature + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun PromoContent( + isLoading: Boolean, + promoList: List, + onRefresh: () -> Unit, +) { + val pullToRefreshState = rememberPullToRefreshState() + PullToRefreshBox( + modifier = Modifier.fillMaxSize(), + state = pullToRefreshState, + isRefreshing = isLoading, + onRefresh = onRefresh, + ) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + ) { + items( + items = promoList, + key = { it.id }, + ) { promo -> + PromoItem( + promoState = promo, + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/marketsample/promo/feature/PromoItem.kt b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoItem.kt new file mode 100644 index 0000000..30ca693 --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoItem.kt @@ -0,0 +1,68 @@ +package ru.otus.marketsample.promo.feature + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage + +@Composable +fun PromoItem( + promoState: PromoState, +) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp) + ) { + AsyncImage( + model = promoState.image, + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .height(250.dp), + contentScale = ContentScale.Crop, + ) + Box( + modifier = Modifier + .fillMaxWidth() + .height(100.dp) + .align(Alignment.BottomStart) + .background( + brush = Brush.verticalGradient( + colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.67f)) + ) + ) + ) + Column( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(10.dp) + ) { + Text( + text = promoState.name, + color = Color.White, + fontSize = 24.sp, + fontWeight = FontWeight.Bold + ) + + Text( + text = promoState.description, + color = Color.White, + fontSize = 14.sp + ) + } + } +} diff --git a/app/src/main/java/ru/otus/marketsample/promo/feature/PromoListFragment.kt b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoListFragment.kt deleted file mode 100644 index 2e4f533..0000000 --- a/app/src/main/java/ru/otus/marketsample/promo/feature/PromoListFragment.kt +++ /dev/null @@ -1,106 +0,0 @@ -package ru.otus.marketsample.promo.feature - -import android.content.Context -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.coroutines.launch -import ru.otus.common.di.findDependencies -import ru.otus.marketsample.databinding.FragmentPromoListBinding -import ru.otus.marketsample.promo.feature.adapter.PromoAdapter -import ru.otus.marketsample.promo.feature.di.DaggerPromoComponent -import javax.inject.Inject - -class PromoListFragment : Fragment() { - - private var _binding: FragmentPromoListBinding? = null - private val binding get() = _binding!! - - @Inject - lateinit var adapter: PromoAdapter - - @Inject - lateinit var factory: PromoListViewModelFactory - - private val viewModel: PromoListViewModel by viewModels { factory } - - override fun onAttach(context: Context) { - super.onAttach(context) - - DaggerPromoComponent.factory() - .create(dependencies = findDependencies()) - .inject(this) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentPromoListBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.recyclerView.adapter = adapter - binding.recyclerView.layoutManager = LinearLayoutManager(context) - - binding.swipeRefreshLayout.setOnRefreshListener { - viewModel.refresh() - } - - subscribeUI() - } - - private fun subscribeUI() { - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.state.collect { state -> - when { - state.isLoading -> showLoading() - state.hasError -> { - Toast.makeText( - requireContext(), - "Error wile loading data", - Toast.LENGTH_SHORT - ).show() - - viewModel.errorHasShown() - } - - else -> showPromoList(promoListState = state.promoListState) - } - } - } - } - } - } - - private fun showPromoList(promoListState: List) { - binding.progress.visibility = View.GONE - binding.recyclerView.visibility = View.VISIBLE - adapter.submitList(promoListState) - binding.swipeRefreshLayout.isRefreshing = false - } - - private fun showLoading() { - binding.progress.visibility = View.VISIBLE - binding.recyclerView.visibility = View.GONE - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} diff --git a/app/src/main/java/ru/otus/marketsample/promo/feature/PromoListViewModel.kt b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoListViewModel.kt index 6343012..bb6c9cf 100644 --- a/app/src/main/java/ru/otus/marketsample/promo/feature/PromoListViewModel.kt +++ b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoListViewModel.kt @@ -46,6 +46,7 @@ class PromoListViewModel( _state.update { screenState -> screenState.copy( hasError = true, + isLoading = false, errorProvider = { context -> context.getString(R.string.error_wile_loading_data) } ) } diff --git a/app/src/main/java/ru/otus/marketsample/promo/feature/PromoScreen.kt b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoScreen.kt new file mode 100644 index 0000000..18b2973 --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoScreen.kt @@ -0,0 +1,39 @@ +package ru.otus.marketsample.promo.feature + +import android.widget.Toast +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.viewmodel.compose.viewModel +import ru.otus.marketsample.di.AppComponent +import ru.otus.marketsample.promo.feature.di.DaggerPromoComponent + +@Composable +fun PromoScreen( + appComponent: AppComponent, +) { + val component = remember { + DaggerPromoComponent.factory().create(appComponent) + } + val context = LocalContext.current + val factory = component.getFactory() + val viewModel: PromoListViewModel = viewModel(factory = factory) + val state by viewModel.state.collectAsState() + when { + state.hasError -> { + Toast.makeText( + context, + "Error wile loading data", + Toast.LENGTH_SHORT + ).show() + viewModel.errorHasShown() + } + else -> PromoContent( + isLoading = state.isLoading, + promoList = state.promoListState, + onRefresh = viewModel::refresh, + ) + } +} diff --git a/app/src/main/java/ru/otus/marketsample/promo/feature/adapter/PromoAdapter.kt b/app/src/main/java/ru/otus/marketsample/promo/feature/adapter/PromoAdapter.kt deleted file mode 100644 index 0f6b562..0000000 --- a/app/src/main/java/ru/otus/marketsample/promo/feature/adapter/PromoAdapter.kt +++ /dev/null @@ -1,40 +0,0 @@ -package ru.otus.marketsample.promo.feature.adapter - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import ru.otus.common.di.FeatureScope -import ru.otus.marketsample.databinding.ItemPromoBinding -import ru.otus.marketsample.promo.feature.PromoState -import javax.inject.Inject - -@FeatureScope -class PromoAdapter @Inject constructor() : ListAdapter(DiffCallback()) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PromoHolder { - return PromoHolder( - ItemPromoBinding.inflate( - LayoutInflater.from(parent.context), parent, false - ) - ) - } - - override fun onBindViewHolder(holder: PromoHolder, position: Int) { - val entity = getItem(position) - entity?.let { - holder.bind(entity) - } - } -} - -private class DiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: PromoState, newItem: PromoState): Boolean { - return oldItem.id == newItem.id - } - - override fun areContentsTheSame(oldItem: PromoState, newItem: PromoState): Boolean { - return oldItem == newItem - } -} diff --git a/app/src/main/java/ru/otus/marketsample/promo/feature/adapter/PromoHolder.kt b/app/src/main/java/ru/otus/marketsample/promo/feature/adapter/PromoHolder.kt deleted file mode 100644 index 5d08f5d..0000000 --- a/app/src/main/java/ru/otus/marketsample/promo/feature/adapter/PromoHolder.kt +++ /dev/null @@ -1,17 +0,0 @@ -package ru.otus.marketsample.promo.feature.adapter - -import androidx.recyclerview.widget.RecyclerView -import coil.load -import ru.otus.marketsample.databinding.ItemPromoBinding -import ru.otus.marketsample.promo.feature.PromoState - -class PromoHolder( - private val binding: ItemPromoBinding, -) : RecyclerView.ViewHolder(binding.root) { - - fun bind(promoState: PromoState) { - binding.image.load(promoState.image) - binding.name.text = promoState.name - binding.description.text = promoState.description - } -} diff --git a/app/src/main/java/ru/otus/marketsample/promo/feature/di/PromoComponent.kt b/app/src/main/java/ru/otus/marketsample/promo/feature/di/PromoComponent.kt index b1ad582..d8f5766 100644 --- a/app/src/main/java/ru/otus/marketsample/promo/feature/di/PromoComponent.kt +++ b/app/src/main/java/ru/otus/marketsample/promo/feature/di/PromoComponent.kt @@ -3,7 +3,7 @@ package ru.otus.marketsample.promo.feature.di import dagger.Component import ru.otus.common.data.promo.PromoRepository import ru.otus.common.di.FeatureScope -import ru.otus.marketsample.promo.feature.PromoListFragment +import ru.otus.marketsample.promo.feature.PromoListViewModelFactory @FeatureScope @Component(dependencies = [PromoComponentDependencies::class]) @@ -13,8 +13,8 @@ interface PromoComponent { interface Factory { fun create(dependencies: PromoComponentDependencies): PromoComponent } + fun getFactory(): PromoListViewModelFactory - fun inject(productFragment: PromoListFragment) } interface PromoComponentDependencies { diff --git a/app/src/main/res/drawable/price_background.xml b/app/src/main/res/drawable/price_background.xml deleted file mode 100644 index c8fa67f..0000000 --- a/app/src/main/res/drawable/price_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 9945e95..0000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml deleted file mode 100644 index c37c1f0..0000000 --- a/app/src/main/res/layout/fragment_details.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - -