Optimize the feeds page

This commit is contained in:
Ash 2022-05-31 02:21:28 +08:00
parent 5867186751
commit 433ff6e6f2
16 changed files with 485 additions and 386 deletions

View File

@ -10,12 +10,12 @@ import androidx.work.WorkManager
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapLatest
import me.ash.reader.data.dao.AccountDao import me.ash.reader.data.dao.AccountDao
import me.ash.reader.data.dao.ArticleDao import me.ash.reader.data.dao.ArticleDao
import me.ash.reader.data.dao.FeedDao import me.ash.reader.data.dao.FeedDao
import me.ash.reader.data.dao.GroupDao import me.ash.reader.data.dao.GroupDao
import me.ash.reader.data.entity.* import me.ash.reader.data.entity.*
import me.ash.reader.data.model.ImportantCount
import me.ash.reader.ui.ext.currentAccountId import me.ash.reader.ui.ext.currentAccountId
import java.util.* import java.util.*
@ -99,7 +99,7 @@ abstract class AbstractRssRepository constructor(
fun pullImportant( fun pullImportant(
isStarred: Boolean = false, isStarred: Boolean = false,
isUnread: Boolean = false, isUnread: Boolean = false,
): Flow<List<ImportantCount>> { ): Flow<Map<String, Int>> {
val accountId = context.currentAccountId val accountId = context.currentAccountId
Log.i( Log.i(
"RLog", "RLog",
@ -111,6 +111,12 @@ abstract class AbstractRssRepository constructor(
isUnread -> articleDao isUnread -> articleDao
.queryImportantCountWhenIsUnread(accountId, isUnread) .queryImportantCountWhenIsUnread(accountId, isUnread)
else -> articleDao.queryImportantCountWhenIsAll(accountId) else -> articleDao.queryImportantCountWhenIsAll(accountId)
}.mapLatest {
mapOf(
*(it.map {
it.feedId to it.important
}.toTypedArray())
)
}.flowOn(dispatcherIO) }.flowOn(dispatcherIO)
} }

View File

@ -4,6 +4,7 @@ import android.os.Build
import android.view.SoundEffectConstants import android.view.SoundEffectConstants
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -20,7 +21,6 @@ import me.ash.reader.ui.theme.palette.onDark
@Composable @Composable
fun FilterBar( fun FilterBar(
modifier: Modifier = Modifier,
filter: Filter, filter: Filter,
filterBarStyle: Int, filterBarStyle: Int,
filterBarFilled: Boolean, filterBarFilled: Boolean,
@ -33,7 +33,8 @@ fun FilterBar(
NavigationBar( NavigationBar(
modifier = Modifier modifier = Modifier
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(filterBarTonalElevation)), .background(MaterialTheme.colorScheme.surfaceColorAtElevation(filterBarTonalElevation))
.navigationBarsPadding(),
tonalElevation = filterBarTonalElevation, tonalElevation = filterBarTonalElevation,
) { ) {
Spacer(modifier = Modifier.width(filterBarPadding)) Spacer(modifier = Modifier.width(filterBarPadding))

View File

@ -31,14 +31,14 @@ fun RYScaffold(
color = containerColor color = containerColor
) )
) )
.statusBarsPadding() .statusBarsPadding(),
.run { // .run {
if (bottomBar != null || floatingActionButton != null) { // if (bottomBar != null || floatingActionButton != null) {
navigationBarsPadding() // navigationBarsPadding()
} else { // } else {
this // this
} // }
}, // },
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
containerTonalElevation, containerTonalElevation,
color = containerColor color = containerColor

View File

@ -67,7 +67,7 @@ fun Context.share(content: String) {
}, getString(R.string.share))) }, getString(R.string.share)))
} }
fun Context.openURL(url: String? = null) { fun Context.openURL(url: String?) {
url?.takeIf { it.trim().isNotEmpty() } url?.takeIf { it.trim().isNotEmpty() }
?.let { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it))) } ?.let { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it))) }
} }

View File

