Add click notification feature

This commit is contained in:
Ash 2022-03-04 00:14:36 +08:00
parent bfe1307b27
commit ee9e0a4553
19 changed files with 274 additions and 103 deletions

View File

@ -1,6 +1,10 @@
package me.ash.reader package me.ash.reader
import android.app.Application 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 dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -14,6 +18,10 @@ import me.ash.reader.data.source.ReaderDatabase
import me.ash.reader.data.source.RssNetworkDataSource import me.ash.reader.data.source.RssNetworkDataSource
import javax.inject.Inject import javax.inject.Inject
@ExperimentalAnimationApi
@ExperimentalMaterial3Api
@ExperimentalPagerApi
@ExperimentalFoundationApi
@DelicateCoroutinesApi @DelicateCoroutinesApi
@HiltAndroidApp @HiltAndroidApp
class App : Application() { class App : Application() {

View File

@ -271,6 +271,15 @@ interface ArticleDao {
) )
suspend fun queryLatestByFeedId(accountId: Int, feedId: Int): Article? suspend fun queryLatestByFeedId(accountId: Int, feedId: Int): Article?
@Transaction
@Query(
"""
SELECT * FROM article
WHERE id = :id
"""
)
suspend fun queryById(id: Int): ArticleWithFeed?
@Insert @Insert
suspend fun insert(article: Article): Long suspend fun insert(article: Article): Long

View File

@ -1,4 +1,4 @@
package me.ash.reader.ui.data package me.ash.reader.data.constant
class Filter( class Filter(
var index: Int, var index: Int,

View File

@ -1,7 +1,9 @@
package me.ash.reader.ui.data package me.ash.reader.data.constant
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.* import androidx.compose.material.icons.rounded.FiberManualRecord
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.Subject
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
class NavigationBarItem( class NavigationBarItem(

View File

@ -0,0 +1,7 @@
package me.ash.reader.data.constant
object Symbol {
const val NOTHING: String = "null"
const val NOTIFICATION_CHANNEL_GROUP_ARTICLE_UPDATE: String = "article.update"
const val EXTRA_ARTICLE_ID: String = "article.id"
}

View File

@ -86,4 +86,8 @@ class ArticleRepository @Inject constructor(
suspend fun updateArticleInfo(article: Article) { suspend fun updateArticleInfo(article: Article) {
articleDao.update(article) articleDao.update(article)
} }
suspend fun findArticleById(id: Int): ArticleWithFeed? {
return articleDao.queryById(id)
}
} }

View File

@ -3,29 +3,34 @@ package me.ash.reader.data.repository
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Build import android.os.Build
import android.util.Log 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.app.NotificationCompat
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import androidx.work.* import androidx.work.*
import com.github.muhrifqii.parserss.ParseRSS import com.github.muhrifqii.parserss.ParseRSS
import com.google.accompanist.pager.ExperimentalPagerApi
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import me.ash.reader.DataStoreKeys import me.ash.reader.*
import me.ash.reader.R import me.ash.reader.R
import me.ash.reader.data.account.AccountDao import me.ash.reader.data.account.AccountDao
import me.ash.reader.data.article.Article import me.ash.reader.data.article.Article
import me.ash.reader.data.article.ArticleDao import me.ash.reader.data.article.ArticleDao
import me.ash.reader.data.constant.Symbol
import me.ash.reader.data.feed.Feed import me.ash.reader.data.feed.Feed
import me.ash.reader.data.feed.FeedDao import me.ash.reader.data.feed.FeedDao
import me.ash.reader.data.source.ReaderDatabase import me.ash.reader.data.source.ReaderDatabase
import me.ash.reader.data.source.RssNetworkDataSource import me.ash.reader.data.source.RssNetworkDataSource
import me.ash.reader.dataStore
import me.ash.reader.get
import net.dankito.readability4j.Readability4J import net.dankito.readability4j.Readability4J
import net.dankito.readability4j.extended.Readability4JExtended import net.dankito.readability4j.extended.Readability4JExtended
import okhttp3.* import okhttp3.*
@ -34,7 +39,6 @@ import java.io.IOException
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import kotlin.random.Random
class RssRepository @Inject constructor( class RssRepository @Inject constructor(
@ -83,6 +87,10 @@ class RssRepository @Inject constructor(
return workManager.getWorkInfosByTag("sync").get().size.toString() return workManager.getWorkInfosByTag("sync").get().size.toString()
} }
@ExperimentalAnimationApi
@ExperimentalMaterial3Api
@ExperimentalPagerApi
@ExperimentalFoundationApi
suspend fun sync(isWork: Boolean? = false) { suspend fun sync(isWork: Boolean? = false) {
if (isWork == true) { if (isWork == true) {
workManager.cancelAllWork() workManager.cancelAllWork()
@ -92,7 +100,6 @@ class RssRepository @Inject constructor(
).setConstraints( ).setConstraints(
Constraints.Builder() Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) .setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(true)
.build() .build()
).addTag("sync").build() ).addTag("sync").build()
workManager.enqueue(syncWorkerRequest) workManager.enqueue(syncWorkerRequest)
@ -101,6 +108,10 @@ class RssRepository @Inject constructor(
} }
} }
@ExperimentalAnimationApi
@ExperimentalMaterial3Api
@ExperimentalPagerApi
@ExperimentalFoundationApi
@DelicateCoroutinesApi @DelicateCoroutinesApi
companion object { companion object {
data class SyncState( data class SyncState(
@ -194,26 +205,38 @@ class RssRepository @Inject constructor(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationManager.createNotificationChannel( notificationManager.createNotificationChannel(
NotificationChannel( NotificationChannel(
"ARTICLE_UPDATE", Symbol.NOTIFICATION_CHANNEL_GROUP_ARTICLE_UPDATE,
"文章更新", "文章更新",
NotificationManager.IMPORTANCE_DEFAULT NotificationManager.IMPORTANCE_DEFAULT
) )
) )
} }
it.reversed().forEachIndexed { index, articleList -> it.reversed().forEach { articleList ->
articleList.forEach { article -> val ids = articleDao.insertList(articleList)
Log.i("RlOG", "combine $index ${article.feedId}: ${article.title}") articleList.forEachIndexed { index, article ->
val builder = NotificationCompat.Builder(context, "ARTICLE_UPDATE") Log.i("RlOG", "combine ${article.feedId}: ${article.title}")
val builder = NotificationCompat.Builder(context, Symbol.NOTIFICATION_CHANNEL_GROUP_ARTICLE_UPDATE)
.setSmallIcon(R.drawable.ic_launcher_foreground) .setSmallIcon(R.drawable.ic_launcher_foreground)
.setGroup("ARTICLE_UPDATE") .setGroup(Symbol.NOTIFICATION_CHANNEL_GROUP_ARTICLE_UPDATE)
.setContentTitle(article.title) .setContentTitle(article.title)
.setContentText(article.shortDescription) .setContentText(article.shortDescription)
.setPriority(NotificationCompat.PRIORITY_DEFAULT) .setPriority(NotificationCompat.PRIORITY_DEFAULT)
notificationManager.notify(Random.nextInt(), builder.build().apply { .setContentIntent(
PendingIntent.getActivity(
context,
ids[index].toInt(),
Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra(Symbol.EXTRA_ARTICLE_ID, ids[index].toInt())
},
PendingIntent.FLAG_UPDATE_CURRENT
)
)
notificationManager.notify(ids[index].toInt(), builder.build().apply {
flags = Notification.FLAG_AUTO_CANCEL flags = Notification.FLAG_AUTO_CANCEL
}) })
} }
articleDao.insertList(articleList)
} }
}.buffer().onCompletion { }.buffer().onCompletion {
val afterTime = System.currentTimeMillis() val afterTime = System.currentTimeMillis()
@ -323,6 +346,10 @@ class RssRepository @Inject constructor(
} }
} }
@ExperimentalAnimationApi
@ExperimentalMaterial3Api
@ExperimentalPagerApi
@ExperimentalFoundationApi
@DelicateCoroutinesApi @DelicateCoroutinesApi
class SyncWorker( class SyncWorker(
context: Context, context: Context,

View File

@ -12,12 +12,16 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier 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.ProvideWindowInsets
import com.google.accompanist.insets.navigationBarsHeight import com.google.accompanist.insets.navigationBarsHeight
import com.google.accompanist.insets.statusBarsPadding import com.google.accompanist.insets.statusBarsPadding
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import me.ash.reader.ui.page.home.HomePage import me.ash.reader.ui.page.home.HomePage
import me.ash.reader.ui.page.settings.SettingsPage
import me.ash.reader.ui.theme.AppTheme import me.ash.reader.ui.theme.AppTheme
@ExperimentalAnimationApi @ExperimentalAnimationApi
@ -26,6 +30,8 @@ import me.ash.reader.ui.theme.AppTheme
@ExperimentalFoundationApi @ExperimentalFoundationApi
@Composable @Composable
fun HomeEntry() { fun HomeEntry() {
val navController = rememberNavController()
AppTheme { AppTheme {
ProvideWindowInsets { ProvideWindowInsets {
rememberSystemUiController().run { rememberSystemUiController().run {
@ -33,13 +39,24 @@ fun HomeEntry() {
setSystemBarsColor(MaterialTheme.colorScheme.surface, !isSystemInDarkTheme()) setSystemBarsColor(MaterialTheme.colorScheme.surface, !isSystemInDarkTheme())
setNavigationBarColor(MaterialTheme.colorScheme.surface, !isSystemInDarkTheme()) setNavigationBarColor(MaterialTheme.colorScheme.surface, !isSystemInDarkTheme())
} }
Column (modifier = Modifier.background(MaterialTheme.colorScheme.surface)){ Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
Row( Row(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.statusBarsPadding() .statusBarsPadding()
) { ) {
HomePage() NavHost(
modifier = Modifier.background(MaterialTheme.colorScheme.surface),
navController = navController,
startDestination = RouteName.HOME,
) {
composable(route = RouteName.HOME) {
HomePage(navController)
}
composable(route = RouteName.SETTINGS) {
SettingsPage(navController)
}
}
} }
Spacer( Spacer(
modifier = Modifier modifier = Modifier

View File

@ -1,7 +1,9 @@
package me.ash.reader.ui.page.common package me.ash.reader.ui.page.common
object RouteName { object RouteName {
const val HOME = "home"
const val FEED = "feed" const val FEED = "feed"
const val ARTICLE = "article" const val ARTICLE = "article"
const val READ = "read" const val READ = "read"
const val SETTINGS = "settings"
} }

View File

@ -4,28 +4,31 @@ import android.util.Log
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.rememberNavController import androidx.navigation.NavHostController
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.HorizontalPager
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import me.ash.reader.data.constant.Symbol
import me.ash.reader.ui.page.home.article.ArticlePage 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.feed.FeedPage
import me.ash.reader.ui.page.home.read.ReadPage 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.ReadViewAction
import me.ash.reader.ui.page.home.read.ReadViewModel import me.ash.reader.ui.page.home.read.ReadViewModel
import me.ash.reader.ui.util.collectAsStateValue 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.util.pagerAnimate
import me.ash.reader.ui.widget.AppNavigationBar import me.ash.reader.ui.widget.AppNavigationBar
@ -35,15 +38,44 @@ import me.ash.reader.ui.widget.AppNavigationBar
@ExperimentalFoundationApi @ExperimentalFoundationApi
@Composable @Composable
fun HomePage( fun HomePage(
navController: NavHostController,
viewModel: HomeViewModel = hiltViewModel(), viewModel: HomeViewModel = hiltViewModel(),
readViewModel: ReadViewModel = hiltViewModel(), readViewModel: ReadViewModel = hiltViewModel(),
) { ) {
val context = LocalContext.current
val viewState = viewModel.viewState.collectAsStateValue() val viewState = viewModel.viewState.collectAsStateValue()
val filterState = viewModel.filterState.collectAsStateValue() val filterState = viewModel.filterState.collectAsStateValue()
val readState = readViewModel.viewState.collectAsStateValue() val readState = readViewModel.viewState.collectAsStateValue()
val navController = rememberNavController()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
DisposableEffect(Unit) {
context.findActivity()?.let { activity ->
activity.intent?.let { intent ->
intent.extras?.get(Symbol.EXTRA_ARTICLE_ID)?.let {
readViewModel.dispatch(ReadViewAction.ScrollToItem(2))
scope.launch {
val article =
readViewModel.articleRepository.findArticleById(it.toString().toInt())
?: return@launch
readViewModel.dispatch(ReadViewAction.InitData(article))
if (article.feed.isFullContent) readViewModel.dispatch(ReadViewAction.RenderFullContent)
else readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
viewModel.dispatch(
HomeViewAction.ScrollToPage(
scope = scope,
targetPage = 2,
)
)
}
}
intent.extras?.clear()
}
}
onDispose { }
}
BackHandler(true) { BackHandler(true) {
val currentPage = viewState.pagerState.currentPage val currentPage = viewState.pagerState.currentPage
viewModel.dispatch( viewModel.dispatch(
@ -115,12 +147,11 @@ fun HomePage(
HorizontalPager( HorizontalPager(
count = 3, count = 3,
state = viewState.pagerState, state = viewState.pagerState,
modifier = Modifier modifier = Modifier.weight(1f)
.weight(1f)
.background(MaterialTheme.colorScheme.surface),
) { page -> ) { page ->
when (page) { when (page) {
0 -> FeedPage( 0 -> FeedPage(
navController = navController,
modifier = Modifier.pagerAnimate(this, page), modifier = Modifier.pagerAnimate(this, page),
filter = filterState.filter, filter = filterState.filter,
groupAndFeedOnClick = { currentGroup, currentFeed -> groupAndFeedOnClick = { currentGroup, currentFeed ->
@ -141,6 +172,7 @@ fun HomePage(
}, },
) )
1 -> ArticlePage( 1 -> ArticlePage(
navController = navController,
modifier = Modifier.pagerAnimate(this, page), modifier = Modifier.pagerAnimate(this, page),
BackOnClick = { BackOnClick = {
viewModel.dispatch( viewModel.dispatch(
@ -165,6 +197,7 @@ fun HomePage(
}, },
) )
2 -> ReadPage( 2 -> ReadPage(
navController = navController,
modifier = Modifier.pagerAnimate(this, page), modifier = Modifier.pagerAnimate(this, page),
btnBackOnClickListener = { btnBackOnClickListener = {
viewModel.dispatch( viewModel.dispatch(

View File

@ -1,5 +1,8 @@
package me.ash.reader.ui.page.home 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.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
@ -15,10 +18,13 @@ import kotlinx.coroutines.launch
import me.ash.reader.data.feed.Feed import me.ash.reader.data.feed.Feed
import me.ash.reader.data.group.Group import me.ash.reader.data.group.Group
import me.ash.reader.data.repository.RssRepository import me.ash.reader.data.repository.RssRepository
import me.ash.reader.ui.data.Filter import me.ash.reader.data.constant.Filter
import javax.inject.Inject import javax.inject.Inject
@ExperimentalAnimationApi
@ExperimentalMaterial3Api
@ExperimentalPagerApi @ExperimentalPagerApi
@ExperimentalFoundationApi
@HiltViewModel @HiltViewModel
class HomeViewModel @Inject constructor( class HomeViewModel @Inject constructor(
private val rssRepository: RssRepository, private val rssRepository: RssRepository,

View File

@ -30,6 +30,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.SwipeRefresh
@ -40,8 +41,8 @@ import me.ash.reader.DateTimeExt
import me.ash.reader.DateTimeExt.toString import me.ash.reader.DateTimeExt.toString
import me.ash.reader.R import me.ash.reader.R
import me.ash.reader.data.article.ArticleWithFeed 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.data.repository.RssRepository
import me.ash.reader.ui.data.Filter
import me.ash.reader.ui.page.home.HomeViewAction import me.ash.reader.ui.page.home.HomeViewAction
import me.ash.reader.ui.page.home.HomeViewModel import me.ash.reader.ui.page.home.HomeViewModel
import me.ash.reader.ui.util.collectAsStateValue import me.ash.reader.ui.util.collectAsStateValue
@ -57,6 +58,7 @@ import me.ash.reader.ui.widget.TopTitleBox
@ExperimentalFoundationApi @ExperimentalFoundationApi
@Composable @Composable
fun ArticlePage( fun ArticlePage(
navController: NavHostController,
modifier: Modifier, modifier: Modifier,
homeViewModel: HomeViewModel = hiltViewModel(), homeViewModel: HomeViewModel = hiltViewModel(),
viewModel: ArticleViewModel = hiltViewModel(), viewModel: ArticleViewModel = hiltViewModel(),
@ -92,7 +94,7 @@ fun ArticlePage(
homeViewModel.dispatch(HomeViewAction.Sync()) homeViewModel.dispatch(HomeViewAction.Sync())
} }
) { ) {
Box(modifier.background(MaterialTheme.colorScheme.surface)) { Box {
TopTitleBox( TopTitleBox(
title = when { title = when {
filterState.group != null -> filterState.group.name filterState.group != null -> filterState.group.name
@ -241,65 +243,67 @@ private fun ArticleItem(
) )
} }
Spacer(modifier = modifier.height(1.dp)) Spacer(modifier = modifier.height(1.dp))
if (true) { Row {
Box( if (true) {
modifier = Modifier Box(
.padding(top = 3.dp) modifier = Modifier
.size(24.dp) .padding(top = 3.dp)
.border( .size(24.dp)
2.dp, .border(
MaterialTheme.colorScheme.inverseOnSurface, 2.dp,
RoundedCornerShape(4.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 if (articleWithFeed.feed.icon == null) {
.fillMaxSize() Icon(
.padding(2.dp), 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
)
} }
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
)
} }
} }
} }

View File

@ -6,7 +6,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
@ -26,15 +25,17 @@ import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import me.ash.reader.DateTimeExt import me.ash.reader.DateTimeExt
import me.ash.reader.DateTimeExt.toString 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.feed.Feed
import me.ash.reader.data.group.Group import me.ash.reader.data.group.Group
import me.ash.reader.data.group.GroupWithFeed import me.ash.reader.data.group.GroupWithFeed
import me.ash.reader.data.repository.RssRepository import me.ash.reader.data.repository.RssRepository
import me.ash.reader.ui.data.Filter import me.ash.reader.ui.page.common.RouteName
import me.ash.reader.ui.page.home.HomeViewAction import me.ash.reader.ui.page.home.HomeViewAction
import me.ash.reader.ui.page.home.HomeViewModel import me.ash.reader.ui.page.home.HomeViewModel
import me.ash.reader.ui.util.collectAsStateValue import me.ash.reader.ui.util.collectAsStateValue
@ -47,6 +48,7 @@ import me.ash.reader.ui.widget.*
@ExperimentalFoundationApi @ExperimentalFoundationApi
@Composable @Composable
fun FeedPage( fun FeedPage(
navController: NavHostController,
modifier: Modifier, modifier: Modifier,
viewModel: FeedViewModel = hiltViewModel(), viewModel: FeedViewModel = hiltViewModel(),
homeViewModel: HomeViewModel = hiltViewModel(), homeViewModel: HomeViewModel = hiltViewModel(),
@ -84,9 +86,7 @@ fun FeedPage(
} }
Box( Box(
modifier modifier.fillMaxSize()
.fillMaxSize()
.background(MaterialTheme.colorScheme.surface)
) { ) {
TopTitleBox( TopTitleBox(
title = viewState.account?.name ?: "未知账户", title = viewState.account?.name ?: "未知账户",
@ -109,7 +109,7 @@ fun FeedPage(
title = {}, title = {},
navigationIcon = { navigationIcon = {
IconButton(onClick = { IconButton(onClick = {
navController.navigate(route = RouteName.SETTINGS)
}) { }) {
Icon( Icon(
modifier = Modifier.size(22.dp), modifier = Modifier.size(22.dp),

View File

@ -5,7 +5,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
@ -24,6 +23,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import com.airbnb.lottie.compose.LottieAnimation import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.rememberLottieComposition import com.airbnb.lottie.compose.rememberLottieComposition
@ -44,6 +44,7 @@ import me.ash.reader.ui.widget.WebView
@ExperimentalFoundationApi @ExperimentalFoundationApi
@Composable @Composable
fun ReadPage( fun ReadPage(
navController: NavHostController,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: ReadViewModel = hiltViewModel(), viewModel: ReadViewModel = hiltViewModel(),
btnBackOnClickListener: () -> Unit, btnBackOnClickListener: () -> Unit,
@ -66,9 +67,7 @@ fun ReadPage(
Box { Box {
Column( Column(
modifier modifier.fillMaxSize()
.fillMaxSize()
.background(MaterialTheme.colorScheme.surface)
) { ) {
SmallTopAppBar( SmallTopAppBar(

View File

@ -16,7 +16,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class ReadViewModel @Inject constructor( class ReadViewModel @Inject constructor(
private val articleRepository: ArticleRepository, val articleRepository: ArticleRepository,
private val rssRepository: RssRepository, private val rssRepository: RssRepository,
) : ViewModel() { ) : ViewModel() {
private val _viewState = MutableStateFlow(ReadViewState()) private val _viewState = MutableStateFlow(ReadViewState())

View File

@ -0,0 +1,49 @@
package me.ash.reader.ui.page.settings
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import com.google.accompanist.pager.ExperimentalPagerApi
@ExperimentalAnimationApi
@ExperimentalMaterial3Api
@ExperimentalPagerApi
@ExperimentalFoundationApi
@Composable
fun SettingsPage(
navController: NavHostController,
) {
Scaffold(
topBar = {
LargeTopAppBar(
title = {
Text(
text = "Settings",
color = MaterialTheme.colorScheme.primary,
)
},
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
IconButton(onClick = { /*TODO*/ }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back",
tint = MaterialTheme.colorScheme.primary
)
}
}
},
)
},
content = {
Text(text = "af")
},
)
}

View File

@ -1,5 +1,8 @@
package me.ash.reader.ui.util package me.ash.reader.ui.util
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
@ -69,4 +72,10 @@ fun Modifier.roundClick(onClick: () -> Unit = {}) = this
fun Modifier.paddingFixedHorizontal(top: Dp = 0.dp, bottom: Dp = 0.dp) = this fun Modifier.paddingFixedHorizontal(top: Dp = 0.dp, bottom: Dp = 0.dp) = this
.padding(horizontal = 10.dp) .padding(horizontal = 10.dp)
.padding(top = top, bottom = bottom) .padding(top = top, bottom = bottom)
fun Context.findActivity(): Activity? = when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}

View File

@ -1,5 +0,0 @@
package me.ash.reader.ui.util
object Symbol {
const val nothing: String = "null"
}

View File

@ -34,8 +34,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.PagerState
import me.ash.reader.ui.data.Filter import me.ash.reader.data.constant.Filter
import me.ash.reader.ui.data.NavigationBarItem import me.ash.reader.data.constant.NavigationBarItem
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ExperimentalPagerApi @ExperimentalPagerApi