Skip to content

Commit

Permalink
[feature|build] Support tablet layout on feed screen; update dependen…
Browse files Browse the repository at this point in the history
…cies
  • Loading branch information
SkyD666 committed May 24, 2024
1 parent 87bc054 commit dbefa5e
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 52 deletions.
10 changes: 7 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ android {
minSdk = 24
targetSdk = 34
versionCode = 16
versionName = "1.1-beta31"
versionName = "1.1-beta32"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

Expand Down Expand Up @@ -134,6 +134,7 @@ tasks.withType(KotlinCompile::class.java).configureEach {
kotlinOptions {
freeCompilerArgs += listOf(
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi",
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
Expand All @@ -158,10 +159,13 @@ dependencies {
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
implementation("androidx.navigation:navigation-compose:2.7.7")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.0")
implementation("androidx.compose.ui:ui:1.6.7")
implementation("androidx.compose.material:material:1.6.7")
implementation("androidx.compose.ui:ui:1.7.0-beta01")
implementation("androidx.compose.material:material:1.7.0-beta01")
implementation("androidx.compose.material3:material3:1.3.0-alpha05")
implementation("androidx.compose.material3:material3-window-size-class:1.2.1")
implementation("androidx.compose.material3.adaptive:adaptive:1.0.0-beta01")
implementation("androidx.compose.material3.adaptive:adaptive-layout:1.0.0-beta01")
implementation("androidx.compose.material3.adaptive:adaptive-navigation:1.0.0-beta01")
implementation("androidx.compose.material:material-icons-extended:1.6.7")
implementation("com.materialkolor:material-kolor:1.4.4")
implementation("androidx.room:room-runtime:2.6.1")
Expand Down
5 changes: 2 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:node="remove">
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Expand All @@ -15,7 +14,7 @@
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
tools:ignore="ScopedStorage"
tools:node="replace" />
tools:node="remove" />

<application
android:name=".App"
Expand Down
18 changes: 15 additions & 3 deletions app/src/main/java/com/skyd/anivu/base/mvi/MviIntent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,20 @@ interface MviIntent
@Composable
fun <I : MviIntent, S : MviViewState, E : MviSingleEvent>
AbstractMviViewModel<I, S, E>.getDispatcher(startWith: I): (I) -> Unit {
val intentChannel = remember { Channel<I>(Channel.UNLIMITED) }
LaunchedEffect(Unit) {
return getDispatcher(Unit, startWith = startWith)
}

@Composable
fun <I : MviIntent, S : MviViewState, E : MviSingleEvent>
AbstractMviViewModel<I, S, E>.getDispatcher(key1: Any?, startWith: I): (I) -> Unit {
return getDispatcher(*arrayOf(key1), startWith = startWith)
}

@Composable
fun <I : MviIntent, S : MviViewState, E : MviSingleEvent>
AbstractMviViewModel<I, S, E>.getDispatcher(vararg keys: Any?, startWith: I): (I) -> Unit {
val intentChannel = remember(*keys) { Channel<I>(Channel.UNLIMITED) }
LaunchedEffect(*keys, intentChannel) {
withContext(Dispatchers.Main.immediate) {
intentChannel
.consumeAsFlow()
Expand All @@ -29,7 +41,7 @@ fun <I : MviIntent, S : MviViewState, E : MviSingleEvent>
.collect()
}
}
return remember {
return remember(*keys, intentChannel) {
{ intent: I ->
intentChannel.trySend(intent).getOrThrow()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class Feed1Proxy(
private val visible: (groupId: String) -> Boolean = { true },
private val isEnded: (index: Int) -> Boolean = { false },
private val useCardLayout: () -> Boolean = { false },
private val onClick: ((FeedBean) -> Unit)? = null,
private val onRemove: ((FeedBean) -> Unit)? = null,
private val onEdit: ((FeedBean) -> Unit)? = null,
) : LazyGridAdapter.Proxy<FeedViewBean>() {
Expand All @@ -59,6 +60,7 @@ class Feed1Proxy(
visible = visible,
isEnded = isEnded,
useCardLayout = useCardLayout,
onClick = onClick,
onRemove = onRemove,
onEdit = onEdit,
)
Expand All @@ -71,6 +73,7 @@ fun Feed1Item(
data: FeedViewBean,
visible: (groupId: String) -> Boolean,
useCardLayout: () -> Boolean,
onClick: ((FeedBean) -> Unit)? = null,
isEnded: (index: Int) -> Boolean,
onRemove: ((FeedBean) -> Unit)? = null,
onEdit: ((FeedBean) -> Unit)? = null,
Expand Down Expand Up @@ -102,9 +105,14 @@ fun Feed1Item(
{ expandMenu = true }
} else null,
onClick = {
navController.navigate(R.id.action_to_article_fragment, Bundle().apply {
putStringArrayList(ArticleFragment.FEED_URLS_KEY, arrayListOf(feed.url))
})
if (onClick == null) {
navController.navigate(R.id.action_to_article_fragment, Bundle().apply {
putStringArrayList(
ArticleFragment.FEED_URLS_KEY,
arrayListOf(feed.url)
)
})
} else onClick(feed)
},
)
.padding(horizontal = if (useCardLayout()) 20.dp else 16.dp, vertical = 10.dp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import com.skyd.anivu.model.bean.ArticleWithFeed
import com.skyd.anivu.ui.component.AniVuIconButton
import com.skyd.anivu.ui.component.AniVuTopBar
import com.skyd.anivu.ui.component.AniVuTopBarStyle
import com.skyd.anivu.ui.component.BackIcon
import com.skyd.anivu.ui.component.dialog.AniVuDialog
import com.skyd.anivu.ui.component.dialog.WaitingDialog
import com.skyd.anivu.ui.component.lazyverticalgrid.AniVuLazyVerticalGrid
Expand All @@ -69,8 +70,14 @@ class ArticleFragment : BaseComposeFragment() {
): View = setContentBase { ArticleScreen(feedUrls = feedUrls, viewModel = feedViewModel) }
}

private val DefaultBackClick = { }

@Composable
fun ArticleScreen(feedUrls: List<String>, viewModel: ArticleViewModel = hiltViewModel()) {
fun ArticleScreen(
feedUrls: List<String>,
onBackClick: () -> Unit = DefaultBackClick,
viewModel: ArticleViewModel = hiltViewModel(),
) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val snackbarHostState = remember { SnackbarHostState() }
val navController = LocalNavController.current
Expand All @@ -86,6 +93,10 @@ fun ArticleScreen(feedUrls: List<String>, viewModel: ArticleViewModel = hiltView
AniVuTopBar(
style = AniVuTopBarStyle.CenterAligned,
title = { Text(text = stringResource(R.string.article_screen_name)) },
navigationIcon = {
if (onBackClick == DefaultBackClick) BackIcon()
else BackIcon(onClick = onBackClick)
},
actions = {
AniVuIconButton(
onClick = {
Expand Down Expand Up @@ -118,7 +129,8 @@ fun ArticleScreen(feedUrls: List<String>, viewModel: ArticleViewModel = hiltView
}
)
} else {
val dispatch = viewModel.getDispatcher(startWith = ArticleIntent.Init(feedUrls))
val dispatch =
viewModel.getDispatcher(feedUrls, startWith = ArticleIntent.Init(feedUrls))
val state = rememberPullRefreshState(
refreshing = uiState.articleListState.loading,
onRefresh = { dispatch(ArticleIntent.Refresh(feedUrls)) },
Expand Down Expand Up @@ -172,7 +184,7 @@ private fun ArticleList(
}
AniVuLazyVerticalGrid(
modifier = modifier.fillMaxSize(),
columns = GridCells.Adaptive(250.dp),
columns = GridCells.Adaptive(300.dp),
dataList = articles,
adapter = adapter,
contentPadding = contentPadding,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flatMapConcat
Expand All @@ -34,7 +35,7 @@ class ArticleViewModel @Inject constructor(
val initialVS = ArticleState.initial()

viewState = merge(
intentSharedFlow.filterIsInstance<ArticleIntent.Init>().take(1),
intentSharedFlow.filterIsInstance<ArticleIntent.Init>().distinctUntilChanged(),
intentSharedFlow.filterNot { it is ArticleIntent.Init }
)
.shareWhileSubscribed()
Expand Down
114 changes: 79 additions & 35 deletions app/src/main/java/com/skyd/anivu/ui/fragment/feed/FeedScreen.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.skyd.anivu.ui.fragment.feed

import android.os.Bundle
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
Expand All @@ -15,6 +16,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Article
Expand All @@ -36,6 +38,12 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
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.layout.calculatePaneScaffoldDirective
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
Expand Down Expand Up @@ -87,6 +95,7 @@ import com.skyd.anivu.ui.component.lazyverticalgrid.adapter.proxy.DefaultGroup1P
import com.skyd.anivu.ui.component.lazyverticalgrid.adapter.proxy.Feed1Proxy
import com.skyd.anivu.ui.component.lazyverticalgrid.adapter.proxy.Group1Proxy
import com.skyd.anivu.ui.fragment.article.ArticleFragment
import com.skyd.anivu.ui.fragment.article.ArticleScreen
import com.skyd.anivu.ui.fragment.search.SearchFragment
import com.skyd.anivu.ui.local.LocalFeedGroupExpand
import com.skyd.anivu.ui.local.LocalHideEmptyDefault
Expand All @@ -99,7 +108,59 @@ import java.util.UUID
const val FEED_SCREEN_ROUTE = "feedScreen"

@Composable
fun FeedScreen(viewModel: FeedViewModel = hiltViewModel()) {
fun FeedScreen() {
val navigator = rememberListDetailPaneScaffoldNavigator<List<String>>(
scaffoldDirective = calculatePaneScaffoldDirective(currentWindowAdaptiveInfo()).copy(
horizontalPartitionSpacerSize = 0.dp,
)
)
val navController = LocalNavController.current
val windowSizeClass = LocalWindowSizeClass.current

BackHandler(navigator.canNavigateBack()) {
navigator.navigateBack()
}

ListDetailPaneScaffold(
modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing.only(
WindowInsetsSides.Right.run {
if (windowSizeClass.isCompact) plus(WindowInsetsSides.Left) else this
}
)),
directive = navigator.scaffoldDirective,
value = navigator.scaffoldValue,
listPane = {
AnimatedPane {
FeedList(
onShowArticleList = { feedUrls ->
if (navigator.scaffoldDirective.maxHorizontalPartitions > 1) {
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, feedUrls)
} else {
navController.navigate(R.id.action_to_article_fragment, Bundle().apply {
putStringArrayList(
ArticleFragment.FEED_URLS_KEY, ArrayList(feedUrls),
)
})
}
},
)
}
},
detailPane = {
AnimatedPane {
navigator.currentDestination?.content?.let {
ArticleScreen(feedUrls = it, onBackClick = { navigator.navigateBack() })
}
}
},
)
}

@Composable
private fun FeedList(
onShowArticleList: (List<String>) -> Unit,
viewModel: FeedViewModel = hiltViewModel(),
) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val navController = LocalNavController.current
val snackbarHostState = remember { SnackbarHostState() }
Expand Down Expand Up @@ -134,18 +195,13 @@ fun FeedScreen(viewModel: FeedViewModel = hiltViewModel()) {
actions = {
AniVuIconButton(
onClick = {
navController.navigate(R.id.action_to_article_fragment, Bundle().apply {
putStringArrayList(
ArticleFragment.FEED_URLS_KEY,
ArrayList(
(uiState.groupListState as? GroupListState.Success)
?.dataList
?.filterIsInstance<FeedViewBean>()
?.map { it.feed.url }
.orEmpty()
)
)
})
onShowArticleList(
(uiState.groupListState as? GroupListState.Success)
?.dataList
?.filterIsInstance<FeedViewBean>()
?.map { it.feed.url }
.orEmpty()
)
},
imageVector = Icons.AutoMirrored.Outlined.Article,
contentDescription = stringResource(id = R.string.feed_screen_all_articles),
Expand Down Expand Up @@ -187,11 +243,6 @@ fun FeedScreen(viewModel: FeedViewModel = hiltViewModel()) {
Icon(imageVector = Icons.Outlined.Add, contentDescription = null)
}
},
contentWindowInsets = WindowInsets.safeDrawing.only(
(WindowInsetsSides.Top + WindowInsetsSides.Right).run {
if (windowSizeClass.isCompact) plus(WindowInsetsSides.Left) else this
}
)
) { innerPadding ->
when (val groupListState = uiState.groupListState) {
is GroupListState.Failed, GroupListState.Init, GroupListState.Loading -> {}
Expand All @@ -200,21 +251,7 @@ fun FeedScreen(viewModel: FeedViewModel = hiltViewModel()) {
result = groupListState.dataList,
contentPadding = innerPadding + PaddingValues(bottom = fabHeight + 16.dp),
onRemoveFeed = { feed -> dispatch(FeedIntent.RemoveFeed(feed.url)) },
onShowAllArticles = { group ->
val feedUrls = (uiState.groupListState as? GroupListState.Success)
?.dataList
?.filterIsInstance<FeedViewBean>()
?.filter { it.feed.groupId == group.groupId || group.isDefaultGroup() && it.feed.isDefaultGroup() }
?.map { it.feed.url }
.orEmpty()
if (feedUrls.isNotEmpty()) {
navController.navigate(R.id.action_to_article_fragment, Bundle().apply {
putStringArrayList(
ArticleFragment.FEED_URLS_KEY, ArrayList(feedUrls)
)
})
}
},
onShowArticleList = { feedUrls -> onShowArticleList(feedUrls) },
onEditFeed = { feed ->
openEditDialog = feed
editDialogUrl = feed.url
Expand Down Expand Up @@ -519,9 +556,9 @@ private fun CreateGroupDialog(
private fun FeedList(
result: List<Any>,
contentPadding: PaddingValues = PaddingValues(),
onShowArticleList: (List<String>) -> Unit,
onRemoveFeed: (FeedBean) -> Unit,
onEditFeed: (FeedBean) -> Unit,
onShowAllArticles: (GroupBean) -> Unit,
onDeleteGroup: (GroupBean) -> Unit,
onMoveToGroup: (from: GroupBean, to: GroupBean) -> Unit,
openCreateGroupDialog: () -> Unit,
Expand Down Expand Up @@ -561,7 +598,13 @@ private fun FeedList(
isExpand = { feedVisible[it.groupId] ?: false },
onExpandChange = { data, expand -> feedVisible[data.groupId] = expand },
isEmpty = { it == result.lastIndex || result[it + 1] is GroupBean },
onShowAllArticles = onShowAllArticles,
onShowAllArticles = { group ->
val feedUrls = result
.filterIsInstance<FeedViewBean>()
.filter { it.feed.groupId == group.groupId || group.isDefaultGroup() && it.feed.isDefaultGroup() }
.map { it.feed.url }
onShowArticleList(feedUrls)
},
onDelete = onDeleteGroup,
onMoveFeedsTo = {
openSelectGroupDialog = it
Expand All @@ -578,6 +621,7 @@ private fun FeedList(
Feed1Proxy(
visible = { feedVisible[it] ?: false },
useCardLayout = { true },
onClick = { onShowArticleList(listOf(it.url)) },
isEnded = { it == result.lastIndex || result[it + 1] is GroupBean },
onRemove = onRemoveFeed,
onEdit = onEditFeed
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/com/skyd/anivu/ui/local/LocalValue.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ val LocalHideEmptyDefault = compositionLocalOf { HideEmptyDefaultPreference.defa
// Player
val LocalPlayerDoubleTap = compositionLocalOf { PlayerDoubleTapPreference.default }
val LocalPlayerShow85sButton = compositionLocalOf { PlayerShow85sButtonPreference.default }
val LocalPlayerShowScreenshotButton = compositionLocalOf { PlayerShowScreenshotButtonPreference.default }
val LocalPlayerShowScreenshotButton =
compositionLocalOf { PlayerShowScreenshotButtonPreference.default }
val LocalHardwareDecode = compositionLocalOf { HardwareDecodePreference.default }

0 comments on commit dbefa5e

Please sign in to comment.