From 11ca1f1ae8aa3ddec40dcbeee868d4edc077d264 Mon Sep 17 00:00:00 2001 From: Ash Date: Mon, 7 Mar 2022 17:05:38 +0800 Subject: [PATCH] Add subscribe feed UI --- app/build.gradle | 5 +- app/src/main/java/me/ash/reader/App.kt | 8 - .../main/java/me/ash/reader/DataStoreExt.kt | 6 +- .../main/java/me/ash/reader/MainActivity.kt | 8 - .../me/ash/reader/data/account/AccountDao.kt | 1 - .../reader/data/repository/RssRepository.kt | 23 +- .../me/ash/reader/ui/extension/ContextExt.kt | 11 + .../reader/ui/extension/LazyListStateExt.kt | 14 + .../Extension.kt => extension/ModifierExt.kt} | 35 +-- .../ash/reader/ui/extension/StateFlowExt.kt | 12 + .../me/ash/reader/ui/page/common/HomeEntry.kt | 103 +++++- .../home/HomeBottomNavBar.kt} | 7 +- .../me/ash/reader/ui/page/home/HomePage.kt | 190 +++++------ .../ash/reader/ui/page/home/HomeViewModel.kt | 16 +- .../ui/page/home/article/ArticleDateHeader.kt | 37 +++ .../ui/page/home/article/ArticleItem.kt | 143 +++++++++ .../ui/page/home/article/ArticlePage.kt | 216 +------------ .../ui/page/home/article/ArticlePageTopBar.kt | 47 +++ .../home/feed/FeedBar.kt} | 11 +- .../ash/reader/ui/page/home/feed/FeedList.kt | 51 +++ .../ash/reader/ui/page/home/feed/FeedPage.kt | 296 +++--------------- .../ui/page/home/feed/FeedPageTopBar.kt | 61 ++++ .../reader/ui/page/home/feed/FeedViewModel.kt | 43 ++- .../ash/reader/ui/page/home/feed/GroupList.kt | 48 +++ .../home/feed/subscribe/ResultViewPage.kt | 171 ++++++++++ .../home/feed/subscribe/SearchViewPage.kt | 75 +++++ .../home/feed/subscribe/SubscribeDialog.kt | 80 +++++ .../home/feed/subscribe/SubscribeViewPager.kt | 28 ++ .../me/ash/reader/ui/page/home/read/Header.kt | 67 ++++ .../ash/reader/ui/page/home/read/ReadPage.kt | 107 +------ .../ui/page/home/read/ReadPageTopBar.kt | 51 +++ .../reader/ui/page/settings/SettingsPage.kt | 15 +- .../main/java/me/ash/reader/ui/theme/Color.kt | 8 +- .../main/java/me/ash/reader/ui/theme/Type.kt | 212 ++++++------- .../reader/ui/widget/AnimatedItemsIndexed.kt | 2 - .../me/ash/reader/ui/widget/AnimatedText.kt | 2 +- .../me/ash/reader/ui/widget/CustomPager.kt | 12 +- .../java/me/ash/reader/ui/widget/MaskBox.kt | 2 +- .../me/ash/reader/ui/widget/SelectionChip.kt | 59 ++++ .../me/ash/reader/ui/widget/TopTitleBox.kt | 4 +- .../java/me/ash/reader/ui/widget/ViewPager.kt | 33 ++ .../java/me/ash/reader/ui/widget/WebView.kt | 2 +- 42 files changed, 1396 insertions(+), 926 deletions(-) create mode 100644 app/src/main/java/me/ash/reader/ui/extension/ContextExt.kt create mode 100644 app/src/main/java/me/ash/reader/ui/extension/LazyListStateExt.kt rename app/src/main/java/me/ash/reader/ui/{util/Extension.kt => extension/ModifierExt.kt} (61%) create mode 100644 app/src/main/java/me/ash/reader/ui/extension/StateFlowExt.kt rename app/src/main/java/me/ash/reader/ui/{widget/AppNavigationBar.kt => page/home/HomeBottomNavBar.kt} (98%) create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/article/ArticleDateHeader.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/article/ArticleItem.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/article/ArticlePageTopBar.kt rename app/src/main/java/me/ash/reader/ui/{widget/BarButton.kt => page/home/feed/FeedBar.kt} (96%) create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/feed/FeedList.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/feed/FeedPageTopBar.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/feed/GroupList.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/ResultViewPage.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SearchViewPage.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeDialog.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeViewPager.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/read/Header.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/read/ReadPageTopBar.kt create mode 100644 app/src/main/java/me/ash/reader/ui/widget/SelectionChip.kt create mode 100644 app/src/main/java/me/ash/reader/ui/widget/ViewPager.kt diff --git a/app/build.gradle b/app/build.gradle index 135ca7c..c726bd3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,7 +50,8 @@ android { } dependencies { - implementation("io.coil-kt:coil-compose:1.4.0") + implementation("com.google.accompanist:accompanist-flowlayout:0.24.3-alpha") + implementation("com.google.accompanist:accompanist-navigation-animation:0.24.3-alpha") implementation "androidx.datastore:datastore-preferences:1.0.0" implementation "com.airbnb.android:lottie-compose:4.2.2" implementation "androidx.work:work-runtime-ktx:2.8.0-alpha01" @@ -82,7 +83,7 @@ dependencies { implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0-alpha01" implementation "androidx.compose.material:material:1.2.0-alpha03" - implementation "androidx.compose.material3:material3:1.0.0-alpha05" + implementation "androidx.compose.material3:material3:1.0.0-alpha06" implementation "androidx.compose.material:material-icons-extended:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' diff --git a/app/src/main/java/me/ash/reader/App.kt b/app/src/main/java/me/ash/reader/App.kt index f8b32f3..69e4fcc 100644 --- a/app/src/main/java/me/ash/reader/App.kt +++ b/app/src/main/java/me/ash/reader/App.kt @@ -1,10 +1,6 @@ package me.ash.reader import android.app.Application -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.material3.ExperimentalMaterial3Api -import com.google.accompanist.pager.ExperimentalPagerApi import dagger.hilt.android.HiltAndroidApp import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope @@ -18,10 +14,6 @@ import me.ash.reader.data.source.ReaderDatabase import me.ash.reader.data.source.RssNetworkDataSource import javax.inject.Inject -@ExperimentalAnimationApi -@ExperimentalMaterial3Api -@ExperimentalPagerApi -@ExperimentalFoundationApi @DelicateCoroutinesApi @HiltAndroidApp class App : Application() { diff --git a/app/src/main/java/me/ash/reader/DataStoreExt.kt b/app/src/main/java/me/ash/reader/DataStoreExt.kt index 3e23103..c5f0513 100644 --- a/app/src/main/java/me/ash/reader/DataStoreExt.kt +++ b/app/src/main/java/me/ash/reader/DataStoreExt.kt @@ -29,9 +29,9 @@ fun DataStore.get(dataStoreKeys: DataStoreKeys): T? { if (exception is IOException) { Log.e("RLog", "Get data store error $exception") exception.printStackTrace() - emit(emptyPreferences()) - } else { - throw exception + emit(emptyPreferences()) + } else { + throw exception } }.map { it[dataStoreKeys.key] diff --git a/app/src/main/java/me/ash/reader/MainActivity.kt b/app/src/main/java/me/ash/reader/MainActivity.kt index 00d3af4..46c48bb 100644 --- a/app/src/main/java/me/ash/reader/MainActivity.kt +++ b/app/src/main/java/me/ash/reader/MainActivity.kt @@ -3,18 +3,10 @@ package me.ash.reader import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.core.view.WindowCompat -import com.google.accompanist.pager.ExperimentalPagerApi import dagger.hilt.android.AndroidEntryPoint import me.ash.reader.ui.page.common.HomeEntry -@ExperimentalAnimationApi -@ExperimentalMaterial3Api -@ExperimentalPagerApi -@ExperimentalFoundationApi @AndroidEntryPoint class MainActivity : ComponentActivity() { diff --git a/app/src/main/java/me/ash/reader/data/account/AccountDao.kt b/app/src/main/java/me/ash/reader/data/account/AccountDao.kt index 184cbbf..07d80ec 100644 --- a/app/src/main/java/me/ash/reader/data/account/AccountDao.kt +++ b/app/src/main/java/me/ash/reader/data/account/AccountDao.kt @@ -11,7 +11,6 @@ interface AccountDao { ) suspend fun queryAll(): List - @Query( """ SELECT * FROM account diff --git a/app/src/main/java/me/ash/reader/data/repository/RssRepository.kt b/app/src/main/java/me/ash/reader/data/repository/RssRepository.kt index d16229c..ba3cae3 100644 --- a/app/src/main/java/me/ash/reader/data/repository/RssRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/RssRepository.kt @@ -8,14 +8,10 @@ import android.content.Context import android.content.Intent import android.os.Build import android.util.Log -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat.getSystemService import androidx.work.* import com.github.muhrifqii.parserss.ParseRSS -import com.google.accompanist.pager.ExperimentalPagerApi import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.flow.* @@ -40,7 +36,7 @@ import java.util.* import java.util.concurrent.TimeUnit import javax.inject.Inject - +@DelicateCoroutinesApi class RssRepository @Inject constructor( @ApplicationContext private val context: Context, @@ -87,10 +83,6 @@ class RssRepository @Inject constructor( return workManager.getWorkInfosByTag("sync").get().size.toString() } - @ExperimentalAnimationApi - @ExperimentalMaterial3Api - @ExperimentalPagerApi - @ExperimentalFoundationApi suspend fun sync(isWork: Boolean? = false) { if (isWork == true) { workManager.cancelAllWork() @@ -108,10 +100,6 @@ class RssRepository @Inject constructor( } } - @ExperimentalAnimationApi - @ExperimentalMaterial3Api - @ExperimentalPagerApi - @ExperimentalFoundationApi @DelicateCoroutinesApi companion object { data class SyncState( @@ -215,7 +203,10 @@ class RssRepository @Inject constructor( val ids = articleDao.insertList(articleList) articleList.forEachIndexed { index, article -> Log.i("RlOG", "combine ${article.feedId}: ${article.title}") - val builder = NotificationCompat.Builder(context, Symbol.NOTIFICATION_CHANNEL_GROUP_ARTICLE_UPDATE) + val builder = NotificationCompat.Builder( + context, + Symbol.NOTIFICATION_CHANNEL_GROUP_ARTICLE_UPDATE + ) .setSmallIcon(R.drawable.ic_launcher_foreground) .setGroup(Symbol.NOTIFICATION_CHANNEL_GROUP_ARTICLE_UPDATE) .setContentTitle(article.title) @@ -346,10 +337,6 @@ class RssRepository @Inject constructor( } } -@ExperimentalAnimationApi -@ExperimentalMaterial3Api -@ExperimentalPagerApi -@ExperimentalFoundationApi @DelicateCoroutinesApi class SyncWorker( context: Context, diff --git a/app/src/main/java/me/ash/reader/ui/extension/ContextExt.kt b/app/src/main/java/me/ash/reader/ui/extension/ContextExt.kt new file mode 100644 index 0000000..18c79b0 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/extension/ContextExt.kt @@ -0,0 +1,11 @@ +package me.ash.reader.ui.extension + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper + +fun Context.findActivity(): Activity? = when (this) { + is Activity -> this + is ContextWrapper -> baseContext.findActivity() + else -> null +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/extension/LazyListStateExt.kt b/app/src/main/java/me/ash/reader/ui/extension/LazyListStateExt.kt new file mode 100644 index 0000000..908d180 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/extension/LazyListStateExt.kt @@ -0,0 +1,14 @@ +package me.ash.reader.ui.extension + +import androidx.compose.foundation.lazy.LazyListState +import kotlin.math.abs + +fun LazyListState.calculateTopBarAnimateValue(start: Float, end: Float): Float = + if (firstVisibleItemIndex != 0) end + else { + val variable = firstVisibleItemScrollOffset.coerceAtLeast(0).toFloat() + val duration = 256f + val increase = abs(start - end) * (variable / duration) + if (start < end) (start + increase).coerceIn(start, end) + else (start - increase).coerceIn(end, start) + } diff --git a/app/src/main/java/me/ash/reader/ui/util/Extension.kt b/app/src/main/java/me/ash/reader/ui/extension/ModifierExt.kt similarity index 61% rename from app/src/main/java/me/ash/reader/ui/util/Extension.kt rename to app/src/main/java/me/ash/reader/ui/extension/ModifierExt.kt index 3a6916a..986cb5c 100644 --- a/app/src/main/java/me/ash/reader/ui/util/Extension.kt +++ b/app/src/main/java/me/ash/reader/ui/extension/ModifierExt.kt @@ -1,14 +1,8 @@ -package me.ash.reader.ui.util +package me.ash.reader.ui.extension -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.graphicsLayer @@ -18,28 +12,9 @@ import androidx.compose.ui.unit.lerp import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.PagerScope import com.google.accompanist.pager.calculateCurrentOffsetForPage -import kotlinx.coroutines.flow.StateFlow -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext -import kotlin.math.abs import kotlin.math.absoluteValue -@Composable -fun StateFlow.collectAsStateValue( - context: CoroutineContext = EmptyCoroutineContext -): T = collectAsState(context).value - -fun LazyListState.calculateTopBarAnimateValue(start: Float, end: Float): Float = - if (firstVisibleItemIndex != 0) end - else { - val variable = firstVisibleItemScrollOffset.coerceAtLeast(0).toFloat() - val duration = 256f - val increase = abs(start - end) * (variable / duration) - if (start < end) (start + increase).coerceIn(start, end) - else (start - increase).coerceIn(end, start) - } - -@ExperimentalPagerApi +@OptIn(ExperimentalPagerApi::class) fun Modifier.pagerAnimate(pagerScope: PagerScope, page: Int): Modifier { return graphicsLayer { // Calculate the absolute offset for the current page from the @@ -73,9 +48,3 @@ fun Modifier.roundClick(onClick: () -> Unit = {}) = this fun Modifier.paddingFixedHorizontal(top: Dp = 0.dp, bottom: Dp = 0.dp) = this .padding(horizontal = 10.dp) .padding(top = top, bottom = bottom) - -fun Context.findActivity(): Activity? = when (this) { - is Activity -> this - is ContextWrapper -> baseContext.findActivity() - else -> null -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/extension/StateFlowExt.kt b/app/src/main/java/me/ash/reader/ui/extension/StateFlowExt.kt new file mode 100644 index 0000000..a67aa95 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/extension/StateFlowExt.kt @@ -0,0 +1,12 @@ +package me.ash.reader.ui.extension + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import kotlinx.coroutines.flow.StateFlow +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +@Composable +fun StateFlow.collectAsStateValue( + context: CoroutineContext = EmptyCoroutineContext +): T = collectAsState(context).value diff --git a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt index f971bff..696620d 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt @@ -1,36 +1,33 @@ package me.ash.reader.ui.page.common -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.animation.* +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController import com.google.accompanist.insets.ProvideWindowInsets import com.google.accompanist.insets.navigationBarsHeight import com.google.accompanist.insets.statusBarsPadding -import com.google.accompanist.pager.ExperimentalPagerApi +import com.google.accompanist.navigation.animation.AnimatedNavHost +import com.google.accompanist.navigation.animation.composable +import com.google.accompanist.navigation.animation.rememberAnimatedNavController import com.google.accompanist.systemuicontroller.rememberSystemUiController import me.ash.reader.ui.page.home.HomePage import me.ash.reader.ui.page.settings.SettingsPage import me.ash.reader.ui.theme.AppTheme -@ExperimentalAnimationApi -@ExperimentalMaterial3Api -@ExperimentalPagerApi -@ExperimentalFoundationApi +@OptIn(ExperimentalAnimationApi::class) @Composable fun HomeEntry() { - val navController = rememberNavController() + val navController = rememberAnimatedNavController() AppTheme { ProvideWindowInsets { @@ -45,15 +42,91 @@ fun HomeEntry() { .weight(1f) .statusBarsPadding() ) { - NavHost( + AnimatedNavHost( modifier = Modifier.background(MaterialTheme.colorScheme.surface), navController = navController, startDestination = RouteName.HOME, ) { - composable(route = RouteName.HOME) { + composable( + route = RouteName.HOME, + enterTransition = { + slideInHorizontally( + animationSpec = spring( + stiffness = Spring.StiffnessLow, + dampingRatio = Spring.DampingRatioNoBouncy + ), + initialOffsetX = { -it } + ) + fadeIn(animationSpec = tween(220, delayMillis = 90)) + }, + exitTransition = { + slideOutHorizontally( + animationSpec = spring( + stiffness = Spring.StiffnessLow, + dampingRatio = Spring.DampingRatioNoBouncy + ), + targetOffsetX = { it } + ) + fadeOut(animationSpec = tween(220, delayMillis = 90)) + }, + popEnterTransition = { + slideInHorizontally( + animationSpec = spring( + stiffness = Spring.StiffnessLow, + dampingRatio = Spring.DampingRatioNoBouncy + ), + initialOffsetX = { -it } + ) + fadeIn(animationSpec = tween(220, delayMillis = 90)) + }, + popExitTransition = { + slideOutHorizontally( + animationSpec = spring( + stiffness = Spring.StiffnessLow, + dampingRatio = Spring.DampingRatioNoBouncy + ), + targetOffsetX = { it } + ) + fadeOut(animationSpec = tween(220, delayMillis = 90)) + }, + ) { HomePage(navController) } - composable(route = RouteName.SETTINGS) { + composable( + route = RouteName.SETTINGS, + enterTransition = { + slideInHorizontally( + animationSpec = spring( + stiffness = Spring.StiffnessLow, + dampingRatio = Spring.DampingRatioNoBouncy + ), + initialOffsetX = { -it } + ) + fadeIn(animationSpec = tween(220, delayMillis = 90)) + }, + exitTransition = { + slideOutHorizontally( + animationSpec = spring( + stiffness = Spring.StiffnessLow, + dampingRatio = Spring.DampingRatioNoBouncy + ), + targetOffsetX = { -it } + ) + fadeOut(animationSpec = tween(220, delayMillis = 90)) + }, + popEnterTransition = { + slideInHorizontally( + animationSpec = spring( + stiffness = Spring.StiffnessLow, + dampingRatio = Spring.DampingRatioNoBouncy + ), + initialOffsetX = { -it } + ) + fadeIn(animationSpec = tween(220, delayMillis = 90)) + }, + popExitTransition = { + slideOutHorizontally( + animationSpec = spring( + stiffness = Spring.StiffnessLow, + dampingRatio = Spring.DampingRatioNoBouncy + ), + targetOffsetX = { -it } + ) + fadeOut(animationSpec = tween(220, delayMillis = 90)) + }, + ) { SettingsPage(navController) } } diff --git a/app/src/main/java/me/ash/reader/ui/widget/AppNavigationBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/HomeBottomNavBar.kt similarity index 98% rename from app/src/main/java/me/ash/reader/ui/widget/AppNavigationBar.kt rename to app/src/main/java/me/ash/reader/ui/page/home/HomeBottomNavBar.kt index cfa1562..e7c1062 100644 --- a/app/src/main/java/me/ash/reader/ui/widget/AppNavigationBar.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/HomeBottomNavBar.kt @@ -1,4 +1,4 @@ -package me.ash.reader.ui.widget +package me.ash.reader.ui.page.home import android.view.HapticFeedbackConstants import androidx.compose.animation.AnimatedVisibility @@ -36,11 +36,12 @@ import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.PagerState import me.ash.reader.data.constant.Filter import me.ash.reader.data.constant.NavigationBarItem +import me.ash.reader.ui.widget.CanBeDisabledIconButton import kotlin.math.absoluteValue -@ExperimentalPagerApi +@OptIn(ExperimentalPagerApi::class) @Composable -fun AppNavigationBar( +fun HomeBottomNavBar( modifier: Modifier = Modifier, pagerState: PagerState, filter: Filter, 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 9b096f3..ec1ec76 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 @@ -2,14 +2,10 @@ package me.ash.reader.ui.page.home import android.util.Log import androidx.activity.compose.BackHandler -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -18,24 +14,19 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import com.google.accompanist.pager.ExperimentalPagerApi -import com.google.accompanist.pager.HorizontalPager import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch 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.read.ReadPage import me.ash.reader.ui.page.home.read.ReadViewAction import me.ash.reader.ui.page.home.read.ReadViewModel -import me.ash.reader.ui.util.collectAsStateValue -import me.ash.reader.ui.util.findActivity -import me.ash.reader.ui.util.pagerAnimate -import me.ash.reader.ui.widget.AppNavigationBar +import me.ash.reader.ui.widget.ViewPager -@ExperimentalAnimationApi -@ExperimentalMaterial3Api -@ExperimentalPagerApi -@ExperimentalFoundationApi +@OptIn(ExperimentalPagerApi::class) @Composable fun HomePage( navController: NavHostController, @@ -48,7 +39,7 @@ fun HomePage( val readState = readViewModel.viewState.collectAsStateValue() val scope = rememberCoroutineScope() - DisposableEffect(Unit) { + LaunchedEffect(Unit) { context.findActivity()?.let { activity -> activity.intent?.let { intent -> intent.extras?.get(Symbol.EXTRA_ARTICLE_ID)?.let { @@ -69,11 +60,9 @@ fun HomePage( ) } } - intent.extras?.clear() + intent.extras?.remove(Symbol.EXTRA_ARTICLE_ID) } } - - onDispose { } } BackHandler(true) { @@ -103,117 +92,78 @@ fun HomePage( } } -// val items = listOf( -// Color.Red, -// Color.Blue, -// Color.Green, -// ) - Column { -// CustomPager( -// items = items, -// modifier = Modifier -// .fillMaxWidth() -// .height(256.dp), -// itemFraction = .75f, -// overshootFraction = .75f, -// initialIndex = 3, -// itemSpacing = 16.dp, -// ) { -// items.forEachIndexed { index, item -> -// if (index % 2 == 0) { -// Box( -// modifier = Modifier -// .fillMaxSize() -// .background(item), -// contentAlignment = Alignment.Center -// ) { -// Text( -// text = item.toString(), -// modifier = Modifier.padding(all = 16.dp), -//// style = MaterialTheme.typography.h6, -// ) -// } -// } else { -// Image( -// modifier = Modifier.fillMaxSize(), -// painter = painterResource(id = R.drawable.ic_launcher_foreground), -// contentDescription = null, -// ) -// } -// } -// } - - HorizontalPager( - count = 3, + ViewPager( + modifier = Modifier.weight(1f), state = viewState.pagerState, - modifier = Modifier.weight(1f) - ) { page -> - when (page) { - 0 -> FeedPage( - navController = navController, - modifier = Modifier.pagerAnimate(this, page), - filter = filterState.filter, - groupAndFeedOnClick = { currentGroup, currentFeed -> - viewModel.dispatch( - HomeViewAction.ChangeFilter( - filterState.copy( - group = currentGroup, - feed = currentFeed, + composableList = listOf( + { + FeedPage( + 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, + viewModel.dispatch( + HomeViewAction.ScrollToPage( + scope = scope, + targetPage = 1, + ) ) - ) - }, - ) - 1 -> ArticlePage( - navController = navController, - modifier = Modifier.pagerAnimate(this, page), - BackOnClick = { - viewModel.dispatch( - HomeViewAction.ScrollToPage( - scope = scope, - targetPage = 0, + }, + ) + }, + { + ArticlePage( + navController = navController, + BackOnClick = { + viewModel.dispatch( + HomeViewAction.ScrollToPage( + scope = scope, + targetPage = 0, + ) ) - ) - }, - articleOnClick = { - readViewModel.dispatch(ReadViewAction.ScrollToItem(0)) - readViewModel.dispatch(ReadViewAction.InitData(it)) - if (it.feed.isFullContent) readViewModel.dispatch(ReadViewAction.RenderFullContent) - else readViewModel.dispatch(ReadViewAction.RenderDescriptionContent) - readViewModel.dispatch(ReadViewAction.RenderDescriptionContent) - viewModel.dispatch( - HomeViewAction.ScrollToPage( - scope = scope, - targetPage = 2, + }, + articleOnClick = { + readViewModel.dispatch(ReadViewAction.ScrollToItem(0)) + readViewModel.dispatch(ReadViewAction.InitData(it)) + if (it.feed.isFullContent) readViewModel.dispatch(ReadViewAction.RenderFullContent) + else readViewModel.dispatch(ReadViewAction.RenderDescriptionContent) + readViewModel.dispatch(ReadViewAction.RenderDescriptionContent) + viewModel.dispatch( + HomeViewAction.ScrollToPage( + scope = scope, + targetPage = 2, + ) ) - ) - }, - ) - 2 -> ReadPage( - navController = navController, - modifier = Modifier.pagerAnimate(this, page), - btnBackOnClickListener = { - viewModel.dispatch( - HomeViewAction.ScrollToPage( - scope = scope, - targetPage = 1, - callback = { - readViewModel.dispatch(ReadViewAction.ClearArticle) - } + }, + ) + }, + { + ReadPage( + navController = navController, + btnBackOnClickListener = { + viewModel.dispatch( + HomeViewAction.ScrollToPage( + scope = scope, + targetPage = 1, + callback = { + readViewModel.dispatch(ReadViewAction.ClearArticle) + } + ) ) - ) - }, - ) - } - } - AppNavigationBar( + }, + ) + }, + ), + ) + HomeBottomNavBar( modifier = Modifier .height(60.dp) .fillMaxWidth(), diff --git a/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt index edcad5d..7a71286 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt @@ -1,8 +1,5 @@ package me.ash.reader.ui.page.home -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.accompanist.pager.ExperimentalPagerApi @@ -15,16 +12,13 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +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.data.repository.RssRepository -import me.ash.reader.data.constant.Filter import javax.inject.Inject -@ExperimentalAnimationApi -@ExperimentalMaterial3Api -@ExperimentalPagerApi -@ExperimentalFoundationApi +@OptIn(ExperimentalPagerApi::class) @HiltViewModel class HomeViewModel @Inject constructor( private val rssRepository: RssRepository, @@ -36,6 +30,8 @@ class HomeViewModel @Inject constructor( private val _filterState = MutableStateFlow(FilterState()) val filterState = _filterState.asStateFlow() + val syncState = RssRepository.syncState + fun dispatch(action: HomeViewAction) { when (action) { is HomeViewAction.Sync -> sync(action.callback) @@ -79,8 +75,8 @@ data class FilterState( val filter: Filter = Filter.All, ) -@ExperimentalPagerApi -data class HomeViewState( +@OptIn(ExperimentalPagerApi::class) +data class HomeViewState constructor( val pagerState: PagerState = PagerState(1), ) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/article/ArticleDateHeader.kt b/app/src/main/java/me/ash/reader/ui/page/home/article/ArticleDateHeader.kt new file mode 100644 index 0000000..ef7c618 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/article/ArticleDateHeader.kt @@ -0,0 +1,37 @@ +package me.ash.reader.ui.page.home.article + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun ArticleDateHeader( + date: String, + isDisplayIcon: Boolean +) { + Row( + modifier = Modifier + .height(28.dp) + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surface), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = date, + fontSize = 13.sp, + color = MaterialTheme.colorScheme.secondary, + modifier = Modifier.padding(start = (if (isDisplayIcon) 52 else 20).dp), + fontWeight = FontWeight.SemiBold, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/article/ArticleItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/article/ArticleItem.kt new file mode 100644 index 0000000..48f055d --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/article/ArticleItem.kt @@ -0,0 +1,143 @@ +package me.ash.reader.ui.page.home.article + +import android.graphics.BitmapFactory +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +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.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.BitmapPainter +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.dp +import androidx.compose.ui.unit.sp +import me.ash.reader.DateTimeExt +import me.ash.reader.DateTimeExt.toString +import me.ash.reader.R +import me.ash.reader.data.article.ArticleWithFeed +import me.ash.reader.ui.extension.paddingFixedHorizontal +import me.ash.reader.ui.extension.roundClick + +@Composable +fun ArticleItem( + modifier: Modifier = Modifier, + articleWithFeed: ArticleWithFeed? = null, + isStarredFilter: Boolean, + index: Int, + articleOnClick: (ArticleWithFeed) -> Unit, +) { + if (articleWithFeed == null) return + Column( + modifier = modifier + .paddingFixedHorizontal( + top = if (index == 0) 8.dp else 0.dp, + bottom = 8.dp + ) + .roundClick { + articleOnClick(articleWithFeed) + } + .alpha( + if (isStarredFilter || articleWithFeed.article.isUnread) { + 1f + } else { + 0.7f + } + ) + ) { + Column(modifier = modifier.padding(10.dp)) { + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + modifier = Modifier.padding(start = 32.dp), + text = articleWithFeed.feed.name, + fontSize = 13.sp, + fontWeight = FontWeight.Medium, + color = if (isStarredFilter || articleWithFeed.article.isUnread) { + MaterialTheme.colorScheme.tertiary + } else { + MaterialTheme.colorScheme.outline + }, + ) + Text( + text = articleWithFeed.article.date.toString( + DateTimeExt.HH_MM + ), + fontSize = 13.sp, + color = MaterialTheme.colorScheme.outline + ) + } + Spacer(modifier = modifier.height(1.dp)) + Row { + if (true) { + Box( + modifier = Modifier + .padding(top = 3.dp) + .size(24.dp) + .border( + 2.dp, + MaterialTheme.colorScheme.inverseOnSurface, + RoundedCornerShape(4.dp) + ), + ) { + if (articleWithFeed.feed.icon == null) { + Icon( + painter = painterResource(id = R.drawable.default_folder), + contentDescription = "icon", + modifier = modifier + .fillMaxSize() + .padding(2.dp), + tint = MaterialTheme.colorScheme.onPrimaryContainer, + ) + } else { + Image( + painter = BitmapPainter( + BitmapFactory.decodeByteArray( + articleWithFeed.feed.icon, + 0, + articleWithFeed.feed.icon!!.size + ).asImageBitmap() + ), + contentDescription = "icon", + modifier = modifier + .fillMaxSize() + .padding(2.dp), + ) + } + } + Spacer(modifier = Modifier.width(8.dp)) + } + Column { + Text( + text = articleWithFeed.article.title, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + color = if (isStarredFilter || articleWithFeed.article.isUnread) { + MaterialTheme.colorScheme.onPrimaryContainer + } else { + MaterialTheme.colorScheme.outline + }, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = modifier.height(1.dp)) + Text( + text = articleWithFeed.article.shortDescription, + fontSize = 18.sp, + color = MaterialTheme.colorScheme.outline, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/article/ArticlePage.kt b/app/src/main/java/me/ash/reader/ui/page/home/article/ArticlePage.kt index aef640e..428a50a 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/article/ArticlePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/article/ArticlePage.kt @@ -1,65 +1,41 @@ package me.ash.reader.ui.page.home.article -import android.graphics.BitmapFactory import android.util.Log import android.widget.Toast -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.ArrowBackIosNew -import androidx.compose.material.icons.rounded.DoneAll -import androidx.compose.material.icons.rounded.Search -import androidx.compose.material3.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.platform.LocalContext -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.dp -import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import androidx.paging.compose.collectAsLazyPagingItems -import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.flow.collect import me.ash.reader.DateTimeExt import me.ash.reader.DateTimeExt.toString -import me.ash.reader.R import me.ash.reader.data.article.ArticleWithFeed import me.ash.reader.data.constant.Filter -import me.ash.reader.data.repository.RssRepository +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.util.collectAsStateValue -import me.ash.reader.ui.util.paddingFixedHorizontal -import me.ash.reader.ui.util.roundClick import me.ash.reader.ui.widget.AnimateLazyColumn import me.ash.reader.ui.widget.TopTitleBox +@OptIn(ExperimentalFoundationApi::class) @DelicateCoroutinesApi -@ExperimentalAnimationApi -@ExperimentalMaterial3Api -@ExperimentalPagerApi -@ExperimentalFoundationApi @Composable fun ArticlePage( navController: NavHostController, - modifier: Modifier, + modifier: Modifier = Modifier, homeViewModel: HomeViewModel = hiltViewModel(), viewModel: ArticleViewModel = hiltViewModel(), BackOnClick: () -> Unit, @@ -70,7 +46,7 @@ fun ArticlePage( val filterState = homeViewModel.filterState.collectAsStateValue() val pagingItems = viewState.pagingData?.collectAsLazyPagingItems() val refreshState = rememberSwipeRefreshState(isRefreshing = viewState.isRefreshing) - val syncState = RssRepository.syncState.collectAsStateValue() + val syncState = homeViewModel.syncState.collectAsStateValue() LaunchedEffect(homeViewModel.filterState) { homeViewModel.filterState.collect { state -> @@ -115,39 +91,15 @@ fun ArticlePage( viewModel.dispatch(ArticleViewAction.ScrollToItem(0)) } Column { - SmallTopAppBar( - title = {}, - navigationIcon = { - IconButton(BackOnClick) { - Icon( - imageVector = Icons.Rounded.ArrowBackIosNew, - contentDescription = "Back", - tint = MaterialTheme.colorScheme.primary - ) - } + ArticlePageTopBar( + backOnClick = BackOnClick, + readAllOnClick = { + viewModel.dispatch(ArticleViewAction.PeekSyncWork) + Toast.makeText(context, viewState.syncWorkInfo, Toast.LENGTH_LONG) + .show() }, - actions = { - IconButton(onClick = { - viewModel.dispatch(ArticleViewAction.PeekSyncWork) - Toast.makeText(context, viewState.syncWorkInfo, Toast.LENGTH_LONG) - .show() - }) { - Icon( - imageVector = Icons.Rounded.DoneAll, - contentDescription = "Done All", - tint = MaterialTheme.colorScheme.primary - ) - } - IconButton(onClick = { - if (syncState.isSyncing) return@IconButton - homeViewModel.dispatch(HomeViewAction.Sync()) - }) { - Icon( - imageVector = Icons.Rounded.Search, - contentDescription = "Search", - tint = MaterialTheme.colorScheme.primary - ) - } + searchOnClick = { + }, ) @@ -190,140 +142,4 @@ fun ArticlePage( } } } -} - -@Composable -private fun ArticleItem( - modifier: Modifier = Modifier, - articleWithFeed: ArticleWithFeed?, - isStarredFilter: Boolean, - index: Int, - articleOnClick: (ArticleWithFeed) -> Unit, -) { - if (articleWithFeed == null) return - Column( - modifier = modifier - .paddingFixedHorizontal( - top = if (index == 0) 8.dp else 0.dp, - bottom = 8.dp - ) - .roundClick { - articleOnClick(articleWithFeed) - } - .alpha( - if (isStarredFilter || articleWithFeed.article.isUnread) { - 1f - } else { - 0.7f - } - ) - ) { - Column(modifier = modifier.padding(10.dp)) { - Row( - modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - modifier = Modifier.padding(start = 32.dp), - text = articleWithFeed.feed.name, - fontSize = 13.sp, - fontWeight = FontWeight.Medium, - color = if (isStarredFilter || articleWithFeed.article.isUnread) { - MaterialTheme.colorScheme.tertiary - } else { - MaterialTheme.colorScheme.outline - }, - ) - Text( - text = articleWithFeed.article.date.toString( - DateTimeExt.HH_MM - ), - fontSize = 13.sp, - color = MaterialTheme.colorScheme.outline - ) - } - Spacer(modifier = modifier.height(1.dp)) - Row { - if (true) { - Box( - modifier = Modifier - .padding(top = 3.dp) - .size(24.dp) - .border( - 2.dp, - MaterialTheme.colorScheme.inverseOnSurface, - RoundedCornerShape(4.dp) - ), - ) { - if (articleWithFeed.feed.icon == null) { - Icon( - painter = painterResource(id = R.drawable.default_folder), - contentDescription = "icon", - modifier = modifier - .fillMaxSize() - .padding(2.dp), - tint = MaterialTheme.colorScheme.onPrimaryContainer, - ) - } else { - Image( - painter = BitmapPainter( - BitmapFactory.decodeByteArray( - articleWithFeed.feed.icon, - 0, - articleWithFeed.feed.icon!!.size - ).asImageBitmap() - ), - contentDescription = "icon", - modifier = modifier - .fillMaxSize() - .padding(2.dp), - ) - } - } - Spacer(modifier = Modifier.width(8.dp)) - } - Column { - Text( - text = articleWithFeed.article.title, - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - color = if (isStarredFilter || articleWithFeed.article.isUnread) { - MaterialTheme.colorScheme.onPrimaryContainer - } else { - MaterialTheme.colorScheme.outline - }, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - Spacer(modifier = modifier.height(1.dp)) - Text( - text = articleWithFeed.article.shortDescription, - fontSize = 18.sp, - color = MaterialTheme.colorScheme.outline, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } - } - } - } -} - -@Composable -private fun ArticleDateHeader(date: String, isDisplayIcon: Boolean) { - Row( - modifier = Modifier - .height(28.dp) - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surface), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = date, - fontSize = 13.sp, - color = MaterialTheme.colorScheme.secondary, - modifier = Modifier.padding(start = (if (isDisplayIcon) 52 else 20).dp), - fontWeight = FontWeight.SemiBold, - ) - } } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/article/ArticlePageTopBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/article/ArticlePageTopBar.kt new file mode 100644 index 0000000..851fed7 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/article/ArticlePageTopBar.kt @@ -0,0 +1,47 @@ +package me.ash.reader.ui.page.home.article + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ArrowBackIosNew +import androidx.compose.material.icons.rounded.DoneAll +import androidx.compose.material.icons.rounded.Search +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 + +@Composable +fun ArticlePageTopBar( + backOnClick: () -> Unit = {}, + readAllOnClick: () -> Unit = {}, + searchOnClick: () -> Unit = {}, +) { + SmallTopAppBar( + title = {}, + navigationIcon = { + IconButton(onClick = backOnClick) { + Icon( + imageVector = Icons.Rounded.ArrowBackIosNew, + contentDescription = "Back", + tint = MaterialTheme.colorScheme.primary + ) + } + }, + actions = { + IconButton(onClick = readAllOnClick) { + Icon( + imageVector = Icons.Rounded.DoneAll, + contentDescription = "Done All", + tint = MaterialTheme.colorScheme.primary + ) + } + IconButton(onClick = searchOnClick) { + Icon( + imageVector = Icons.Rounded.Search, + contentDescription = "Search", + tint = MaterialTheme.colorScheme.primary + ) + } + }, + ) +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/widget/BarButton.kt b/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedBar.kt similarity index 96% rename from app/src/main/java/me/ash/reader/ui/widget/BarButton.kt rename to app/src/main/java/me/ash/reader/ui/page/home/feed/FeedBar.kt index 712de3f..74bd908 100644 --- a/app/src/main/java/me/ash/reader/ui/widget/BarButton.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedBar.kt @@ -1,7 +1,6 @@ -package me.ash.reader.ui.widget +package me.ash.reader.ui.page.home.feed import android.graphics.BitmapFactory -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -25,12 +24,12 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import me.ash.reader.R -import me.ash.reader.ui.util.paddingFixedHorizontal -import me.ash.reader.ui.util.roundClick +import me.ash.reader.ui.extension.paddingFixedHorizontal +import me.ash.reader.ui.extension.roundClick +import me.ash.reader.ui.widget.AnimatedText -@ExperimentalAnimationApi @Composable -fun BarButton( +fun FeedBar( barButtonType: BarButtonType, iconOnClickListener: () -> Unit = {}, onClickListener: () -> Unit = {}, 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 new file mode 100644 index 0000000..79a8088 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedList.kt @@ -0,0 +1,51 @@ +package me.ash.reader.ui.page.home.feed + +import android.graphics.BitmapFactory +import android.util.Log +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 -> + Log.i("RLog", "FeedList: ${feed.icon}") + FeedBar( + barButtonType = ItemType( +// icon = feed.icon ?: "", + icon = if (feed.icon == null) { + null + } else { + BitmapPainter( + BitmapFactory.decodeByteArray( + feed.icon, + 0, + feed.icon!!.size + ).asImageBitmap() + ) + }, + content = feed.name, + important = feed.important ?: 0 + ) + ) { + onClick(feed) + } + } + } + } +} \ No newline at end of file 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 index 38395d2..02f3856 100644 --- 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 @@ -1,72 +1,41 @@ package me.ash.reader.ui.page.home.feed -import android.graphics.BitmapFactory -import android.util.Log -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.animation.* -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.material.TextField -import androidx.compose.material.TextFieldDefaults import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Settings -import androidx.compose.material.icons.rounded.* -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.ExperimentalComposeUiApi +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.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.graphics.painter.BitmapPainter -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController -import com.google.accompanist.pager.ExperimentalPagerApi 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.data.group.GroupWithFeed -import me.ash.reader.data.repository.RssRepository -import me.ash.reader.ui.page.common.RouteName +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.util.collectAsStateValue -import me.ash.reader.ui.widget.* -import java.io.InputStream +import me.ash.reader.ui.page.home.feed.subscribe.SubscribeDialog +import me.ash.reader.ui.widget.TopTitleBox - -@ExperimentalComposeUiApi -@ExperimentalAnimationApi -@ExperimentalMaterial3Api -@ExperimentalPagerApi -@ExperimentalFoundationApi @Composable fun FeedPage( navController: NavHostController, - modifier: Modifier, + modifier: Modifier = Modifier, viewModel: FeedViewModel = hiltViewModel(), homeViewModel: HomeViewModel = hiltViewModel(), filter: Filter, groupAndFeedOnClick: (currentGroup: Group?, currentFeed: Feed?) -> Unit = { _, _ -> }, ) { val viewState = viewModel.viewState.collectAsStateValue() - val syncState = RssRepository.syncState.collectAsStateValue() - var addFeedDialogVisible by remember { mutableStateOf(false) } + val syncState = homeViewModel.syncState.collectAsStateValue() LaunchedEffect(homeViewModel.filterState) { homeViewModel.filterState.collect { state -> @@ -87,11 +56,24 @@ fun FeedPage( } Box( - modifier.fillMaxSize() + modifier = modifier.fillMaxSize() ) { - AddFeedDialog( - visible = addFeedDialogVisible, - hiddenFunction = { addFeedDialogVisible = false }, + SubscribeDialog( + visible = viewState.subscribeDialogVisible, + hiddenFunction = { + viewModel.dispatch(FeedViewAction.ChangeSubscribeDialogVisible(false)) + }, + inputContent = viewState.subscribeDialogFeedLink, + onValueChange = { + viewModel.dispatch( + FeedViewAction.InputSubscribeFeedLink(it) + ) + }, + onKeyboardAction = { + viewModel.dispatch( + FeedViewAction.ChangeSubscribeDialogVisible(false) + ) + }, openInputStreamCallback = { viewModel.dispatch(FeedViewAction.AddFromFile(it)) }, @@ -113,42 +95,14 @@ fun FeedPage( viewModel.dispatch(FeedViewAction.ScrollToItem(0)) } Column { - SmallTopAppBar( - title = {}, - navigationIcon = { - IconButton(onClick = { - navController.navigate(route = RouteName.SETTINGS) - }) { - Icon( - modifier = Modifier.size(22.dp), - imageVector = Icons.Outlined.Settings, - contentDescription = "Back", - tint = MaterialTheme.colorScheme.primary - ) - } + FeedPageTopBar( + navController = navController, + isSyncing = syncState.isSyncing, + syncOnClick = { + homeViewModel.dispatch(HomeViewAction.Sync()) }, - actions = { - IconButton(onClick = { - if (syncState.isSyncing) return@IconButton - homeViewModel.dispatch(HomeViewAction.Sync()) - }) { - Icon( - modifier = Modifier.size(26.dp), - imageVector = Icons.Rounded.Refresh, - contentDescription = "Sync", - tint = MaterialTheme.colorScheme.primary, - ) - } - IconButton(onClick = { - addFeedDialogVisible = true - }) { - Icon( - modifier = Modifier.size(26.dp), - imageVector = Icons.Rounded.Add, - contentDescription = "Subscribe", - tint = MaterialTheme.colorScheme.primary, - ) - } + subscribeOnClick = { + viewModel.dispatch(FeedViewAction.ChangeSubscribeDialogVisible(true)) }, ) LazyColumn( @@ -157,7 +111,7 @@ fun FeedPage( ) { item { Spacer(modifier = Modifier.height(114.dp)) - BarButton( + FeedBar( barButtonType = ButtonType( content = filter.title, important = viewState.filterImportant @@ -168,13 +122,13 @@ fun FeedPage( } item { Spacer(modifier = Modifier.height(10.dp)) - BarButton( + FeedBar( barButtonType = FirstExpandType( content = "Feeds", icon = Icons.Rounded.ExpandMore ) ) { - viewModel.dispatch(FeedViewAction.ChangeGroupVisible) + viewModel.dispatch(FeedViewAction.ChangeGroupVisible(!viewState.groupsVisible)) } } itemsIndexed(viewState.groupWithFeedList) { index, groupWithFeed -> @@ -193,181 +147,3 @@ fun FeedPage( } } } - -@ExperimentalComposeUiApi -@Composable -private fun AddFeedDialog( - visible: Boolean, - hiddenFunction: () -> Unit, - openInputStreamCallback: (InputStream) -> Unit, -) { - val context = LocalContext.current - var inputString by remember { mutableStateOf("") } - val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { - it?.let { uri -> - context.contentResolver.openInputStream(uri)?.let { inputStream -> - openInputStreamCallback(inputStream) - } - } - } - val focusRequester = remember { FocusRequester() } - val localFocusManager = LocalFocusManager.current - val localSoftwareKeyboardController = LocalSoftwareKeyboardController.current - - Dialog( - visible = visible, - onDismissRequest = hiddenFunction, - icon = { - Icon( - imageVector = Icons.Rounded.RssFeed, - contentDescription = "Subscribe", - ) - }, - title = { Text("订阅") }, - text = { - Spacer(modifier = Modifier.height(10.dp)) - TextField( - modifier = Modifier - .focusRequester(focusRequester) - .onFocusChanged { if(it.isFocused) localSoftwareKeyboardController?.hide() } - .focusable(), - colors = TextFieldDefaults.textFieldColors( - backgroundColor = Color.Transparent, - cursorColor = MaterialTheme.colorScheme.onSurface, - textColor = MaterialTheme.colorScheme.onSurface, - focusedIndicatorColor = MaterialTheme.colorScheme.primary, - ), - value = inputString, - onValueChange = { - inputString = it - }, - placeholder = { - Text( - text = "订阅源或站点链接", - color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f) - ) - }, - singleLine = true, - trailingIcon = { - IconButton(onClick = {}) { - Icon( - imageVector = Icons.Rounded.ContentPaste, - contentDescription = "Paste", - tint = MaterialTheme.colorScheme.primary - ) - } - }, - keyboardActions = KeyboardActions( - onDone = { - hiddenFunction() - } - ) - ) - Spacer(modifier = Modifier.height(10.dp)) - }, - confirmButton = { - TextButton( - enabled = inputString.isNotEmpty(), - onClick = { - hiddenFunction() - } - ) { - Text( - text = "搜索", - color = if (inputString.isNotEmpty()) { - Color.Unspecified - } else { - MaterialTheme.colorScheme.outline.copy(alpha = 0.7f) - } - ) - } - }, - dismissButton = { - TextButton( - onClick = { - launcher.launch("*/*") - hiddenFunction() - } - ) { - Text("导入OPML文件") - } - }, - ) -} - -@ExperimentalAnimationApi -@Composable -private 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) { - BarButton( - barButtonType = SecondExpandType( - content = groupWithFeed.group.name, - icon = Icons.Rounded.ExpandMore, - important = groupWithFeed.group.important ?: 0, - ), - iconOnClickListener = expandOnClick - ) { - groupAndFeedOnClick(groupWithFeed.group, null) - } - FeedList( - visible = feedVisible, - feeds = groupWithFeed.feeds, - onClick = { currentFeed -> - groupAndFeedOnClick(null, currentFeed) - } - ) - } - } -} - -@ExperimentalAnimationApi -@Composable -private 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 -> - Log.i("RLog", "FeedList: ${feed.icon}") - BarButton( - barButtonType = ItemType( -// icon = feed.icon ?: "", - icon = if (feed.icon == null) { - null - } else { - BitmapPainter( - BitmapFactory.decodeByteArray( - feed.icon, - 0, - feed.icon!!.size - ).asImageBitmap() - ) - }, - content = feed.name, - important = feed.important ?: 0 - ) - ) { - onClick(feed) - } - } - } - } -} \ No newline at end of file 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 new file mode 100644 index 0000000..7594e72 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedPageTopBar.kt @@ -0,0 +1,61 @@ +package me.ash.reader.ui.page.home.feed + +import androidx.compose.foundation.layout.size +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.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import me.ash.reader.ui.page.common.RouteName + +@Composable +fun FeedPageTopBar( + navController: NavHostController, + isSyncing: Boolean = false, + syncOnClick: () -> Unit = {}, + subscribeOnClick: () -> Unit = {}, +) { + SmallTopAppBar( + title = {}, + navigationIcon = { + IconButton(onClick = { + navController.navigate(route = RouteName.SETTINGS) + }) { + Icon( + modifier = Modifier.size(22.dp), + imageVector = Icons.Outlined.Settings, + contentDescription = "Back", + tint = MaterialTheme.colorScheme.primary + ) + } + }, + actions = { + IconButton(onClick = { + if (isSyncing) return@IconButton + syncOnClick() + }) { + Icon( + modifier = Modifier.size(26.dp), + imageVector = Icons.Rounded.Refresh, + contentDescription = "Sync", + tint = MaterialTheme.colorScheme.primary, + ) + } + IconButton(onClick = 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/FeedViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feed/FeedViewModel.kt index 945e810..49a89ed 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/feed/FeedViewModel.kt @@ -31,8 +31,30 @@ class FeedViewModel @Inject constructor( 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.ChangeGroupVisible -> changeGroupVisible(action.visible) is FeedViewAction.ScrollToItem -> scrollToItem(action.index) + is FeedViewAction.ChangeSubscribeDialogVisible -> changeAddFeedDialogVisible(action.visible) + is FeedViewAction.InputSubscribeFeedLink -> inputSubscribeFeedLink(action.subscribeFeedLink) + } + } + + private fun inputSubscribeFeedLink(subscribeFeedLink: String) { + viewModelScope.launch { + _viewState.update { + it.copy( + subscribeDialogFeedLink = subscribeFeedLink + ) + } + } + } + + private fun changeAddFeedDialogVisible(visible: Boolean) { + viewModelScope.launch { + _viewState.update { + it.copy( + subscribeDialogVisible = visible + ) + } } } @@ -121,10 +143,10 @@ class FeedViewModel @Inject constructor( } } - private fun changeGroupVisible() { + private fun changeGroupVisible(visible: Boolean) { _viewState.update { it.copy( - groupsVisible = !_viewState.value.groupsVisible + groupsVisible = visible ) } } @@ -143,6 +165,8 @@ data class FeedViewState( val feedsVisible: List = emptyList(), val listState: LazyListState = LazyListState(), val groupsVisible: Boolean = true, + var subscribeDialogVisible: Boolean = false, + var subscribeDialogFeedLink: String = "", ) sealed class FeedViewAction { @@ -163,8 +187,19 @@ sealed class FeedViewAction { val index: Int ) : FeedViewAction() - object ChangeGroupVisible : FeedViewAction() + data class ChangeGroupVisible( + val visible: Boolean + ) : FeedViewAction() + data class ScrollToItem( val index: Int ) : FeedViewAction() + + data class ChangeSubscribeDialogVisible( + val visible: Boolean + ) : FeedViewAction() + + data class InputSubscribeFeedLink( + val subscribeFeedLink: String + ) : FeedViewAction() } \ 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 new file mode 100644 index 0000000..43156c1 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/feed/GroupList.kt @@ -0,0 +1,48 @@ +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) { + FeedBar( + barButtonType = SecondExpandType( + content = groupWithFeed.group.name, + icon = Icons.Rounded.ExpandMore, + important = groupWithFeed.group.important ?: 0, + ), + iconOnClickListener = expandOnClick + ) { + 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/feed/subscribe/ResultViewPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/ResultViewPage.kt new file mode 100644 index 0000000..c59d903 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/ResultViewPage.kt @@ -0,0 +1,171 @@ +package me.ash.reader.ui.page.home.feed.subscribe + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Article +import androidx.compose.material.icons.outlined.Notifications +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.google.accompanist.flowlayout.FlowRow +import com.google.accompanist.flowlayout.MainAxisAlignment +import me.ash.reader.ui.widget.SelectionChip + +@Composable +fun ResultViewPage() { + Column { + Link() + Spacer(modifier = Modifier.height(26.dp)) + + Preset() + Spacer(modifier = Modifier.height(26.dp)) + + AddToGroup() + Spacer(modifier = Modifier.height(6.dp)) + } +} + +@Composable +private fun Link() { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + SelectionContainer { + Text( + text = "https://material.io/feed.xml", + color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f), + ) + } + } +} + +@Composable +private fun Preset() { + Text( + text = "预设", + color = MaterialTheme.colorScheme.primary, + fontSize = 14.sp, + ) + Spacer(modifier = Modifier.height(10.dp)) + FlowRow( + mainAxisAlignment = MainAxisAlignment.Start, + crossAxisSpacing = 10.dp, + mainAxisSpacing = 10.dp, + ) { + SelectionChip( + selected = true, + selectedIcon = { + Icon( + imageVector = Icons.Outlined.Notifications, + contentDescription = "Check", + modifier = Modifier.size(20.dp) + ) + }, + onClick = { /*TODO*/ }, + ) { + Text( + text = "接收通知", + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp, + ) + } + SelectionChip( + selected = false, + selectedIcon = { + Icon( + imageVector = Icons.Outlined.Article, + contentDescription = "Check", + modifier = Modifier.size(20.dp) + ) + }, + onClick = { /*TODO*/ } + ) { + Text( + text = "全文输出", + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp, + ) + } + } +} + +@Composable +private fun AddToGroup() { + Text( + text = "添加到组", + color = MaterialTheme.colorScheme.primary, + fontSize = 14.sp, + ) + Spacer(modifier = Modifier.height(10.dp)) + FlowRow( + mainAxisAlignment = MainAxisAlignment.Start, + crossAxisSpacing = 10.dp, + mainAxisSpacing = 10.dp, + ) { + SelectionChip( + selected = false, + onClick = { /*TODO*/ }, + ) { + Text( + text = "未分组", + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp, + ) + } + SelectionChip( + selected = true, + onClick = { /*TODO*/ } + ) { + Text( + text = "技术", + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp, + ) + } + SelectionChip( + selected = true, + onClick = { /*TODO*/ } + ) { + Text( + text = "新鲜事", + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp, + ) + } + SelectionChip( + selected = false, + onClick = { /*TODO*/ } + ) { + Text( + text = "游戏", + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp, + ) + } + SelectionChip( + selected = true, + onClick = { /*TODO*/ }, + ) { + BasicTextField( + modifier = Modifier.width(56.dp), + value = "新建分组", + onValueChange = {}, + textStyle = TextStyle( + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f) + ), + singleLine = true, + ) + } + } +} \ No newline at end of file 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/feed/subscribe/SearchViewPage.kt new file mode 100644 index 0000000..42d2f8c --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SearchViewPage.kt @@ -0,0 +1,75 @@ +package me.ash.reader.ui.page.home.feed.subscribe + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ContentPaste +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay + +@Composable +fun SearchViewPage( + inputContent: String = "", + onValueChange: (String) -> Unit = {}, + onKeyboardAction: () -> Unit = {}, +) { + val focusRequester = remember { FocusRequester() } + + LaunchedEffect(Unit) { + delay(100) // ??? + focusRequester.requestFocus() + } + + Spacer(modifier = Modifier.height(10.dp)) + TextField( + modifier = Modifier.focusRequester(focusRequester), + colors = TextFieldDefaults.textFieldColors( + backgroundColor = Color.Transparent, + cursorColor = MaterialTheme.colorScheme.onSurface, + textColor = MaterialTheme.colorScheme.onSurface, + focusedIndicatorColor = MaterialTheme.colorScheme.primary, + ), + value = inputContent, + onValueChange = { + onValueChange(it) + }, + placeholder = { + Text( + text = "订阅源或站点链接", + color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f) + ) + }, + singleLine = true, + trailingIcon = { + IconButton(onClick = { +// focusRequester.requestFocus() + }) { + Icon( + imageVector = Icons.Rounded.ContentPaste, + contentDescription = "Paste", + tint = MaterialTheme.colorScheme.primary + ) + } + }, + keyboardActions = KeyboardActions( + onDone = { + onKeyboardAction() + } + ) + ) + Spacer(modifier = Modifier.height(10.dp)) +} \ No newline at end of file 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/feed/subscribe/SubscribeDialog.kt new file mode 100644 index 0000000..f68c077 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeDialog.kt @@ -0,0 +1,80 @@ +package me.ash.reader.ui.page.home.feed.subscribe + +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.RssFeed +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import me.ash.reader.ui.widget.Dialog +import java.io.InputStream + +@Composable +fun SubscribeDialog( + visible: Boolean, + hiddenFunction: () -> Unit, + inputContent: String = "", + onValueChange: (String) -> Unit = {}, + onKeyboardAction: () -> Unit = {}, + openInputStreamCallback: (InputStream) -> Unit, +) { + val context = LocalContext.current + val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { + it?.let { uri -> + context.contentResolver.openInputStream(uri)?.let { inputStream -> + openInputStreamCallback(inputStream) + } + } + } + + Dialog( + visible = visible, + onDismissRequest = hiddenFunction, + icon = { + Icon( + imageVector = Icons.Rounded.RssFeed, + contentDescription = "Subscribe", + ) + }, + title = { Text("订阅") }, + text = { + SubscribeViewPager( + inputContent = inputContent, + onValueChange = onValueChange, + onKeyboardAction = onKeyboardAction, + ) + }, + confirmButton = { + TextButton( + enabled = inputContent.isNotEmpty(), + onClick = { + hiddenFunction() + } + ) { + Text( + text = "搜索", + color = if (inputContent.isNotEmpty()) { + Color.Unspecified + } else { + MaterialTheme.colorScheme.outline.copy(alpha = 0.7f) + } + ) + } + }, + dismissButton = { + TextButton( + onClick = { + launcher.launch("*/*") + hiddenFunction() + } + ) { + Text("导入OPML文件") + } + }, + ) +} \ No newline at end of file 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/feed/subscribe/SubscribeViewPager.kt new file mode 100644 index 0000000..7e0f314 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/feed/subscribe/SubscribeViewPager.kt @@ -0,0 +1,28 @@ +package me.ash.reader.ui.page.home.feed.subscribe + +import androidx.compose.runtime.Composable +import com.google.accompanist.pager.ExperimentalPagerApi +import me.ash.reader.ui.widget.ViewPager + +@OptIn(ExperimentalPagerApi::class) +@Composable +fun SubscribeViewPager( + inputContent: String = "", + onValueChange: (String) -> Unit = {}, + onKeyboardAction: () -> Unit = {} +) { + ViewPager( + composableList = listOf( + { + SearchViewPage( + inputContent = inputContent, + onValueChange = onValueChange, + onKeyboardAction = onKeyboardAction, + ) + }, + { + ResultViewPage() + } + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/read/Header.kt b/app/src/main/java/me/ash/reader/ui/page/home/read/Header.kt new file mode 100644 index 0000000..bbcdcae --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/read/Header.kt @@ -0,0 +1,67 @@ +package me.ash.reader.ui.page.home.read + +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import me.ash.reader.DateTimeExt +import me.ash.reader.DateTimeExt.toString +import me.ash.reader.data.article.Article +import me.ash.reader.data.feed.Feed +import me.ash.reader.ui.extension.roundClick + +@Composable +fun Header( + context: Context, + article: Article, + feed: Feed +) { + Column( + modifier = Modifier + .fillMaxWidth() + .roundClick { + context.startActivity( + Intent(Intent.ACTION_VIEW, Uri.parse(article.link)) + ) + } + ) { + Column(modifier = Modifier.padding(10.dp)) { + Text( + text = article.date.toString(DateTimeExt.YYYY_MM_DD_HH_MM, true), + fontSize = 12.sp, + color = MaterialTheme.colorScheme.outline, + fontWeight = FontWeight.Medium, + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = article.title, + fontSize = 27.sp, + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Bold, + lineHeight = 34.sp, + ) + Spacer(modifier = Modifier.height(4.dp)) + article.author?.let { + Text( + text = article.author, + fontSize = 12.sp, + color = MaterialTheme.colorScheme.outline, + fontWeight = FontWeight.Medium, + ) + } + Text( + text = feed.name, + fontSize = 12.sp, + color = MaterialTheme.colorScheme.outline, + fontWeight = FontWeight.Medium, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPage.kt index 136cacf..0c5e1eb 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPage.kt @@ -1,47 +1,26 @@ package me.ash.reader.ui.page.home.read -import android.content.Context -import android.content.Intent -import android.net.Uri import androidx.compose.animation.* -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Close -import androidx.compose.material.icons.rounded.MoreHoriz -import androidx.compose.material.icons.rounded.Share -import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp 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 com.google.accompanist.pager.ExperimentalPagerApi import kotlinx.coroutines.flow.collect -import me.ash.reader.DateTimeExt -import me.ash.reader.DateTimeExt.toString -import me.ash.reader.data.article.Article -import me.ash.reader.data.feed.Feed -import me.ash.reader.ui.util.collectAsStateValue -import me.ash.reader.ui.util.paddingFixedHorizontal -import me.ash.reader.ui.util.roundClick +import me.ash.reader.ui.extension.collectAsStateValue +import me.ash.reader.ui.extension.paddingFixedHorizontal import me.ash.reader.ui.widget.WebView - -@ExperimentalMaterial3Api -@ExperimentalPagerApi -@ExperimentalFoundationApi @Composable fun ReadPage( navController: NavHostController, @@ -69,38 +48,7 @@ fun ReadPage( Column( modifier.fillMaxSize() ) { - - SmallTopAppBar( - title = {}, - navigationIcon = { - IconButton(onClick = { btnBackOnClickListener() }) { - Icon( - modifier = Modifier.size(28.dp), - imageVector = Icons.Rounded.Close, - contentDescription = "Back", - tint = MaterialTheme.colorScheme.primary - ) - } - }, - actions = { - IconButton(onClick = { /*TODO*/ }) { - Icon( - modifier = Modifier.size(20.dp), - imageVector = Icons.Rounded.Share, - contentDescription = "Add", - tint = MaterialTheme.colorScheme.primary, - ) - } - IconButton(onClick = { /*TODO*/ }) { - Icon( - modifier = Modifier.size(28.dp), - imageVector = Icons.Rounded.MoreHoriz, - contentDescription = "Add", - tint = MaterialTheme.colorScheme.primary, - ) - } - }, - ) + ReadPageTopBar(btnBackOnClickListener) val composition by rememberLottieComposition( LottieCompositionSpec.Url( @@ -158,52 +106,3 @@ fun ReadPage( } } } - -@Composable -private fun Header( - context: Context, - article: Article, - feed: Feed -) { - Column( - modifier = Modifier - .fillMaxWidth() - .roundClick { - context.startActivity( - Intent(Intent.ACTION_VIEW, Uri.parse(article.link)) - ) - } - ) { - Column(modifier = Modifier.padding(10.dp)) { - Text( - text = article.date.toString(DateTimeExt.YYYY_MM_DD_HH_MM, true), - fontSize = 12.sp, - color = MaterialTheme.colorScheme.outline, - fontWeight = FontWeight.Medium, - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = article.title, - fontSize = 27.sp, - color = MaterialTheme.colorScheme.primary, - fontWeight = FontWeight.Bold, - lineHeight = 34.sp, - ) - Spacer(modifier = Modifier.height(4.dp)) - article.author?.let { - Text( - text = article.author, - fontSize = 12.sp, - color = MaterialTheme.colorScheme.outline, - fontWeight = FontWeight.Medium, - ) - } - Text( - text = feed.name, - fontSize = 12.sp, - color = MaterialTheme.colorScheme.outline, - fontWeight = FontWeight.Medium, - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPageTopBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPageTopBar.kt new file mode 100644 index 0000000..abfd134 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/read/ReadPageTopBar.kt @@ -0,0 +1,51 @@ +package me.ash.reader.ui.page.home.read + +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Close +import androidx.compose.material.icons.rounded.MoreHoriz +import androidx.compose.material.icons.rounded.Share +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.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun ReadPageTopBar( + btnBackOnClickListener: () -> Unit = {}, +) { + SmallTopAppBar( + title = {}, + navigationIcon = { + IconButton(onClick = { btnBackOnClickListener() }) { + Icon( + modifier = Modifier.size(28.dp), + imageVector = Icons.Rounded.Close, + contentDescription = "Back", + tint = MaterialTheme.colorScheme.primary + ) + } + }, + actions = { + IconButton(onClick = { /*TODO*/ }) { + Icon( + modifier = Modifier.size(20.dp), + imageVector = Icons.Rounded.Share, + contentDescription = "Add", + tint = MaterialTheme.colorScheme.primary, + ) + } + IconButton(onClick = { /*TODO*/ }) { + Icon( + modifier = Modifier.size(28.dp), + imageVector = Icons.Rounded.MoreHoriz, + contentDescription = "Add", + tint = MaterialTheme.colorScheme.primary, + ) + } + }, + ) +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt index 2d5ff6d..30dee5b 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt @@ -1,7 +1,5 @@ package me.ash.reader.ui.page.settings -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -20,15 +18,10 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController -import com.google.accompanist.pager.ExperimentalPagerApi -import me.ash.reader.ui.util.paddingFixedHorizontal -import me.ash.reader.ui.util.roundClick +import me.ash.reader.ui.extension.paddingFixedHorizontal +import me.ash.reader.ui.extension.roundClick import me.ash.reader.ui.widget.TopTitleBox -@ExperimentalAnimationApi -@ExperimentalMaterial3Api -@ExperimentalPagerApi -@ExperimentalFoundationApi @Composable fun SettingsPage( navController: NavHostController, @@ -156,10 +149,6 @@ fun Item( } } -@ExperimentalAnimationApi -@ExperimentalMaterial3Api -@ExperimentalPagerApi -@ExperimentalFoundationApi @Preview @Composable fun SettingsPreview() { 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 41595b6..6799065 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 @@ -1,18 +1,21 @@ package me.ash.reader.ui.theme -import androidx.compose.ui.graphics.Color +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) @@ -22,13 +25,16 @@ 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) 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 1bcf1fa..a7accac 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 @@ -1,116 +1,118 @@ package me.ash.reader.ui.theme + import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp + //Replace with your font locations val Roboto = FontFamily.Default val AppTypography = Typography( - displayLarge = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.W400, - fontSize = 57.sp, - lineHeight = 64.sp, - letterSpacing = -0.25.sp, - ), - displayMedium = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.W400, - fontSize = 45.sp, - lineHeight = 52.sp, - letterSpacing = 0.sp, - ), - displaySmall = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.W400, - fontSize = 36.sp, - lineHeight = 44.sp, - letterSpacing = 0.sp, - ), - headlineLarge = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.W400, - fontSize = 32.sp, - lineHeight = 40.sp, - letterSpacing = 0.sp, - ), - headlineMedium = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.W400, - fontSize = 28.sp, - lineHeight = 36.sp, - letterSpacing = 0.sp, - ), - headlineSmall = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.W400, - fontSize = 24.sp, - lineHeight = 32.sp, - letterSpacing = 0.sp, - ), - titleLarge = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.W400, - fontSize = 22.sp, - lineHeight = 28.sp, - letterSpacing = 0.sp, - ), - titleMedium = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.Medium, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.1.sp, - ), - titleSmall = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.Medium, - fontSize = 14.sp, - lineHeight = 20.sp, - letterSpacing = 0.1.sp, - ), - labelLarge = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.Medium, - fontSize = 14.sp, - lineHeight = 20.sp, - letterSpacing = 0.1.sp, - ), - bodyLarge = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.W400, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp, - ), - bodyMedium = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.W400, - fontSize = 14.sp, - lineHeight = 20.sp, - letterSpacing = 0.25.sp, - ), - bodySmall = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.W400, - fontSize = 12.sp, - lineHeight = 16.sp, - letterSpacing = 0.4.sp, - ), - labelMedium = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.Medium, - fontSize = 12.sp, - lineHeight = 16.sp, - letterSpacing = 0.5.sp, - ), - labelSmall = TextStyle( - fontFamily = Roboto, - fontWeight = FontWeight.Medium, - fontSize = 11.sp, - lineHeight = 16.sp, - letterSpacing = 0.5.sp, - ), + displayLarge = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.W400, + fontSize = 57.sp, + lineHeight = 64.sp, + letterSpacing = -0.25.sp, + ), + displayMedium = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.W400, + fontSize = 45.sp, + lineHeight = 52.sp, + letterSpacing = 0.sp, + ), + displaySmall = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.W400, + fontSize = 36.sp, + lineHeight = 44.sp, + letterSpacing = 0.sp, + ), + headlineLarge = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.W400, + fontSize = 32.sp, + lineHeight = 40.sp, + letterSpacing = 0.sp, + ), + headlineMedium = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.W400, + fontSize = 28.sp, + lineHeight = 36.sp, + letterSpacing = 0.sp, + ), + headlineSmall = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.W400, + fontSize = 24.sp, + lineHeight = 32.sp, + letterSpacing = 0.sp, + ), + titleLarge = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.W400, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp, + ), + titleMedium = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.Medium, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.1.sp, + ), + titleSmall = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.1.sp, + ), + labelLarge = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.1.sp, + ), + bodyLarge = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.W400, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp, + ), + bodyMedium = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.W400, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.25.sp, + ), + bodySmall = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.W400, + fontSize = 12.sp, + lineHeight = 16.sp, + letterSpacing = 0.4.sp, + ), + labelMedium = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.Medium, + fontSize = 12.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp, + ), + labelSmall = TextStyle( + fontFamily = Roboto, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp, + ), ) diff --git a/app/src/main/java/me/ash/reader/ui/widget/AnimatedItemsIndexed.kt b/app/src/main/java/me/ash/reader/ui/widget/AnimatedItemsIndexed.kt index f348359..a34a3c6 100644 --- a/app/src/main/java/me/ash/reader/ui/widget/AnimatedItemsIndexed.kt +++ b/app/src/main/java/me/ash/reader/ui/widget/AnimatedItemsIndexed.kt @@ -10,7 +10,6 @@ import androidx.recyclerview.widget.ListUpdateCallback import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -@ExperimentalAnimationApi @Suppress("UpdateTransitionLabel", "TransitionPropertiesLabel") @SuppressLint("ComposableNaming", "UnusedTransitionTargetStateParameter") /** @@ -63,7 +62,6 @@ fun updateAnimatedItemsState( override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = oldList[oldItemPosition].item == newList[newItemPosition] - } val diffResult = calculateDiff(false, diffCb) val compositeList = oldList.toMutableList() diff --git a/app/src/main/java/me/ash/reader/ui/widget/AnimatedText.kt b/app/src/main/java/me/ash/reader/ui/widget/AnimatedText.kt index 66912a4..3fcfd26 100644 --- a/app/src/main/java/me/ash/reader/ui/widget/AnimatedText.kt +++ b/app/src/main/java/me/ash/reader/ui/widget/AnimatedText.kt @@ -18,7 +18,7 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit -@ExperimentalAnimationApi +@OptIn(ExperimentalAnimationApi::class) @Composable fun AnimatedText( text: String, diff --git a/app/src/main/java/me/ash/reader/ui/widget/CustomPager.kt b/app/src/main/java/me/ash/reader/ui/widget/CustomPager.kt index 7408f08..6e6b641 100644 --- a/app/src/main/java/me/ash/reader/ui/widget/CustomPager.kt +++ b/app/src/main/java/me/ash/reader/ui/widget/CustomPager.kt @@ -64,7 +64,7 @@ fun CustomPager( overshootFraction, onItemSelect = { index -> onItemSelect(items[index]) }, ) { - items.forEach{ item -> + items.forEach { item -> Box( modifier = when (orientation) { Orientation.Horizontal -> Modifier.fillMaxWidth() @@ -126,7 +126,7 @@ fun Pager( val spacing = state.itemSpacing.roundToInt() val itemDimensionWithSpace = itemDimension + state.itemSpacing val first = ceil( - (dragOffset -itemDimension - centerOffset) / itemDimensionWithSpace + (dragOffset - itemDimension - centerOffset) / itemDimensionWithSpace ).toInt().coerceAtLeast(0) val last = ((dimension + dragOffset - centerOffset) / itemDimensionWithSpace).toInt() .coerceAtMost(items.lastIndex) @@ -269,8 +269,12 @@ private class PagerState { val extra = if (remainder > itemDimension / 2f) 1 else 0 val lastVisibleIndex = (targetOffset.absoluteValue / itemDimension.toFloat()).toInt() + extra - targetOffset = (lastVisibleIndex * (itemDimension + itemSpacing) * targetOffset.sign) - .coerceIn(0f, (numberOfItems - 1).toFloat() * (itemDimension + itemSpacing)) + targetOffset = + (lastVisibleIndex * (itemDimension + itemSpacing) * targetOffset.sign) + .coerceIn( + 0f, + (numberOfItems - 1).toFloat() * (itemDimension + itemSpacing) + ) dragOffset.animateTo( animationSpec = animationSpec, targetValue = targetOffset, diff --git a/app/src/main/java/me/ash/reader/ui/widget/MaskBox.kt b/app/src/main/java/me/ash/reader/ui/widget/MaskBox.kt index cdf6bb5..4e38a32 100644 --- a/app/src/main/java/me/ash/reader/ui/widget/MaskBox.kt +++ b/app/src/main/java/me/ash/reader/ui/widget/MaskBox.kt @@ -16,7 +16,7 @@ import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.PagerState import kotlin.math.absoluteValue -@ExperimentalPagerApi +@OptIn(ExperimentalPagerApi::class) @Composable fun BoxScope.MaskBox( modifier: Modifier = Modifier, diff --git a/app/src/main/java/me/ash/reader/ui/widget/SelectionChip.kt b/app/src/main/java/me/ash/reader/ui/widget/SelectionChip.kt new file mode 100644 index 0000000..613c7ab --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/widget/SelectionChip.kt @@ -0,0 +1,59 @@ +package me.ash.reader.ui.widget + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +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.outlined.Check +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun SelectionChip( + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + shape: Shape = CircleShape, + selectedIcon: @Composable () -> Unit = { + Icon( + imageVector = Icons.Outlined.Check, + contentDescription = "Check", + modifier = Modifier.size(20.dp) + ) + }, + content: @Composable RowScope.() -> Unit +) { + FilterChip( + modifier = modifier, + colors = ChipDefaults.filterChipColors( + backgroundColor = MaterialTheme.colorScheme.surfaceVariant, + contentColor = MaterialTheme.colorScheme.onSurface, + leadingIconColor = MaterialTheme.colorScheme.onSurface, + 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 = MaterialTheme.colorScheme.primaryContainer, + selectedContentColor = MaterialTheme.colorScheme.onSurface, + selectedLeadingIconColor = MaterialTheme.colorScheme.onSurface + ), + interactionSource = interactionSource, + enabled = enabled, + selected = selected, + selectedIcon = selectedIcon, + shape = shape, + onClick = onClick, + content = content, + ) +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/widget/TopTitleBox.kt b/app/src/main/java/me/ash/reader/ui/widget/TopTitleBox.kt index 75db6b0..f9a73f5 100644 --- a/app/src/main/java/me/ash/reader/ui/widget/TopTitleBox.kt +++ b/app/src/main/java/me/ash/reader/ui/widget/TopTitleBox.kt @@ -1,6 +1,5 @@ package me.ash.reader.ui.widget -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.* import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -17,9 +16,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex -import me.ash.reader.ui.util.calculateTopBarAnimateValue +import me.ash.reader.ui.extension.calculateTopBarAnimateValue -@ExperimentalAnimationApi @Composable fun BoxScope.TopTitleBox( title: String, diff --git a/app/src/main/java/me/ash/reader/ui/widget/ViewPager.kt b/app/src/main/java/me/ash/reader/ui/widget/ViewPager.kt new file mode 100644 index 0000000..a42bcc0 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/widget/ViewPager.kt @@ -0,0 +1,33 @@ +package me.ash.reader.ui.widget + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.google.accompanist.pager.ExperimentalPagerApi +import com.google.accompanist.pager.HorizontalPager +import com.google.accompanist.pager.PagerState + +@OptIn(ExperimentalPagerApi::class) +@Composable +fun ViewPager( + modifier: Modifier = Modifier, + state: PagerState = com.google.accompanist.pager.rememberPagerState(), + composableList: List<@Composable () -> Unit>, + userScrollEnabled: Boolean = true +) { + HorizontalPager( + count = composableList.size, + state = state, + verticalAlignment = Alignment.Top, + modifier = modifier + .animateContentSize() + .wrapContentHeight() + .fillMaxWidth(), + userScrollEnabled = userScrollEnabled, + ) { page -> + composableList[page]() + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/widget/WebView.kt b/app/src/main/java/me/ash/reader/ui/widget/WebView.kt index 61c1459..8ff95f6 100644 --- a/app/src/main/java/me/ash/reader/ui/widget/WebView.kt +++ b/app/src/main/java/me/ash/reader/ui/widget/WebView.kt @@ -13,9 +13,9 @@ 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 -import me.ash.reader.ui.util.collectAsStateValue @Composable fun WebView(