diff --git a/app/build.gradle b/app/build.gradle index 1cdd96f..4612da7 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.compiler) } android { @@ -34,9 +35,16 @@ android { } buildFeatures { viewBinding true + compose true } } +composeCompiler { + enableStrongSkippingMode = true + reportsDestination = layout.buildDirectory.dir("compose_compiler") + metricsDestination = layout.buildDirectory.dir("compose_compiler") +} + dependencies { implementation project(":common:di") implementation project(":common:formatters") @@ -44,6 +52,21 @@ dependencies { implementation project(":common:data:products") implementation project(":common:data:promo") + def composeBom = platform(libs.compose.bom) + implementation composeBom + androidTestImplementation composeBom + + implementation libs.compose.ui + implementation libs.compose.ui.graphics + implementation libs.compose.ui.tooling.preview + implementation libs.compose.material3 + implementation libs.compose.activity + implementation libs.compose.viewmodel + implementation libs.compose.navigation + implementation libs.coil.compose + debugImplementation libs.compose.ui.tooling + + implementation libs.core.ktx implementation libs.appcompat implementation libs.material diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1818854..b7b1aaf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,6 +15,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:enableOnBackInvokedCallback="true" android:supportsRtl="true" + android:usesCleartextTraffic="true" android:theme="@style/Theme.MarketSample" tools:targetApi="31"> diff --git a/app/src/main/java/ru/otus/marketsample/DetailProduct.png b/app/src/main/java/ru/otus/marketsample/DetailProduct.png new file mode 100644 index 0000000..96c1af1 Binary files /dev/null and b/app/src/main/java/ru/otus/marketsample/DetailProduct.png differ diff --git a/app/src/main/java/ru/otus/marketsample/MainActivity.kt b/app/src/main/java/ru/otus/marketsample/MainActivity.kt index 7e34aaf..6fcba65 100644 --- a/app/src/main/java/ru/otus/marketsample/MainActivity.kt +++ b/app/src/main/java/ru/otus/marketsample/MainActivity.kt @@ -1,26 +1,17 @@ package ru.otus.marketsample import android.os.Bundle +import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import ru.otus.marketsample.databinding.ActivityMainBinding +import androidx.activity.ComponentActivity -class MainActivity : AppCompatActivity() { - - private lateinit var binding: ActivityMainBinding +class MainActivity : ComponentActivity() { 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 + setContent { + MainScreen() } } } \ 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/MainScreen.kt b/app/src/main/java/ru/otus/marketsample/MainScreen.kt new file mode 100644 index 0000000..4be1e8f --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/MainScreen.kt @@ -0,0 +1,127 @@ +package ru.otus.marketsample +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import ru.otus.marketsample.details.feature.DetailsScreen +import ru.otus.marketsample.details.feature.DetailsViewModel +import ru.otus.marketsample.details.feature.di.DaggerDetailsComponent +import ru.otus.marketsample.details.feature.di.DetailsComponentDependencies +import ru.otus.marketsample.products.feature.ProductListScreen +import ru.otus.marketsample.products.feature.ProductListViewModel +import ru.otus.marketsample.products.feature.di.DaggerProductListComponent +import ru.otus.marketsample.products.feature.di.ProductListComponentDependencies +import ru.otus.marketsample.promo.feature.PromoListScreen +import ru.otus.marketsample.promo.feature.PromoListViewModel +import ru.otus.marketsample.promo.feature.di.DaggerPromoComponent +import ru.otus.marketsample.promo.feature.di.PromoComponentDependencies +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MainScreen() { + val navController = rememberNavController() + Scaffold( + bottomBar = { + NavigationBar { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + NavigationBarItem( + icon = { Icon(painterResource(ru.otus.common.ui.R.drawable.ic_list), tint = null, contentDescription = "Продукты") }, + label = { Text("Продукты") }, + selected = currentDestination?.hierarchy?.any { it.route == "products" } == true, + onClick = { + navController.navigate("products") { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + ) + NavigationBarItem( + icon = { Icon(painterResource(ru.otus.common.ui.R.drawable.ic_discount), tint = null, contentDescription = "Акции") }, + label = { Text("Акции") }, + selected = currentDestination?.hierarchy?.any { it.route == "promo" } == true, + onClick = { + navController.navigate("promo") { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + ) + } + } + ) { innerPadding -> + val context = LocalContext.current + val dependencies = (context.applicationContext as MarketSampleApp).getDependencies() + NavHost( + navController = navController, + startDestination = "products", + modifier = Modifier.padding(innerPadding) + ) { + composable("products") { + val component = remember { + DaggerProductListComponent.factory().create(dependencies as ProductListComponentDependencies) + } + val factory = component.getViewModelFactory() + val viewModel: ProductListViewModel = viewModel(factory = factory) + val state by viewModel.state.collectAsState() + ProductListScreen( + state = state, + onRefresh = { viewModel.refresh() }, + onItemClick = { productId -> + navController.navigate("details/$productId") + } + ) + } + composable("promo") { + val component = remember { + DaggerPromoComponent.factory().create(dependencies as PromoComponentDependencies) + } + val factory = component.getViewModelFactory() + val viewModel: PromoListViewModel = viewModel(factory = factory) + val state by viewModel.state.collectAsState() + PromoListScreen( + state = state, + onRefresh = { viewModel.refresh() } + ) + } + composable( + route = "details/{productId}", + arguments = listOf(navArgument("productId") { type = NavType.StringType }) + ) { backStackEntry -> + val productId = backStackEntry.arguments?.getString("productId") ?: return@composable + + val component = remember(productId) { + DaggerDetailsComponent.factory().create(dependencies as DetailsComponentDependencies, productId) + } + val factory = component.getViewModelFactory() + val viewModel: DetailsViewModel = viewModel(factory = factory) + val state by viewModel.state.collectAsState() + DetailsScreen(state = state) + } + } + } +} diff --git a/app/src/main/java/ru/otus/marketsample/ProductList.png b/app/src/main/java/ru/otus/marketsample/ProductList.png new file mode 100644 index 0000000..c0e8e2e Binary files /dev/null and b/app/src/main/java/ru/otus/marketsample/ProductList.png differ diff --git a/app/src/main/java/ru/otus/marketsample/PromoList.png b/app/src/main/java/ru/otus/marketsample/PromoList.png new file mode 100644 index 0000000..79c4a4d Binary files /dev/null and b/app/src/main/java/ru/otus/marketsample/PromoList.png differ diff --git a/app/src/main/java/ru/otus/marketsample/app_debug-classes.txt b/app/src/main/java/ru/otus/marketsample/app_debug-classes.txt new file mode 100644 index 0000000..e8ba387 --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/app_debug-classes.txt @@ -0,0 +1,163 @@ +unstable class MainActivity { + = Unstable +} +unstable class MarketSampleApp { + runtime val appComponent: AppComponent + = Unstable +} +unstable class ConsumeProductDetailsUseCase { + unstable val productRepository: ProductRepository + stable val productDetailsDomainMapper: ProductDetailsDomainMapper + = Unstable +} +stable class ProductDetails { + stable val id: String + stable val name: String + stable val image: String + stable val price: Double + = Stable +} +stable class ProductDetailsDomainMapper { + = Stable +} +stable class DetailsScreenState { + stable val isLoading: Boolean + stable val detailsState: DetailsState + stable val hasError: Boolean + stable val errorProvider: Function1 +} +stable class DetailsState { + stable val id: String + stable val name: String + stable val image: String + stable val price: String + stable val hasDiscount: Boolean + stable val discount: String + = Stable +} +unstable class DetailsStateFactory { + unstable val priceFormatter: PriceFormatter + = Unstable +} +unstable class DetailsViewModel { + unstable val consumeProductDetailsUseCase: ConsumeProductDetailsUseCase + unstable val detailsStateFactory: DetailsStateFactory + stable val productId: String + unstable val _state: MutableStateFlow + unstable val state: StateFlow + = Unstable +} +unstable class DetailsViewModelFactory { + unstable val consumeProductDetailsUseCase: ConsumeProductDetailsUseCase + unstable val detailsStateFactory: DetailsStateFactory + stable val productId: String + = Unstable +} +unstable class DataModule { + unstable val appDataStore$delegate: ReadOnlyProperty> + = Unstable +} +stable class NetworkModule { + = Stable +} +unstable class ConsumeProductsUseCase { + unstable val productRepository: ProductRepository + unstable val promoRepository: PromoRepository + stable val productDomainMapper: ProductDomainMapper + = Unstable +} +stable class Product { + stable val id: String + stable val name: String + stable val image: String + stable val price: Double + stable val hasDiscount: Boolean + stable val discount: Double? + = Stable +} +stable class ProductDomainMapper { + = Stable +} +unstable class ProductListViewModel { + unstable val consumeProductsUseCase: ConsumeProductsUseCase + unstable val productStateFactory: ProductStateFactory + unstable val _state: MutableStateFlow + unstable val state: StateFlow + = Unstable +} +unstable class ProductListViewModelFactory { + unstable val consumeProductsUseCase: ConsumeProductsUseCase + unstable val productStateFactory: ProductStateFactory + = Unstable +} +stable class ProductsScreenState { + stable val isLoading: Boolean + unstable val productListState: List + stable val hasError: Boolean + stable val errorProvider: Function1 +} +stable class ProductState { + stable val id: String + stable val name: String + stable val image: String + stable val price: String + stable val hasDiscount: Boolean + stable val discount: String + = Stable +} +unstable class ProductStateFactory { + unstable val discountFormatter: DiscountFormatter + unstable val priceFormatter: PriceFormatter + = Unstable +} +unstable class ConsumePromosUseCase { + unstable val promoRepository: PromoRepository + stable val promoDomainMapper: PromoDomainMapper + = Unstable +} +unstable class PromoForProducts { + unstable val products: List + = Unstable +} +stable class PromoForPrice { + = Stable +} +stable class Promo { + stable val id: String + stable val name: String + stable val image: String + stable val description: String + stable val discount: Double + = Stable +} +stable class PromoDomainMapper { + = Stable +} +unstable class PromoListViewModel { + stable val promoStateFactory: PromoStateFactory + unstable val consumePromosUseCase: ConsumePromosUseCase + unstable val _state: MutableStateFlow + unstable val state: StateFlow + = Unstable +} +unstable class PromoListViewModelFactory { + stable val promoStateFactory: PromoStateFactory + unstable val consumePromosUseCase: ConsumePromosUseCase + = Unstable +} +stable class PromoScreenState { + stable val isLoading: Boolean + unstable val promoListState: List + stable val hasError: Boolean + stable val errorProvider: Function1 +} +stable class PromoState { + stable val id: String + stable val name: String + stable val description: String + stable val image: String + = Stable +} +stable class PromoStateFactory { + = Stable +} diff --git a/app/src/main/java/ru/otus/marketsample/app_debug-composables.txt b/app/src/main/java/ru/otus/marketsample/app_debug-composables.txt new file mode 100644 index 0000000..2c0f90b --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/app_debug-composables.txt @@ -0,0 +1,21 @@ +restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun MainScreen() +restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun DetailsScreen( + stable state: DetailsScreenState +) +restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun DetailsScreenPreview() +restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun ProductListScreen( + stable state: ProductsScreenState + unused stable onRefresh: Function0 + stable onItemClick: Function1 +) +restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun ProductItem( + stable product: ProductState + stable onClick: Function0 +) +restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun PromoListScreen( + stable state: PromoScreenState + unused stable onRefresh: Function0 +) +restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun PromoItem( + stable promo: PromoState +) 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..63b857e --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/details/feature/DetailsScreen.kt @@ -0,0 +1,113 @@ +package ru.otus.marketsample.details.feature + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.material3.Button +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +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.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage + +@Composable +fun DetailsScreen(state: DetailsScreenState) { + if (state.isLoading) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + return + } + + val details = state.detailsState + + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.White) + ) { + AsyncImage( + model = details.image, + contentDescription = details.name, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .height(300.dp) + ) + + Text( + text = details.name, + fontSize = 24.sp, + color = Color.Black, + fontWeight = FontWeight.Bold, + modifier = Modifier + .align(Alignment.End) + .padding(top = 10.dp, end = 10.dp) + ) + + if (details.hasDiscount) { + Text( + text = details.discount, + fontSize = 20.sp, + color = Color.White, + fontWeight = FontWeight.Bold, + modifier = Modifier + .align(Alignment.End) + .padding(end = 10.dp, top = 5.dp) + .background(Color(0xFFFF0000)) + .padding(horizontal = 10.dp, vertical = 2.dp) + ) + } + + Text( + text = "${details.price} руб", + color = Color(0xFF6200EE), + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier + .align(Alignment.End) + .padding(14.dp) + ) + + Button( + onClick = { /* Добавить в корзину */ }, + modifier = Modifier + .align(Alignment.End) + .padding(10.dp) + ) { + Text(text = "Add to cart", fontSize = 18.sp) + } + } +} + +@Preview(showBackground = true) +@Composable +fun DetailsScreenPreview() { + MaterialTheme { + DetailsScreen( + state = DetailsScreenState( + isLoading = false, + detailsState = DetailsState( + id = "1", + name = "MacBook Pro 14", + price = "150 000", + hasDiscount = true, + discount = "-10%", + image = "" + ) + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/marketsample/details/feature/DetailsState.kt b/app/src/main/java/ru/otus/marketsample/details/feature/DetailsState.kt index 62a4a15..ef9167a 100644 --- a/app/src/main/java/ru/otus/marketsample/details/feature/DetailsState.kt +++ b/app/src/main/java/ru/otus/marketsample/details/feature/DetailsState.kt @@ -1,9 +1,11 @@ package ru.otus.marketsample.details.feature import android.content.Context +import androidx.compose.runtime.Immutable typealias ErrorProvider = (Context) -> String +@Immutable data class DetailsScreenState( val isLoading: Boolean = false, val detailsState: DetailsState = DetailsState(), diff --git a/app/src/main/java/ru/otus/marketsample/details/feature/DetailsViewModel.kt b/app/src/main/java/ru/otus/marketsample/details/feature/DetailsViewModel.kt index 9d03698..d8b6d6f 100644 --- a/app/src/main/java/ru/otus/marketsample/details/feature/DetailsViewModel.kt +++ b/app/src/main/java/ru/otus/marketsample/details/feature/DetailsViewModel.kt @@ -48,7 +48,7 @@ class DetailsViewModel( _state.update { screenState -> screenState.copy( hasError = true, - errorProvider = { context -> context.getString(R.string.error_wile_loading_data) } + errorProvider = { context -> context.getString(R.string.error_while_loading_data) } ) } } 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..4185e79 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,8 @@ interface DetailsComponent { @BindsInstance @Named("productId") productId: String, ): DetailsComponent } - - fun inject(detailsFragment: DetailsFragment) + + fun getViewModelFactory(): DetailsViewModelFactory } interface DetailsComponentDependencies { 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/ProductListScreen.kt b/app/src/main/java/ru/otus/marketsample/products/feature/ProductListScreen.kt new file mode 100644 index 0000000..dddef2a --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/products/feature/ProductListScreen.kt @@ -0,0 +1,120 @@ +package ru.otus.marketsample.products.feature + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +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.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 + +@Composable +fun ProductListScreen( + state: ProductsScreenState, + onRefresh: () -> Unit, + onItemClick: (String) -> Unit +) { + Box(modifier = Modifier.fillMaxSize().background(Color.White)) { + if (state.isLoading && state.productListState.isEmpty()) { + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } else if (state.hasError && state.productListState.isEmpty()) { + Text(text = "Ошибка при загрузке данных.", color = Color.Red, modifier = Modifier.align(Alignment.Center)) + } else { + LazyColumn(modifier = Modifier.fillMaxSize()) { + items(state.productListState) { product -> + ProductItem(product = product, onClick = { onItemClick(product.id) }) + HorizontalDivider(color = Color.LightGray) + } + } + } + } +} + +@Composable +fun ProductItem(product: ProductState, onClick: () -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 16.dp, vertical = 24.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .weight(1f) + .height(130.dp) + ) { + AsyncImage( + model = product.image, + contentDescription = product.name, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(8.dp)) + ) + + if (product.hasDiscount) { + Text( + text = product.discount, + color = Color.White, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(8.dp) + .background(Color(0xFFFF0000), RoundedCornerShape(8.dp)) + .padding(horizontal = 10.dp, vertical = 4.dp) + ) + } + } + + Column( + modifier = Modifier + .weight(1f) + .padding(horizontal = 12.dp) + .height(130.dp) + ) { + Text( + text = product.name, + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = Color.Black, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + lineHeight = 22.sp, + modifier = Modifier.weight(1f) + ) + + Text( + text = product.price, + color = Color(0xFF6200EE), + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier + .align(Alignment.End) + .background(Color(0xFFE0E0E0), RoundedCornerShape(4.dp)) + .padding(horizontal = 12.dp, vertical = 8.dp) + ) + } + } +} 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..5d90c70 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 @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import ru.otus.marketsample.products.domain.ConsumeProductsUseCase import ru.otus.marketsample.R @@ -46,7 +47,7 @@ class ProductListViewModel( _state.update { screenState -> screenState.copy( hasError = true, - errorProvider = { context -> context.getString(R.string.error_wile_loading_data) } + errorProvider = { context -> context.getString(R.string.error_while_loading_data) } ) } } diff --git a/app/src/main/java/ru/otus/marketsample/products/feature/ProductState.kt b/app/src/main/java/ru/otus/marketsample/products/feature/ProductState.kt index b500b08..295b6f8 100644 --- a/app/src/main/java/ru/otus/marketsample/products/feature/ProductState.kt +++ b/app/src/main/java/ru/otus/marketsample/products/feature/ProductState.kt @@ -2,8 +2,11 @@ package ru.otus.marketsample.products.feature import android.content.Context +import androidx.compose.runtime.Immutable + typealias ErrorProvider = (Context) -> String +@Immutable data class ProductsScreenState( val isLoading: Boolean = false, val productListState: List = emptyList(), 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..20702d8 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]) @@ -17,7 +17,7 @@ interface ProductListComponent { ): ProductListComponent } - fun inject(productListFragment: ProductListFragment) + fun getViewModelFactory(): ProductListViewModelFactory } interface ProductListComponentDependencies { 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/PromoListScreen.kt b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoListScreen.kt new file mode 100644 index 0000000..1330a5e --- /dev/null +++ b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoListScreen.kt @@ -0,0 +1,92 @@ +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.Spacer +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.CircularProgressIndicator +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 PromoListScreen( + state: PromoScreenState, + onRefresh: () -> Unit +) { + Box(modifier = Modifier.fillMaxSize().background(Color.White)) { + if (state.isLoading && state.promoListState.isEmpty()) { + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } else if (state.hasError && state.promoListState.isEmpty()) { + Text(text = "Ошибка при загрузке данных.", color = Color.Red, modifier = Modifier.align(Alignment.Center)) + } else { + LazyColumn(modifier = Modifier.fillMaxSize()) { + items(state.promoListState) { promo -> + PromoItem(promo = promo) + Spacer(modifier = Modifier.height(8.dp)) + } + } + } + } +} + +@Composable +fun PromoItem(promo: PromoState) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp) + .height(250.dp) + ) { + AsyncImage( + model = promo.image, + contentDescription = promo.name, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .height(100.dp) + .align(Alignment.BottomCenter) + .background( + brush = Brush.verticalGradient( + colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.7f)) + ) + ) + ) + + Column( + modifier = Modifier + .align(Alignment.BottomStart) + .padding(10.dp) + ) { + Text( + text = promo.name, + color = Color.White, + fontSize = 24.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = promo.description, + color = Color.White, + fontSize = 14.sp + ) + } + } +} \ No newline at end of file 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..a5b3ada 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 @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import ru.otus.marketsample.promo.domain.ConsumePromosUseCase import ru.otus.marketsample.R @@ -46,7 +47,7 @@ class PromoListViewModel( _state.update { screenState -> screenState.copy( hasError = true, - errorProvider = { context -> context.getString(R.string.error_wile_loading_data) } + errorProvider = { context -> context.getString(R.string.error_while_loading_data) } ) } } diff --git a/app/src/main/java/ru/otus/marketsample/promo/feature/PromoState.kt b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoState.kt index 8a45b7d..cf64850 100644 --- a/app/src/main/java/ru/otus/marketsample/promo/feature/PromoState.kt +++ b/app/src/main/java/ru/otus/marketsample/promo/feature/PromoState.kt @@ -2,8 +2,11 @@ package ru.otus.marketsample.promo.feature import android.content.Context +import androidx.compose.runtime.Immutable + typealias ErrorProvider = (Context) -> String +@Immutable data class PromoScreenState( val isLoading: Boolean = false, val promoListState: List = emptyList(), 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..3cfd563 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]) @@ -14,7 +14,7 @@ interface PromoComponent { fun create(dependencies: PromoComponentDependencies): PromoComponent } - fun inject(productFragment: PromoListFragment) + fun getViewModelFactory(): PromoListViewModelFactory } interface PromoComponentDependencies { 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 @@ - - - - - - - - - - - - - -