@ -1,95 +1,99 @@
package me.ash.reader.ui.page.home.feeds package me.ash.reader.ui.page.home.feeds
import RYExtensibleVisibility
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Badge import androidx.compose.material3.Badge
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import me.ash.reader.data.entity.Feed import me.ash.reader.data.entity.Feed
import me.ash.reader.ui.component.FeedIcon import me.ash.reader.ui.component.FeedIcon
import me.ash.reader.ui.page.home.feeds.drawer.feed.FeedOptionViewModel import me.ash.reader.ui.page.home.feeds.drawer.feed.FeedOptionViewModel
import kotlin.math.ln import me.ash.reader.ui.theme.ShapeBottom32
@OptIn( @OptIn(
androidx.compose.foundation.ExperimentalFoundationApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class,
androidx.compose.material.ExperimentalMaterialApi::class,
) )
@Composable @Composable
fun FeedItem( fun FeedItem(
feed: Feed, feed: Feed,
alpha: Float = 1f,
badgeAlpha: Float = 1f,
isEnded: Boolean = false,
isExpanded: () -> Boolean,
feedOptionViewModel: FeedOptionViewModel = hiltViewModel(), feedOptionViewModel: FeedOptionViewModel = hiltViewModel(),
tonalElevation: Dp,
onClick: () -> Unit = {}, onClick: () -> Unit = {},
) { ) {
val view = LocalView.current val view = LocalView.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val tonalElevationAlpha by remember {
derivedStateOf {
(ln(tonalElevation.value + 1.4f) + 2f) / 100f
}
}
Row( RYExtensibleVisibility(visible = isExpanded()) {
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 14.dp)
.clip(RoundedCornerShape(32.dp))
.combinedClickable(
onClick = {
onClick()
},
onLongClick = {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
feedOptionViewModel.showDrawer(scope, feed.id)
}
)
.padding(vertical = 14.dp),
) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 14.dp, end = 6.dp), .padding(horizontal = 16.dp)
horizontalArrangement = Arrangement.SpaceBetween, .background(
verticalAlignment = Alignment.CenterVertically, color = MaterialTheme.colorScheme.secondary.copy(alpha = alpha),
) { shape = if (isEnded) ShapeBottom32 else RectangleShape,
Row(modifier = Modifier.weight(1f)) {
FeedIcon(feed.name)
Text(
modifier = Modifier.padding(start = 12.dp, end = 6.dp),
text = feed.name,
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
) )
} .combinedClickable(
if ((feed.important ?: 0) != 0) { onClick = {
Badge( onClick()
containerColor = MaterialTheme.colorScheme.surfaceTint.copy(
alpha = tonalElevationAlpha
),
contentColor = MaterialTheme.colorScheme.outline,
content = {
Text(
text = feed.important.toString(),
style = MaterialTheme.typography.labelSmall
)
}, },
onLongClick = {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
feedOptionViewModel.showDrawer(scope, feed.id)
}
) )
.padding(horizontal = 14.dp)
.padding(top = 14.dp, bottom = if (isEnded) 22.dp else 14.dp),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 14.dp, end = 6.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Row(modifier = Modifier.weight(1f)) {
FeedIcon(feed.name)
Text(
modifier = Modifier.padding(start = 12.dp, end = 6.dp),
text = feed.name,
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
if ((feed.important ?: 0) != 0) {
Badge(
containerColor = MaterialTheme.colorScheme.surfaceTint.copy(
alpha = badgeAlpha
),
contentColor = MaterialTheme.colorScheme.outline,
content = {
Text(
text = feed.important.toString(),
style = MaterialTheme.typography.labelSmall
)
},
)
}
} }
} }
} }

View File

