From 656fd3cb2c8c584279f8a5b060496bfed29c585f Mon Sep 17 00:00:00 2001 From: Harry Gao Date: Tue, 23 Jan 2024 12:43:45 +0800 Subject: [PATCH] feat(ui): large screen support --- app/build.gradle | 1 + .../infrastructure/android/MainActivity.kt | 6 +- .../reader/ui/component/base/RYScaffold.kt | 3 +- .../me/ash/reader/ui/page/common/HomeEntry.kt | 13 ++-- .../ash/reader/ui/page/home/flow/FlowPage.kt | 41 +++++++++++- .../ash/reader/ui/page/home/flow/FlowRoute.kt | 64 +++++++++++++++++++ .../reader/ui/page/home/flow/FlowViewModel.kt | 8 +++ .../ui/page/home/reading/ReadingPage.kt | 8 ++- .../ash/reader/ui/page/home/reading/TopBar.kt | 15 +++-- 9 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/flow/FlowRoute.kt diff --git a/app/build.gradle b/app/build.gradle index b245934a3..d12143fbd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -159,6 +159,7 @@ dependencies { // https://developer.android.com/jetpack/androidx/releases/compose-material3 implementation "androidx.compose.material3:material3:$material3" + implementation "androidx.compose.material3:material3-window-size-class:$material3" // https://github.com/google/accompanist/releases implementation "com.google.accompanist:accompanist-insets:$accompanist" diff --git a/app/src/main/java/me/ash/reader/infrastructure/android/MainActivity.kt b/app/src/main/java/me/ash/reader/infrastructure/android/MainActivity.kt index a95e08989..411b877f1 100644 --- a/app/src/main/java/me/ash/reader/infrastructure/android/MainActivity.kt +++ b/app/src/main/java/me/ash/reader/infrastructure/android/MainActivity.kt @@ -8,6 +8,8 @@ import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.CompositionLocalProvider import androidx.core.view.WindowCompat import androidx.profileinstaller.ProfileInstallerInitializer @@ -36,6 +38,7 @@ class MainActivity : ComponentActivity() { @Inject lateinit var accountDao: AccountDao + @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) @@ -60,12 +63,13 @@ class MainActivity : ComponentActivity() { } setContent { + val widthSizeClass = calculateWindowSizeClass(this).widthSizeClass CompositionLocalProvider( LocalImageLoader provides imageLoader, ) { AccountSettingsProvider(accountDao) { SettingsProvider { - HomeEntry() + HomeEntry(widthSizeClass = widthSizeClass) } } } diff --git a/app/src/main/java/me/ash/reader/ui/component/base/RYScaffold.kt b/app/src/main/java/me/ash/reader/ui/component/base/RYScaffold.kt index 103a708cd..4821cc356 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/RYScaffold.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/RYScaffold.kt @@ -17,6 +17,7 @@ import me.ash.reader.ui.theme.palette.onDark @OptIn(ExperimentalMaterial3Api::class) @Composable fun RYScaffold( + modifier: Modifier = Modifier, containerColor: Color = MaterialTheme.colorScheme.surface, topBarTonalElevation: Dp = 0.dp, containerTonalElevation: Dp = 0.dp, @@ -27,7 +28,7 @@ fun RYScaffold( content: @Composable () -> Unit = {}, ) { Scaffold( - modifier = Modifier + modifier = modifier .background( MaterialTheme.colorScheme.surfaceColorAtElevation( topBarTonalElevation, diff --git a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt index 08b533a7d..6bf181649 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt @@ -4,6 +4,7 @@ import android.util.Log import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.background import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier @@ -21,7 +22,7 @@ import me.ash.reader.infrastructure.preference.LocalReadingDarkTheme import me.ash.reader.ui.ext.* import me.ash.reader.ui.page.home.HomeViewModel import me.ash.reader.ui.page.home.feeds.FeedsPage -import me.ash.reader.ui.page.home.flow.FlowPage +import me.ash.reader.ui.page.home.flow.FlowRoute import me.ash.reader.ui.page.home.reading.ReadingPage import me.ash.reader.ui.page.settings.SettingsPage import me.ash.reader.ui.page.settings.accounts.AccountDetailsPage @@ -42,6 +43,7 @@ import me.ash.reader.ui.theme.AppTheme @Composable fun HomeEntry( homeViewModel: HomeViewModel = hiltViewModel(), + widthSizeClass: WindowWidthSizeClass, ) { val context = LocalContext.current var isReadingPage by rememberSaveable { mutableStateOf(false) } @@ -55,6 +57,8 @@ fun HomeEntry( intent?.replaceExtras(null) } + val isExpandedScreen = widthSizeClass == WindowWidthSizeClass.Expanded + LaunchedEffect(Unit) { when (context.initialPage) { 1 -> { @@ -129,13 +133,14 @@ fun HomeEntry( FeedsPage(navController = navController, homeViewModel = homeViewModel) } forwardAndBackwardComposable(route = RouteName.FLOW) { - FlowPage( + FlowRoute( navController = navController, - homeViewModel = homeViewModel, + isExpandedScreen = isExpandedScreen, + homeViewModel = homeViewModel ) } forwardAndBackwardComposable(route = "${RouteName.READING}/{articleId}") { - ReadingPage(navController = navController, homeViewModel = homeViewModel) + ReadingPage(navController = navController, homeViewModel = homeViewModel, isExpandedScreen = isExpandedScreen) } // Settings diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt index 9c37b13d1..690186249 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt @@ -23,7 +23,7 @@ import androidx.work.WorkInfo import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.ash.reader.R -import me.ash.reader.domain.model.article.ArticleFlowItem +import me.ash.reader.domain.model.article.ArticleWithFeed import me.ash.reader.domain.model.general.Filter import me.ash.reader.domain.model.general.MarkAsReadConditions import me.ash.reader.infrastructure.preference.* @@ -32,6 +32,7 @@ import me.ash.reader.ui.component.base.* import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.home.HomeViewModel +import me.ash.reader.ui.page.home.reading.ReadingPage @OptIn( com.google.accompanist.pager.ExperimentalPagerApi::class, @@ -41,6 +42,8 @@ import me.ash.reader.ui.page.home.HomeViewModel fun FlowPage( navController: NavHostController, flowViewModel: FlowViewModel = hiltViewModel(), + isExpandedScreen: Boolean, + onArticleClick: ((ArticleWithFeed) -> Unit)? = null, homeViewModel: HomeViewModel, ) { val keyboardController = LocalSoftwareKeyboardController.current @@ -97,7 +100,9 @@ fun FlowPage( onSearch = false } + val width = if (isExpandedScreen) Modifier.width(334.dp) else Modifier RYScaffold( + modifier = width, topBarTonalElevation = topBarTonalElevation.value.dp, containerTonalElevation = articleListTonalElevation.value.dp, navigationIcon = { @@ -240,8 +245,13 @@ fun FlowPage( articleListTonalElevation = articleListTonalElevation.value, onClick = { onSearch = false - navController.navigate("${RouteName.READING}/${it.article.id}") { - launchSingleTop = true + flowViewModel.openArticle() + if (onArticleClick == null) { + navController.navigate("${RouteName.READING}/${it.article.id}") { + launchSingleTop = true + } + } else { + onArticleClick(it) } } ) { @@ -281,3 +291,28 @@ fun FlowPage( } ) } + +@Composable +fun FlowWithArticleDetailsScreen( + navController: NavHostController, + isExpandedScreen: Boolean, + homeViewModel: HomeViewModel, + flowViewModel: FlowViewModel, +) { + var articleId : String? by remember { + mutableStateOf(null) + } + + Row { + FlowPage( + navController = navController, + isExpandedScreen = isExpandedScreen, + onArticleClick = { + articleId = it.article.id + }, + homeViewModel = homeViewModel, + flowViewModel = flowViewModel, + ) + ReadingPage(navController = navController, homeViewModel = homeViewModel, isExpandedScreen = isExpandedScreen, articleId = articleId) + } +} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowRoute.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowRoute.kt new file mode 100644 index 000000000..12892f500 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowRoute.kt @@ -0,0 +1,64 @@ +package me.ash.reader.ui.page.home.flow + +import androidx.compose.runtime.Composable +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavHostController +import me.ash.reader.ui.ext.collectAsStateValue +import me.ash.reader.ui.page.home.HomeViewModel +import me.ash.reader.ui.page.home.reading.ReadingPage + +enum class FlowScreenType { + FlowWithArticleDetails, + Flow, + ArticleDetails, +} + +fun getFlowScreenType(isExpanded: Boolean, flowUiState: FlowUiState): FlowScreenType = when (isExpanded) { + true -> { + FlowScreenType.FlowWithArticleDetails + } + false -> { + when (flowUiState.isArticleOpen) { + true -> FlowScreenType.ArticleDetails + false -> FlowScreenType.Flow + } + } +} + +@Composable +fun FlowRoute( + navController: NavHostController, + isExpandedScreen: Boolean, + homeViewModel: HomeViewModel, + flowViewModel: FlowViewModel = hiltViewModel(), +) { + val flowState = flowViewModel.flowUiState.collectAsStateValue() + + val flowScreenType = getFlowScreenType(isExpandedScreen, flowState) + + when (flowScreenType) { + FlowScreenType.FlowWithArticleDetails -> { + FlowWithArticleDetailsScreen( + navController = navController, + isExpandedScreen = isExpandedScreen, + homeViewModel = homeViewModel, + flowViewModel = flowViewModel, + ) + } + FlowScreenType.Flow -> { + FlowPage( + navController = navController, + isExpandedScreen = isExpandedScreen, + homeViewModel = homeViewModel, + flowViewModel = flowViewModel, + ) + } + FlowScreenType.ArticleDetails -> { + ReadingPage( + navController = navController, + homeViewModel = homeViewModel, + isExpandedScreen = isExpandedScreen, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt index 3459f78bf..6b037c3a4 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowViewModel.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import me.ash.reader.domain.model.general.MarkAsReadConditions import me.ash.reader.domain.service.RssService @@ -46,9 +47,16 @@ class FlowViewModel @Inject constructor( ) } } + + fun openArticle() { + _flowUiState.update { + it.copy(isArticleOpen = true) + } + } } data class FlowUiState( + val isArticleOpen: Boolean = false, val filterImportant: Int = 0, val listState: LazyListState = LazyListState(), val isBack: Boolean = false, diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt index fa5e45fd2..ed8db305f 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt @@ -30,6 +30,8 @@ import me.ash.reader.ui.page.home.HomeViewModel fun ReadingPage( navController: NavHostController, homeViewModel: HomeViewModel, + isExpandedScreen: Boolean, + articleId: String? = null, readingViewModel: ReadingViewModel = hiltViewModel(), ) { val tonalElevation = LocalReadingPageTonalElevation.current @@ -44,9 +46,10 @@ fun ReadingPage( val pagingItems = homeUiState.pagingData.collectAsLazyPagingItems().itemSnapshotList - LaunchedEffect(Unit) { + LaunchedEffect(articleId) { navController.currentBackStackEntryFlow.collect { - it.arguments?.getString("articleId")?.let { articleId -> + val getArticleId : String? = articleId ?: it.arguments?.getString("articleId") + getArticleId?.let { articleId -> if (readingUiState.articleId != articleId) { readingViewModel.initData(articleId) } @@ -78,6 +81,7 @@ fun ReadingPage( isShow = isShowToolBar, title = readerState.title, link = readerState.link, + isExpandedScreen = isExpandedScreen, onClose = { navController.popBackStack() }, diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/TopBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/TopBar.kt index 8332d7e7d..83a212c9d 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/TopBar.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/TopBar.kt @@ -34,6 +34,7 @@ fun TopBar( isShow: Boolean, title: String? = "", link: String? = "", + isExpandedScreen: Boolean, onClose: () -> Unit = {}, ) { val context = LocalContext.current @@ -51,12 +52,14 @@ fun TopBar( modifier = Modifier, windowInsets = WindowInsets(0.dp), navigationIcon = { - FeedbackIconButton( - imageVector = Icons.Rounded.Close, - contentDescription = stringResource(R.string.close), - tint = MaterialTheme.colorScheme.onSurface - ) { - onClose() + if (!isExpandedScreen) { + FeedbackIconButton( + imageVector = Icons.Rounded.Close, + contentDescription = stringResource(R.string.close), + tint = MaterialTheme.colorScheme.onSurface + ) { + onClose() + } } }, actions = {