diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 28ac6a91..c3e94a29 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -48,6 +48,8 @@ dependencies { implementation(lifecycle.runtime.ktx) implementation(work.runtime.ktx) implementation(hilt.navigation.compose) + implementation(compose.material3.adaptive.navigation.suite) + implementation(compose.material3.adaptive.navigation) implementation(espresso.core) implementation(compose.ui.test.manifest) implementation(compose.ui.test.junit4) diff --git a/app/src/main/java/ir/composenews/navigation/ComposeNewsNavHost.kt b/app/src/main/java/ir/composenews/navigation/ComposeNewsNavHost.kt deleted file mode 100644 index f720170d..00000000 --- a/app/src/main/java/ir/composenews/navigation/ComposeNewsNavHost.kt +++ /dev/null @@ -1,53 +0,0 @@ -package ir.composenews.navigation - -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.window.layout.DisplayFeature -import ir.composenews.navigation.graph.favoriteList -import ir.composenews.navigation.graph.marketDetail -import ir.composenews.navigation.graph.marketList -import ir.composenews.uimarket.model.MarketModel -import ir.composenews.utils.ContentType -import kotlinx.collections.immutable.PersistentList - -@Composable -fun ComposeNewsNavHost( - navController: NavHostController, - modifier: Modifier, - contentType: ContentType, - displayFeatures: PersistentList, - onMarketSelected: ((MarketModel, ContentType) -> Unit)? = null, - closeDetailScreen: () -> Unit, - uiState: MainContract.State -) { - NavHost( - navController = navController, - startDestination = Destinations.MarketListScreen.route, - modifier = modifier, - enterTransition = { EnterTransition.None }, - exitTransition = { ExitTransition.None }, - ) { - marketList( - displayFeature = displayFeatures, - contentType = contentType, - showFavorite = false, - onMarketSelected = onMarketSelected, - closeDetailScreen = closeDetailScreen, - uiState = uiState - ) - favoriteList( - displayFeature = displayFeatures, - contentType = contentType, - onMarketSelected = onMarketSelected, - closeDetailScreen = closeDetailScreen, - uiState = uiState - ) - marketDetail( - uiState = uiState, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ir/composenews/ui/ComposeNewsApp.kt b/app/src/main/java/ir/composenews/ui/ComposeNewsApp.kt index 419e054b..3b97d279 100644 --- a/app/src/main/java/ir/composenews/ui/ComposeNewsApp.kt +++ b/app/src/main/java/ir/composenews/ui/ComposeNewsApp.kt @@ -1,116 +1,31 @@ package ir.composenews.ui -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Home +import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold -import androidx.compose.material3.windowsizeclass.WindowSizeClass -import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.navigation.compose.currentBackStackEntryAsState -import androidx.navigation.compose.rememberNavController -import androidx.window.layout.DisplayFeature -import androidx.window.layout.FoldingFeature import ir.composenews.navigation.BottomNavItem -import ir.composenews.navigation.ComposeNewsNavHost import ir.composenews.navigation.Destinations -import ir.composenews.navigation.MainContract -import ir.composenews.ui.component.BottomNavigationBar -import ir.composenews.ui.component.ComposeNewsNavigationRail -import ir.composenews.uimarket.model.MarketModel -import ir.composenews.utils.ContentType -import ir.composenews.utils.DevicePosture -import ir.composenews.utils.NavigationType -import ir.composenews.utils.isBookPosture -import ir.composenews.utils.isSeparating -import kotlinx.collections.immutable.PersistentList +import ir.composenews.navigation.graph.ListWithDetailScreen import kotlinx.collections.immutable.persistentListOf +@OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable -fun ComposeNewsApp( - windowSize: WindowSizeClass, - displayFeatures: PersistentList, - onMarketSelected: ((MarketModel, ContentType) -> Unit)? = null, - closeDetailScreen: () -> Unit, - uiState: MainContract.State -) { - - /** - * This will help us select type of navigation and content type depending on window size and - * fold state of the device. - */ - val navigationType: NavigationType - val contentType: ContentType - - /** - * We are using display's folding features to map the device postures a fold is in. - * In the state of folding device If it's half fold in BookPosture we want to avoid content - * at the crease/hinge - */ - val foldingFeature = displayFeatures.filterIsInstance().firstOrNull() - val foldingDevicePosture = when { - isBookPosture(foldingFeature) -> - DevicePosture.BookPosture(foldingFeature.bounds) - - isSeparating(foldingFeature) -> - DevicePosture.Separating(foldingFeature.bounds, foldingFeature.orientation) - - else -> DevicePosture.NormalPosture - } - - when (windowSize.widthSizeClass) { - WindowWidthSizeClass.Compact -> { - navigationType = NavigationType.BOTTOM_NAVIGATION - contentType = ContentType.SINGLE_PANE - } - - WindowWidthSizeClass.Medium -> { - navigationType = NavigationType.NAVIGATION_RAIL - contentType = if (foldingDevicePosture != DevicePosture.NormalPosture) { - ContentType.DUAL_PANE - } else { - ContentType.SINGLE_PANE - } - } - - WindowWidthSizeClass.Expanded -> { - navigationType = NavigationType.NAVIGATION_RAIL - contentType = ContentType.DUAL_PANE - } - - else -> { - navigationType = NavigationType.BOTTOM_NAVIGATION - contentType = ContentType.SINGLE_PANE - } - } - - ComposeNewsAppWrapper( - navigationType = navigationType, - contentType = contentType, - displayFeatures = displayFeatures, - onMarketSelected = onMarketSelected, - closeDetailScreen = closeDetailScreen, - uiState = uiState - ) - -} - -@Composable -fun ComposeNewsAppWrapper( - navigationType: NavigationType, - contentType: ContentType, - displayFeatures: PersistentList, - onMarketSelected: ((MarketModel, ContentType) -> Unit)? = null, - closeDetailScreen: () -> Unit, - uiState: MainContract.State -) { +fun ComposeNewsApp() { val items = remember { persistentListOf( BottomNavItem( @@ -126,47 +41,48 @@ fun ComposeNewsAppWrapper( ) } - val navController = rememberNavController() - val backStackEntry = navController.currentBackStackEntryAsState() - val currentScreenRoute = backStackEntry.value?.destination?.route - val bottomNavVisible = - navigationType == NavigationType.BOTTOM_NAVIGATION && !uiState.isDetailOnlyOpen - + var currentRoute by remember { mutableStateOf(Destinations.MarketListScreen.route) } Scaffold { paddingValues -> - Row( - modifier = Modifier.padding(paddingValues) + NavigationSuiteScaffold( + navigationSuiteItems = { + items.forEach { item -> + item(selected = item.route == currentRoute, onClick = { + currentRoute = item.route + }, icon = { + Icon( + imageVector = item.icon, + contentDescription = item.name, + ) + }, label = { + Text( + text = item.name, + ) + }) + } + }, + layoutType = NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo( + currentWindowAdaptiveInfo() + ) ) { - AnimatedVisibility(visible = navigationType == NavigationType.NAVIGATION_RAIL) { - ComposeNewsNavigationRail( - items = items, - currentScreenRoute = currentScreenRoute, - onItemClick = { navController.navigate(it.route) }) - } - Column( - modifier = Modifier - .fillMaxSize() - ) { - ComposeNewsNavHost( - modifier = Modifier - .weight(1f) - .padding(bottom = paddingValues.calculateBottomPadding()), - navController = navController, - contentType = contentType, - displayFeatures = displayFeatures, - onMarketSelected = onMarketSelected, - closeDetailScreen = closeDetailScreen, - uiState = uiState - ) - AnimatedVisibility(visible = bottomNavVisible) { - BottomNavigationBar( - items = items, - currentScreenRoute = currentScreenRoute, - onItemClick = { navController.navigate(it.route) } + val navigator = rememberListDetailPaneScaffoldNavigator() + when (currentRoute) { + Destinations.MarketListScreen.route -> { + ListWithDetailScreen( + Modifier.padding(paddingValues), + navigator, + showFavorite = false, ) } - } + Destinations.FavoriteMarketScreen.route -> { + ListWithDetailScreen( + Modifier.padding(paddingValues), + navigator, + showFavorite = true, + ) + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/ir/composenews/ui/MainActivity.kt b/app/src/main/java/ir/composenews/ui/MainActivity.kt index 85367b58..6a2f4e2a 100644 --- a/app/src/main/java/ir/composenews/ui/MainActivity.kt +++ b/app/src/main/java/ir/composenews/ui/MainActivity.kt @@ -3,32 +3,17 @@ package ir.composenews.ui import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.activity.viewModels -import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi -import androidx.compose.material3.windowsizeclass.WindowSizeClass -import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.dp -import com.google.accompanist.adaptive.calculateDisplayFeatures import dagger.hilt.android.AndroidEntryPoint import ir.composenews.designsystem.theme.ComposeNewsTheme -import ir.composenews.navigation.MainContract import ir.composenews.permission.enum.PermissionType import ir.composenews.permission.manager.PermissionManager import ir.composenews.permission.manager.PermissionManagerImpl -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toPersistentList @AndroidEntryPoint class MainActivity : ComponentActivity(), PermissionManager by PermissionManagerImpl() { - - private val viewModel: MainViewModel by viewModels() - - @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -39,96 +24,53 @@ class MainActivity : ComponentActivity(), PermissionManager by PermissionManager setContent { ComposeNewsTheme { - - val windowSize = calculateWindowSizeClass(this) - val displayFeatures = calculateDisplayFeatures(this) - val uiState = viewModel.state.collectAsState() - - ComposeNewsApp( - windowSize = windowSize, - displayFeatures = displayFeatures.toPersistentList(), - uiState = uiState.value, - closeDetailScreen = { viewModel.closeDetailScreen() }, - onMarketSelected = { market, contentType -> - viewModel.event( - MainContract.Event.SetMarket( - market = market, - contentType = contentType - ) - ) - } - ) + ComposeNewsApp() } } } } -@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @Preview(showBackground = true) @Composable fun ComposeNewsAppPreview() { ComposeNewsTheme { ComposeNewsApp( - windowSize = WindowSizeClass.calculateFromSize(DpSize(400.dp, 900.dp)), - displayFeatures = persistentListOf(), - uiState = MainContract.State(), - closeDetailScreen = {}, ) } } -@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @Preview(showBackground = true, widthDp = 700, heightDp = 500) @Composable fun ComposeNewsAppPreviewTablet() { ComposeNewsTheme { ComposeNewsApp( - windowSize = WindowSizeClass.calculateFromSize(DpSize(700.dp, 500.dp)), - displayFeatures = persistentListOf(), - uiState = MainContract.State(), - closeDetailScreen = {}, ) } } -@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @Preview(showBackground = true, widthDp = 500, heightDp = 700) @Composable fun ComposeNewsAppPreviewTabletPortrait() { ComposeNewsTheme { ComposeNewsApp( - windowSize = WindowSizeClass.calculateFromSize(DpSize(500.dp, 700.dp)), - displayFeatures = persistentListOf(), - uiState = MainContract.State(), - closeDetailScreen = {}, ) } } -@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @Preview(showBackground = true, widthDp = 1100, heightDp = 600) @Composable fun ComposeNewsAppPreviewDesktop() { ComposeNewsTheme { ComposeNewsApp( - windowSize = WindowSizeClass.calculateFromSize(DpSize(1100.dp, 600.dp)), - displayFeatures = persistentListOf(), - uiState = MainContract.State(), - closeDetailScreen = {}, ) } } -@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @Preview(showBackground = true, widthDp = 600, heightDp = 1100) @Composable fun ComposeNewsAppPreviewDesktopPortrait() { ComposeNewsTheme { ComposeNewsApp( - windowSize = WindowSizeClass.calculateFromSize(DpSize(600.dp, 1100.dp)), - displayFeatures = persistentListOf(), - uiState = MainContract.State(), - closeDetailScreen = {}, ) } } \ No newline at end of file diff --git a/app/src/main/java/ir/composenews/ui/MainViewModel.kt b/app/src/main/java/ir/composenews/ui/MainViewModel.kt deleted file mode 100644 index 7c67be49..00000000 --- a/app/src/main/java/ir/composenews/ui/MainViewModel.kt +++ /dev/null @@ -1,45 +0,0 @@ -package ir.composenews.ui - -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import ir.composenews.base.BaseViewModel -import ir.composenews.core_test.dispatcher.DispatcherProvider -import ir.composenews.navigation.MainContract -import ir.composenews.uimarket.model.MarketModel -import ir.composenews.utils.ContentType -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class MainViewModel @Inject constructor( - dispatcherProvider: DispatcherProvider, -) : BaseViewModel(dispatcherProvider), MainContract { - - private val mutableState = MutableStateFlow(MainContract.State()) - override val state: StateFlow = mutableState.asStateFlow() - override fun event(event: MainContract.Event) { - when (event) { - is MainContract.Event.SetMarket -> setMarket(event.market, event.contentType) - } - } - - private fun setMarket(market: MarketModel?, contentType: ContentType) = viewModelScope.launch { - mutableState.emit( - mutableState.value.copy( - market, - isDetailOnlyOpen = contentType == ContentType.SINGLE_PANE - ) - ) - } - - fun closeDetailScreen() = viewModelScope.launch { - mutableState.emit( - mutableState.value.copy( - isDetailOnlyOpen = false - ) - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/ir/composenews/ui/component/BottomNavigationBar.kt b/app/src/main/java/ir/composenews/ui/component/BottomNavigationBar.kt deleted file mode 100644 index 1a744cd9..00000000 --- a/app/src/main/java/ir/composenews/ui/component/BottomNavigationBar.kt +++ /dev/null @@ -1,39 +0,0 @@ -package ir.composenews.ui.component - -import androidx.compose.material3.NavigationBar -import androidx.compose.material3.NavigationBarItem -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import ir.composenews.designsystem.component.BottomNavigationIcon -import ir.composenews.navigation.BottomNavItem -import kotlinx.collections.immutable.PersistentList - -@Composable -fun BottomNavigationBar( - items: PersistentList, - currentScreenRoute: String?, - modifier: Modifier = Modifier, - onItemClick: (BottomNavItem) -> Unit -) { - NavigationBar( - modifier = modifier, - tonalElevation = 15.dp - ) { - items.forEach { item -> - val selected = item.route == currentScreenRoute - NavigationBarItem( - selected = selected, - onClick = { onItemClick(item) }, - icon = { - BottomNavigationIcon( - name = item.name, - icon = item.icon, - selected = selected, - badgeCount = item.badgeCount, - ) - } - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ir/composenews/ui/component/NavigationRail.kt b/app/src/main/java/ir/composenews/ui/component/NavigationRail.kt deleted file mode 100644 index ced15831..00000000 --- a/app/src/main/java/ir/composenews/ui/component/NavigationRail.kt +++ /dev/null @@ -1,41 +0,0 @@ -package ir.composenews.ui.component - -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.NavigationRail -import androidx.compose.material3.NavigationRailItem -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import ir.composenews.designsystem.component.BottomNavigationIcon -import ir.composenews.navigation.BottomNavItem -import kotlinx.collections.immutable.PersistentList - -@Composable -fun ComposeNewsNavigationRail( - items: PersistentList, - currentScreenRoute: String?, - modifier: Modifier = Modifier, - onItemClick: (BottomNavItem) -> Unit -) { - NavigationRail( - modifier = modifier.fillMaxHeight() - .padding(end = 30.dp), - ) { - items.forEach { item -> - val selected = item.route == currentScreenRoute - NavigationRailItem( - selected = selected, - onClick = { onItemClick(item) }, - icon = { - BottomNavigationIcon( - name = item.name, - icon = item.icon, - selected = selected, - badgeCount = item.badgeCount, - ) - } - ) - } - } -} \ No newline at end of file diff --git a/core/uimarket/src/main/java/ir/composenews/uimarket/model/MarketModel.kt b/core/uimarket/src/main/java/ir/composenews/uimarket/model/MarketModel.kt index a5f332de..a89a374f 100644 --- a/core/uimarket/src/main/java/ir/composenews/uimarket/model/MarketModel.kt +++ b/core/uimarket/src/main/java/ir/composenews/uimarket/model/MarketModel.kt @@ -1,5 +1,8 @@ package ir.composenews.uimarket.model +import android.os.Parcel +import android.os.Parcelable + data class MarketModel( val id: String, val name: String, @@ -8,4 +11,39 @@ data class MarketModel( val priceChangePercentage24h: Double, val imageUrl: String, val isFavorite: Boolean = false, -) +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readString().orEmpty(), + parcel.readString().orEmpty(), + parcel.readString().orEmpty(), + parcel.readDouble(), + parcel.readDouble(), + parcel.readString().orEmpty(), + parcel.readByte() != 0.toByte(), + ) { + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(id) + parcel.writeString(name) + parcel.writeString(symbol) + parcel.writeDouble(currentPrice) + parcel.writeDouble(priceChangePercentage24h) + parcel.writeString(imageUrl) + parcel.writeByte(if (isFavorite) 1 else 0) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): MarketModel { + return MarketModel(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} diff --git a/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListScreen.kt b/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListScreen.kt index c2ae865a..35cad8a1 100644 --- a/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListScreen.kt +++ b/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListScreen.kt @@ -33,7 +33,6 @@ import ir.composenews.designsystem.theme.ComposeNewsTheme import ir.composenews.marketlist.component.MarketListItem import ir.composenews.marketlist.preview_provider.MarketListStateProvider import ir.composenews.uimarket.model.MarketModel -import ir.composenews.utils.ContentType /** * LongParameterList - > compose unimited @@ -42,11 +41,7 @@ import ir.composenews.utils.ContentType fun MarketListRoute( viewModel: MarketListViewModel = hiltViewModel(), showFavoriteList: Boolean = false, - isDetailOnlyOpen: Boolean, - marketModel: MarketModel?, - closeDetailScreen: () -> Unit, onNavigateToDetailScreen: (market: MarketModel) -> Unit, - contentType: ContentType, ) { val (state, event) = use(viewModel = viewModel) LaunchedEffect(key1 = Unit) { @@ -56,16 +51,6 @@ fun MarketListRoute( } } - LaunchedEffect(key1 = contentType) { - if (contentType == ContentType.SINGLE_PANE && !isDetailOnlyOpen) { - closeDetailScreen() - } - } - - if (contentType == ContentType.DUAL_PANE && !state.refreshing && state.marketList.isNotEmpty() && marketModel == null) { - onNavigateToDetailScreen(state.marketList[0]) - } - BaseRoute( baseViewModel = viewModel, shimmerView = { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3b771a99..2cbe0d2c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,7 @@ composeBOM = "2024.09.03" composeActivity = "1.9.2" composeCoil = "2.7.0" composeMaterial3 = "1.3.0" +material3Adaptive = "1.0.0" composeWear = "1.4.0" wearToolingPreview = "1.0.0" horologistComposeLayout = "0.6.17" @@ -80,6 +81,8 @@ compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "composeMaterial3" } compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "composeActivity" } compose-coil = { group = "io.coil-kt", name = "coil-compose", version.ref = "composeCoil" } +compose-material3-adaptive-navigation = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation", version.ref = "material3Adaptive" } +compose-material3-adaptive-navigation-suite = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite", version.ref = "composeMaterial3" } compose-material-wear = { group = "androidx.wear.compose", name = "compose-material", version.ref = "composeWear" } compose-foundation-wear = { group = "androidx.wear.compose", name = "compose-foundation", version.ref = "composeWear" } compose-ui-preview-wear = { group = "androidx.wear", name = "wear-tooling-preview", version.ref = "wearToolingPreview" } diff --git a/library/navigation/build.gradle.kts b/library/navigation/build.gradle.kts index d61927f9..78d75caf 100644 --- a/library/navigation/build.gradle.kts +++ b/library/navigation/build.gradle.kts @@ -15,4 +15,6 @@ dependencies { implementation(domain.market) } api(libs.navigation.compose) + implementation(libs.compose.material3.adaptive.navigation) + implementation(libs.compose.material3.adaptive.navigation.suite) } \ No newline at end of file diff --git a/library/navigation/src/main/java/ir/composenews/navigation/MainContract.kt b/library/navigation/src/main/java/ir/composenews/navigation/MainContract.kt deleted file mode 100644 index 7dec4cd0..00000000 --- a/library/navigation/src/main/java/ir/composenews/navigation/MainContract.kt +++ /dev/null @@ -1,18 +0,0 @@ -package ir.composenews.navigation - -import ir.composenews.base.UnidirectionalViewModel -import ir.composenews.uimarket.model.MarketModel -import ir.composenews.utils.ContentType - -interface MainContract : - UnidirectionalViewModel { - - data class State( - val market: MarketModel? = null, - val isDetailOnlyOpen: Boolean = false, - ) - - sealed class Event { - data class SetMarket(val market: MarketModel?, val contentType: ContentType) : Event() - } -} diff --git a/library/navigation/src/main/java/ir/composenews/navigation/graph/FavoriteMarketList.kt b/library/navigation/src/main/java/ir/composenews/navigation/graph/FavoriteMarketList.kt deleted file mode 100644 index 560a605f..00000000 --- a/library/navigation/src/main/java/ir/composenews/navigation/graph/FavoriteMarketList.kt +++ /dev/null @@ -1,48 +0,0 @@ -@file:Suppress("ImportOrdering") - -package ir.composenews.navigation.graph - -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import androidx.window.layout.DisplayFeature -import ir.composenews.navigation.Destinations -import ir.composenews.navigation.MainContract -import ir.composenews.navigation.extension_function.parcelableData -import ir.composenews.uimarket.model.MarketModel -import ir.composenews.utils.ContentType -import kotlinx.collections.immutable.PersistentList - -fun NavGraphBuilder.favoriteList( - contentType: ContentType, - displayFeature: PersistentList, - uiState: MainContract.State, - closeDetailScreen: () -> Unit, - onMarketSelected: ((MarketModel, ContentType) -> Unit)? = null, -) { - composable(Destinations.FavoriteMarketScreen.route) { entry -> - when (contentType) { - ContentType.SINGLE_PANE -> SingleListScreen( - showFavorite = true, - uiState = uiState, - onMarketSelected = onMarketSelected, - closeDetailScreen = closeDetailScreen, - contentType = contentType, - ) - - ContentType.DUAL_PANE -> { - val market = - entry.parcelableData(Destinations.MarketDetailScreen().market) - ?: uiState.market - ListWithDetailScreen( - displayFeatures = displayFeature, - market = market, - showFavorite = true, - uiState = uiState, - onMarketSelected = onMarketSelected, - closeDetailScreen = closeDetailScreen, - contentType = contentType, - ) - } - } - } -} diff --git a/library/navigation/src/main/java/ir/composenews/navigation/graph/ListWithDetailScreen.kt b/library/navigation/src/main/java/ir/composenews/navigation/graph/ListWithDetailScreen.kt new file mode 100644 index 00000000..6f98c4de --- /dev/null +++ b/library/navigation/src/main/java/ir/composenews/navigation/graph/ListWithDetailScreen.kt @@ -0,0 +1,52 @@ +package ir.composenews.navigation.graph + +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole +import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold +import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import ir.composenews.marketdetail.MarketDetailRoute +import ir.composenews.marketlist.MarketListRoute +import ir.composenews.uimarket.model.MarketModel + +@OptIn(ExperimentalMaterial3AdaptiveApi::class) +@Composable +fun ListWithDetailScreen( + modifier: Modifier = Modifier, + navigator: ThreePaneScaffoldNavigator, + showFavorite: Boolean, +) { + NavigableListDetailPaneScaffold( + modifier = modifier, + navigator = navigator, + listPane = { + MarketListRoute( + onNavigateToDetailScreen = { market -> + navigator.navigateTo( + pane = ListDetailPaneScaffoldRole.Detail, + content = market, + ) + }, + showFavoriteList = showFavorite, + ) + }, + detailPane = { + (navigator.currentDestination?.content as? MarketModel)?.let { content -> + MarketDetailRoute( + market = content, + ) + } ?: run { + Box( + modifier = modifier, + contentAlignment = Alignment.Center, + ) { + Text("Select item from left") + } + } + }, + ) +} diff --git a/library/navigation/src/main/java/ir/composenews/navigation/graph/MarketDetail.kt b/library/navigation/src/main/java/ir/composenews/navigation/graph/MarketDetail.kt deleted file mode 100644 index 002d2727..00000000 --- a/library/navigation/src/main/java/ir/composenews/navigation/graph/MarketDetail.kt +++ /dev/null @@ -1,41 +0,0 @@ -@file:Suppress("MagicNumber") - -package ir.composenews.navigation.graph - -import androidx.compose.animation.AnimatedContentTransitionScope -import androidx.compose.animation.core.EaseIn -import androidx.compose.animation.core.EaseOut -import androidx.compose.animation.core.tween -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import ir.composenews.marketdetail.MarketDetailRoute -import ir.composenews.navigation.Destinations -import ir.composenews.navigation.MainContract -import ir.composenews.navigation.extension_function.parcelableData -import ir.composenews.uimarket.model.MarketModel - -fun NavGraphBuilder.marketDetail( - uiState: MainContract.State, -) { - composable( - route = Destinations.MarketDetailScreen().route, - enterTransition = { - slideIntoContainer( - animationSpec = tween(300, easing = EaseIn), - towards = AnimatedContentTransitionScope.SlideDirection.Start, - ) - }, - exitTransition = { - slideOutOfContainer( - animationSpec = tween(300, easing = EaseOut), - towards = AnimatedContentTransitionScope.SlideDirection.End, - ) - }, - ) { entry -> - val market = entry.parcelableData(Destinations.MarketDetailScreen().market) - ?: uiState.market as MarketModel - MarketDetailRoute( - market = market, - ) - } -} diff --git a/library/navigation/src/main/java/ir/composenews/navigation/graph/MarketList.kt b/library/navigation/src/main/java/ir/composenews/navigation/graph/MarketList.kt deleted file mode 100644 index 999c42c9..00000000 --- a/library/navigation/src/main/java/ir/composenews/navigation/graph/MarketList.kt +++ /dev/null @@ -1,116 +0,0 @@ -package ir.composenews.navigation.graph - -import androidx.activity.compose.BackHandler -import androidx.compose.runtime.Composable -import androidx.compose.ui.unit.dp -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import androidx.window.layout.DisplayFeature -import com.google.accompanist.adaptive.HorizontalTwoPaneStrategy -import com.google.accompanist.adaptive.TwoPane -import ir.composenews.marketdetail.MarketDetailRoute -import ir.composenews.marketlist.MarketListRoute -import ir.composenews.navigation.Destinations -import ir.composenews.navigation.MainContract -import ir.composenews.navigation.extension_function.parcelableData -import ir.composenews.uimarket.model.MarketModel -import ir.composenews.utils.ContentType -import kotlinx.collections.immutable.PersistentList - -fun NavGraphBuilder.marketList( - showFavorite: Boolean, - contentType: ContentType, - displayFeature: PersistentList, - uiState: MainContract.State, - closeDetailScreen: () -> Unit, - onMarketSelected: ((MarketModel, ContentType) -> Unit)? = null, -) { - composable(Destinations.MarketListScreen.route) { entry -> - when (contentType) { - ContentType.SINGLE_PANE -> SingleListScreen( - showFavorite = showFavorite, - uiState = uiState, - onMarketSelected = onMarketSelected, - contentType = contentType, - closeDetailScreen = closeDetailScreen, - ) - - ContentType.DUAL_PANE -> { - val market = - entry.parcelableData(Destinations.MarketDetailScreen().market) - ?: uiState.market - ListWithDetailScreen( - displayFeatures = displayFeature, - market = market, - showFavorite = showFavorite, - uiState = uiState, - onMarketSelected = onMarketSelected, - closeDetailScreen = closeDetailScreen, - contentType = contentType, - ) - } - } - } -} - -@Composable -fun SingleListScreen( - showFavorite: Boolean, - uiState: MainContract.State, - closeDetailScreen: () -> Unit, - contentType: ContentType, - onMarketSelected: ((MarketModel, ContentType) -> Unit)? = null, -) { - if (uiState.market != null && uiState.isDetailOnlyOpen) { - BackHandler { - closeDetailScreen() - } - MarketDetailRoute( - market = uiState.market, - ) - } else { - MarketListRoute( - onNavigateToDetailScreen = { market -> - onMarketSelected?.invoke(market, contentType) - }, - isDetailOnlyOpen = uiState.isDetailOnlyOpen, - marketModel = uiState.market, - showFavoriteList = showFavorite, - closeDetailScreen = closeDetailScreen, - contentType = contentType, - ) - } -} - -@Composable -fun ListWithDetailScreen( - showFavorite: Boolean, - displayFeatures: PersistentList, - market: MarketModel?, - uiState: MainContract.State, - contentType: ContentType, - closeDetailScreen: () -> Unit, - onMarketSelected: ((MarketModel, ContentType) -> Unit)? = null, -) { - TwoPane( - first = { - MarketListRoute( - onNavigateToDetailScreen = { market -> - onMarketSelected?.invoke(market, contentType) - }, - isDetailOnlyOpen = uiState.isDetailOnlyOpen, - marketModel = uiState.market, - showFavoriteList = showFavorite, - closeDetailScreen = closeDetailScreen, - contentType = contentType, - ) - }, - second = { - MarketDetailRoute( - market = market, - ) - }, - strategy = HorizontalTwoPaneStrategy(splitFraction = 0.5f, gapWidth = 16.dp), - displayFeatures = displayFeatures, - ) -}