Merge notifications by feeds

This commit is contained in:
Ash 2022-04-29 01:32:28 +08:00
parent ee5e6e3687
commit 72b07a7e0a
8 changed files with 70 additions and 46 deletions

View File

@ -562,7 +562,7 @@ interface ArticleDao {
} }
@Transaction @Transaction
suspend fun insertIfNotExist(articles: List<Article>): List<Article?> { suspend fun insertIfNotExist(articles: List<Article>): List<Article> {
return articles.map { if (insertIfNotExist(it) > 0) it else null } return articles.mapNotNull { if (insertIfNotExist(it) > 0) it else null }
} }
} }

View File

@ -1,14 +1,12 @@
package me.ash.reader.data.repository package me.ash.reader.data.repository
import android.app.Notification import android.app.*
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat.getSystemService import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.ListenableWorker import androidx.work.ListenableWorker
import androidx.work.WorkManager import androidx.work.WorkManager
@ -25,6 +23,7 @@ import me.ash.reader.data.dao.FeedDao
import me.ash.reader.data.dao.GroupDao import me.ash.reader.data.dao.GroupDao
import me.ash.reader.data.entity.Article import me.ash.reader.data.entity.Article
import me.ash.reader.data.entity.Feed import me.ash.reader.data.entity.Feed
import me.ash.reader.data.entity.FeedWithArticle
import me.ash.reader.data.entity.Group import me.ash.reader.data.entity.Group
import me.ash.reader.data.module.DispatcherDefault import me.ash.reader.data.module.DispatcherDefault
import me.ash.reader.data.module.DispatcherIO import me.ash.reader.data.module.DispatcherIO
@ -56,12 +55,9 @@ class LocalRssRepository @Inject constructor(
feedDao, rssNetworkDataSource, workManager, feedDao, rssNetworkDataSource, workManager,
dispatcherIO dispatcherIO
) { ) {
private val notificationManager: NotificationManager = private val notificationManager: NotificationManagerCompat =
(getSystemService( NotificationManagerCompat.from(context).apply {
context, createNotificationChannel(
NotificationManager::class.java
) as NotificationManager).also {
it.createNotificationChannel(
NotificationChannel( NotificationChannel(
NotificationGroupName.ARTICLE_UPDATE, NotificationGroupName.ARTICLE_UPDATE,
NotificationGroupName.ARTICLE_UPDATE, NotificationGroupName.ARTICLE_UPDATE,
@ -105,9 +101,14 @@ class LocalRssRepository @Inject constructor(
.awaitAll() .awaitAll()
.forEach { .forEach {
if (it.isNotify) { if (it.isNotify) {
notify(articleDao.insertIfNotExist(it.articles)) notify(
FeedWithArticle(
it.feedWithArticle.feed,
articleDao.insertIfNotExist(it.feedWithArticle.articles)
)
)
} else { } else {
articleDao.insertIfNotExist(it.articles) articleDao.insertIfNotExist(it.feedWithArticle.articles)
} }
} }
Log.i("RlOG", "onCompletion: ${System.currentTimeMillis() - preTime}") Log.i("RlOG", "onCompletion: ${System.currentTimeMillis() - preTime}")
@ -158,7 +159,7 @@ class LocalRssRepository @Inject constructor(
} }
data class ArticleNotify( data class ArticleNotify(
val articles: List<Article>, val feedWithArticle: FeedWithArticle,
val isNotify: Boolean, val isNotify: Boolean,
) )
@ -170,7 +171,7 @@ class LocalRssRepository @Inject constructor(
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Log.e("RLog", "queryRssXml[${feed.name}]: ${e.message}") Log.e("RLog", "queryRssXml[${feed.name}]: ${e.message}")
return ArticleNotify(listOf(), false) return ArticleNotify(FeedWithArticle(feed, listOf()), false)
} }
try { try {
// if (feed.icon == null && !articles.isNullOrEmpty()) { // if (feed.icon == null && !articles.isNullOrEmpty()) {
@ -178,32 +179,33 @@ class LocalRssRepository @Inject constructor(
// } // }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("RLog", "queryRssIcon[${feed.name}]: ${e.message}") Log.e("RLog", "queryRssIcon[${feed.name}]: ${e.message}")
return ArticleNotify(listOf(), false) return ArticleNotify(FeedWithArticle(feed, listOf()), false)
} }
return ArticleNotify( return ArticleNotify(
articles = articles, feedWithArticle = FeedWithArticle(feed, articles),
isNotify = articles.isNotEmpty() && feed.isNotification isNotify = articles.isNotEmpty() && feed.isNotification
) )
} }
private fun notify( private fun notify(
articles: List<Article?>, feedWithArticle: FeedWithArticle,
) { ) {
articles.filterNotNull().forEach { article -> notificationManager.createNotificationChannelGroup(
val builder = NotificationCompat.Builder( NotificationChannelGroup(
context, feedWithArticle.feed.id,
NotificationGroupName.ARTICLE_UPDATE feedWithArticle.feed.name
).setSmallIcon(R.drawable.ic_notification) )
// .setLargeIcon( )
// BitmapFactory.decodeResource( feedWithArticle.articles.forEach { article ->
// context.resources, val builder = NotificationCompat.Builder(context, NotificationGroupName.ARTICLE_UPDATE)
// R.mipmap.ic_launcher_round, .setSmallIcon(R.drawable.ic_notification)
// ) .setLargeIcon(
// ) (BitmapFactory.decodeResource(
.setGroup(NotificationGroupName.ARTICLE_UPDATE) context.resources,
R.drawable.ic_notification
))
)
.setContentTitle(article.title) .setContentTitle(article.title)
.setContentText(article.shortDescription)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent( .setContentIntent(
PendingIntent.getActivity( PendingIntent.getActivity(
context, context,
@ -219,6 +221,13 @@ class LocalRssRepository @Inject constructor(
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
) )
) )
.setGroup(feedWithArticle.feed.id)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(article.shortDescription)
.setSummaryText(feedWithArticle.feed.name)
)
notificationManager.notify( notificationManager.notify(
Random().nextInt() + article.id.hashCode(), Random().nextInt() + article.id.hashCode(),
builder.build().apply { builder.build().apply {
@ -226,5 +235,26 @@ class LocalRssRepository @Inject constructor(
} }
) )
} }
if (feedWithArticle.articles.size > 1) {
notificationManager.notify(
Random().nextInt() + feedWithArticle.feed.id.hashCode(),
NotificationCompat.Builder(context, NotificationGroupName.ARTICLE_UPDATE)
.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(
(BitmapFactory.decodeResource(
context.resources,
R.drawable.ic_notification
))
)
.setStyle(
NotificationCompat.InboxStyle()
.setSummaryText(feedWithArticle.feed.name)
)
.setGroup(feedWithArticle.feed.id)
.setGroupSummary(true)
.build()
)
}
} }
} }

View File

@ -163,7 +163,7 @@ class RssHelper @Inject constructor(
} }
private fun parseDate( private fun parseDate(
inputDate: String, patterns: Array<String?> = arrayOf( inputDate: String, patterns: Array<String> = arrayOf(
"yyyy-MM-dd'T'HH:mm:ss'Z'", "yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd", "yyyy-MM-dd",
"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss",
@ -174,7 +174,6 @@ class RssHelper @Inject constructor(
) )
): Date? { ): Date? {
val df = SimpleDateFormat() val df = SimpleDateFormat()
df.timeZone = TimeZone.getDefault()
for (pattern in patterns) { for (pattern in patterns) {
df.applyPattern(pattern) df.applyPattern(pattern)
df.isLenient = false df.isLenient = false

View File

@ -10,8 +10,6 @@ package me.ash.reader.ui.component
import android.view.SoundEffectConstants import android.view.SoundEffectConstants
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@ -86,7 +84,7 @@ fun Banner(
) )
desc?.let { desc?.let {
Text( Text(
modifier = Modifier.animateContentSize(tween()), // modifier = Modifier.animateContentSize(tween()),
text = it, text = it,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = (MaterialTheme.colorScheme.onSurface alwaysLight true).copy(alpha = 0.7f), color = (MaterialTheme.colorScheme.onSurface alwaysLight true).copy(alpha = 0.7f),

View File

@ -1,7 +1,6 @@
package me.ash.reader.ui.component package me.ash.reader.ui.component
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.tween
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
@ -32,8 +31,8 @@ fun DisplayText(
) { ) {
Text( Text(
modifier = Modifier modifier = Modifier
.height(44.dp) .height(44.dp),
.animateContentSize(tween()), // .animateContentSize(tween()),
text = text, text = text,
style = MaterialTheme.typography.displaySmall.copy( style = MaterialTheme.typography.displaySmall.copy(
baselineShift = BaselineShift.Superscript baselineShift = BaselineShift.Superscript

View File

@ -78,7 +78,7 @@ fun HomeEntry(
FlowPage( FlowPage(
navController = navController, navController = navController,
homeViewModel = homeViewModel, homeViewModel = homeViewModel,
pagingItems = pagingItems pagingItems = pagingItems,
) )
} }
animatedComposable(route = "${RouteName.READING}/{articleId}") { animatedComposable(route = "${RouteName.READING}/{articleId}") {

View File

@ -166,7 +166,7 @@ fun FeedsPage(
} }
) )
}, },
text = feedsViewState.account?.name ?: "", text = feedsViewState.account?.name ?: stringResource(R.string.read_you),
desc = if (isSyncing) stringResource(R.string.syncing) else "", desc = if (isSyncing) stringResource(R.string.syncing) else "",
) )
} }

View File

@ -1,6 +1,5 @@
package me.ash.reader.ui.page.home.flow package me.ash.reader.ui.page.home.flow
import android.util.Log
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
@ -89,7 +88,6 @@ fun FlowPage(
LaunchedEffect(viewState.listState) { LaunchedEffect(viewState.listState) {
snapshotFlow { viewState.listState.firstVisibleItemIndex }.collect { snapshotFlow { viewState.listState.firstVisibleItemIndex }.collect {
Log.i("RLog", "FlowPage: ${it}")
if (it > 0) { if (it > 0) {
keyboardController?.hide() keyboardController?.hide()
} }