feat(ui): ArticleListReaderPage
This commit is contained in:
parent
d1f9641976
commit
aca2028f5d
@ -8,6 +8,7 @@ plugins {
|
||||
alias(libs.plugins.aboutlibraries)
|
||||
alias(libs.plugins.room)
|
||||
alias(libs.plugins.hilt)
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
}
|
||||
|
||||
fun fetchGitCommitHash(): String {
|
||||
@ -131,6 +132,9 @@ dependencies {
|
||||
implementation(libs.compose.ui.tooling.preview)
|
||||
androidTestImplementation(libs.compose.ui.test.junit4)
|
||||
implementation(libs.compose.material3)
|
||||
implementation(libs.compose.material3.adaptive)
|
||||
implementation(libs.compose.material3.adaptive.layout)
|
||||
implementation(libs.compose.material3.adaptive.navigation)
|
||||
|
||||
// Accompanist
|
||||
implementation(libs.accompanist.swiperefresh)
|
||||
|
@ -28,6 +28,7 @@ import me.ash.reader.ui.ext.initialFilter
|
||||
import me.ash.reader.ui.ext.initialPage
|
||||
import me.ash.reader.ui.ext.isFirstLaunch
|
||||
import me.ash.reader.ui.page.home.HomeViewModel
|
||||
import me.ash.reader.ui.page.home.adaptive.ArticleListReaderPage
|
||||
import me.ash.reader.ui.page.home.feeds.FeedsPage
|
||||
import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewModel
|
||||
import me.ash.reader.ui.page.home.flow.FlowPage
|
||||
@ -164,13 +165,37 @@ fun HomeEntry(
|
||||
)
|
||||
}
|
||||
animatedComposable(route = RouteName.FLOW) {
|
||||
FlowPage(
|
||||
|
||||
ArticleListReaderPage(
|
||||
modifier = Modifier,
|
||||
navController = navController,
|
||||
homeViewModel = homeViewModel,
|
||||
homeViewModel = homeViewModel
|
||||
)
|
||||
/* FlowPage(
|
||||
homeViewModel = homeViewModel,
|
||||
onNavigateToFeeds = {
|
||||
if (navController.previousBackStackEntry == null) {
|
||||
navController.navigate(RouteName.FEEDS) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
} else {
|
||||
navController.popBackStack()
|
||||
}
|
||||
}, onOpenArticle = {
|
||||
navController.navigate("${RouteName.READING}/${it}") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
)*/
|
||||
|
||||
}
|
||||
animatedComposable(route = "${RouteName.READING}/{articleId}") {
|
||||
ReadingPage(navController = navController, homeViewModel = homeViewModel)
|
||||
val articleId = it.arguments?.getString("articleId")
|
||||
ReadingPage(
|
||||
navController = navController,
|
||||
articleId = articleId,
|
||||
homeViewModel = homeViewModel
|
||||
)
|
||||
}
|
||||
|
||||
// Settings
|
||||
|
@ -0,0 +1,77 @@
|
||||
package me.ash.reader.ui.page.home.adaptive
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||
import androidx.compose.material3.adaptive.layout.AnimatedPane
|
||||
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
|
||||
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
|
||||
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import me.ash.reader.ui.page.common.RouteName
|
||||
import me.ash.reader.ui.page.home.HomeViewModel
|
||||
import me.ash.reader.ui.page.home.flow.FlowPage
|
||||
import me.ash.reader.ui.page.home.flow.FlowViewModel
|
||||
import me.ash.reader.ui.page.home.reading.ReadingPage
|
||||
import me.ash.reader.ui.page.home.reading.ReadingViewModel
|
||||
|
||||
@Parcelize
|
||||
data class ArticleData(val id: String) : Parcelable
|
||||
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
@Composable
|
||||
fun ArticleListReaderPage(
|
||||
modifier: Modifier = Modifier,
|
||||
navController: NavHostController,
|
||||
flowViewModel: FlowViewModel = hiltViewModel(),
|
||||
readingViewModel: ReadingViewModel = hiltViewModel(),
|
||||
homeViewModel: HomeViewModel,
|
||||
) {
|
||||
val navigator = rememberListDetailPaneScaffoldNavigator<ArticleData>()
|
||||
BackHandler(navigator.canNavigateBack()) {
|
||||
navigator.navigateBack()
|
||||
}
|
||||
val currentArticle = navigator.currentDestination?.content
|
||||
|
||||
ListDetailPaneScaffold(
|
||||
modifier = modifier,
|
||||
directive = navigator.scaffoldDirective,
|
||||
value = navigator.scaffoldValue,
|
||||
listPane = {
|
||||
AnimatedPane {
|
||||
FlowPage(
|
||||
homeViewModel = homeViewModel,
|
||||
flowViewModel = flowViewModel,
|
||||
readingArticleId = currentArticle?.id,
|
||||
onNavigateToFeeds = {
|
||||
if (navController.previousBackStackEntry == null) {
|
||||
navController.navigate(RouteName.FEEDS) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
} else {
|
||||
navController.popBackStack()
|
||||
}
|
||||
}, onOpenArticle = {
|
||||
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, ArticleData(it))
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
detailPane = {
|
||||
AnimatedPane {
|
||||
navigator.currentDestination?.content?.let {
|
||||
ReadingPage(
|
||||
navController = navController,
|
||||
articleId = it.id,
|
||||
homeViewModel = homeViewModel,
|
||||
readingViewModel = readingViewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
@ -1,13 +1,6 @@
|
||||
package me.ash.reader.ui.page.home.flow
|
||||
|
||||
import android.util.Log
|
||||
import android.view.HapticFeedbackConstants
|
||||
import androidx.compose.animation.Animatable
|
||||
import androidx.compose.animation.core.AnimationSpec
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.exponentialDecay
|
||||
import androidx.compose.animation.core.snap
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
@ -17,7 +10,6 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@ -43,32 +35,27 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.size.Precision
|
||||
import coil.size.Scale
|
||||
@ -104,6 +91,7 @@ private const val TAG = "ArticleItem"
|
||||
fun ArticleItem(
|
||||
modifier: Modifier = Modifier,
|
||||
articleWithFeed: ArticleWithFeed,
|
||||
isHighlighted: Boolean = false,
|
||||
onClick: (ArticleWithFeed) -> Unit = {},
|
||||
onLongClick: (() -> Unit)? = null
|
||||
) {
|
||||
@ -120,6 +108,7 @@ fun ArticleItem(
|
||||
imgData = article.img,
|
||||
isStarred = article.isStarred,
|
||||
isUnread = article.isUnread,
|
||||
isHighlighted = isHighlighted,
|
||||
onClick = { onClick(articleWithFeed) },
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
@ -138,6 +127,7 @@ fun ArticleItem(
|
||||
imgData: Any? = null,
|
||||
isStarred: Boolean = false,
|
||||
isUnread: Boolean = false,
|
||||
isHighlighted: Boolean = false,
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: (() -> Unit)? = null
|
||||
) {
|
||||
@ -152,12 +142,14 @@ fun ArticleItem(
|
||||
modifier = modifier
|
||||
.padding(horizontal = 12.dp)
|
||||
.clip(Shape20)
|
||||
.background(if (isHighlighted) MaterialTheme.colorScheme.primaryContainer else Color.Transparent)
|
||||
.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
)
|
||||
.padding(horizontal = 12.dp, vertical = 12.dp)
|
||||
.alpha(
|
||||
if (isHighlighted) 1f else {
|
||||
when (articleListReadIndicator) {
|
||||
FlowArticleReadIndicatorPreference.AllRead -> {
|
||||
if (isUnread) 1f else 0.5f
|
||||
@ -167,6 +159,7 @@ fun ArticleItem(
|
||||
if (isUnread || isStarred) 1f else 0.5f
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
) {
|
||||
// Top
|
||||
@ -292,10 +285,9 @@ private const val SwipeActionDelay = 300L
|
||||
@Composable
|
||||
fun SwipeableArticleItem(
|
||||
articleWithFeed: ArticleWithFeed,
|
||||
isFilterUnread: Boolean = false,
|
||||
isHighlighted: Boolean = false,
|
||||
articleListTonalElevation: Int = 0,
|
||||
onClick: (ArticleWithFeed) -> Unit = {},
|
||||
isSwipeEnabled: () -> Boolean = { false },
|
||||
isMenuEnabled: Boolean = true,
|
||||
onToggleStarred: (ArticleWithFeed) -> Unit = { },
|
||||
onToggleRead: (ArticleWithFeed) -> Unit = { },
|
||||
@ -349,8 +341,9 @@ fun SwipeableArticleItem(
|
||||
) {
|
||||
ArticleItem(
|
||||
articleWithFeed = articleWithFeed,
|
||||
isHighlighted = isHighlighted,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
onLongClick = onLongClick,
|
||||
)
|
||||
with(articleWithFeed.article) {
|
||||
if (isMenuEnabled) {
|
||||
|
@ -16,11 +16,10 @@ import me.ash.reader.domain.model.article.ArticleWithFeed
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun LazyListScope.ArticleList(
|
||||
pagingItems: LazyPagingItems<ArticleFlowItem>,
|
||||
isFilterUnread: Boolean,
|
||||
isShowFeedIcon: Boolean,
|
||||
isShowStickyHeader: Boolean,
|
||||
articleListTonalElevation: Int,
|
||||
isSwipeEnabled: () -> Boolean = { false },
|
||||
readingArticleId: String?,
|
||||
isMenuEnabled: Boolean = true,
|
||||
onClick: (ArticleWithFeed) -> Unit = {},
|
||||
onToggleStarred: (ArticleWithFeed) -> Unit = { },
|
||||
@ -42,10 +41,9 @@ fun LazyListScope.ArticleList(
|
||||
is ArticleFlowItem.Article -> {
|
||||
SwipeableArticleItem(
|
||||
articleWithFeed = item.articleWithFeed,
|
||||
isFilterUnread = isFilterUnread,
|
||||
isHighlighted = readingArticleId == item.articleWithFeed.article.id,
|
||||
articleListTonalElevation = articleListTonalElevation,
|
||||
onClick = onClick,
|
||||
isSwipeEnabled = isSwipeEnabled,
|
||||
isMenuEnabled = isMenuEnabled,
|
||||
onToggleStarred = onToggleStarred,
|
||||
onToggleRead = onToggleRead,
|
||||
@ -72,10 +70,9 @@ fun LazyListScope.ArticleList(
|
||||
item(key = key(item), contentType = contentType(item)) {
|
||||
SwipeableArticleItem(
|
||||
articleWithFeed = item.articleWithFeed,
|
||||
isFilterUnread = isFilterUnread,
|
||||
isHighlighted = readingArticleId == item.articleWithFeed.article.id,
|
||||
articleListTonalElevation = articleListTonalElevation,
|
||||
onClick = onClick,
|
||||
isSwipeEnabled = isSwipeEnabled,
|
||||
isMenuEnabled = isMenuEnabled,
|
||||
onToggleStarred = onToggleStarred,
|
||||
onToggleRead = onToggleRead,
|
||||
|
@ -65,7 +65,9 @@ import me.ash.reader.ui.page.home.HomeViewModel
|
||||
)
|
||||
@Composable
|
||||
fun FlowPage(
|
||||
navController: NavHostController,
|
||||
onNavigateToFeeds: () -> Unit,
|
||||
readingArticleId: String?,
|
||||
onOpenArticle: (articleId: String) -> Unit,
|
||||
flowViewModel: FlowViewModel = hiltViewModel(),
|
||||
homeViewModel: HomeViewModel,
|
||||
) {
|
||||
@ -189,13 +191,7 @@ fun FlowPage(
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
) {
|
||||
onSearch = false
|
||||
if (navController.previousBackStackEntry == null) {
|
||||
navController.navigate(RouteName.FEEDS) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
} else {
|
||||
navController.popBackStack()
|
||||
}
|
||||
onNavigateToFeeds()
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
@ -318,16 +314,13 @@ fun FlowPage(
|
||||
}
|
||||
ArticleList(
|
||||
pagingItems = pagingItems,
|
||||
isFilterUnread = filterUiState.filter == Filter.Unread,
|
||||
readingArticleId = readingArticleId,
|
||||
isShowFeedIcon = articleListFeedIcon.value,
|
||||
isShowStickyHeader = articleListDateStickyHeader.value,
|
||||
articleListTonalElevation = articleListTonalElevation.value,
|
||||
isSwipeEnabled = { listState.isScrollInProgress },
|
||||
onClick = {
|
||||
onSearch = false
|
||||
navController.navigate("${RouteName.READING}/${it.article.id}") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
onOpenArticle(it.article.id)
|
||||
},
|
||||
onToggleStarred = onToggleStarred,
|
||||
onToggleRead = onToggleRead,
|
||||
|
@ -52,6 +52,7 @@ private const val DOWNWARD = -1
|
||||
@Composable
|
||||
fun ReadingPage(
|
||||
navController: NavHostController,
|
||||
articleId: String?,
|
||||
homeViewModel: HomeViewModel,
|
||||
readingViewModel: ReadingViewModel = hiltViewModel(),
|
||||
) {
|
||||
@ -75,7 +76,8 @@ fun ReadingPage(
|
||||
|
||||
val pagingItems = homeUiState.pagingData.collectAsLazyPagingItems().itemSnapshotList
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
LaunchedEffect(articleId) {
|
||||
if (articleId == null) {
|
||||
navController.currentBackStackEntryFlow.collect {
|
||||
it.arguments?.getString("articleId")?.let { articleId ->
|
||||
if (readerState.articleId != articleId) {
|
||||
@ -83,6 +85,9 @@ fun ReadingPage(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
readingViewModel.initData(articleId)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(readerState.articleId, pagingItems.isNotEmpty()) {
|
||||
|
@ -6,6 +6,7 @@ plugins {
|
||||
alias(libs.plugins.aboutlibraries) apply false
|
||||
alias(libs.plugins.room) apply false
|
||||
alias(libs.plugins.hilt) apply false
|
||||
alias(libs.plugins.kotlin.parcelize) apply false
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
|
@ -30,6 +30,7 @@ work = "2.9.0"
|
||||
composeBom = "2024.09.02"
|
||||
composeCompiler = "1.5.8"
|
||||
composeHtml = "1.0.2"
|
||||
material3Adaptive = "1.0.0"
|
||||
|
||||
# Coil
|
||||
coil = "2.5.0"
|
||||
@ -72,6 +73,10 @@ compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||
compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||
compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
compose-material3-adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive", version.ref = "material3Adaptive" }
|
||||
compose-material3-adaptive-layout = { group = "androidx.compose.material3.adaptive", name = "adaptive-layout", version.ref = "material3Adaptive" }
|
||||
compose-material3-adaptive-navigation = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation", version.ref = "material3Adaptive" }
|
||||
|
||||
|
||||
# Accompanist
|
||||
accompanist-swiperefresh = { group = "com.google.accompanist", name = "accompanist-swiperefresh", version.ref = "accompanist" }
|
||||
@ -134,3 +139,4 @@ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutLibsRelease" }
|
||||
room = { id = "androidx.room", version.ref = "room" }
|
||||
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hiltAndroid" }
|
||||
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
|
Loading…
x
Reference in New Issue
Block a user