@ -31,6 +31,7 @@ import me.ash.reader.data.preference.*
import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing
import me.ash.reader.ui.component.FilterBar import me.ash.reader.ui.component.FilterBar
import me.ash.reader.ui.component.base.* import me.ash.reader.ui.component.base.*
import me.ash.reader.ui.ext.alphaLN
import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.findActivity import me.ash.reader.ui.ext.findActivity
import me.ash.reader.ui.ext.getCurrentVersion import me.ash.reader.ui.ext.getCurrentVersion
@ -41,9 +42,9 @@ import me.ash.reader.ui.page.home.feeds.drawer.feed.FeedOptionDrawer
import me.ash.reader.ui.page.home.feeds.drawer.group.GroupOptionDrawer import me.ash.reader.ui.page.home.feeds.drawer.group.GroupOptionDrawer
import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeDialog import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeDialog
import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewModel import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewModel
import kotlin.math.ln
@OptIn( @OptIn(
com.google.accompanist.pager.ExperimentalPagerApi::class,
androidx.compose.foundation.ExperimentalFoundationApi::class androidx.compose.foundation.ExperimentalFoundationApi::class
) )
@Composable @Composable
@ -96,6 +97,24 @@ fun FeedsPage(
} }
} }
val feedBadgeAlpha by remember { derivedStateOf { (ln(groupListTonalElevation.value + 1.4f) + 2f) / 100f } }
val groupAlpha by remember { derivedStateOf { groupListTonalElevation.value.dp.alphaLN(weight = 1.2f) } }
val groupIndicatorAlpha by remember {
derivedStateOf {
groupListTonalElevation.value.dp.alphaLN(
weight = 1.4f
)
}
}
val groupsVisible = remember(feedsUiState.groupWithFeedList) {
mutableStateMapOf(
*(feedsUiState.groupWithFeedList.filterIsInstance<GroupFeedsView.Group>().map {
it.group.id to groupListExpand.value
}.toTypedArray())
)
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
feedsViewModel.fetchAccount() feedsViewModel.fetchAccount()
} }
@ -161,7 +180,7 @@ fun FeedsPage(
item { item {
Banner( Banner(
title = filterUiState.filter.getName(), title = filterUiState.filter.getName(),
desc = feedsUiState.importantCount.ifEmpty { stringResource(R.string.loading) }, desc = feedsUiState.importantSum.ifEmpty { stringResource(R.string.loading) },
icon = filterUiState.filter.iconOutline, icon = filterUiState.filter.iconOutline,
action = { action = {
Icon( Icon(
@ -189,14 +208,21 @@ fun FeedsPage(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
} }
itemsIndexed(feedsUiState.groupWithFeedList) { index, groupWithFeed -> itemsIndexed(feedsUiState.groupWithFeedList) { index, groupWithFeed ->
// Crossfade(targetState = groupWithFeed) { groupWithFeed -> when (groupWithFeed) {
Column { is GroupFeedsView.Group -> {
GroupItem( if (index != 0) {
isExpanded = groupListExpand.value, Spacer(modifier = Modifier.height(16.dp))
tonalElevation = groupListTonalElevation.value.dp, }
group = groupWithFeed.group, GroupItem(
feeds = groupWithFeed.feeds, isExpanded = { groupsVisible[groupWithFeed.group.id] ?: false },
groupOnClick = { group = groupWithFeed.group,
alpha = groupAlpha,
indicatorAlpha = groupIndicatorAlpha,
onExpanded = {
groupsVisible[groupWithFeed.group.id] =
!(groupsVisible[groupWithFeed.group.id] ?: false)
}
) {
filterChange( filterChange(
navController = navController, navController = navController,
homeViewModel = homeViewModel, homeViewModel = homeViewModel,
@ -205,23 +231,27 @@ fun FeedsPage(
feed = null, feed = null,
) )
) )
}, }
feedOnClick = { feed -> }
is GroupFeedsView.Feed -> {
FeedItem(
feed = groupWithFeed.feed,
alpha = groupAlpha,
badgeAlpha = feedBadgeAlpha,
isEnded = index != feedsUiState.groupWithFeedList.lastIndex && feedsUiState.groupWithFeedList[index + 1] is GroupFeedsView.Group,
isExpanded = { groupsVisible[groupWithFeed.feed.groupId] ?: false },
) {
filterChange( filterChange(
navController = navController, navController = navController,
homeViewModel = homeViewModel, homeViewModel = homeViewModel,
filterState = filterUiState.copy( filterState = filterUiState.copy(
group = null, group = null,
feed = feed, feed = groupWithFeed.feed,
) )
) )
} }
)
if (index != feedsUiState.groupWithFeedList.lastIndex) {
Spacer(modifier = Modifier.height(8.dp))
} }
} }
// }
} }
item { item {
Spacer(modifier = Modifier.height(128.dp)) Spacer(modifier = Modifier.height(128.dp))

View File

@ -2,6 +2,7 @@ package me.ash.reader.ui.page.home.feeds
import android.util.Log import android.util.Log
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.ui.util.fastForEach
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -10,7 +11,6 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.ash.reader.R import me.ash.reader.R
import me.ash.reader.data.entity.Account import me.ash.reader.data.entity.Account
import me.ash.reader.data.entity.GroupWithFeed
import me.ash.reader.data.module.DispatcherDefault import me.ash.reader.data.module.DispatcherDefault
import me.ash.reader.data.module.DispatcherIO import me.ash.reader.data.module.DispatcherIO
import me.ash.reader.data.repository.AccountRepository import me.ash.reader.data.repository.AccountRepository
@ -67,42 +67,20 @@ class FeedsViewModel @Inject constructor(
combine( combine(
rssRepository.get().pullFeeds(), rssRepository.get().pullFeeds(),
rssRepository.get().pullImportant(isStarred, isUnread), rssRepository.get().pullImportant(isStarred, isUnread),
) { groupWithFeedList, importantList -> ) { groupWithFeedList, importantMap ->
val groupImportantMap = mutableMapOf<String, Int>() groupWithFeedList.fastForEach {
val feedImportantMap = mutableMapOf<String, Int>() var groupImportant = 0
importantList.groupBy { it.groupId }.forEach { (i, list) -> it.feeds.fastForEach {
var groupImportantSum = 0 it.important = importantMap[it.id]
list.forEach { groupImportant += it.important ?: 0
feedImportantMap[it.feedId] = it.important
groupImportantSum += it.important
}
groupImportantMap[i] = groupImportantSum
}
val groupsIt = groupWithFeedList.iterator()
while (groupsIt.hasNext()) {
val groupWithFeed = groupsIt.next()
val groupImportant = groupImportantMap[groupWithFeed.group.id]
if (groupImportant == null && (isStarred || isUnread)) {
groupsIt.remove()
} else {
groupWithFeed.group.important = groupImportant
val feedsIt = groupWithFeed.feeds.iterator()
while (feedsIt.hasNext()) {
val feed = feedsIt.next()
val feedImportant = feedImportantMap[feed.id]
if (feedImportant == null && (isStarred || isUnread)) {
feedsIt.remove()
} else {
feed.important = feedImportant
}
}
} }
it.group.important = groupImportant
} }
groupWithFeedList groupWithFeedList
}.onEach { groupWithFeedList -> }.mapLatest { groupWithFeedList ->
_feedsUiState.update { _feedsUiState.update {
it.copy( it.copy(
importantCount = groupWithFeedList.sumOf { it.group.important ?: 0 }.run { importantSum = groupWithFeedList.sumOf { it.group.important ?: 0 }.run {
when { when {
isStarred -> stringsRepository.getQuantityString( isStarred -> stringsRepository.getQuantityString(
R.plurals.starred_desc, R.plurals.starred_desc,
@ -121,8 +99,15 @@ class FeedsViewModel @Inject constructor(
) )
} }
}, },
groupWithFeedList = groupWithFeedList, groupWithFeedList = groupWithFeedList.map {
feedsVisible = List(groupWithFeedList.size, init = { true }) mutableListOf<GroupFeedsView>(GroupFeedsView.Group(it.group)).apply {
addAll(
it.feeds.map {
GroupFeedsView.Feed(it)
}
)
}
}.flatten(),
) )
} }
}.catch { }.catch {
@ -133,9 +118,13 @@ class FeedsViewModel @Inject constructor(
data class FeedsUiState( data class FeedsUiState(
val account: Account? = null, val account: Account? = null,
val importantCount: String = "", val importantSum: String = "",
val groupWithFeedList: List<GroupWithFeed> = emptyList(), val groupWithFeedList: List<GroupFeedsView> = emptyList(),
val feedsVisible: List<Boolean> = emptyList(),
val listState: LazyListState = LazyListState(), val listState: LazyListState = LazyListState(),
val groupsVisible: Boolean = true, val groupsVisible: Boolean = true,
) )
sealed class GroupFeedsView {
class Group(val group: me.ash.reader.data.entity.Group) : GroupFeedsView()
class Feed(val feed: me.ash.reader.data.entity.Feed) : GroupFeedsView()
}

View File

@ -1,58 +1,56 @@
package me.ash.reader.ui.page.home.feeds package me.ash.reader.ui.page.home.feeds
import RYExtensibleVisibility
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ExpandLess import androidx.compose.material.icons.rounded.ExpandLess
import androidx.compose.material.icons.rounded.ExpandMore import androidx.compose.material.icons.rounded.ExpandMore
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import me.ash.reader.R import me.ash.reader.R
import me.ash.reader.data.entity.Feed
import me.ash.reader.data.entity.Group import me.ash.reader.data.entity.Group
import me.ash.reader.ui.ext.alphaLN
import me.ash.reader.ui.page.home.feeds.drawer.group.GroupOptionViewModel import me.ash.reader.ui.page.home.feeds.drawer.group.GroupOptionViewModel
import me.ash.reader.ui.theme.Shape32
import me.ash.reader.ui.theme.ShapeTop32
@OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class) @OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class)
@Composable @Composable
fun GroupItem( fun GroupItem(
modifier: Modifier = Modifier,
tonalElevation: Dp,
group: Group, group: Group,
feeds: List<Feed>, alpha: Float = 1f,
isExpanded: Boolean = true, indicatorAlpha: Float = 1f,
isExpanded: () -> Boolean,
groupOptionViewModel: GroupOptionViewModel = hiltViewModel(), groupOptionViewModel: GroupOptionViewModel = hiltViewModel(),
onExpanded: () -> Unit = {},
groupOnClick: () -> Unit = {}, groupOnClick: () -> Unit = {},
feedOnClick: (feed: Feed) -> Unit = {},
) { ) {
val view = LocalView.current val view = LocalView.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var expanded by remember { mutableStateOf(isExpanded) }
Column( Column(
modifier = Modifier modifier = Modifier
.animateContentSize()
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.clip(RoundedCornerShape(32.dp)) .clip(if (isExpanded()) ShapeTop32 else Shape32)
.background( .background(
MaterialTheme.colorScheme.secondary.copy(alpha = tonalElevation.alphaLN(weight = 1.2f)) MaterialTheme.colorScheme.secondary.copy(alpha = alpha)
) )
.combinedClickable( .combinedClickable(
onClick = { onClick = {
@ -66,7 +64,7 @@ fun GroupItem(
.padding(top = 22.dp) .padding(top = 22.dp)
) { ) {
Row( Row(
modifier = modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@ -86,38 +84,21 @@ fun GroupItem(
.size(24.dp) .size(24.dp)
.clip(CircleShape) .clip(CircleShape)
.background( .background(
MaterialTheme.colorScheme.surfaceTint.copy( MaterialTheme.colorScheme.surfaceTint.copy(alpha = indicatorAlpha)
alpha = tonalElevation.alphaLN(weight = 1.4f)
)
) )
.clickable { .clickable {
expanded = !expanded onExpanded()
}, },
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Icon( Icon(
imageVector = if (expanded) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore, imageVector = if (isExpanded()) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore,
contentDescription = stringResource(if (expanded) R.string.expand_less else R.string.expand_more), contentDescription = stringResource(if (isExpanded()) R.string.expand_less else R.string.expand_more),
tint = MaterialTheme.colorScheme.onSecondaryContainer, tint = MaterialTheme.colorScheme.onSecondaryContainer,
) )
} }
} }
Spacer(modifier = Modifier.height(22.dp)) Spacer(modifier = Modifier.height(22.dp))
RYExtensibleVisibility(visible = expanded) {
Column {
feeds.forEach { feed ->
FeedItem(
feed = feed,
tonalElevation = tonalElevation,
) {
feedOnClick(feed)
}
}
if (feeds.isNotEmpty()) {
Spacer(modifier = Modifier.height(16.dp))
}
}
}
} }
} }

View File

@ -1,5 +1,6 @@
package me.ash.reader.ui.page.home.feeds.drawer.feed package me.ash.reader.ui.page.home.feeds.drawer.feed
import android.view.HapticFeedbackConstants
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
@ -13,6 +14,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -23,6 +25,7 @@ import me.ash.reader.ui.component.FeedIcon
import me.ash.reader.ui.component.base.BottomDrawer import me.ash.reader.ui.component.base.BottomDrawer
import me.ash.reader.ui.component.base.TextFieldDialog import me.ash.reader.ui.component.base.TextFieldDialog
import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.openURL
import me.ash.reader.ui.ext.roundClick import me.ash.reader.ui.ext.roundClick
import me.ash.reader.ui.ext.showToast import me.ash.reader.ui.ext.showToast
import me.ash.reader.ui.page.home.feeds.subscribe.ResultView import me.ash.reader.ui.page.home.feeds.subscribe.ResultView
@ -34,6 +37,7 @@ fun FeedOptionDrawer(
content: @Composable () -> Unit = {}, content: @Composable () -> Unit = {},
) { ) {
val context = LocalContext.current val context = LocalContext.current
val view = LocalView.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val feedOptionUiState = feedOptionViewModel.feedOptionUiState.collectAsStateValue() val feedOptionUiState = feedOptionViewModel.feedOptionUiState.collectAsStateValue()
val feed = feedOptionUiState.feed val feed = feedOptionUiState.feed
@ -77,7 +81,8 @@ fun FeedOptionDrawer(
ResultView( ResultView(
link = feed?.url ?: stringResource(R.string.unknown), link = feed?.url ?: stringResource(R.string.unknown),
groups = feedOptionUiState.groups, groups = feedOptionUiState.groups,
selectedAllowNotificationPreset = feedOptionUiState.feed?.isNotification ?: false, selectedAllowNotificationPreset = feedOptionUiState.feed?.isNotification
?: false,
selectedParseFullContentPreset = feedOptionUiState.feed?.isFullContent ?: false, selectedParseFullContentPreset = feedOptionUiState.feed?.isFullContent ?: false,
isMoveToGroup = true, isMoveToGroup = true,
showUnsubscribe = true, showUnsubscribe = true,
@ -101,6 +106,10 @@ fun FeedOptionDrawer(
feedOptionViewModel.showNewGroupDialog() feedOptionViewModel.showNewGroupDialog()
}, },
onFeedUrlClick = { onFeedUrlClick = {
context.openURL(feed?.url)
},
onFeedUrlLongClick = {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
feedOptionViewModel.showFeedUrlDialog() feedOptionViewModel.showFeedUrlDialog()
} }
) )

View File

@ -1,15 +1,11 @@
package me.ash.reader.ui.page.home.feeds.subscribe package me.ash.reader.ui.page.home.feeds.subscribe
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background import androidx.compose.foundation.*
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.Article import androidx.compose.material.icons.outlined.Article
@ -32,7 +28,6 @@ import me.ash.reader.R
import me.ash.reader.data.entity.Group import me.ash.reader.data.entity.Group
import me.ash.reader.ui.component.base.RYSelectionChip import me.ash.reader.ui.component.base.RYSelectionChip
import me.ash.reader.ui.component.base.Subtitle import me.ash.reader.ui.component.base.Subtitle
import me.ash.reader.ui.ext.roundClick
import me.ash.reader.ui.theme.palette.alwaysLight import me.ash.reader.ui.theme.palette.alwaysLight
@Composable @Composable
@ -51,7 +46,8 @@ fun ResultView(
unsubscribeOnClick: () -> Unit = {}, unsubscribeOnClick: () -> Unit = {},
onGroupClick: (groupId: String) -> Unit = {}, onGroupClick: (groupId: String) -> Unit = {},
onAddNewGroup: () -> Unit = {}, onAddNewGroup: () -> Unit = {},
onFeedUrlClick: () -> Unit = {} onFeedUrlClick: () -> Unit = {},
onFeedUrlLongClick: () -> Unit = {},
) { ) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
if (groups.isNotEmpty() && selectedGroupId.isEmpty()) onGroupClick(groups.first().id) if (groups.isNotEmpty() && selectedGroupId.isEmpty()) onGroupClick(groups.first().id)
@ -60,7 +56,11 @@ fun ResultView(
Column( Column(
modifier = modifier.verticalScroll(rememberScrollState()) modifier = modifier.verticalScroll(rememberScrollState())
) { ) {
EditableUrl(text = link, onFeedUrlClick) EditableUrl(
text = link,
onClick = onFeedUrlClick,
onLongClick = onFeedUrlLongClick,
)
Spacer(modifier = Modifier.height(26.dp)) Spacer(modifier = Modifier.height(26.dp))
Preset( Preset(
@ -85,27 +85,30 @@ fun ResultView(
} }
} }
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
private fun EditableUrl( private fun EditableUrl(
text: String, text: String,
onClick: () -> Unit onClick: () -> Unit,
onLongClick: () -> Unit,
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center horizontalArrangement = Arrangement.Center
) { ) {
SelectionContainer { Text(
Text( modifier = Modifier
modifier = Modifier.roundClick { .clip(MaterialTheme.shapes.small)
onClick() .combinedClickable(
}, onClick = onClick,
text = text, onLongClick = onLongClick,
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f), ),
style = MaterialTheme.typography.bodyMedium, text = text,
maxLines = 1, color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium,
) maxLines = 1,
} overflow = TextOverflow.Ellipsis,
)
} }
} }

View File

@ -13,7 +13,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -25,14 +24,13 @@ import me.ash.reader.data.preference.*
import me.ash.reader.ui.component.FeedIcon import me.ash.reader.ui.component.FeedIcon
import me.ash.reader.ui.component.base.RYAsyncImage import me.ash.reader.ui.component.base.RYAsyncImage
import me.ash.reader.ui.component.base.SIZE_1000 import me.ash.reader.ui.component.base.SIZE_1000
import me.ash.reader.ui.theme.SHAPE_20 import me.ash.reader.ui.theme.Shape20
@Composable @Composable
fun ArticleItem( fun ArticleItem(
articleWithFeed: ArticleWithFeed, articleWithFeed: ArticleWithFeed,
onClick: (ArticleWithFeed) -> Unit = {}, onClick: (ArticleWithFeed) -> Unit = {},
) { ) {
val context = LocalContext.current
val articleListFeedIcon = LocalFlowArticleListFeedIcon.current val articleListFeedIcon = LocalFlowArticleListFeedIcon.current
val articleListFeedName = LocalFlowArticleListFeedName.current val articleListFeedName = LocalFlowArticleListFeedName.current
val articleListImage = LocalFlowArticleListImage.current val articleListImage = LocalFlowArticleListImage.current
@ -42,12 +40,12 @@ fun ArticleItem(
Column( Column(
modifier = Modifier modifier = Modifier
.padding(horizontal = 12.dp) .padding(horizontal = 12.dp)
.clip(SHAPE_20) .clip(Shape20)
.clickable { onClick(articleWithFeed) } .clickable { onClick(articleWithFeed) }
.padding(horizontal = 12.dp, vertical = 12.dp) .padding(horizontal = 12.dp, vertical = 12.dp)
.alpha(if (articleWithFeed.article.isStarred || articleWithFeed.article.isUnread) 1f else 0.5f), .alpha(if (articleWithFeed.article.isStarred || articleWithFeed.article.isUnread) 1f else 0.5f),
) { ) {
// Upper // Top
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
@ -99,7 +97,7 @@ fun ArticleItem(
} }
} }
// Lower // Bottom
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { ) {
@ -142,7 +140,7 @@ fun ArticleItem(
modifier = Modifier modifier = Modifier
.padding(start = 10.dp) .padding(start = 10.dp)
.size(80.dp) .size(80.dp)
.clip(SHAPE_20), .clip(Shape20),
data = articleWithFeed.article.img, data = articleWithFeed.article.img,
scale = Scale.FILL, scale = Scale.FILL,
precision = Precision.INEXACT, precision = Precision.INEXACT,

View File

@ -0,0 +1,143 @@
package me.ash.reader.ui.page.settings.color.feeds
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import me.ash.reader.R
import me.ash.reader.data.entity.Feed
import me.ash.reader.data.entity.Group
import me.ash.reader.data.model.Filter
import me.ash.reader.data.preference.FeedsGroupListExpandPreference
import me.ash.reader.data.preference.FeedsGroupListTonalElevationPreference
import me.ash.reader.data.preference.FeedsTopBarTonalElevationPreference
import me.ash.reader.ui.component.FilterBar
import me.ash.reader.ui.component.base.FeedbackIconButton
import me.ash.reader.ui.ext.alphaLN
import me.ash.reader.ui.ext.surfaceColorAtElevation
import me.ash.reader.ui.page.home.feeds.FeedItem
import me.ash.reader.ui.page.home.feeds.GroupItem
import me.ash.reader.ui.theme.palette.onDark
import kotlin.math.ln
@Composable
fun FeedsPagePreview(
topBarTonalElevation: FeedsTopBarTonalElevationPreference,
groupListExpand: FeedsGroupListExpandPreference,
groupListTonalElevation: FeedsGroupListTonalElevationPreference,
filterBarStyle: Int,
filterBarFilled: Boolean,
filterBarPadding: Dp,
filterBarTonalElevation: Dp,
) {
var filter by remember { mutableStateOf(Filter.Unread) }
val feedBadgeAlpha by remember { derivedStateOf { (ln(groupListTonalElevation.value + 1.4f) + 2f) / 100f } }
val groupAlpha by remember { derivedStateOf { groupListTonalElevation.value.dp.alphaLN(weight = 1.2f) } }
val groupIndicatorAlpha by remember {
derivedStateOf {
groupListTonalElevation.value.dp.alphaLN(
weight = 1.4f
)
}
}
Column(
modifier = Modifier
.animateContentSize()
.background(
color = MaterialTheme.colorScheme.surfaceColorAtElevation(
groupListTonalElevation.value.dp
) onDark MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(24.dp)
)
) {
SmallTopAppBar(
title = {},
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
topBarTonalElevation.value.dp
),
),
navigationIcon = {
FeedbackIconButton(
imageVector = Icons.Rounded.ArrowBack,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colorScheme.onSurface
)
},
actions = {
FeedbackIconButton(
imageVector = Icons.Rounded.Refresh,
contentDescription = stringResource(R.string.refresh),
tint = MaterialTheme.colorScheme.onSurface,
)
FeedbackIconButton(
imageVector = Icons.Rounded.Add,
contentDescription = stringResource(R.string.subscribe),
tint = MaterialTheme.colorScheme.onSurface,
)
}
)
Spacer(modifier = Modifier.height(12.dp))
GroupItem(
isExpanded = { groupListExpand.value },
group = generateGroupPreview(),
alpha = groupAlpha,
indicatorAlpha = groupIndicatorAlpha,
)
FeedItem(
feed = generateFeedPreview(),
alpha = groupAlpha,
badgeAlpha = feedBadgeAlpha,
isEnded = true,
isExpanded = { true },
)
Spacer(modifier = Modifier.height(12.dp))
FilterBar(
filter = filter,
filterBarStyle = filterBarStyle,
filterBarFilled = filterBarFilled,
filterBarPadding = filterBarPadding,
filterBarTonalElevation = filterBarTonalElevation,
) {
filter = it
}
}
}
@Stable
@Composable
fun generateFeedPreview(): Feed =
Feed(
id = "",
name = stringResource(R.string.preview_feed_name),
icon = "",
accountId = 0,
groupId = "",
url = "",
).apply {
important = 100
}
@Stable
@Composable
fun generateGroupPreview(): Group =
Group(
id = "",
name = stringResource(R.string.defaults),
accountId = 0,
)

View File

@ -1,38 +1,25 @@
package me.ash.reader.ui.page.settings.color.feeds package me.ash.reader.ui.page.settings.color.feeds
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.ArrowBack import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import me.ash.reader.R import me.ash.reader.R
import me.ash.reader.data.entity.Feed
import me.ash.reader.data.entity.Group
import me.ash.reader.data.model.Filter
import me.ash.reader.data.preference.* import me.ash.reader.data.preference.*
import me.ash.reader.ui.component.FilterBar
import me.ash.reader.ui.component.base.* import me.ash.reader.ui.component.base.*
import me.ash.reader.ui.ext.surfaceColorAtElevation
import me.ash.reader.ui.page.home.feeds.GroupItem
import me.ash.reader.ui.page.settings.SettingItem import me.ash.reader.ui.page.settings.SettingItem
import me.ash.reader.ui.theme.palette.onDark
import me.ash.reader.ui.theme.palette.onLight import me.ash.reader.ui.theme.palette.onLight
@Composable @Composable
@ -270,88 +257,3 @@ fun FeedsPageStylePage(
groupListTonalElevationDialogVisible = false groupListTonalElevationDialogVisible = false
} }
} }
@Composable
fun FeedsPagePreview(
topBarTonalElevation: FeedsTopBarTonalElevationPreference,
groupListExpand: FeedsGroupListExpandPreference,
groupListTonalElevation: FeedsGroupListTonalElevationPreference,
filterBarStyle: Int,
filterBarFilled: Boolean,
filterBarPadding: Dp,
filterBarTonalElevation: Dp,
) {
var filter by remember { mutableStateOf(Filter.Unread) }
Column(
modifier = Modifier
.animateContentSize()
.background(
color = MaterialTheme.colorScheme.surfaceColorAtElevation(
groupListTonalElevation.value.dp
) onDark MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(24.dp)
)
) {
SmallTopAppBar(
title = {},
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
topBarTonalElevation.value.dp
),
),
navigationIcon = {
FeedbackIconButton(
imageVector = Icons.Rounded.ArrowBack,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colorScheme.onSurface
) {}
},
actions = {
FeedbackIconButton(
imageVector = Icons.Rounded.Refresh,
contentDescription = stringResource(R.string.refresh),
tint = MaterialTheme.colorScheme.onSurface,
) {}
FeedbackIconButton(
imageVector = Icons.Rounded.Add,
contentDescription = stringResource(R.string.subscribe),
tint = MaterialTheme.colorScheme.onSurface,
) {}
}
)
Spacer(modifier = Modifier.height(12.dp))
GroupItem(
isExpanded = groupListExpand.value,
tonalElevation = groupListTonalElevation.value.dp,
group = Group(
id = "",
name = stringResource(R.string.defaults),
accountId = 0,
),
feeds = listOf(
Feed(
id = "",
name = stringResource(R.string.preview_feed_name),
icon = "",
accountId = 0,
groupId = "",
url = "",
).apply {
important = 100
}
),
)
Spacer(modifier = Modifier.height(12.dp))
FilterBar(
modifier = Modifier.padding(horizontal = 12.dp),
filter = filter,
filterBarStyle = filterBarStyle,
filterBarFilled = filterBarFilled,
filterBarPadding = filterBarPadding,
filterBarTonalElevation = filterBarTonalElevation,
) {
filter = it
}
}
}

View File

@ -0,0 +1,124 @@
package me.ash.reader.ui.page.settings.color.flow
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.DoneAll
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import me.ash.reader.R
import me.ash.reader.data.entity.Article
import me.ash.reader.data.entity.ArticleWithFeed
import me.ash.reader.data.entity.Feed
import me.ash.reader.data.model.Filter
import me.ash.reader.data.preference.FlowArticleListTonalElevationPreference
import me.ash.reader.data.preference.FlowTopBarTonalElevationPreference
import me.ash.reader.ui.component.FilterBar
import me.ash.reader.ui.component.base.FeedbackIconButton
import me.ash.reader.ui.ext.surfaceColorAtElevation
import me.ash.reader.ui.page.home.flow.ArticleItem
import me.ash.reader.ui.theme.palette.onDark
import java.util.*
@Composable
fun FlowPagePreview(
topBarTonalElevation: FlowTopBarTonalElevationPreference,
articleListTonalElevation: FlowArticleListTonalElevationPreference,
filterBarStyle: Int,
filterBarFilled: Boolean,
filterBarPadding: Dp,
filterBarTonalElevation: Dp,
) {
var filter by remember { mutableStateOf(Filter.Unread) }
Column(
modifier = Modifier
.animateContentSize()
.background(
color = MaterialTheme.colorScheme.surfaceColorAtElevation(
articleListTonalElevation.value.dp
) onDark MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(24.dp)
)
) {
SmallTopAppBar(
title = {},
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
topBarTonalElevation.value.dp
),
),
navigationIcon = {
FeedbackIconButton(
imageVector = Icons.Rounded.ArrowBack,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colorScheme.onSurface
) {}
},
actions = {
FeedbackIconButton(
imageVector = Icons.Rounded.DoneAll,
contentDescription = stringResource(R.string.mark_all_as_read),
tint = MaterialTheme.colorScheme.onSurface,
) {}
FeedbackIconButton(
imageVector = Icons.Rounded.Search,
contentDescription = stringResource(R.string.search),
tint = MaterialTheme.colorScheme.onSurface,
) {}
}
)
Spacer(modifier = Modifier.height(12.dp))
ArticleItem(
articleWithFeed = generateArticleWithFeedPreview(),
)
Spacer(modifier = Modifier.height(12.dp))
FilterBar(
filter = filter,
filterBarStyle = filterBarStyle,
filterBarFilled = filterBarFilled,
filterBarPadding = filterBarPadding,
filterBarTonalElevation = filterBarTonalElevation,
) {
filter = it
}
}
}
@Stable
@Composable
fun generateArticleWithFeedPreview(): ArticleWithFeed =
ArticleWithFeed(
Article(
id = "",
title = stringResource(R.string.preview_article_title),
shortDescription = stringResource(R.string.preview_article_desc),
rawDescription = stringResource(R.string.preview_article_desc),
link = "",
feedId = "",
accountId = 0,
date = Date(1654053729L),
isStarred = true,
img = "https://images.unsplash.com/photo-1544716278-ca5e3f4abd8c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1yZWxhdGVkfDJ8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=800&q=60"
),
feed = Feed(
id = "",
name = stringResource(R.string.preview_feed_name),
icon = "",
accountId = 0,
groupId = "",
url = "",
),
)

View File

@ -1,6 +1,5 @@
package me.ash.reader.ui.page.settings.color.flow package me.ash.reader.ui.page.settings.color.flow
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@ -8,34 +7,20 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.DoneAll
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import me.ash.reader.R import me.ash.reader.R
import me.ash.reader.data.entity.Article
import me.ash.reader.data.entity.ArticleWithFeed
import me.ash.reader.data.entity.Feed
import me.ash.reader.data.model.Filter
import me.ash.reader.data.preference.* import me.ash.reader.data.preference.*
import me.ash.reader.ui.component.FilterBar
import me.ash.reader.ui.component.base.* import me.ash.reader.ui.component.base.*
import me.ash.reader.ui.ext.surfaceColorAtElevation
import me.ash.reader.ui.page.home.flow.ArticleItem
import me.ash.reader.ui.page.settings.SettingItem import me.ash.reader.ui.page.settings.SettingItem
import me.ash.reader.ui.theme.palette.onDark
import me.ash.reader.ui.theme.palette.onLight import me.ash.reader.ui.theme.palette.onLight
import java.util.*
@Composable @Composable
fun FlowPageStylePage( fun FlowPageStylePage(
@ -332,90 +317,3 @@ fun FlowPageStylePage(
articleListTonalElevationDialogVisible = false articleListTonalElevationDialogVisible = false
} }
} }
@Composable
fun FlowPagePreview(
topBarTonalElevation: FlowTopBarTonalElevationPreference,
articleListTonalElevation: FlowArticleListTonalElevationPreference,
filterBarStyle: Int,
filterBarFilled: Boolean,
filterBarPadding: Dp,
filterBarTonalElevation: Dp,
) {
var filter by remember { mutableStateOf(Filter.Unread) }
Column(
modifier = Modifier
.animateContentSize()
.background(
color = MaterialTheme.colorScheme.surfaceColorAtElevation(
articleListTonalElevation.value.dp
) onDark MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(24.dp)
)
) {
SmallTopAppBar(
title = {},
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
topBarTonalElevation.value.dp
),
),
navigationIcon = {
FeedbackIconButton(
imageVector = Icons.Rounded.ArrowBack,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colorScheme.onSurface
) {}
},
actions = {
FeedbackIconButton(
imageVector = Icons.Rounded.DoneAll,
contentDescription = stringResource(R.string.mark_all_as_read),
tint = MaterialTheme.colorScheme.onSurface,
) {}
FeedbackIconButton(
imageVector = Icons.Rounded.Search,
contentDescription = stringResource(R.string.search),
tint = MaterialTheme.colorScheme.onSurface,
) {}
}
)
Spacer(modifier = Modifier.height(12.dp))
ArticleItem(
articleWithFeed = ArticleWithFeed(
Article(
id = "",
title = stringResource(R.string.preview_article_title),
shortDescription = stringResource(R.string.preview_article_desc),
rawDescription = stringResource(R.string.preview_article_desc),
link = "",
feedId = "",
accountId = 0,
date = Date(),
isStarred = true,
img = "https://images.unsplash.com/photo-1544716278-ca5e3f4abd8c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1yZWxhdGVkfDJ8fHxlbnwwfHx8fA%3D%3D&auto=format&fit=crop&w=800&q=60"
),
feed = Feed(
id = "",
name = stringResource(R.string.preview_feed_name),
icon = "",
accountId = 0,
groupId = "",
url = "",
),
)
)
Spacer(modifier = Modifier.height(12.dp))
FilterBar(
modifier = Modifier.padding(horizontal = 12.dp),
filter = filter,
filterBarStyle = filterBarStyle,
filterBarFilled = filterBarFilled,
filterBarPadding = filterBarPadding,
filterBarTonalElevation = filterBarTonalElevation,
) {
filter = it
}
}
}

View File

@ -2,6 +2,7 @@ package me.ash.reader.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes import androidx.compose.material3.Shapes
import androidx.compose.runtime.Stable
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
val Shapes = Shapes( val Shapes = Shapes(
@ -12,4 +13,14 @@ val Shapes = Shapes(
extraLarge = RoundedCornerShape(28.0.dp) extraLarge = RoundedCornerShape(28.0.dp)
) )
val SHAPE_20 = RoundedCornerShape(20.0.dp) @Stable
val Shape20 = RoundedCornerShape(20.0.dp)
@Stable
val Shape32 = RoundedCornerShape(32.0.dp)
@Stable
val ShapeTop32 = RoundedCornerShape(32.0.dp, 32.0.dp, 0.0.dp, 0.0.dp)
@Stable
val ShapeBottom32 = RoundedCornerShape(0.0.dp, 0.0.dp, 32.0.dp, 32.0.dp)