diff --git a/app/build.gradle b/app/build.gradle index fceadb3..14b037c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,7 +25,7 @@ android { buildTypes { release { minifyEnabled true -// shrinkResources true + shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } diff --git a/app/src/main/java/me/ash/reader/data/constant/Filter.kt b/app/src/main/java/me/ash/reader/data/constant/Filter.kt index 647fc82..d00d48c 100644 --- a/app/src/main/java/me/ash/reader/data/constant/Filter.kt +++ b/app/src/main/java/me/ash/reader/data/constant/Filter.kt @@ -1,29 +1,39 @@ package me.ash.reader.data.constant +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.FiberManualRecord +import androidx.compose.material.icons.rounded.StarOutline +import androidx.compose.material.icons.rounded.Subject +import androidx.compose.ui.graphics.vector.ImageVector + class Filter( var index: Int, var title: String, var description: String, var important: Int, + var icon: ImageVector, ) { companion object { val Starred = Filter( index = 0, title = "Starred", description = " Starred Items", - important = 13 + important = 13, + icon = Icons.Rounded.StarOutline, ) val Unread = Filter( index = 1, title = "Unread", description = " Unread Items", - important = 666 + important = 666, + icon = Icons.Outlined.FiberManualRecord, ) val All = Filter( index = 2, title = "All", description = " Unread Items", - important = 666 + important = 666, + icon = Icons.Rounded.Subject, ) } } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/repository/AccountRepository.kt b/app/src/main/java/me/ash/reader/data/repository/AccountRepository.kt index e4275d2..51c6018 100644 --- a/app/src/main/java/me/ash/reader/data/repository/AccountRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/AccountRepository.kt @@ -26,7 +26,7 @@ class AccountRepository @Inject constructor( suspend fun addDefaultAccount(): Account { return Account( - name = "Reader You", + name = "Read You", type = Account.Type.LOCAL, ).apply { id = accountDao.insert(this).toInt() diff --git a/app/src/main/java/me/ash/reader/ui/page/home/HomePage.kt b/app/src/main/java/me/ash/reader/ui/page/home/HomePage.kt index 48524d9..aeb7357 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/HomePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/HomePage.kt @@ -20,7 +20,7 @@ import me.ash.reader.data.constant.Symbol import me.ash.reader.ui.extension.collectAsStateValue import me.ash.reader.ui.extension.findActivity import me.ash.reader.ui.page.home.article.ArticlePage -import me.ash.reader.ui.page.home.feed.FeedPage +import me.ash.reader.ui.page.home.feeds.FeedsPage import me.ash.reader.ui.page.home.read.ReadPage import me.ash.reader.ui.page.home.read.ReadViewAction import me.ash.reader.ui.page.home.read.ReadViewModel @@ -98,25 +98,8 @@ fun HomePage( state = viewState.pagerState, composableList = listOf( { - FeedPage( + FeedsPage( navController = navController, - filter = filterState.filter, - groupAndFeedOnClick = { currentGroup, currentFeed -> - viewModel.dispatch( - HomeViewAction.ChangeFilter( - filterState.copy( - group = currentGroup, - feed = currentFeed, - ) - ) - ) - viewModel.dispatch( - HomeViewAction.ScrollToPage( - scope = scope, - targetPage = 1, - ) - ) - }, ) }, { diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feed/ButtonBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/feed/ButtonBar.kt deleted file mode 100644 index 191fffd..0000000 --- a/app/src/main/java/me/ash/reader/ui/page/home/feed/ButtonBar.kt +++ /dev/null @@ -1,231 +0,0 @@ -package me.ash.reader.ui.page.home.feed - -import android.view.HapticFeedbackConstants -import androidx.compose.foundation.Image -import androidx.compose.foundation.LocalIndication -import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.indication -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.interaction.PressInteraction -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.DpOffset -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import me.ash.reader.R -import me.ash.reader.ui.widget.AnimatedText -import me.ash.reader.ui.widget.Menu - -@Composable -fun ButtonBar( - modifier: Modifier = Modifier, - onClick: () -> Unit = {}, - buttonBarType: ButtonBarType, -) { - var expanded by remember { mutableStateOf(false) } - var offset by remember { mutableStateOf(DpOffset.Zero) } - val interactionSource = remember { MutableInteractionSource() } - val view = LocalView.current - - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = modifier - .fillMaxWidth() - .height(50.dp) - .indication(interactionSource, LocalIndication.current) - .padding(horizontal = 20.dp) - .clip(RoundedCornerShape(8.dp)) - .pointerInput(Unit) { - detectTapGestures( - onPress = { offset -> - val press = PressInteraction.Press(offset) - interactionSource.emit(press) - tryAwaitRelease() - interactionSource.emit(PressInteraction.Release(press)) - }, - onTap = { - onClick() - }, - onLongPress = { - view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) - offset = DpOffset(it.x.toDp(), it.y.toDp()) - expanded = true - }, - ) - }, - ) { - when (buttonBarType) { - is ButtonBarType.FilterBar -> FilterBar(buttonBarType) - is ButtonBarType.AllBar -> AllBar(buttonBarType) - is ButtonBarType.GroupBar -> GroupBar(buttonBarType) - is ButtonBarType.FeedBar -> FeedBar(buttonBarType) - } - } - - Menu( - offset = offset, - expanded = expanded, - dismissFunction = { expanded = false }, - ) -} - -@Composable -fun FilterBar( - buttonBarType: ButtonBarType.FilterBar, -) { - AnimatedText( - text = buttonBarType.title, - fontSize = 22.sp, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary, - ) - AnimatedText( - text = buttonBarType.important.toString(), - fontSize = 18.sp, - color = MaterialTheme.colorScheme.outline, - ) -} - -@Composable -fun AllBar( - buttonBarType: ButtonBarType.AllBar, -) { - AnimatedText( - text = buttonBarType.title, - fontSize = 22.sp, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary, - ) - Icon( - imageVector = buttonBarType.icon, - contentDescription = "Expand More", - tint = MaterialTheme.colorScheme.outline, - ) -} - -@Composable -fun GroupBar( - buttonBarType: ButtonBarType.GroupBar, -) { - Row { - Icon( - imageVector = buttonBarType.icon, - contentDescription = "icon", - modifier = Modifier - .padding(end = 4.dp) - .clip(CircleShape) - .clickable(onClick = buttonBarType.iconOnClick), - tint = MaterialTheme.colorScheme.onPrimaryContainer, - ) - Text( - modifier = Modifier - .weight(1f) - .padding(end = 20.dp), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - text = buttonBarType.title, - fontSize = 18.sp, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.onPrimaryContainer, - ) - } - AnimatedText( - text = buttonBarType.important.toString(), - fontSize = 18.sp, - color = MaterialTheme.colorScheme.outline, - ) -} - -@Composable -fun FeedBar( - buttonBarType: ButtonBarType.FeedBar, -) { - Row { - Surface( - modifier = Modifier - .padding(start = 28.dp, end = 4.dp) - .size(24.dp), - //.background(MaterialTheme.colorScheme.inversePrimary), - color = if (buttonBarType.icon == null) { - MaterialTheme.colorScheme.inversePrimary - } else { - Color.Unspecified - } - ) { - if (buttonBarType.icon == null) { - Icon( - painter = painterResource(id = R.drawable.default_folder), - contentDescription = "icon", - modifier = Modifier.fillMaxSize(), - tint = MaterialTheme.colorScheme.onPrimaryContainer, - ) - } else { - Image( - painter = buttonBarType.icon, - contentDescription = "icon", - modifier = Modifier.fillMaxSize(), - ) - } - } - Text( - modifier = Modifier - .padding(end = 20.dp), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - text = buttonBarType.title, - fontSize = 18.sp, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.onPrimaryContainer, - ) - } - AnimatedText( - text = buttonBarType.important.toString(), - fontSize = 18.sp, - color = MaterialTheme.colorScheme.outline, - ) -} - -sealed class ButtonBarType { - data class FilterBar( - val modifier: Modifier = Modifier, - val title: String = "", - val important: Int = 0, - ) : ButtonBarType() - - data class AllBar( - val title: String = "", - val icon: ImageVector, - ) : ButtonBarType() - - data class GroupBar( - val title: String = "", - val important: Int = 0, - val icon: ImageVector, - val iconOnClick: () -> Unit = {}, - ) : ButtonBarType() - - data class FeedBar( - val title: String = "", - val important: Int = 0, - val icon: Painter? = null, - ) : ButtonBarType() -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedList.kt b/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedList.kt deleted file mode 100644 index 64f03ea..0000000 --- a/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedList.kt +++ /dev/null @@ -1,49 +0,0 @@ -package me.ash.reader.ui.page.home.feed - -import android.graphics.BitmapFactory -import androidx.compose.animation.* -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.graphics.painter.BitmapPainter -import me.ash.reader.data.feed.Feed - -@Composable -fun ColumnScope.FeedList( - visible: Boolean, - feeds: List, - onClick: (currentFeed: Feed?) -> Unit = {}, -) { - AnimatedVisibility( - visible = visible, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { - Column(modifier = Modifier.animateContentSize()) { - feeds.forEach { feed -> - ButtonBar( - buttonBarType = ButtonBarType.FeedBar( - title = feed.name, - important = feed.important ?: 0, - icon = if (feed.icon == null) { - null - } else { - BitmapPainter( - BitmapFactory.decodeByteArray( - feed.icon, - 0, - feed.icon!!.size - ).asImageBitmap() - ) - }, - ), - onClick = { - onClick(feed) - }, - ) - } - } - } -} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedPage.kt deleted file mode 100644 index 653ddf8..0000000 --- a/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedPage.kt +++ /dev/null @@ -1,139 +0,0 @@ -package me.ash.reader.ui.page.home.feed - -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.ExpandMore -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.navigation.NavHostController -import kotlinx.coroutines.flow.collect -import me.ash.reader.DateTimeExt -import me.ash.reader.DateTimeExt.toString -import me.ash.reader.data.constant.Filter -import me.ash.reader.data.feed.Feed -import me.ash.reader.data.group.Group -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.page.home.feed.subscribe.SubscribeDialog -import me.ash.reader.ui.page.home.feed.subscribe.SubscribeViewAction -import me.ash.reader.ui.page.home.feed.subscribe.SubscribeViewModel -import me.ash.reader.ui.widget.TopTitleBox - -@Composable -fun FeedPage( - navController: NavHostController, - modifier: Modifier = Modifier, - viewModel: FeedViewModel = hiltViewModel(), - homeViewModel: HomeViewModel = hiltViewModel(), - subscribeViewModel: SubscribeViewModel = hiltViewModel(), - filter: Filter, - groupAndFeedOnClick: (currentGroup: Group?, currentFeed: Feed?) -> Unit = { _, _ -> }, -) { - val viewState = viewModel.viewState.collectAsStateValue() - val syncState = homeViewModel.syncState.collectAsStateValue() - - LaunchedEffect(homeViewModel.filterState) { - homeViewModel.filterState.collect { state -> - viewModel.dispatch( - FeedViewAction.FetchData( - isStarred = state.filter.let { it != Filter.All && it == Filter.Starred }, - isUnread = state.filter.let { it != Filter.All && it == Filter.Unread }, - ) - ) - } - } - - DisposableEffect(Unit) { - viewModel.dispatch( - FeedViewAction.FetchAccount() - ) - onDispose { } - } - - Box( - modifier = modifier.fillMaxSize() - ) { - SubscribeDialog( - openInputStreamCallback = { - viewModel.dispatch(FeedViewAction.AddFromFile(it)) - }, - ) - TopTitleBox( - title = viewState.account?.name ?: "未知账户", - description = if (syncState.isSyncing) { - "Syncing (${syncState.syncedCount}/${syncState.feedCount}) : ${syncState.currentFeedName}" - } else { - viewState.account?.updateAt?.toString(DateTimeExt.YYYY_MM_DD_HH_MM, true) - ?: "从未同步" - }, - listState = viewState.listState, - startOffset = Offset(20f, 80f), - startHeight = 72f, - startTitleFontSize = 38f, - startDescriptionFontSize = 16f, - ) { - viewModel.dispatch(FeedViewAction.ScrollToItem(0)) - } - Column { - FeedPageTopBar( - navController = navController, - isSyncing = syncState.isSyncing, - syncOnClick = { - homeViewModel.dispatch(HomeViewAction.Sync()) - }, - subscribeOnClick = { - subscribeViewModel.dispatch(SubscribeViewAction.Show) - }, - ) - LazyColumn( - state = viewState.listState, - modifier = Modifier.weight(1f), - ) { - item { - Spacer(modifier = Modifier.height(114.dp)) - ButtonBar( - buttonBarType = ButtonBarType.FilterBar( - title = filter.title, - important = viewState.filterImportant, - ), - onClick = { - groupAndFeedOnClick(null, null) - } - ) - } - item { - Spacer(modifier = Modifier.height(10.dp)) - ButtonBar( - buttonBarType = ButtonBarType.AllBar( - title = "Feeds", - icon = Icons.Rounded.ExpandMore - ), - onClick = { - viewModel.dispatch(FeedViewAction.ChangeGroupVisible) - } - ) - } - itemsIndexed(viewState.groupWithFeedList) { index, groupWithFeed -> - GroupList( - modifier = modifier, - groupVisible = viewState.groupsVisible, - feedVisible = viewState.feedsVisible[index], - groupWithFeed = groupWithFeed, - groupAndFeedOnClick = groupAndFeedOnClick, - expandOnClick = { - viewModel.dispatch(FeedViewAction.ChangeFeedVisible(index)) - } - ) - } - } - } - } -} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedPageTopBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedPageTopBar.kt deleted file mode 100644 index 96562ec..0000000 --- a/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedPageTopBar.kt +++ /dev/null @@ -1,66 +0,0 @@ -package me.ash.reader.ui.page.home.feed - -import android.view.HapticFeedbackConstants -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Settings -import androidx.compose.material.icons.rounded.Add -import androidx.compose.material.icons.rounded.Refresh -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SmallTopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalView -import androidx.navigation.NavHostController -import me.ash.reader.ui.page.common.RouteName - -@Composable -fun FeedPageTopBar( - navController: NavHostController, - isSyncing: Boolean = false, - syncOnClick: () -> Unit = {}, - subscribeOnClick: () -> Unit = {}, -) { - val view = LocalView.current - SmallTopAppBar( - title = {}, - navigationIcon = { - IconButton(onClick = { - view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) - navController.navigate(route = RouteName.SETTINGS) - }) { - Icon( -// modifier = Modifier.size(22.dp), - imageVector = Icons.Outlined.Settings, - contentDescription = "Back", - tint = MaterialTheme.colorScheme.primary - ) - } - }, - actions = { - IconButton(onClick = { - view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) - if (isSyncing) return@IconButton - syncOnClick() - }) { - Icon( -// modifier = Modifier.size(26.dp), - imageVector = Icons.Rounded.Refresh, - contentDescription = "Sync", - tint = MaterialTheme.colorScheme.primary, - ) - } - IconButton(onClick = { - view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) - subscribeOnClick() - }) { - Icon( -// modifier = Modifier.size(26.dp), - imageVector = Icons.Rounded.Add, - contentDescription = "Subscribe", - tint = MaterialTheme.colorScheme.primary, - ) - } - }, - ) -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feed/GroupList.kt b/app/src/main/java/me/ash/reader/ui/page/home/feed/GroupList.kt deleted file mode 100644 index bff194d..0000000 --- a/app/src/main/java/me/ash/reader/ui/page/home/feed/GroupList.kt +++ /dev/null @@ -1,49 +0,0 @@ -package me.ash.reader.ui.page.home.feed - -import androidx.compose.animation.* -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.ExpandMore -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import me.ash.reader.data.feed.Feed -import me.ash.reader.data.group.Group -import me.ash.reader.data.group.GroupWithFeed - -@Composable -fun ColumnScope.GroupList( - modifier: Modifier = Modifier, - groupVisible: Boolean, - feedVisible: Boolean, - groupWithFeed: GroupWithFeed, - groupAndFeedOnClick: (currentGroup: Group?, currentFeed: Feed?) -> Unit = { _, _ -> }, - expandOnClick: () -> Unit, -) { - AnimatedVisibility( - visible = groupVisible, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { - Column(modifier = modifier) { - ButtonBar( - buttonBarType = ButtonBarType.GroupBar( - title = groupWithFeed.group.name, - icon = Icons.Rounded.ExpandMore, - important = groupWithFeed.group.important ?: 0, - iconOnClick = expandOnClick, - ), - onClick = { - groupAndFeedOnClick(groupWithFeed.group, null) - } - ) - FeedList( - visible = feedVisible, - feeds = groupWithFeed.feeds, - onClick = { currentFeed -> - groupAndFeedOnClick(null, currentFeed) - } - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/Feed.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/Feed.kt new file mode 100644 index 0000000..79e027e --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/Feed.kt @@ -0,0 +1,66 @@ +package me.ash.reader.ui.page.home.feeds + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Badge +import androidx.compose.material3.MaterialTheme +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.unit.dp + +@Composable +fun Feed( + modifier: Modifier = Modifier, + name: String, + important: Int, + onClick: () -> Unit = {}, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp) + .clip(RoundedCornerShape(32.dp)) + .clickable { onClick() } + .padding(vertical = 14.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Row(modifier = Modifier.padding(start = 14.dp)) { + Row( + modifier = Modifier + .size(20.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.outline), + ) {} + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = name, + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + if (important != 0) { + Badge( + modifier = Modifier.padding(end = 6.dp), + containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f), + contentColor = MaterialTheme.colorScheme.outline, + content = { + Text( + text = important.toString(), + style = MaterialTheme.typography.labelSmall + ) + }, + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt new file mode 100644 index 0000000..ec1be88 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt @@ -0,0 +1,201 @@ +package me.ash.reader.ui.page.home.feeds + +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.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.KeyboardArrowRight +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.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavHostController +import kotlinx.coroutines.flow.collect +import me.ash.reader.data.constant.Filter +import me.ash.reader.data.constant.Symbol +import me.ash.reader.ui.extension.collectAsStateValue +import me.ash.reader.ui.page.home.FilterState +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 +import me.ash.reader.ui.page.home.feeds.subscribe.SubscribeViewAction +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) +@Composable +fun FeedsPage( + modifier: Modifier = Modifier, + navController: NavHostController, + viewModel: FeedsViewModel = hiltViewModel(), + homeViewModel: HomeViewModel = hiltViewModel(), + subscribeViewModel: SubscribeViewModel = hiltViewModel(), +) { + val scope = rememberCoroutineScope() + val viewState = viewModel.viewState.collectAsStateValue() + val syncState = homeViewModel.syncState.collectAsStateValue() + + val infiniteTransition = rememberInfiniteTransition() + val angle by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 360f, + animationSpec = infiniteRepeatable( + animation = tween(1000, easing = LinearEasing) + ) + ) + + LaunchedEffect(Unit) { + viewModel.dispatch(FeedsViewAction.FetchAccount()) + } + + LaunchedEffect(homeViewModel.filterState) { + homeViewModel.filterState.collect { state -> + viewModel.dispatch( + FeedsViewAction.FetchData( + isStarred = state.filter.let { it != Filter.All && it == Filter.Starred }, + isUnread = state.filter.let { it != Filter.All && it == Filter.Unread }, + ) + ) + } + } + + Scaffold( + modifier = Modifier.background(MaterialTheme.colorScheme.surface), + topBar = { + SmallTopAppBar( + title = {}, + navigationIcon = { + IconButton(onClick = { }) { + Icon( + imageVector = Icons.Rounded.ArrowBack, + contentDescription = "Back", + tint = MaterialTheme.colorScheme.onSurface + ) + } + }, + actions = { + IconButton(onClick = { + if (syncState.isSyncing) return@IconButton + homeViewModel.dispatch(HomeViewAction.Sync()) + }) { + Icon( + modifier = Modifier.graphicsLayer { + rotationZ = if (syncState.isSyncing) angle else 0f + }, + imageVector = Icons.Rounded.Refresh, + contentDescription = "Refresh", + tint = MaterialTheme.colorScheme.onSurface, + ) + } + IconButton(onClick = { + subscribeViewModel.dispatch(SubscribeViewAction.Show) + }) { + Icon( + imageVector = Icons.Rounded.Add, + contentDescription = "Subscribe", + tint = MaterialTheme.colorScheme.onSurface, + ) + } + } + ) + }, + content = { + SubscribeDialog( + openInputStreamCallback = { + viewModel.dispatch(FeedsViewAction.AddFromFile(it)) + }, + ) + Column( + modifier = Modifier.verticalScroll(rememberScrollState()) + ) { + Text( + modifier = Modifier.padding( + start = 24.dp, + top = 48.dp, + end = 24.dp, + bottom = 24.dp + ), + text = viewState.account?.name ?: Symbol.Unknown, + style = MaterialTheme.typography.displaySmall, + color = MaterialTheme.colorScheme.onSurface, + ) + Banner( + title = viewState.filter.title, + desc = "${viewState.filter.important}${viewState.filter.description}", + icon = viewState.filter.icon, + action = { + Icon( + imageVector = Icons.Outlined.KeyboardArrowRight, + contentDescription = "Goto", + tint = MaterialTheme.colorScheme.onSurface, + ) + }, + ) + Spacer(modifier = Modifier.height(24.dp)) + Subtitle( + modifier = Modifier.padding(start = 4.dp), + text = "Feeds" + ) + Spacer(modifier = Modifier.height(8.dp)) + Column { + viewState.groupWithFeedList.forEachIndexed { index, groupWithFeed -> + Group( + text = groupWithFeed.group.name, + feeds = groupWithFeed.feeds, + groupOnClick = { + homeViewModel.dispatch( + HomeViewAction.ChangeFilter( + FilterState( + group = groupWithFeed.group, + feed = null, + ) + ) + ) + homeViewModel.dispatch( + HomeViewAction.ScrollToPage( + scope = scope, + targetPage = 1, + ) + ) + }, + feedOnClick = { feed -> + homeViewModel.dispatch( + HomeViewAction.ChangeFilter( + FilterState( + group = null, + feed = feed, + ) + ) + ) + homeViewModel.dispatch( + HomeViewAction.ScrollToPage( + scope = scope, + targetPage = 1, + ) + ) + } + ) + if (index != viewState.groupWithFeedList.lastIndex) { + Spacer(modifier = Modifier.height(8.dp)) + } + } + } + } + } + ) +} + diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt similarity index 72% rename from app/src/main/java/me/ash/reader/ui/page/home/feed/FeedViewModel.kt rename to app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt index 4fc69ca..620d2c4 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt @@ -1,4 +1,4 @@ -package me.ash.reader.ui.page.home.feed +package me.ash.reader.ui.page.home.feeds import android.util.Log import androidx.compose.foundation.lazy.LazyListState @@ -9,6 +9,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import me.ash.reader.data.account.Account +import me.ash.reader.data.constant.Filter import me.ash.reader.data.group.GroupWithFeed import me.ash.reader.data.repository.AccountRepository import me.ash.reader.data.repository.OpmlRepository @@ -17,22 +18,20 @@ import java.io.InputStream import javax.inject.Inject @HiltViewModel -class FeedViewModel @Inject constructor( +class FeedsViewModel @Inject constructor( private val accountRepository: AccountRepository, private val rssRepository: RssRepository, private val opmlRepository: OpmlRepository, ) : ViewModel() { - private val _viewState = MutableStateFlow(FeedViewState()) - val viewState: StateFlow = _viewState.asStateFlow() + private val _viewState = MutableStateFlow(FeedsViewState()) + val viewState: StateFlow = _viewState.asStateFlow() - fun dispatch(action: FeedViewAction) { + fun dispatch(action: FeedsViewAction) { when (action) { - is FeedViewAction.FetchAccount -> fetchAccount(action.callback) - is FeedViewAction.FetchData -> fetchData(action.isStarred, action.isUnread) - is FeedViewAction.AddFromFile -> addFromFile(action.inputStream) - is FeedViewAction.ChangeFeedVisible -> changeFeedVisible(action.index) - is FeedViewAction.ChangeGroupVisible -> changeGroupVisible() - is FeedViewAction.ScrollToItem -> scrollToItem(action.index) + is FeedsViewAction.FetchAccount -> fetchAccount(action.callback) + is FeedsViewAction.FetchData -> fetchData(action.isStarred, action.isUnread) + is FeedsViewAction.AddFromFile -> addFromFile(action.inputStream) + is FeedsViewAction.ScrollToItem -> scrollToItem(action.index) } } @@ -57,6 +56,7 @@ class FeedViewModel @Inject constructor( private fun fetchData(isStarred: Boolean, isUnread: Boolean) { viewModelScope.launch(Dispatchers.IO) { pullFeeds(isStarred, isUnread) + _viewState } } @@ -101,7 +101,13 @@ class FeedViewModel @Inject constructor( }.onEach { groupWithFeedList -> _viewState.update { it.copy( - filterImportant = groupWithFeedList.sumOf { it.group.important ?: 0 }, + filter = when { + isStarred -> Filter.Starred + isUnread -> Filter.Unread + else -> Filter.All + }.apply { + important = groupWithFeedList.sumOf { it.group.important ?: 0 } + }, groupWithFeedList = groupWithFeedList, feedsVisible = List(groupWithFeedList.size, init = { true }) ) @@ -111,24 +117,6 @@ class FeedViewModel @Inject constructor( }.collect() } - private fun changeFeedVisible(index: Int) { - _viewState.update { - it.copy( - feedsVisible = _viewState.value.feedsVisible.toMutableList().apply { - this[index] = !this[index] - } - ) - } - } - - private fun changeGroupVisible() { - _viewState.update { - it.copy( - groupsVisible = !_viewState.value.groupsVisible - ) - } - } - private fun scrollToItem(index: Int) { viewModelScope.launch { _viewState.value.listState.scrollToItem(index) @@ -136,36 +124,30 @@ class FeedViewModel @Inject constructor( } } -data class FeedViewState( +data class FeedsViewState( val account: Account? = null, - val filterImportant: Int = 0, + val filter: Filter = Filter.All, val groupWithFeedList: List = emptyList(), val feedsVisible: List = emptyList(), val listState: LazyListState = LazyListState(), val groupsVisible: Boolean = true, ) -sealed class FeedViewAction { +sealed class FeedsViewAction { data class FetchData( val isStarred: Boolean, val isUnread: Boolean, - ) : FeedViewAction() + ) : FeedsViewAction() data class FetchAccount( val callback: () -> Unit = {}, - ) : FeedViewAction() + ) : FeedsViewAction() data class AddFromFile( val inputStream: InputStream - ) : FeedViewAction() - - data class ChangeFeedVisible( - val index: Int - ) : FeedViewAction() - - object ChangeGroupVisible : FeedViewAction() + ) : FeedsViewAction() data class ScrollToItem( val index: Int - ) : FeedViewAction() + ) : FeedsViewAction() } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/Group.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/Group.kt new file mode 100644 index 0000000..07d660d --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/Group.kt @@ -0,0 +1,92 @@ +package me.ash.reader.ui.page.home.feeds + +import androidx.compose.animation.* +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ExpandLess +import androidx.compose.material.icons.rounded.ExpandMore +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.clip +import androidx.compose.ui.unit.dp +import me.ash.reader.data.feed.Feed + +@Composable +fun Group( + modifier: Modifier = Modifier, + text: String, + feeds: List, + isExpanded: Boolean = true, + groupOnClick: () -> Unit = {}, + feedOnClick: (feed: Feed) -> Unit = {}, +) { + var expanded by remember { mutableStateOf(isExpanded) } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .clip(RoundedCornerShape(32.dp)) + .background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.14f)) + .clickable { groupOnClick() } + .padding(top = 22.dp, bottom = if (expanded) 14.dp else 22.dp) + ) { + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + modifier = Modifier.padding(start = 28.dp), + text = text, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSecondaryContainer, + ) + Row( + modifier = Modifier + .padding(end = 20.dp) + .size(24.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f)) + .clickable { + expanded = !expanded + }, + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = if (expanded) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore, + contentDescription = if (expanded) "Expand Less" else "Expand More", + tint = MaterialTheme.colorScheme.onSecondaryContainer, + ) + } + } + + AnimatedVisibility( + visible = expanded, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), + ) { + Column { + Spacer(modifier = Modifier.height(16.dp)) + feeds.forEach { feed -> + Feed( + modifier = Modifier.padding(horizontal = 20.dp), + name = feed.name, + important = feed.important ?: 0, + ) { + feedOnClick(feed) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/ResultViewPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultViewPage.kt similarity index 99% rename from app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/ResultViewPage.kt rename to app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultViewPage.kt index 51ee7f7..4386aca 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/ResultViewPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultViewPage.kt @@ -1,4 +1,4 @@ -package me.ash.reader.ui.page.home.feed.subscribe +package me.ash.reader.ui.page.home.feeds.subscribe import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.* diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SearchViewPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SearchViewPage.kt similarity index 98% rename from app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SearchViewPage.kt rename to app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SearchViewPage.kt index 0a0921c..bd3fcbb 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SearchViewPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SearchViewPage.kt @@ -1,4 +1,4 @@ -package me.ash.reader.ui.page.home.feed.subscribe +package me.ash.reader.ui.page.home.feeds.subscribe import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeDialog.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeDialog.kt similarity index 99% rename from app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeDialog.kt rename to app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeDialog.kt index cd9cf2c..df0c9b0 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeDialog.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeDialog.kt @@ -1,4 +1,4 @@ -package me.ash.reader.ui.page.home.feed.subscribe +package me.ash.reader.ui.page.home.feeds.subscribe import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt similarity index 99% rename from app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeViewModel.kt rename to app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt index baf07e2..df02ad5 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewModel.kt @@ -1,4 +1,4 @@ -package me.ash.reader.ui.page.home.feed.subscribe +package me.ash.reader.ui.page.home.feeds.subscribe import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeViewPager.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewPager.kt similarity index 97% rename from app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeViewPager.kt rename to app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewPager.kt index e5b4698..eb97173 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeViewPager.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/SubscribeViewPager.kt @@ -1,4 +1,4 @@ -package me.ash.reader.ui.page.home.feed.subscribe +package me.ash.reader.ui.page.home.feeds.subscribe import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable diff --git a/app/src/main/java/me/ash/reader/ui/theme/Color.kt b/app/src/main/java/me/ash/reader/ui/theme/Color.kt index 6799065..29fee49 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/Color.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/Color.kt @@ -2,74 +2,61 @@ package me.ash.reader.ui.theme import androidx.compose.ui.graphics.Color -//val md_theme_light_primary = Color(0xFF4D4D4D) -val md_theme_light_primary = Color(0xFF6750A4) -val md_theme_light_onPrimary = Color(0xFFFFFFFF) -val md_theme_light_primaryContainer = Color(0xFFEADDFF) -val md_theme_light_onPrimaryContainer = Color(0xFF21005D) - -//val md_theme_light_secondary = Color(0xFF868686) -val md_theme_light_secondary = Color(0xFF625B71) -val md_theme_light_onSecondary = Color(0xFFFFFFFF) - -//val md_theme_light_secondaryContainer = Color(0xFFEAEAEA) -val md_theme_light_secondaryContainer = Color(0xFFE8DEF8) -val md_theme_light_onSecondaryContainer = Color(0xFF1D192B) - -//val md_theme_light_tertiary = Color(0xFFC1C1C1) -val md_theme_light_tertiary = Color(0xFF7D5260) -val md_theme_light_onTertiary = Color(0xFFFFFFFF) -val md_theme_light_tertiaryContainer = Color(0xFFFFD8E4) -val md_theme_light_onTertiaryContainer = Color(0xFF31111D) -val md_theme_light_error = Color(0xFFB3261E) -val md_theme_light_errorContainer = Color(0xFFF9DEDC) -val md_theme_light_onError = Color(0xFFFFFFFF) -val md_theme_light_onErrorContainer = Color(0xFF410E0B) - -//val md_theme_light_background = Color(0xFFF7F5F4) -val md_theme_light_background = Color(0xFFFFFBFE) -val md_theme_light_onBackground = Color(0xFF1C1B1F) - -//val md_theme_light_surface = Color(0xFFF7F5F4) -val md_theme_light_surface = Color(0xFFFFFBFE) -val md_theme_light_onSurface = Color(0xFF1C1B1F) -val md_theme_light_surfaceVariant = Color(0xFFE7E0EC) - -//val md_theme_light_onSurfaceVariant = md_theme_light_secondary -val md_theme_light_onSurfaceVariant = Color(0xFF49454F) -val md_theme_light_outline = Color(0xFF79747E) -val md_theme_light_inverseOnSurface = Color(0xFFF4EFF4) -val md_theme_light_inverseSurface = Color(0xFF313033) -val md_theme_light_inversePrimary = Color(0xFFD0BCFF) +val md_theme_light_primary = Color(0xFF00658e) +val md_theme_light_onPrimary = Color(0xFFffffff) +val md_theme_light_primaryContainer = Color(0xFFc3e7ff) +val md_theme_light_onPrimaryContainer = Color(0xFF001e2e) +val md_theme_light_secondary = Color(0xFF4f616e) +val md_theme_light_onSecondary = Color(0xFFffffff) +val md_theme_light_secondaryContainer = Color(0xFFd2e5f4) +val md_theme_light_onSecondaryContainer = Color(0xFF0b1d28) +val md_theme_light_tertiary = Color(0xFF625a7c) +val md_theme_light_onTertiary = Color(0xFFffffff) +val md_theme_light_tertiaryContainer = Color(0xFFe8ddff) +val md_theme_light_onTertiaryContainer = Color(0xFF1e1735) +val md_theme_light_error = Color(0xFFba1b1b) +val md_theme_light_errorContainer = Color(0xFFffdad4) +val md_theme_light_onError = Color(0xFFffffff) +val md_theme_light_onErrorContainer = Color(0xFF410001) +val md_theme_light_background = Color(0xFFfbfcff) +val md_theme_light_onBackground = Color(0xFF191c1e) +val md_theme_light_surface = Color(0xFFfbfcff) +val md_theme_light_onSurface = Color(0xFF191c1e) +val md_theme_light_surfaceVariant = Color(0xFFdde3ea) +val md_theme_light_onSurfaceVariant = Color(0xFF41484d) +val md_theme_light_outline = Color(0xFF71787e) +val md_theme_light_inverseOnSurface = Color(0xFFf0f1f4) +val md_theme_light_inverseSurface = Color(0xFF2e3133) +val md_theme_light_inversePrimary = Color(0xFF7fcfff) val md_theme_light_shadow = Color(0xFF000000) -val md_theme_dark_primary = Color(0xFFD0BCFF) -val md_theme_dark_onPrimary = Color(0xFF381E72) -val md_theme_dark_primaryContainer = Color(0xFF4F378B) -val md_theme_dark_onPrimaryContainer = Color(0xFFEADDFF) -val md_theme_dark_secondary = Color(0xFFCCC2DC) -val md_theme_dark_onSecondary = Color(0xFF332D41) -val md_theme_dark_secondaryContainer = Color(0xFF4A4458) -val md_theme_dark_onSecondaryContainer = Color(0xFFE8DEF8) -val md_theme_dark_tertiary = Color(0xFFEFB8C8) -val md_theme_dark_onTertiary = Color(0xFF492532) -val md_theme_dark_tertiaryContainer = Color(0xFF633B48) -val md_theme_dark_onTertiaryContainer = Color(0xFFFFD8E4) -val md_theme_dark_error = Color(0xFFF2B8B5) -val md_theme_dark_errorContainer = Color(0xFF8C1D18) -val md_theme_dark_onError = Color(0xFF601410) -val md_theme_dark_onErrorContainer = Color(0xFFF9DEDC) -val md_theme_dark_background = Color(0xFF1C1B1F) -val md_theme_dark_onBackground = Color(0xFFE6E1E5) -val md_theme_dark_surface = Color(0xFF1C1B1F) -val md_theme_dark_onSurface = Color(0xFFE6E1E5) -val md_theme_dark_surfaceVariant = Color(0xFF49454F) -val md_theme_dark_onSurfaceVariant = Color(0xFFCAC4D0) -val md_theme_dark_outline = Color(0xFF938F99) -val md_theme_dark_inverseOnSurface = Color(0xFF1C1B1F) -val md_theme_dark_inverseSurface = Color(0xFFE6E1E5) -val md_theme_dark_inversePrimary = Color(0xFF6750A4) +val md_theme_dark_primary = Color(0xFF7fcfff) +val md_theme_dark_onPrimary = Color(0xFF00344b) +val md_theme_dark_primaryContainer = Color(0xFF004c6c) +val md_theme_dark_onPrimaryContainer = Color(0xFFc3e7ff) +val md_theme_dark_secondary = Color(0xFFb6c9d8) +val md_theme_dark_onSecondary = Color(0xFF21333e) +val md_theme_dark_secondaryContainer = Color(0xFF374955) +val md_theme_dark_onSecondaryContainer = Color(0xFFd2e5f4) +val md_theme_dark_tertiary = Color(0xFFccc1e9) +val md_theme_dark_onTertiary = Color(0xFF332c4b) +val md_theme_dark_tertiaryContainer = Color(0xFF4a4263) +val md_theme_dark_onTertiaryContainer = Color(0xFFe8ddff) +val md_theme_dark_error = Color(0xFFffb4a9) +val md_theme_dark_errorContainer = Color(0xFF930006) +val md_theme_dark_onError = Color(0xFF680003) +val md_theme_dark_onErrorContainer = Color(0xFFffdad4) +val md_theme_dark_background = Color(0xFF191c1e) +val md_theme_dark_onBackground = Color(0xFFe1e2e5) +val md_theme_dark_surface = Color(0xFF191c1e) +val md_theme_dark_onSurface = Color(0xFFe1e2e5) +val md_theme_dark_surfaceVariant = Color(0xFF41484d) +val md_theme_dark_onSurfaceVariant = Color(0xFFc1c7ce) +val md_theme_dark_outline = Color(0xFF8b9298) +val md_theme_dark_inverseOnSurface = Color(0xFF191c1e) +val md_theme_dark_inverseSurface = Color(0xFFe1e2e5) +val md_theme_dark_inversePrimary = Color(0xFF00658e) val md_theme_dark_shadow = Color(0xFF000000) -val seed = Color(0xFF6750A4) -val error = Color(0xFFB3261E) \ No newline at end of file +val seed = Color(0xFF006187) +val error = Color(0xFFba1b1b) \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/theme/Theme.kt b/app/src/main/java/me/ash/reader/ui/theme/Theme.kt index 77c2f56..9242c37 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/Theme.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/Theme.kt @@ -71,7 +71,7 @@ private val DarkThemeColors = darkColorScheme( @Composable fun AppTheme( useDarkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable() () -> Unit + content: @Composable () -> Unit ) { // Dynamic color is available on Android 12+ val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S diff --git a/app/src/main/java/me/ash/reader/ui/theme/Type.kt b/app/src/main/java/me/ash/reader/ui/theme/Type.kt index a7accac..acbbd0d 100644 --- a/app/src/main/java/me/ash/reader/ui/theme/Type.kt +++ b/app/src/main/java/me/ash/reader/ui/theme/Type.kt @@ -2,114 +2,165 @@ package me.ash.reader.ui.theme import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp +import me.ash.reader.R -//Replace with your font locations -val Roboto = FontFamily.Default +val googleSansDisplay: FontFamily = FontFamily( + Font( + resId = R.font.google_sans_display_regular, + weight = FontWeight.Normal, + style = FontStyle.Normal + ), + Font( + resId = R.font.google_sans_display_medium, + weight = FontWeight.Medium, + style = FontStyle.Normal + ), + Font( + resId = R.font.google_sans_display_bold, + weight = FontWeight.Bold, + style = FontStyle.Normal + ), +) + +val googleSansText: FontFamily = FontFamily( + Font( + resId = R.font.google_sans_text_regular, + weight = FontWeight.Normal, + style = FontStyle.Normal + ), + Font( + resId = R.font.google_sans_text_italic, + weight = FontWeight.Normal, + style = FontStyle.Italic + ), + Font( + resId = R.font.google_sans_text_medium, + weight = FontWeight.Medium, + style = FontStyle.Normal + ), + Font( + resId = R.font.google_sans_text_medium_italic, + weight = FontWeight.Medium, + style = FontStyle.Italic + ), + Font( + resId = R.font.google_sans_text_bold, + weight = FontWeight.Bold, + style = FontStyle.Normal + ), + Font( + resId = R.font.google_sans_text_bold_italic, + weight = FontWeight.Bold, + style = FontStyle.Italic + ), +) val AppTypography = Typography( displayLarge = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansDisplay, fontWeight = FontWeight.W400, fontSize = 57.sp, lineHeight = 64.sp, letterSpacing = -0.25.sp, ), displayMedium = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansDisplay, fontWeight = FontWeight.W400, fontSize = 45.sp, lineHeight = 52.sp, letterSpacing = 0.sp, ), displaySmall = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansDisplay, fontWeight = FontWeight.W400, fontSize = 36.sp, lineHeight = 44.sp, letterSpacing = 0.sp, ), headlineLarge = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansDisplay, fontWeight = FontWeight.W400, fontSize = 32.sp, lineHeight = 40.sp, letterSpacing = 0.sp, ), headlineMedium = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansDisplay, fontWeight = FontWeight.W400, fontSize = 28.sp, lineHeight = 36.sp, letterSpacing = 0.sp, ), headlineSmall = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansDisplay, fontWeight = FontWeight.W400, fontSize = 24.sp, lineHeight = 32.sp, letterSpacing = 0.sp, ), titleLarge = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansDisplay, fontWeight = FontWeight.W400, fontSize = 22.sp, lineHeight = 28.sp, letterSpacing = 0.sp, ), titleMedium = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansText, fontWeight = FontWeight.Medium, fontSize = 16.sp, lineHeight = 24.sp, letterSpacing = 0.1.sp, ), titleSmall = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansText, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.1.sp, ), labelLarge = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansText, fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.1.sp, ), bodyLarge = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansText, fontWeight = FontWeight.W400, fontSize = 16.sp, lineHeight = 24.sp, letterSpacing = 0.5.sp, ), bodyMedium = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansText, fontWeight = FontWeight.W400, fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.25.sp, ), bodySmall = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansText, fontWeight = FontWeight.W400, fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.4.sp, ), labelMedium = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansText, fontWeight = FontWeight.Medium, fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.5.sp, ), labelSmall = TextStyle( - fontFamily = Roboto, + fontFamily = googleSansText, fontWeight = FontWeight.Medium, fontSize = 11.sp, lineHeight = 16.sp, diff --git a/app/src/main/java/me/ash/reader/ui/widget/Banner.kt b/app/src/main/java/me/ash/reader/ui/widget/Banner.kt new file mode 100644 index 0000000..ce5e291 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/widget/Banner.kt @@ -0,0 +1,73 @@ +package me.ash.reader.ui.widget + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +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.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun Banner( + modifier: Modifier = Modifier, + title: String, + desc: String? = null, + icon: ImageVector? = null, + onClick: () -> Unit = {}, + action: (@Composable () -> Unit)? = null +) { + Surface( + modifier = modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.surface, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .clip(RoundedCornerShape(32.dp)) + .background(MaterialTheme.colorScheme.primaryContainer) + .clickable { onClick() } + .padding(16.dp, 20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + icon?.let { + Icon( + imageVector = it, + contentDescription = null, + modifier = Modifier.padding(end = 16.dp), + tint = MaterialTheme.colorScheme.onSurface, + ) + } + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + maxLines = if (desc == null) 2 else 1, + style = MaterialTheme.typography.titleLarge.copy(fontSize = 20.sp), + color = MaterialTheme.colorScheme.onSurface, + ) + desc?.let { + Text( + text = it, + maxLines = 1, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + ) + } + } + action?.let { + Box(Modifier.padding(start = 16.dp)) { + it() + } + } + } + } +} diff --git a/app/src/main/java/me/ash/reader/ui/widget/SubTitle.kt b/app/src/main/java/me/ash/reader/ui/widget/SubTitle.kt new file mode 100644 index 0000000..f698d18 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/widget/SubTitle.kt @@ -0,0 +1,26 @@ +package me.ash.reader.ui.widget + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun Subtitle( + text: String, + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary, +) { + Text( + text = text, + modifier = modifier + .fillMaxWidth() + .padding(24.dp, 8.dp, 16.dp, 8.dp), + color = color, + style = MaterialTheme.typography.labelLarge + ) +} \ No newline at end of file diff --git a/app/src/main/res/font/google_sans_display_bold.ttf b/app/src/main/res/font/google_sans_display_bold.ttf new file mode 100644 index 0000000..1b8bb1b Binary files /dev/null and b/app/src/main/res/font/google_sans_display_bold.ttf differ diff --git a/app/src/main/res/font/google_sans_display_medium.ttf b/app/src/main/res/font/google_sans_display_medium.ttf new file mode 100644 index 0000000..1029e55 Binary files /dev/null and b/app/src/main/res/font/google_sans_display_medium.ttf differ diff --git a/app/src/main/res/font/google_sans_display_regular.ttf b/app/src/main/res/font/google_sans_display_regular.ttf new file mode 100644 index 0000000..cc027ed Binary files /dev/null and b/app/src/main/res/font/google_sans_display_regular.ttf differ diff --git a/app/src/main/res/font/google_sans_text_bold.ttf b/app/src/main/res/font/google_sans_text_bold.ttf new file mode 100644 index 0000000..a583deb Binary files /dev/null and b/app/src/main/res/font/google_sans_text_bold.ttf differ diff --git a/app/src/main/res/font/google_sans_text_bold_italic.ttf b/app/src/main/res/font/google_sans_text_bold_italic.ttf new file mode 100644 index 0000000..4b759da Binary files /dev/null and b/app/src/main/res/font/google_sans_text_bold_italic.ttf differ diff --git a/app/src/main/res/font/google_sans_text_italic.ttf b/app/src/main/res/font/google_sans_text_italic.ttf new file mode 100644 index 0000000..1c27d84 Binary files /dev/null and b/app/src/main/res/font/google_sans_text_italic.ttf differ diff --git a/app/src/main/res/font/google_sans_text_medium.ttf b/app/src/main/res/font/google_sans_text_medium.ttf new file mode 100644 index 0000000..e7066dc Binary files /dev/null and b/app/src/main/res/font/google_sans_text_medium.ttf differ diff --git a/app/src/main/res/font/google_sans_text_medium_italic.ttf b/app/src/main/res/font/google_sans_text_medium_italic.ttf new file mode 100644 index 0000000..b86aeff Binary files /dev/null and b/app/src/main/res/font/google_sans_text_medium_italic.ttf differ diff --git a/app/src/main/res/font/google_sans_text_regular.ttf b/app/src/main/res/font/google_sans_text_regular.ttf new file mode 100644 index 0000000..a3ae8a6 Binary files /dev/null and b/app/src/main/res/font/google_sans_text_regular.ttf differ