Min SDK upgraded to 26 and Full screen when scrolling down in the ReadPage
This commit is contained in:
parent
67b033f1ec
commit
11d839fff4
|
@ -11,7 +11,7 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "me.ash.reader"
|
||||
minSdk 24
|
||||
minSdk 26
|
||||
targetSdk 32
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
@ -50,6 +50,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation("io.coil-kt:coil-compose:2.0.0-rc02")
|
||||
implementation("androidx.compose.animation:animation-graphics:$compose_version")
|
||||
implementation("com.google.accompanist:accompanist-flowlayout:0.24.3-alpha")
|
||||
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.3-alpha")
|
||||
|
|
|
@ -6,6 +6,43 @@ import kotlinx.coroutines.flow.Flow
|
|||
|
||||
@Dao
|
||||
interface ArticleDao {
|
||||
@Query(
|
||||
"""
|
||||
UPDATE article SET isUnread = 0
|
||||
WHERE accountId = :accountId
|
||||
AND isUnread = 1
|
||||
AND date <= :before
|
||||
"""
|
||||
)
|
||||
suspend fun markAllAsRead(accountId: Int, before: Long)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
UPDATE article SET isUnread = 0
|
||||
WHERE accountId = :accountId
|
||||
AND isUnread = 1
|
||||
AND date <= :before
|
||||
AND feedId = :feedId
|
||||
"""
|
||||
)
|
||||
suspend fun markAllAsReadByFeedId(accountId: Int, before: Long, feedId: String)
|
||||
//
|
||||
// @Query(
|
||||
// """
|
||||
// UPDATE article SET isUnread = 0
|
||||
// WHERE accountId = :accountId
|
||||
// AND isUnread = 1
|
||||
// AND date <= :before
|
||||
// AND feedId = :feedId
|
||||
//
|
||||
// SELECT * FROM `group` AS a, feed AS b, article AS c
|
||||
// WHERE a.accountId = :accountId
|
||||
// AND a.id = b.groupId
|
||||
// AND b.groupId = :groupId
|
||||
// AND c.feedId = b.id
|
||||
// """
|
||||
// )
|
||||
// suspend fun markAllAsReadByGroupId(accountId: Int, before: Long, groupId: String)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
|
|
58
app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt
Normal file
58
app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt
Normal file
|
@ -0,0 +1,58 @@
|
|||
package me.ash.reader.ui.page.home
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import me.ash.reader.data.constant.Filter
|
||||
import me.ash.reader.ui.extension.getName
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun FilterBar(
|
||||
modifier: Modifier = Modifier,
|
||||
filter: Filter,
|
||||
filterOnClick: (Filter) -> Unit = {},
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.height(60.dp)
|
||||
) {
|
||||
Divider(
|
||||
modifier = Modifier.fillMaxWidth().height(1.dp).zIndex(1f),
|
||||
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f)
|
||||
)
|
||||
NavigationBar(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
tonalElevation = 0.dp,
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(60.dp))
|
||||
listOf(
|
||||
Filter.Starred,
|
||||
Filter.Unread,
|
||||
Filter.All,
|
||||
).forEach { item ->
|
||||
NavigationBarItem(
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = item.icon,
|
||||
contentDescription = item.getName()
|
||||
)
|
||||
},
|
||||
selected = filter == item,
|
||||
onClick = { filterOnClick(item) },
|
||||
// colors = NavigationBarItemDefaults.colors(
|
||||
// selectedIconColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
// unselectedIconColor = MaterialTheme.colorScheme.outline,
|
||||
// selectedTextColor = MaterialTheme.colorScheme.onSurface,
|
||||
// unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
// indicatorColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
// )
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(60.dp))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package me.ash.reader.ui.page.home
|
||||
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import me.ash.reader.data.constant.Filter
|
||||
import me.ash.reader.ui.extension.getName
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun FilterBar2(
|
||||
modifier: Modifier = Modifier,
|
||||
filter: Filter,
|
||||
onSelected: (Filter) -> Unit = {},
|
||||
) {
|
||||
NavigationBar(
|
||||
tonalElevation = 0.dp,
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(60.dp))
|
||||
listOf(
|
||||
Filter.Starred,
|
||||
Filter.Unread,
|
||||
Filter.All,
|
||||
).forEach { item ->
|
||||
NavigationBarItem(
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = item.icon,
|
||||
contentDescription = item.getName()
|
||||
)
|
||||
},
|
||||
// label = { Text(text = item.getName()) },
|
||||
selected = filter == item,
|
||||
onClick = { onSelected(item) },
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
selectedIconColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
unselectedIconColor = MaterialTheme.colorScheme.outline,
|
||||
selectedTextColor = MaterialTheme.colorScheme.onSurface,
|
||||
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
indicatorColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
)
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(60.dp))
|
||||
}
|
||||
}
|
|
@ -1,409 +0,0 @@
|
|||
package me.ash.reader.ui.page.home
|
||||
|
||||
import android.util.Log
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.SoundEffectConstants
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.ChipDefaults
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.FilterChip
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.FiberManualRecord
|
||||
import androidx.compose.material.icons.outlined.Article
|
||||
import androidx.compose.material.icons.outlined.FiberManualRecord
|
||||
import androidx.compose.material.icons.outlined.TextFormat
|
||||
import androidx.compose.material.icons.rounded.Article
|
||||
import androidx.compose.material.icons.rounded.ExpandMore
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material.icons.rounded.StarOutline
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.flowlayout.FlowCrossAxisAlignment
|
||||
import com.google.accompanist.flowlayout.FlowRow
|
||||
import com.google.accompanist.flowlayout.MainAxisAlignment
|
||||
import com.google.accompanist.flowlayout.SizeMode
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.PagerState
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.data.constant.Filter
|
||||
import me.ash.reader.ui.extension.getName
|
||||
import me.ash.reader.ui.theme.LocalLightThemeColors
|
||||
import me.ash.reader.ui.widget.CanBeDisabledIconButton
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun HomeBottomNavBar(
|
||||
modifier: Modifier = Modifier,
|
||||
pagerState: PagerState,
|
||||
filter: Filter,
|
||||
filterOnClick: (Filter) -> Unit = {},
|
||||
disabled: Boolean,
|
||||
isUnread: Boolean,
|
||||
isStarred: Boolean,
|
||||
isFullContent: Boolean,
|
||||
unreadOnClick: (afterIsUnread: Boolean) -> Unit = {},
|
||||
starredOnClick: (afterIsStarred: Boolean) -> Unit = {},
|
||||
fullContentOnClick: (afterIsFullContent: Boolean) -> Unit = {},
|
||||
) {
|
||||
val transition = updateTransition(targetState = pagerState, label = "")
|
||||
val readerBarAlpha by transition.animateFloat(
|
||||
label = "",
|
||||
transitionSpec = {
|
||||
tween(
|
||||
easing = FastOutLinearInEasing,
|
||||
)
|
||||
}
|
||||
) {
|
||||
if (it.currentPage < 2) {
|
||||
if (it.currentPage == it.targetPage) {
|
||||
0f
|
||||
} else {
|
||||
if (it.targetPage == 2) {
|
||||
it.currentPageOffset.absoluteValue
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (it.currentPage == it.targetPage) {
|
||||
1f
|
||||
} else {
|
||||
if (it.targetPage == 1) {
|
||||
1f - it.currentPageOffset.absoluteValue
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider(
|
||||
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f)
|
||||
)
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = readerBarAlpha < 1f,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
.animateContentSize()
|
||||
.alpha(1 - readerBarAlpha),
|
||||
) {
|
||||
Log.i("RLog", "AppNavigationBar: ${readerBarAlpha}, ${1f - readerBarAlpha}")
|
||||
// FilterBar(
|
||||
// modifier = modifier,
|
||||
// filter = filter,
|
||||
// onSelected = filterOnClick,
|
||||
// )
|
||||
FilterBar2(
|
||||
modifier = modifier,
|
||||
filter = filter,
|
||||
onSelected = filterOnClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = readerBarAlpha > 0f,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
.animateContentSize()
|
||||
.alpha(readerBarAlpha),
|
||||
) {
|
||||
ReaderBar(
|
||||
modifier = Modifier,
|
||||
disabled = disabled,
|
||||
isUnread = isUnread,
|
||||
isStarred = isStarred,
|
||||
isFullContent = isFullContent,
|
||||
unreadOnClick = unreadOnClick,
|
||||
starredOnClick = starredOnClick,
|
||||
fullContentOnClick = fullContentOnClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FilterBar(
|
||||
modifier: Modifier = Modifier,
|
||||
filter: Filter,
|
||||
onSelected: (Filter) -> Unit = {},
|
||||
) {
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
FlowRow(
|
||||
mainAxisSize = SizeMode.Expand,
|
||||
mainAxisAlignment = MainAxisAlignment.Center,
|
||||
crossAxisAlignment = FlowCrossAxisAlignment.Center,
|
||||
crossAxisSpacing = 0.dp,
|
||||
mainAxisSpacing = 20.dp,
|
||||
) {
|
||||
listOf(
|
||||
Filter.Starred,
|
||||
Filter.Unread,
|
||||
Filter.All
|
||||
).forEach { item ->
|
||||
Item(
|
||||
icon = if (filter == item) item.filledIcon else item.icon,
|
||||
name = item.getName(),
|
||||
selected = filter == item,
|
||||
) {
|
||||
onSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun Item(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: ImageVector,
|
||||
name: String,
|
||||
selected: Boolean = false,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
val view = LocalView.current
|
||||
val lightThemeColors = LocalLightThemeColors.current
|
||||
val lightPrimaryContainer = lightThemeColors.primaryContainer
|
||||
val lightOnSurface = lightThemeColors.onSurface
|
||||
|
||||
FilterChip(
|
||||
modifier = Modifier
|
||||
.height(36.dp)
|
||||
.animateContentSize(),
|
||||
colors = ChipDefaults.filterChipColors(
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.outline,
|
||||
leadingIconColor = MaterialTheme.colorScheme.outline,
|
||||
disabledBackgroundColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||
disabledContentColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||
disabledLeadingIconColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||
selectedBackgroundColor = lightPrimaryContainer,
|
||||
selectedContentColor = lightOnSurface,
|
||||
selectedLeadingIconColor = lightOnSurface
|
||||
),
|
||||
selected = selected,
|
||||
selectedIcon = {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = name,
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(20.dp),
|
||||
tint = lightOnSurface,
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
view.playSoundEffect(SoundEffectConstants.CLICK)
|
||||
onClick()
|
||||
},
|
||||
content = {
|
||||
if (selected) {
|
||||
Text(
|
||||
modifier = modifier.padding(
|
||||
start = 0.dp,
|
||||
top = 8.dp,
|
||||
end = 8.dp,
|
||||
bottom = 8.dp
|
||||
),
|
||||
text = if (selected) name.uppercase() else "",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = if (selected) {
|
||||
lightOnSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = name,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Row(
|
||||
// modifier = Modifier
|
||||
// .animateContentSize()
|
||||
// .height(40.dp)
|
||||
// .width(if (selected) Dp.Unspecified else 40.dp)
|
||||
// .padding(vertical = if (selected) 2.dp else 0.dp)
|
||||
// .clip(CircleShape)
|
||||
// .pointerInput(Unit) {
|
||||
// detectTapGestures(
|
||||
// onTap = {
|
||||
// view.playSoundEffect(SoundEffectConstants.CLICK)
|
||||
// onClick()
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// .background(
|
||||
// if (selected) {
|
||||
// MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.54f)
|
||||
// } else {
|
||||
// Color.Transparent
|
||||
// }
|
||||
// ),
|
||||
// horizontalArrangement = Arrangement.Center,
|
||||
// verticalAlignment = Alignment.CenterVertically,
|
||||
// ) {
|
||||
// Spacer(modifier = Modifier.width(8.dp))
|
||||
// Icon(
|
||||
// modifier = Modifier.size(20.dp),
|
||||
// imageVector = icon,
|
||||
// contentDescription = name,
|
||||
// tint = if (selected) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
// )
|
||||
// if (selected) {
|
||||
// Spacer(modifier = Modifier.width(8.dp))
|
||||
// Text(
|
||||
// modifier = Modifier.padding(horizontal = 8.dp),
|
||||
// text = name.uppercase(),
|
||||
// style = MaterialTheme.typography.titleSmall,
|
||||
// color = if (selected) {
|
||||
// MaterialTheme.colorScheme.onSurface
|
||||
// } else {
|
||||
// MaterialTheme.colorScheme.outline
|
||||
// },
|
||||
// )
|
||||
// Spacer(modifier = Modifier.width(8.dp))
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ReaderBar(
|
||||
modifier: Modifier = Modifier,
|
||||
disabled: Boolean,
|
||||
isUnread: Boolean,
|
||||
isStarred: Boolean,
|
||||
isFullContent: Boolean,
|
||||
unreadOnClick: (afterIsUnread: Boolean) -> Unit = {},
|
||||
starredOnClick: (afterIsStarred: Boolean) -> Unit = {},
|
||||
fullContentOnClick: (afterIsFullContent: Boolean) -> Unit = {},
|
||||
) {
|
||||
val view = LocalView.current
|
||||
var fullContent by remember { mutableStateOf(isFullContent) }
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceAround,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier.fillMaxWidth()
|
||||
) {
|
||||
CanBeDisabledIconButton(
|
||||
modifier = Modifier.size(40.dp),
|
||||
disabled = disabled,
|
||||
imageVector = if (isUnread) {
|
||||
Icons.Filled.FiberManualRecord
|
||||
} else {
|
||||
Icons.Outlined.FiberManualRecord
|
||||
},
|
||||
contentDescription = stringResource(if (isUnread) R.string.mark_as_read else R.string.mark_as_unread),
|
||||
tint = if (isUnread) {
|
||||
MaterialTheme.colorScheme.onSecondaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
},
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
unreadOnClick(!isUnread)
|
||||
}
|
||||
CanBeDisabledIconButton(
|
||||
modifier = Modifier.size(40.dp),
|
||||
disabled = disabled,
|
||||
imageVector = if (isStarred) {
|
||||
Icons.Rounded.Star
|
||||
} else {
|
||||
Icons.Rounded.StarOutline
|
||||
},
|
||||
contentDescription = stringResource(if (isStarred) R.string.mark_as_unstar else R.string.mark_as_starred),
|
||||
tint = if (isStarred) {
|
||||
MaterialTheme.colorScheme.onSecondaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
},
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
starredOnClick(!isStarred)
|
||||
}
|
||||
CanBeDisabledIconButton(
|
||||
disabled = disabled,
|
||||
modifier = Modifier.size(40.dp),
|
||||
imageVector = Icons.Rounded.ExpandMore,
|
||||
contentDescription = "Next Article",
|
||||
tint = MaterialTheme.colorScheme.outline,
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
}
|
||||
CanBeDisabledIconButton(
|
||||
modifier = Modifier.size(40.dp),
|
||||
disabled = disabled,
|
||||
imageVector = Icons.Outlined.TextFormat,
|
||||
contentDescription = "Add Tag",
|
||||
tint = MaterialTheme.colorScheme.outline,
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
}
|
||||
CanBeDisabledIconButton(
|
||||
disabled = disabled,
|
||||
modifier = Modifier.size(40.dp),
|
||||
imageVector = if (fullContent) {
|
||||
Icons.Rounded.Article
|
||||
} else {
|
||||
Icons.Outlined.Article
|
||||
},
|
||||
contentDescription = stringResource(R.string.parse_full_content),
|
||||
tint = if (fullContent) {
|
||||
MaterialTheme.colorScheme.onSecondaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
},
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
val afterIsFullContent = !fullContent
|
||||
fullContent = afterIsFullContent
|
||||
fullContentOnClick(afterIsFullContent)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,13 +3,10 @@ package me.ash.reader.ui.page.home
|
|||
import android.util.Log
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
|
@ -34,8 +31,6 @@ fun HomePage(
|
|||
feedOptionViewModel: FeedOptionViewModel = hiltViewModel(),
|
||||
) {
|
||||
val viewState = viewModel.viewState.collectAsStateValue()
|
||||
val filterState = viewModel.filterState.collectAsStateValue()
|
||||
val readState = readViewModel.viewState.collectAsStateValue()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
OpenArticleByExtras(extrasArticleId)
|
||||
|
@ -86,38 +81,6 @@ fun HomePage(
|
|||
},
|
||||
),
|
||||
)
|
||||
HomeBottomNavBar(
|
||||
modifier = Modifier
|
||||
.height(60.dp)
|
||||
.fillMaxWidth(),
|
||||
pagerState = viewState.pagerState,
|
||||
disabled = readState.articleWithFeed == null,
|
||||
isUnread = readState.articleWithFeed?.article?.isUnread ?: false,
|
||||
isStarred = readState.articleWithFeed?.article?.isStarred ?: false,
|
||||
isFullContent = readState.articleWithFeed?.feed?.isFullContent ?: false,
|
||||
unreadOnClick = {
|
||||
readViewModel.dispatch(ReadViewAction.MarkUnread(it))
|
||||
},
|
||||
starredOnClick = {
|
||||
readViewModel.dispatch(ReadViewAction.MarkStarred(it))
|
||||
},
|
||||
fullContentOnClick = { afterIsFullContent ->
|
||||
readState.articleWithFeed?.let {
|
||||
if (afterIsFullContent) readViewModel.dispatch(ReadViewAction.RenderFullContent)
|
||||
else readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
|
||||
}
|
||||
},
|
||||
filter = filterState.filter,
|
||||
filterOnClick = {
|
||||
viewModel.dispatch(
|
||||
HomeViewAction.ChangeFilter(
|
||||
filterState.copy(
|
||||
filter = it
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
FeedOptionDrawer()
|
||||
|
|
|
@ -3,10 +3,7 @@ package me.ash.reader.ui.page.home.feeds
|
|||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.*
|
||||
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.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
@ -29,6 +26,7 @@ import me.ash.reader.R
|
|||
import me.ash.reader.ui.extension.collectAsStateValue
|
||||
import me.ash.reader.ui.extension.getDesc
|
||||
import me.ash.reader.ui.extension.getName
|
||||
import me.ash.reader.ui.page.home.FilterBar
|
||||
import me.ash.reader.ui.page.home.HomeViewAction
|
||||
import me.ash.reader.ui.page.home.HomeViewModel
|
||||
import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeDialog
|
||||
|
@ -37,17 +35,17 @@ import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewModel
|
|||
import me.ash.reader.ui.widget.Banner
|
||||
import me.ash.reader.ui.widget.Subtitle
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class, com.google.accompanist.pager.ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun FeedsPage(
|
||||
modifier: Modifier = Modifier,
|
||||
navController: NavHostController,
|
||||
viewModel: FeedsViewModel = hiltViewModel(),
|
||||
feedsViewModel: FeedsViewModel = hiltViewModel(),
|
||||
homeViewModel: HomeViewModel = hiltViewModel(),
|
||||
subscribeViewModel: SubscribeViewModel = hiltViewModel(),
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val viewState = viewModel.viewState.collectAsStateValue()
|
||||
val viewState = feedsViewModel.viewState.collectAsStateValue()
|
||||
val filterState = homeViewModel.filterState.collectAsStateValue()
|
||||
val syncState = homeViewModel.syncState.collectAsStateValue()
|
||||
|
||||
|
@ -61,12 +59,12 @@ fun FeedsPage(
|
|||
)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.dispatch(FeedsViewAction.FetchAccount)
|
||||
feedsViewModel.dispatch(FeedsViewAction.FetchAccount)
|
||||
}
|
||||
|
||||
LaunchedEffect(homeViewModel.filterState) {
|
||||
homeViewModel.filterState.collect { state ->
|
||||
viewModel.dispatch(
|
||||
feedsViewModel.dispatch(
|
||||
FeedsViewAction.FetchData(state)
|
||||
)
|
||||
}
|
||||
|
@ -114,7 +112,7 @@ fun FeedsPage(
|
|||
content = {
|
||||
SubscribeDialog(
|
||||
openInputStreamCallback = {
|
||||
viewModel.dispatch(FeedsViewAction.AddFromFile(it))
|
||||
feedsViewModel.dispatch(FeedsViewAction.AddFromFile(it))
|
||||
},
|
||||
)
|
||||
LazyColumn {
|
||||
|
@ -213,9 +211,27 @@ fun FeedsPage(
|
|||
}
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
}
|
||||
}
|
||||
},
|
||||
bottomBar = {
|
||||
FilterBar(
|
||||
modifier = Modifier
|
||||
.height(60.dp)
|
||||
.fillMaxWidth(),
|
||||
filter = filterState.filter,
|
||||
filterOnClick = {
|
||||
homeViewModel.dispatch(
|
||||
HomeViewAction.ChangeFilter(
|
||||
filterState.copy(
|
||||
filter = it
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -32,12 +32,16 @@ import java.io.InputStream
|
|||
@Composable
|
||||
fun SubscribeDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: SubscribeViewModel = hiltViewModel(),
|
||||
subscribeViewModel: SubscribeViewModel = hiltViewModel(),
|
||||
openInputStreamCallback: (InputStream) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val viewState = subscribeViewModel.viewState.collectAsStateValue()
|
||||
val groupsState =
|
||||
viewState.groups.collectAsState(initial = emptyList(), context = Dispatchers.IO)
|
||||
var dialogHeight by remember { mutableStateOf(300.dp) }
|
||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
|
||||
it?.let { uri ->
|
||||
context.contentResolver.openInputStream(uri)?.let { inputStream ->
|
||||
|
@ -45,21 +49,18 @@ fun SubscribeDialog(
|
|||
}
|
||||
}
|
||||
}
|
||||
val viewState = viewModel.viewState.collectAsStateValue()
|
||||
val groupsState =
|
||||
viewState.groups.collectAsState(initial = emptyList(), context = Dispatchers.IO)
|
||||
var dialogHeight by remember { mutableStateOf(300.dp) }
|
||||
val readYouString = stringResource(R.string.read_you)
|
||||
val defaultString = stringResource(R.string.defaults)
|
||||
|
||||
LaunchedEffect(viewState.visible) {
|
||||
if (viewState.visible) {
|
||||
val defaultGroupId = context.dataStore
|
||||
.get(DataStoreKeys.CurrentAccountId)!!
|
||||
.spacerDollar(readYouString + defaultString)
|
||||
viewModel.dispatch(SubscribeViewAction.SelectedGroup(defaultGroupId))
|
||||
viewModel.dispatch(SubscribeViewAction.Init)
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.SelectedGroup(defaultGroupId))
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.Init)
|
||||
} else {
|
||||
viewModel.dispatch(SubscribeViewAction.Reset)
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.Reset)
|
||||
viewState.pagerState.scrollToPage(0)
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +81,7 @@ fun SubscribeDialog(
|
|||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
onDismissRequest = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.dispatch(SubscribeViewAction.Hide)
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.Hide)
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
|
@ -102,10 +103,10 @@ fun SubscribeDialog(
|
|||
inputLink = viewState.linkContent,
|
||||
errorMessage = viewState.errorMessage,
|
||||
onLinkValueChange = {
|
||||
viewModel.dispatch(SubscribeViewAction.InputLink(it))
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.InputLink(it))
|
||||
},
|
||||
onSearchKeyboardAction = {
|
||||
viewModel.dispatch(SubscribeViewAction.Search(scope))
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.Search(scope))
|
||||
},
|
||||
link = viewState.linkContent,
|
||||
groups = groupsState.value,
|
||||
|
@ -114,24 +115,24 @@ fun SubscribeDialog(
|
|||
selectedGroupId = viewState.selectedGroupId,
|
||||
newGroupContent = viewState.newGroupContent,
|
||||
onNewGroupValueChange = {
|
||||
viewModel.dispatch(SubscribeViewAction.InputNewGroup(it))
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.InputNewGroup(it))
|
||||
},
|
||||
newGroupSelected = viewState.newGroupSelected,
|
||||
changeNewGroupSelected = {
|
||||
viewModel.dispatch(SubscribeViewAction.SelectedNewGroup(it))
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.SelectedNewGroup(it))
|
||||
},
|
||||
pagerState = viewState.pagerState,
|
||||
allowNotificationPresetOnClick = {
|
||||
viewModel.dispatch(SubscribeViewAction.ChangeAllowNotificationPreset)
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.ChangeAllowNotificationPreset)
|
||||
},
|
||||
parseFullContentPresetOnClick = {
|
||||
viewModel.dispatch(SubscribeViewAction.ChangeParseFullContentPreset)
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.ChangeParseFullContentPreset)
|
||||
},
|
||||
groupOnClick = {
|
||||
viewModel.dispatch(SubscribeViewAction.SelectedGroup(it))
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.SelectedGroup(it))
|
||||
},
|
||||
onResultKeyboardAction = {
|
||||
viewModel.dispatch(SubscribeViewAction.Subscribe)
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.Subscribe)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
@ -142,7 +143,7 @@ fun SubscribeDialog(
|
|||
enabled = viewState.linkContent.isNotEmpty(),
|
||||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.dispatch(SubscribeViewAction.Search(scope))
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.Search(scope))
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
|
@ -159,7 +160,7 @@ fun SubscribeDialog(
|
|||
TextButton(
|
||||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.dispatch(SubscribeViewAction.Subscribe)
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.Subscribe)
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.subscribe))
|
||||
|
@ -174,7 +175,7 @@ fun SubscribeDialog(
|
|||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
launcher.launch("*/*")
|
||||
viewModel.dispatch(SubscribeViewAction.Hide)
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.Hide)
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(R.string.import_from_opml))
|
||||
|
@ -184,7 +185,7 @@ fun SubscribeDialog(
|
|||
TextButton(
|
||||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.dispatch(SubscribeViewAction.Hide)
|
||||
subscribeViewModel.dispatch(SubscribeViewAction.Hide)
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(R.string.cancel))
|
||||
|
|
|
@ -25,13 +25,14 @@ fun ArticleItem(
|
|||
onClick: (ArticleWithFeed) -> Unit = {},
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.clickable { onClick(articleWithFeed) }
|
||||
.padding(horizontal = 12.dp, vertical = 12.dp)
|
||||
.alpha(if (articleWithFeed.article.isUnread) 1f else 0.5f),
|
||||
.alpha(if (articleWithFeed.article.isStarred || articleWithFeed.article.isUnread) 1f else 0.5f),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
|
|
@ -5,6 +5,7 @@ import androidx.compose.animation.Crossfade
|
|||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
@ -12,9 +13,7 @@ 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.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
@ -26,6 +25,7 @@ import androidx.paging.compose.collectAsLazyPagingItems
|
|||
import me.ash.reader.R
|
||||
import me.ash.reader.ui.extension.collectAsStateValue
|
||||
import me.ash.reader.ui.extension.getName
|
||||
import me.ash.reader.ui.page.home.FilterBar
|
||||
import me.ash.reader.ui.page.home.HomeViewAction
|
||||
import me.ash.reader.ui.page.home.HomeViewModel
|
||||
import me.ash.reader.ui.page.home.read.ReadViewModel
|
||||
|
@ -47,6 +47,7 @@ fun FlowPage(
|
|||
val viewState = viewModel.viewState.collectAsStateValue()
|
||||
val filterState = homeViewModel.filterState.collectAsStateValue()
|
||||
val pagingItems = viewState.pagingData.collectAsLazyPagingItems()
|
||||
var markAsRead by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(homeViewModel.filterState) {
|
||||
homeViewModel.filterState.collect { state ->
|
||||
|
@ -132,6 +133,24 @@ fun FlowPage(
|
|||
generateArticleList(context, pagingItems, readViewModel, homeViewModel, scope)
|
||||
}
|
||||
}
|
||||
},
|
||||
bottomBar = {
|
||||
FilterBar(
|
||||
modifier = Modifier
|
||||
.height(60.dp)
|
||||
.fillMaxWidth(),
|
||||
filter = filterState.filter,
|
||||
filterOnClick = {
|
||||
markAsRead = false
|
||||
homeViewModel.dispatch(
|
||||
HomeViewAction.ChangeFilter(
|
||||
filterState.copy(
|
||||
filter = it
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package me.ash.reader.ui.page.home.flow
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.ash.reader.R
|
||||
|
||||
@Composable
|
||||
fun MarkAsReadBar() {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 24.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
MarkAsReadBarItem(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(R.string.seven_days),
|
||||
)
|
||||
MarkAsReadBarItem(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(R.string.three_days),
|
||||
)
|
||||
MarkAsReadBarItem(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(R.string.one_day),
|
||||
)
|
||||
MarkAsReadBarItem(
|
||||
modifier = Modifier.weight(2.5f),
|
||||
text = stringResource(R.string.mark_all_as_read),
|
||||
isPrimary = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MarkAsReadBarItem(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
isPrimary: Boolean = false,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.height(52.dp)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clickable { },
|
||||
tonalElevation = 2.dp,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
color = if (isPrimary) {
|
||||
MaterialTheme.colorScheme.primaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surface
|
||||
}
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = if (isPrimary) {
|
||||
MaterialTheme.colorScheme.onPrimaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondary
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!isPrimary) {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
}
|
139
app/src/main/java/me/ash/reader/ui/page/home/read/ReadBar.kt
Normal file
139
app/src/main/java/me/ash/reader/ui/page/home/read/ReadBar.kt
Normal file
|
@ -0,0 +1,139 @@
|
|||
package me.ash.reader.ui.page.home.read
|
||||
|
||||
import android.view.HapticFeedbackConstants
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.FiberManualRecord
|
||||
import androidx.compose.material.icons.outlined.Article
|
||||
import androidx.compose.material.icons.outlined.FiberManualRecord
|
||||
import androidx.compose.material.icons.outlined.TextFormat
|
||||
import androidx.compose.material.icons.rounded.Article
|
||||
import androidx.compose.material.icons.rounded.ExpandMore
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material.icons.rounded.StarOutline
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.ui.widget.CanBeDisabledIconButton
|
||||
|
||||
@Composable
|
||||
fun ReadBar(
|
||||
modifier: Modifier = Modifier,
|
||||
disabled: Boolean,
|
||||
isUnread: Boolean,
|
||||
isStarred: Boolean,
|
||||
isFullContent: Boolean,
|
||||
unreadOnClick: (afterIsUnread: Boolean) -> Unit = {},
|
||||
starredOnClick: (afterIsStarred: Boolean) -> Unit = {},
|
||||
fullContentOnClick: (afterIsFullContent: Boolean) -> Unit = {},
|
||||
) {
|
||||
val view = LocalView.current
|
||||
var fullContent by remember { mutableStateOf(isFullContent) }
|
||||
|
||||
Surface(
|
||||
tonalElevation = 0.dp,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.height(60.dp)
|
||||
) {
|
||||
Box {
|
||||
Divider(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(1.dp)
|
||||
.zIndex(1f),
|
||||
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f)
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalArrangement = Arrangement.SpaceAround,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CanBeDisabledIconButton(
|
||||
modifier = Modifier.size(40.dp),
|
||||
disabled = disabled,
|
||||
imageVector = if (isUnread) {
|
||||
Icons.Filled.FiberManualRecord
|
||||
} else {
|
||||
Icons.Outlined.FiberManualRecord
|
||||
},
|
||||
contentDescription = stringResource(if (isUnread) R.string.mark_as_read else R.string.mark_as_unread),
|
||||
tint = if (isUnread) {
|
||||
MaterialTheme.colorScheme.onSecondaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
},
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
unreadOnClick(!isUnread)
|
||||
}
|
||||
CanBeDisabledIconButton(
|
||||
modifier = Modifier.size(40.dp),
|
||||
disabled = disabled,
|
||||
imageVector = if (isStarred) {
|
||||
Icons.Rounded.Star
|
||||
} else {
|
||||
Icons.Rounded.StarOutline
|
||||
},
|
||||
contentDescription = stringResource(if (isStarred) R.string.mark_as_unstar else R.string.mark_as_starred),
|
||||
tint = if (isStarred) {
|
||||
MaterialTheme.colorScheme.onSecondaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
},
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
starredOnClick(!isStarred)
|
||||
}
|
||||
CanBeDisabledIconButton(
|
||||
disabled = disabled,
|
||||
modifier = Modifier.size(40.dp),
|
||||
imageVector = Icons.Rounded.ExpandMore,
|
||||
contentDescription = "Next Article",
|
||||
tint = MaterialTheme.colorScheme.outline,
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
}
|
||||
CanBeDisabledIconButton(
|
||||
modifier = Modifier.size(40.dp),
|
||||
disabled = disabled,
|
||||
imageVector = Icons.Outlined.TextFormat,
|
||||
contentDescription = "Add Tag",
|
||||
tint = MaterialTheme.colorScheme.outline,
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
}
|
||||
CanBeDisabledIconButton(
|
||||
disabled = disabled,
|
||||
modifier = Modifier.size(40.dp),
|
||||
imageVector = if (fullContent) {
|
||||
Icons.Rounded.Article
|
||||
} else {
|
||||
Icons.Outlined.Article
|
||||
},
|
||||
contentDescription = stringResource(R.string.parse_full_content),
|
||||
tint = if (fullContent) {
|
||||
MaterialTheme.colorScheme.onSecondaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.outline
|
||||
},
|
||||
) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
val afterIsFullContent = !fullContent
|
||||
fullContent = afterIsFullContent
|
||||
fullContentOnClick(afterIsFullContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package me.ash.reader.ui.page.home.read
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
@ -9,24 +11,22 @@ import androidx.compose.material.icons.outlined.Headphones
|
|||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material.icons.rounded.Close
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import com.airbnb.lottie.compose.LottieAnimation
|
||||
import com.airbnb.lottie.compose.LottieCompositionSpec
|
||||
import com.airbnb.lottie.compose.rememberLottieComposition
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.data.article.ArticleWithFeed
|
||||
import me.ash.reader.ui.extension.collectAsStateValue
|
||||
import me.ash.reader.ui.page.home.HomeViewAction
|
||||
import me.ash.reader.ui.page.home.HomeViewModel
|
||||
import me.ash.reader.ui.widget.LottieAnimation
|
||||
import me.ash.reader.ui.widget.WebView
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
@ -34,27 +34,45 @@ import me.ash.reader.ui.widget.WebView
|
|||
fun ReadPage(
|
||||
navController: NavHostController,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: ReadViewModel = hiltViewModel(),
|
||||
homeViewModel: HomeViewModel = hiltViewModel(),
|
||||
readViewModel: ReadViewModel = hiltViewModel(),
|
||||
homeViewModel: HomeViewModel = hiltViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val viewState = viewModel.viewState.collectAsStateValue()
|
||||
val composition by rememberLottieComposition(
|
||||
LottieCompositionSpec.Url(
|
||||
"https://assets5.lottiefiles.com/packages/lf20_9tvcldy3.json"
|
||||
)
|
||||
)
|
||||
val viewState = readViewModel.viewState.collectAsStateValue()
|
||||
var isScrollDown by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(viewModel.viewState) {
|
||||
viewModel.viewState.collect {
|
||||
if (viewState.listState.isScrollInProgress) {
|
||||
LaunchedEffect(Unit) {
|
||||
Log.i("RLog", "scroll: start")
|
||||
}
|
||||
|
||||
val preItemIndex by remember { mutableStateOf(viewState.listState.firstVisibleItemIndex) }
|
||||
val preScrollStartOffset by remember { mutableStateOf(viewState.listState.firstVisibleItemScrollOffset) }
|
||||
val currentItemIndex = viewState.listState.firstVisibleItemIndex
|
||||
val currentScrollStartOffset = viewState.listState.firstVisibleItemScrollOffset
|
||||
|
||||
isScrollDown = when {
|
||||
currentItemIndex > preItemIndex -> true
|
||||
currentItemIndex < preItemIndex -> false
|
||||
else -> currentScrollStartOffset > preScrollStartOffset
|
||||
}
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
Log.i("RLog", "scroll: end")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(readViewModel.viewState) {
|
||||
readViewModel.viewState.collect {
|
||||
if (it.articleWithFeed != null) {
|
||||
// if (it.articleWithFeed.article.isUnread) {
|
||||
// viewModel.dispatch(ReadViewAction.MarkUnread(false))
|
||||
// }
|
||||
if (it.articleWithFeed.article.isUnread) {
|
||||
readViewModel.dispatch(ReadViewAction.MarkUnread(false))
|
||||
}
|
||||
if (it.articleWithFeed.feed.isFullContent) {
|
||||
viewModel.dispatch(ReadViewAction.RenderFullContent)
|
||||
readViewModel.dispatch(ReadViewAction.RenderFullContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,8 +80,49 @@ fun ReadPage(
|
|||
|
||||
Scaffold(
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.surface),
|
||||
topBar = {
|
||||
topBar = {},
|
||||
content = {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.zIndex(1f),
|
||||
contentAlignment = Alignment.TopCenter
|
||||
) {
|
||||
TopBar(isScrollDown, homeViewModel, scope, readViewModel, viewState)
|
||||
}
|
||||
Content(viewState, viewState.articleWithFeed, context)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.zIndex(1f),
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
BottomBar(isScrollDown, viewState.articleWithFeed, readViewModel)
|
||||
}
|
||||
}
|
||||
},
|
||||
bottomBar = {}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
isScrollDown: Boolean,
|
||||
homeViewModel: HomeViewModel,
|
||||
scope: CoroutineScope,
|
||||
readViewModel: ReadViewModel,
|
||||
viewState: ReadViewState
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = !isScrollDown,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
),
|
||||
title = {},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
|
@ -104,25 +163,64 @@ fun ReadPage(
|
|||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomBar(
|
||||
isScrollDown: Boolean,
|
||||
articleWithFeed: ArticleWithFeed?,
|
||||
readViewModel: ReadViewModel
|
||||
) {
|
||||
articleWithFeed?.let {
|
||||
AnimatedVisibility(
|
||||
visible = !isScrollDown,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
ReadBar(
|
||||
disabled = false,
|
||||
isUnread = articleWithFeed.article.isUnread,
|
||||
isStarred = articleWithFeed.article.isStarred,
|
||||
isFullContent = articleWithFeed.feed.isFullContent,
|
||||
unreadOnClick = {
|
||||
readViewModel.dispatch(ReadViewAction.MarkUnread(it))
|
||||
},
|
||||
content = {
|
||||
if (viewState.articleWithFeed == null) {
|
||||
starredOnClick = {
|
||||
readViewModel.dispatch(ReadViewAction.MarkStarred(it))
|
||||
},
|
||||
fullContentOnClick = { afterIsFullContent ->
|
||||
if (afterIsFullContent) readViewModel.dispatch(ReadViewAction.RenderFullContent)
|
||||
else readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(
|
||||
viewState: ReadViewState,
|
||||
articleWithFeed: ArticleWithFeed?,
|
||||
context: Context
|
||||
) {
|
||||
Column {
|
||||
if (articleWithFeed == null) {
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
LottieAnimation(
|
||||
composition = composition,
|
||||
modifier = Modifier
|
||||
.padding(50.dp)
|
||||
.alpha(0.6f),
|
||||
isPlaying = true,
|
||||
restartOnPlay = true,
|
||||
iterations = Int.MAX_VALUE
|
||||
modifier = Modifier.padding(80.dp),
|
||||
url = "https://assets8.lottiefiles.com/packages/lf20_jm7mv1ib.json",
|
||||
)
|
||||
} else {
|
||||
LazyColumn(
|
||||
state = viewState.listState,
|
||||
) {
|
||||
val article = viewState.articleWithFeed.article
|
||||
val feed = viewState.articleWithFeed.feed
|
||||
val article = articleWithFeed.article
|
||||
val feed = articleWithFeed.feed
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Column(
|
||||
|
@ -132,7 +230,6 @@ fun ReadPage(
|
|||
Header(context, article, feed)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(22.dp))
|
||||
Crossfade(targetState = viewState.content) { content ->
|
||||
|
@ -142,8 +239,12 @@ fun ReadPage(
|
|||
Spacer(modifier = Modifier.height(50.dp))
|
||||
}
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import me.ash.reader.ui.theme.LocalLightThemeColors
|
||||
|
||||
@Composable
|
||||
fun Banner(
|
||||
|
@ -25,7 +24,7 @@ fun Banner(
|
|||
action: (@Composable () -> Unit)? = null,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
val lightThemeColors = LocalLightThemeColors.current
|
||||
val lightThemeColors = MaterialTheme.colorScheme
|
||||
val lightPrimaryContainer = lightThemeColors.primaryContainer
|
||||
val lightOnSurface = lightThemeColors.onSurface
|
||||
|
||||
|
|
26
app/src/main/java/me/ash/reader/ui/widget/LottieAnimation.kt
Normal file
26
app/src/main/java/me/ash/reader/ui/widget/LottieAnimation.kt
Normal file
|
@ -0,0 +1,26 @@
|
|||
package me.ash.reader.ui.widget
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.airbnb.lottie.compose.LottieAnimation
|
||||
import com.airbnb.lottie.compose.LottieCompositionSpec
|
||||
import com.airbnb.lottie.compose.rememberLottieComposition
|
||||
|
||||
@Composable
|
||||
fun LottieAnimation(
|
||||
modifier: Modifier = Modifier,
|
||||
url: String,
|
||||
) {
|
||||
val composition by rememberLottieComposition(
|
||||
LottieCompositionSpec.Url(url)
|
||||
)
|
||||
|
||||
LottieAnimation(
|
||||
composition = composition,
|
||||
modifier = modifier,
|
||||
isPlaying = true,
|
||||
restartOnPlay = true,
|
||||
iterations = Int.MAX_VALUE,
|
||||
)
|
||||
}
|
|
@ -13,7 +13,6 @@ import androidx.compose.ui.graphics.toArgb
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import me.ash.reader.ui.extension.collectAsStateValue
|
||||
import me.ash.reader.ui.page.home.read.ReadViewAction
|
||||
import me.ash.reader.ui.page.home.read.ReadViewModel
|
||||
|
||||
|
@ -30,7 +29,6 @@ fun WebView(
|
|||
val context = LocalContext.current
|
||||
val color = MaterialTheme.colorScheme.onSurfaceVariant.toArgb()
|
||||
val backgroundColor = MaterialTheme.colorScheme.surface.toArgb()
|
||||
val viewState = viewModel.viewState.collectAsStateValue()
|
||||
val webViewClient = object : WebViewClient() {
|
||||
|
||||
override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? {
|
||||
|
|
|
@ -44,4 +44,10 @@
|
|||
<string name="mark_as_unread">标记为未读</string>
|
||||
<string name="mark_as_starred">标记为已加星标</string>
|
||||
<string name="mark_as_unstar">标记为未加星标</string>
|
||||
<string name="mark_as_read_one_day">超过 1 天标记为已读</string>
|
||||
<string name="mark_as_read_three_days">超过 3 天标记为已读</string>
|
||||
<string name="mark_as_read_seven_days">超过 7 天标记为已读</string>
|
||||
<string name="one_day">1 天</string>
|
||||
<string name="three_days">3 天</string>
|
||||
<string name="seven_days">7 天</string>
|
||||
</resources>
|
|
@ -44,4 +44,10 @@
|
|||
<string name="mark_as_unread">Mark as Unread</string>
|
||||
<string name="mark_as_starred">Mark as Starred</string>
|
||||
<string name="mark_as_unstar">Mark as Unstar</string>
|
||||
<string name="mark_as_read_one_day">Mark as Read More Than 1 Day</string>
|
||||
<string name="mark_as_read_three_days">Mark as Read More Than 3 Days</string>
|
||||
<string name="mark_as_read_seven_days">Mark as Read More Than 7 Days</string>
|
||||
<string name="one_day">1 d</string>
|
||||
<string name="three_days">3 d</string>
|
||||
<string name="seven_days">7 d</string>
|
||||
</resources>
|
Loading…
Reference in New Issue
Block a user