diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d275ec0..5830478 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,9 +5,6 @@ - - - ? = null, - val body: String? = null, -) - -data class AssetsItem( - val name: String? = null, - val content_type: String? = null, - val size: Int? = null, - val download_count: Int? = null, - val created_at: String? = null, - val updated_at: String? = null, - val browser_download_url: String? = null, -) \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/entity/Filter.kt b/app/src/main/java/me/ash/reader/data/model/Filter.kt similarity index 74% rename from app/src/main/java/me/ash/reader/data/entity/Filter.kt rename to app/src/main/java/me/ash/reader/data/model/Filter.kt index 738ebca..4d1ada5 100644 --- a/app/src/main/java/me/ash/reader/data/entity/Filter.kt +++ b/app/src/main/java/me/ash/reader/data/model/Filter.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.entity +package me.ash.reader.data.model import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FiberManualRecord @@ -6,7 +6,10 @@ import androidx.compose.material.icons.rounded.FiberManualRecord import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.StarOutline import androidx.compose.material.icons.rounded.Subject +import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import me.ash.reader.R class Filter( val index: Int, @@ -33,5 +36,13 @@ class Filter( iconOutline = Icons.Rounded.Subject, iconFilled = Icons.Rounded.Subject, ) + val values = listOf(Starred, Unread, All) } +} + +@Composable +fun Filter.getName(): String = when (this) { + Filter.Unread -> stringResource(R.string.unread) + Filter.Starred -> stringResource(R.string.starred) + else -> stringResource(R.string.all) } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/entity/ImportantCount.kt b/app/src/main/java/me/ash/reader/data/model/ImportantCount.kt similarity index 75% rename from app/src/main/java/me/ash/reader/data/entity/ImportantCount.kt rename to app/src/main/java/me/ash/reader/data/model/ImportantCount.kt index c876bb6..5e6ffc0 100644 --- a/app/src/main/java/me/ash/reader/data/entity/ImportantCount.kt +++ b/app/src/main/java/me/ash/reader/data/model/ImportantCount.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.entity +package me.ash.reader.data.model data class ImportantCount( val important: Int, diff --git a/app/src/main/java/me/ash/reader/data/model/LatestRelease.kt b/app/src/main/java/me/ash/reader/data/model/LatestRelease.kt new file mode 100644 index 0000000..ff18b3c --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/LatestRelease.kt @@ -0,0 +1,2 @@ +package me.ash.reader.data.model + diff --git a/app/src/main/java/me/ash/reader/data/entity/Version.kt b/app/src/main/java/me/ash/reader/data/model/Version.kt similarity index 96% rename from app/src/main/java/me/ash/reader/data/entity/Version.kt rename to app/src/main/java/me/ash/reader/data/model/Version.kt index 1d8243f..50ef322 100644 --- a/app/src/main/java/me/ash/reader/data/entity/Version.kt +++ b/app/src/main/java/me/ash/reader/data/model/Version.kt @@ -1,4 +1,4 @@ -package me.ash.reader.data.entity +package me.ash.reader.data.model class Version(identifiers: List) { private var major: Int = 0 diff --git a/app/src/main/java/me/ash/reader/data/module/OkHttpClientModule.kt b/app/src/main/java/me/ash/reader/data/module/OkHttpClientModule.kt index dcd9224..279248b 100644 --- a/app/src/main/java/me/ash/reader/data/module/OkHttpClientModule.kt +++ b/app/src/main/java/me/ash/reader/data/module/OkHttpClientModule.kt @@ -1,3 +1,23 @@ +/* + * Feeder: Android RSS reader app + * https://gitlab.com/spacecowboy/Feeder + * + * Copyright (C) 2022 Jonas Kalderstam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package me.ash.reader.data.module import android.content.Context @@ -7,11 +27,20 @@ import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import me.ash.reader.BuildConfig -import me.ash.reader.cachingHttpClient +import okhttp3.Cache import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response +import java.io.File +import java.security.KeyManagementException +import java.security.NoSuchAlgorithmException +import java.security.cert.X509Certificate +import java.util.concurrent.TimeUnit import javax.inject.Singleton +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager @Module @InstallIn(SingletonComponent::class) @@ -28,6 +57,56 @@ object OkHttpClientModule { .build() } +fun cachingHttpClient( + cacheDirectory: File? = null, + cacheSize: Long = 10L * 1024L * 1024L, + trustAllCerts: Boolean = true, + connectTimeoutSecs: Long = 30L, + readTimeoutSecs: Long = 30L +): OkHttpClient { + val builder: OkHttpClient.Builder = OkHttpClient.Builder() + + if (cacheDirectory != null) { + builder.cache(Cache(cacheDirectory, cacheSize)) + } + + builder + .connectTimeout(connectTimeoutSecs, TimeUnit.SECONDS) + .readTimeout(readTimeoutSecs, TimeUnit.SECONDS) + .followRedirects(true) + + if (trustAllCerts) { + builder.trustAllCerts() + } + + return builder.build() +} + +fun OkHttpClient.Builder.trustAllCerts() { + try { + val trustManager = object : X509TrustManager { + override fun checkClientTrusted(chain: Array?, authType: String?) { + } + + override fun checkServerTrusted(chain: Array?, authType: String?) { + } + + override fun getAcceptedIssuers(): Array = emptyArray() + } + + val sslContext = SSLContext.getInstance("TLS") + sslContext.init(null, arrayOf(trustManager), null) + val sslSocketFactory = sslContext.socketFactory + + sslSocketFactory(sslSocketFactory, trustManager) + .hostnameVerifier(HostnameVerifier { _, _ -> true }) + } catch (e: NoSuchAlgorithmException) { + // ignore + } catch (e: KeyManagementException) { + // ignore + } +} + object UserAgentInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { return chain.proceed( diff --git a/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt b/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt index 71eb82d..75f42f8 100644 --- a/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt @@ -2,11 +2,11 @@ package me.ash.reader.data.repository import android.content.Context import android.util.Log -import androidx.hilt.work.HiltWorker import androidx.paging.PagingSource -import androidx.work.* -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject +import androidx.work.CoroutineWorker +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.ListenableWorker +import androidx.work.WorkManager import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn @@ -15,9 +15,9 @@ import me.ash.reader.data.dao.ArticleDao import me.ash.reader.data.dao.FeedDao import me.ash.reader.data.dao.GroupDao import me.ash.reader.data.entity.* +import me.ash.reader.data.model.ImportantCount import me.ash.reader.ui.ext.currentAccountId import java.util.* -import java.util.concurrent.TimeUnit abstract class AbstractRssRepository constructor( private val context: Context, @@ -130,10 +130,6 @@ abstract class AbstractRssRepository constructor( return feedDao.queryByLink(context.currentAccountId, url).isNotEmpty() } - fun peekWork(): String { - return workManager.getWorkInfosByTag("sync").get().size.toString() - } - suspend fun updateGroup(group: Group) { groupDao.update(group) } @@ -207,34 +203,3 @@ abstract class AbstractRssRepository constructor( } } } - -@HiltWorker -class SyncWorker @AssistedInject constructor( - @Assisted context: Context, - @Assisted workerParams: WorkerParameters, - private val rssRepository: RssRepository, -) : CoroutineWorker(context, workerParams) { - - override suspend fun doWork(): Result { - Log.i("RLog", "doWork: ") - return rssRepository.get().sync(this) - } - - companion object { - const val WORK_NAME = "article.sync" - - val UUID: UUID - - val repeatingRequest = PeriodicWorkRequestBuilder( - 15, TimeUnit.MINUTES - ).setConstraints( - Constraints.Builder() - .build() - ).addTag(WORK_NAME).build().also { - UUID = it.id - } - - fun setIsSyncing(boolean: Boolean) = workDataOf("isSyncing" to boolean) - fun Data.getIsSyncing(): Boolean = getBoolean("isSyncing", false) - } -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/repository/AppRepository.kt b/app/src/main/java/me/ash/reader/data/repository/AppRepository.kt index 5d67c56..bfa0216 100644 --- a/app/src/main/java/me/ash/reader/data/repository/AppRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/AppRepository.kt @@ -4,13 +4,11 @@ import android.content.Context import android.util.Log import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.withContext import me.ash.reader.R -import me.ash.reader.data.entity.toVersion -import me.ash.reader.data.module.ApplicationScope +import me.ash.reader.data.model.toVersion import me.ash.reader.data.module.DispatcherIO import me.ash.reader.data.module.DispatcherMain import me.ash.reader.data.source.AppNetworkDataSource @@ -23,8 +21,6 @@ class AppRepository @Inject constructor( @ApplicationContext private val context: Context, private val appNetworkDataSource: AppNetworkDataSource, - @ApplicationScope - private val applicationScope: CoroutineScope, @DispatcherIO private val dispatcherIO: CoroutineDispatcher, @DispatcherMain @@ -55,14 +51,8 @@ class AppRepository @Inject constructor( val currentVersion = context.getCurrentVersion() val latestLog = latest.body ?: "" val latestPublishDate = latest.published_at ?: latest.created_at ?: "" - val latestSize = latest.assets - ?.first() - ?.size - ?: 0 - val latestDownloadUrl = latest.assets - ?.first() - ?.browser_download_url - ?: "" + val latestSize = latest.assets?.first()?.size ?: 0 + val latestDownloadUrl = latest.assets?.first()?.browser_download_url ?: "" Log.i("RLog", "current version $currentVersion") if (latestVersion.whetherNeedUpdate(currentVersion, skipVersion)) { diff --git a/app/src/main/java/me/ash/reader/data/repository/FeverRssRepository.kt b/app/src/main/java/me/ash/reader/data/repository/FeverRssRepository.kt deleted file mode 100644 index a7b8600..0000000 --- a/app/src/main/java/me/ash/reader/data/repository/FeverRssRepository.kt +++ /dev/null @@ -1,163 +0,0 @@ -//package me.ash.reader.data.repository -// -//import android.content.Context -//import android.util.Log -//import androidx.work.WorkManager -//import dagger.hilt.android.qualifiers.ApplicationContext -//import kotlinx.coroutines.CoroutineDispatcher -//import kotlinx.coroutines.CoroutineScope -//import kotlinx.coroutines.launch -//import kotlinx.coroutines.sync.withLock -//import me.ash.reader.data.dao.AccountDao -//import me.ash.reader.data.dao.ArticleDao -//import me.ash.reader.data.dao.FeedDao -//import me.ash.reader.data.dao.GroupDao -//import me.ash.reader.data.entity.Article -//import me.ash.reader.data.entity.Feed -//import me.ash.reader.data.entity.Group -//import me.ash.reader.data.module.ApplicationScope -//import me.ash.reader.data.module.DispatcherDefault -//import me.ash.reader.data.module.DispatcherIO -//import me.ash.reader.data.source.FeverApiDataSource -//import me.ash.reader.data.source.RssNetworkDataSource -//import me.ash.reader.ui.ext.currentAccountId -//import me.ash.reader.ui.ext.spacerDollar -//import net.dankito.readability4j.extended.Readability4JExtended -//import java.util.* -//import javax.inject.Inject -//import kotlin.collections.set -// -//class FeverRssRepository @Inject constructor( -// @ApplicationContext -// private val context: Context, -// private val articleDao: ArticleDao, -// private val feedDao: FeedDao, -// private val groupDao: GroupDao, -// private val rssHelper: RssHelper, -// private val feverApiDataSource: FeverApiDataSource, -// private val accountDao: AccountDao, -// rssNetworkDataSource: RssNetworkDataSource, -// @ApplicationScope -// private val applicationScope: CoroutineScope, -// @DispatcherDefault -// private val dispatcherDefault: CoroutineDispatcher, -// @DispatcherIO -// private val dispatcherIO: CoroutineDispatcher, -// workManager: WorkManager, -//) : AbstractRssRepository( -// context, accountDao, articleDao, groupDao, -// feedDao, rssNetworkDataSource, workManager, -// dispatcherIO -//) { -// override suspend fun updateArticleInfo(article: Article) { -// articleDao.update(article) -// } -// -// override suspend fun subscribe(feed: Feed, articles: List
) { -// feedDao.insert(feed) -// articleDao.insertList(articles.map { -// it.copy(feedId = feed.id) -// }) -// } -// -// override suspend fun addGroup(name: String): String { -// return UUID.randomUUID().toString().also { -// groupDao.insert( -// Group( -// id = it, -// name = name, -// accountId = context.currentAccountId -// ) -// ) -// } -// } -// -// override suspend fun sync() { -// applicationScope.launch(dispatcherDefault) { -// mutex.withLock { -// val accountId = context.currentAccountId -// -// updateSyncState { -// it.copy( -// feedCount = 1, -// syncedCount = 1, -// currentFeedName = "Fever" -// ) -// } -// -// if (feedDao.queryAll(accountId).isNullOrEmpty()) { -// // Temporary add feeds -// val feverFeeds = feverApiDataSource.feeds().execute().body()!!.feeds -// val feverGroupsBody = feverApiDataSource.groups().execute().body()!! -// Log.i("RLog", "Fever groups: $feverGroupsBody") -// feverGroupsBody.groups.forEach { -// groupDao.insert( -// Group( -// id = accountId.spacerDollar(it.id), -// name = it.title, -// accountId = accountId, -// ) -// ) -// } -// val feverFeedsGroupsMap = mutableMapOf() -// feverGroupsBody.feeds_groups.forEach { item -> -// item.feed_ids -// .split(",") -// .map { it.toInt() } -// .forEach { id -> -// feverFeedsGroupsMap[id] = item.group_id -// } -// } -// val feeds = feverFeeds.map { -// Feed( -// id = accountId.spacerDollar(it.id), -// name = it.title, -// url = it.url, -// groupId = feverFeedsGroupsMap[it.id].toString(), -// accountId = accountId -// ) -// } -// feedDao.insertList(feeds) -// } -// -// // Add articles -// val articles = mutableListOf
() -// feverApiDataSource.itemsBySince(since = 1647444325925621L) -// .execute().body()!!.items -// .forEach { -// articles.add( -// Article( -// id = accountId.spacerDollar(it.id), -// date = Date(it.created_on_time * 1000), -// title = it.title, -// author = it.author, -// rawDescription = it.html, -// shortDescription = ( -// Readability4JExtended("", it.html) -// .parse().textContent ?: "" -// ).take(100).trim(), -// link = it.url, -// accountId = accountId, -// feedId = it.feed_id.toString(), -// isUnread = it.is_read == 0, -// isStarred = it.is_saved == 1, -// ) -// ) -// } -// articleDao.insertList(articles) -// -// // Complete sync -// accountDao.update(accountDao.queryById(accountId)!!.apply { -// updateAt = Date() -// }) -// updateSyncState { -// it.copy( -// feedCount = 0, -// syncedCount = 0, -// currentFeedName = "" -// ) -// } -// } -// } -// } -//} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt b/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt index d101a19..b09d0ad 100644 --- a/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt @@ -1,12 +1,7 @@ package me.ash.reader.data.repository -import android.app.* import android.content.Context -import android.content.Intent -import android.graphics.BitmapFactory import android.util.Log -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat import androidx.work.CoroutineWorker import androidx.work.ListenableWorker import androidx.work.WorkManager @@ -15,8 +10,6 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.withContext -import me.ash.reader.MainActivity -import me.ash.reader.R import me.ash.reader.data.dao.AccountDao import me.ash.reader.data.dao.ArticleDao import me.ash.reader.data.dao.FeedDao @@ -30,8 +23,6 @@ import me.ash.reader.data.module.DispatcherIO import me.ash.reader.data.repository.SyncWorker.Companion.setIsSyncing import me.ash.reader.ui.ext.currentAccountId import me.ash.reader.ui.ext.spacerDollar -import me.ash.reader.ui.page.common.ExtraName -import me.ash.reader.ui.page.common.NotificationGroupName import java.util.* import javax.inject.Inject @@ -41,6 +32,7 @@ class LocalRssRepository @Inject constructor( private val articleDao: ArticleDao, private val feedDao: FeedDao, private val rssHelper: RssHelper, + private val notificationHelper: NotificationHelper, private val accountDao: AccountDao, private val groupDao: GroupDao, @DispatcherDefault @@ -52,16 +44,6 @@ class LocalRssRepository @Inject constructor( context, accountDao, articleDao, groupDao, feedDao, workManager, dispatcherIO ) { - private val notificationManager: NotificationManagerCompat = - NotificationManagerCompat.from(context).apply { - createNotificationChannel( - NotificationChannel( - NotificationGroupName.ARTICLE_UPDATE, - NotificationGroupName.ARTICLE_UPDATE, - NotificationManager.IMPORTANCE_DEFAULT - ) - ) - } override suspend fun updateArticleInfo(article: Article) { articleDao.update(article) @@ -98,7 +80,7 @@ class LocalRssRepository @Inject constructor( .awaitAll() .forEach { if (it.isNotify) { - notify( + notificationHelper.notify( FeedWithArticle( it.feedWithArticle.feed, articleDao.insertIfNotExist(it.feedWithArticle.articles) @@ -183,75 +165,4 @@ class LocalRssRepository @Inject constructor( isNotify = articles.isNotEmpty() && feed.isNotification ) } - - private fun notify( - feedWithArticle: FeedWithArticle, - ) { - notificationManager.createNotificationChannelGroup( - NotificationChannelGroup( - feedWithArticle.feed.id, - feedWithArticle.feed.name - ) - ) - feedWithArticle.articles.forEach { article -> - val builder = NotificationCompat.Builder(context, NotificationGroupName.ARTICLE_UPDATE) - .setSmallIcon(R.drawable.ic_notification) - .setLargeIcon( - (BitmapFactory.decodeResource( - context.resources, - R.drawable.ic_notification - )) - ) - .setContentTitle(article.title) - .setContentIntent( - PendingIntent.getActivity( - context, - Random().nextInt() + article.id.hashCode(), - Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_CLEAR_TASK - putExtra( - ExtraName.ARTICLE_ID, - article.id - ) - }, - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - ) - ) - .setGroup(feedWithArticle.feed.id) - .setStyle( - NotificationCompat.BigTextStyle() - .bigText(article.shortDescription) - .setSummaryText(feedWithArticle.feed.name) - ) - - notificationManager.notify( - Random().nextInt() + article.id.hashCode(), - builder.build().apply { - flags = Notification.FLAG_AUTO_CANCEL - } - ) - } - - 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() - ) - } - } } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/repository/NotificationHelper.kt b/app/src/main/java/me/ash/reader/data/repository/NotificationHelper.kt new file mode 100644 index 0000000..ee421c8 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/repository/NotificationHelper.kt @@ -0,0 +1,103 @@ +package me.ash.reader.data.repository + +import android.app.* +import android.content.Context +import android.content.Intent +import android.graphics.BitmapFactory +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import me.ash.reader.MainActivity +import me.ash.reader.R +import me.ash.reader.data.entity.FeedWithArticle +import me.ash.reader.ui.page.common.ExtraName +import me.ash.reader.ui.page.common.NotificationGroupName +import java.util.* +import javax.inject.Inject + +class NotificationHelper @Inject constructor( + @ApplicationContext + private val context: Context, +) { + private val notificationManager: NotificationManagerCompat = + NotificationManagerCompat.from(context).apply { + createNotificationChannel( + NotificationChannel( + NotificationGroupName.ARTICLE_UPDATE, + NotificationGroupName.ARTICLE_UPDATE, + NotificationManager.IMPORTANCE_DEFAULT + ) + ) + } + + fun notify( + feedWithArticle: FeedWithArticle, + ) { + notificationManager.createNotificationChannelGroup( + NotificationChannelGroup( + feedWithArticle.feed.id, + feedWithArticle.feed.name + ) + ) + feedWithArticle.articles.forEach { article -> + val builder = NotificationCompat.Builder(context, NotificationGroupName.ARTICLE_UPDATE) + .setSmallIcon(R.drawable.ic_notification) + .setLargeIcon( + (BitmapFactory.decodeResource( + context.resources, + R.drawable.ic_notification + )) + ) + .setContentTitle(article.title) + .setContentIntent( + PendingIntent.getActivity( + context, + Random().nextInt() + article.id.hashCode(), + Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK + putExtra( + ExtraName.ARTICLE_ID, + article.id + ) + }, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + .setGroup(feedWithArticle.feed.id) + .setStyle( + NotificationCompat.BigTextStyle() + .bigText(article.shortDescription) + .setSummaryText(feedWithArticle.feed.name) + ) + + notificationManager.notify( + Random().nextInt() + article.id.hashCode(), + builder.build().apply { + flags = Notification.FLAG_AUTO_CANCEL + } + ) + } + + 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() + ) + } + } +} diff --git a/app/src/main/java/me/ash/reader/data/repository/OpmlRepository.kt b/app/src/main/java/me/ash/reader/data/repository/OpmlRepository.kt index 304bed4..7af9c0e 100644 --- a/app/src/main/java/me/ash/reader/data/repository/OpmlRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/OpmlRepository.kt @@ -43,7 +43,7 @@ class OpmlRepository @Inject constructor( repeatList.add(it) } } - feedDao.insertList((groupWithFeed.feeds subtract repeatList).toList()) + feedDao.insertList((groupWithFeed.feeds subtract repeatList.toSet()).toList()) } } diff --git a/app/src/main/java/me/ash/reader/data/repository/RssHelper.kt b/app/src/main/java/me/ash/reader/data/repository/RssHelper.kt index 18df76c..584fa48 100644 --- a/app/src/main/java/me/ash/reader/data/repository/RssHelper.kt +++ b/app/src/main/java/me/ash/reader/data/repository/RssHelper.kt @@ -183,27 +183,4 @@ class RssHelper @Inject constructor( } ) } - - private fun parseDate( - inputDate: String, patterns: Array = arrayOf( - "yyyy-MM-dd'T'HH:mm:ss'Z'", - "yyyy-MM-dd", - "yyyy-MM-dd HH:mm:ss", - "yyyyMMdd", - "yyyy/MM/dd", - "yyyy年MM月dd日", - "yyyy MM dd", - ) - ): Date? { - val df = SimpleDateFormat() - for (pattern in patterns) { - df.applyPattern(pattern) - df.isLenient = false - val date = df.parse(inputDate, ParsePosition(0)) - if (date != null) { - return date - } - } - return null - } } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/repository/SyncWorker.kt b/app/src/main/java/me/ash/reader/data/repository/SyncWorker.kt new file mode 100644 index 0000000..50cdaf2 --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/repository/SyncWorker.kt @@ -0,0 +1,41 @@ +package me.ash.reader.data.repository + +import android.content.Context +import android.util.Log +import androidx.hilt.work.HiltWorker +import androidx.work.* +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import java.util.* +import java.util.concurrent.TimeUnit + +@HiltWorker +class SyncWorker @AssistedInject constructor( + @Assisted context: Context, + @Assisted workerParams: WorkerParameters, + private val rssRepository: RssRepository, +) : CoroutineWorker(context, workerParams) { + + override suspend fun doWork(): Result { + Log.i("RLog", "doWork: ") + return rssRepository.get().sync(this) + } + + companion object { + const val WORK_NAME = "article.sync" + + val UUID: UUID + + val repeatingRequest = PeriodicWorkRequestBuilder( + 15, TimeUnit.MINUTES + ).setConstraints( + Constraints.Builder() + .build() + ).addTag(WORK_NAME).build().also { + UUID = it.id + } + + fun setIsSyncing(boolean: Boolean) = workDataOf("isSyncing" to boolean) + fun Data.getIsSyncing(): Boolean = getBoolean("isSyncing", false) + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/source/AppNetworkDataSource.kt b/app/src/main/java/me/ash/reader/data/source/AppNetworkDataSource.kt index a37d951..df0709e 100644 --- a/app/src/main/java/me/ash/reader/data/source/AppNetworkDataSource.kt +++ b/app/src/main/java/me/ash/reader/data/source/AppNetworkDataSource.kt @@ -5,7 +5,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn -import me.ash.reader.data.entity.LatestRelease import okhttp3.ResponseBody import retrofit2.Response import retrofit2.Retrofit @@ -15,12 +14,6 @@ import retrofit2.http.Streaming import retrofit2.http.Url import java.io.File -sealed class Download { - object NotYet : Download() - data class Progress(val percent: Int) : Download() - data class Finished(val file: File) : Download() -} - interface AppNetworkDataSource { @GET suspend fun getReleaseLatest(@Url url: String): Response @@ -92,4 +85,32 @@ fun ResponseBody.downloadToFileWithProgress(saveFile: File): Flow = saveFile.delete() } } - }.flowOn(Dispatchers.IO).distinctUntilChanged() \ No newline at end of file + }.flowOn(Dispatchers.IO).distinctUntilChanged() + +data class LatestRelease( + val html_url: String? = null, + val tag_name: String? = null, + val name: String? = null, + val draft: Boolean? = null, + val prerelease: Boolean? = null, + val created_at: String? = null, + val published_at: String? = null, + val assets: List? = null, + val body: String? = null, +) + +data class AssetsItem( + val name: String? = null, + val content_type: String? = null, + val size: Int? = null, + val download_count: Int? = null, + val created_at: String? = null, + val updated_at: String? = null, + val browser_download_url: String? = null, +) + +sealed class Download { + object NotYet : Download() + data class Progress(val percent: Int) : Download() + data class Finished(val file: File) : Download() +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/ext/ContextExt.kt b/app/src/main/java/me/ash/reader/ui/ext/ContextExt.kt index 203e575..2db93f0 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/ContextExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/ContextExt.kt @@ -7,8 +7,8 @@ import android.content.Intent import android.util.Log import android.widget.Toast import androidx.core.content.FileProvider -import me.ash.reader.data.entity.Version -import me.ash.reader.data.entity.toVersion +import me.ash.reader.data.model.Version +import me.ash.reader.data.model.toVersion import java.io.File fun Context.findActivity(): Activity? = when (this) { diff --git a/app/src/main/java/me/ash/reader/ui/ext/DateExt.kt b/app/src/main/java/me/ash/reader/ui/ext/DateExt.kt index 887b285..e609595 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/DateExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/DateExt.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.core.os.ConfigurationCompat import me.ash.reader.R import java.text.DateFormat +import java.text.ParsePosition import java.text.SimpleDateFormat import java.util.* @@ -40,4 +41,27 @@ fun Date.formatAsString( } } } +} + +private fun String.parseToDate( + patterns: Array = arrayOf( + "yyyy-MM-dd'T'HH:mm:ss'Z'", + "yyyy-MM-dd", + "yyyy-MM-dd HH:mm:ss", + "yyyyMMdd", + "yyyy/MM/dd", + "yyyy年MM月dd日", + "yyyy MM dd", + ) +): Date? { + val df = SimpleDateFormat() + for (pattern in patterns) { + df.applyPattern(pattern) + df.isLenient = false + val date = df.parse(this, ParsePosition(0)) + if (date != null) { + return date + } + } + return null } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/ext/FilterExt.kt b/app/src/main/java/me/ash/reader/ui/ext/FilterExt.kt deleted file mode 100644 index 8a2327a..0000000 --- a/app/src/main/java/me/ash/reader/ui/ext/FilterExt.kt +++ /dev/null @@ -1,13 +0,0 @@ -package me.ash.reader.ui.ext - -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import me.ash.reader.R -import me.ash.reader.data.entity.Filter - -@Composable -fun Filter.getName(): String = when (this) { - Filter.Unread -> stringResource(R.string.unread) - Filter.Starred -> stringResource(R.string.starred) - else -> stringResource(R.string.all) -} diff --git a/app/src/main/java/me/ash/reader/ui/ext/FlavorExt.kt b/app/src/main/java/me/ash/reader/ui/ext/FlavorExt.kt new file mode 100644 index 0000000..2a3e8b1 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/ext/FlavorExt.kt @@ -0,0 +1,11 @@ +@file:Suppress("SpellCheckingInspection") + +package me.ash.reader.ui.ext + +import me.ash.reader.BuildConfig + +const val GITHUB = "github" +const val FDROID = "fdroid" + +const val isFdroid = BuildConfig.FLAVOR == FDROID +const val notFdroid = !isFdroid \ No newline at end of file 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 87b5015..3855951 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 @@ -12,7 +12,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.google.accompanist.navigation.animation.AnimatedNavHost import com.google.accompanist.navigation.animation.rememberAnimatedNavController import com.google.accompanist.systemuicontroller.rememberSystemUiController -import me.ash.reader.data.entity.Filter +import me.ash.reader.data.model.Filter import me.ash.reader.data.preference.LocalDarkTheme import me.ash.reader.ui.ext.* import me.ash.reader.ui.page.home.HomeViewAction diff --git a/app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt index a9307e7..cb7dad0 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/FilterBar.kt @@ -12,10 +12,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp -import me.ash.reader.data.entity.Filter +import me.ash.reader.data.model.Filter +import me.ash.reader.data.model.getName import me.ash.reader.data.preference.FlowFilterBarStylePreference import me.ash.reader.data.preference.LocalThemeIndex -import me.ash.reader.ui.ext.getName import me.ash.reader.ui.ext.surfaceColorAtElevation import me.ash.reader.ui.theme.palette.onDark @@ -39,11 +39,7 @@ fun FilterBar( tonalElevation = filterBarTonalElevation, ) { Spacer(modifier = Modifier.width(filterBarPadding)) - listOf( - Filter.Starred, - Filter.Unread, - Filter.All, - ).forEach { item -> + Filter.values.forEach { item -> NavigationBarItem( // modifier = Modifier.height(60.dp), alwaysShowLabel = when (filterBarStyle) { 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 9149e23..3f7f258 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 @@ -7,7 +7,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import me.ash.reader.data.entity.Feed -import me.ash.reader.data.entity.Filter +import me.ash.reader.data.model.Filter import me.ash.reader.data.entity.Group import me.ash.reader.data.module.ApplicationScope import me.ash.reader.data.repository.RssRepository diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt index 4b22327..d522d5c 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt @@ -28,7 +28,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import kotlinx.coroutines.flow.map import me.ash.reader.R -import me.ash.reader.data.entity.toVersion +import me.ash.reader.data.model.getName +import me.ash.reader.data.model.toVersion import me.ash.reader.data.preference.* import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing import me.ash.reader.ui.component.Banner diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt index d6a1abd..81dd07f 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt @@ -25,13 +25,13 @@ import androidx.paging.compose.collectAsLazyPagingItems import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.ash.reader.R +import me.ash.reader.data.model.getName import me.ash.reader.data.preference.* import me.ash.reader.data.repository.SyncWorker.Companion.getIsSyncing import me.ash.reader.ui.component.DisplayText import me.ash.reader.ui.component.FeedbackIconButton import me.ash.reader.ui.component.SwipeRefresh import me.ash.reader.ui.ext.collectAsStateValue -import me.ash.reader.ui.ext.getName import me.ash.reader.ui.ext.surfaceColorAtElevation import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.home.FilterBar 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 5a88950..620bc52 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 @@ -19,7 +19,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import kotlinx.coroutines.flow.map import me.ash.reader.R -import me.ash.reader.data.entity.toVersion +import me.ash.reader.data.model.toVersion import me.ash.reader.ui.component.Banner import me.ash.reader.ui.component.DisplayText import me.ash.reader.ui.component.FeedbackIconButton diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStyle.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStyle.kt index a7fce97..2f7a95c 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStyle.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/feeds/FeedsPageStyle.kt @@ -22,7 +22,7 @@ import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import me.ash.reader.R import me.ash.reader.data.entity.Feed -import me.ash.reader.data.entity.Filter +import me.ash.reader.data.model.Filter import me.ash.reader.data.entity.Group import me.ash.reader.data.preference.* import me.ash.reader.ui.component.* diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStyle.kt b/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStyle.kt index a07e097..9471ece 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStyle.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/color/flow/FlowPageStyle.kt @@ -25,7 +25,7 @@ import me.ash.reader.R import me.ash.reader.data.entity.Article import me.ash.reader.data.entity.ArticleWithFeed import me.ash.reader.data.entity.Feed -import me.ash.reader.data.entity.Filter +import me.ash.reader.data.model.Filter import me.ash.reader.data.preference.* import me.ash.reader.ui.component.* import me.ash.reader.ui.ext.surfaceColorAtElevation diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateViewModel.kt index 03739ca..1a4e54a 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/tips/UpdateViewModel.kt @@ -5,9 +5,9 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import me.ash.reader.BuildConfig import me.ash.reader.data.repository.AppRepository import me.ash.reader.data.source.Download +import me.ash.reader.ui.ext.notFdroid import javax.inject.Inject @HiltViewModel @@ -33,7 +33,7 @@ class UpdateViewModel @Inject constructor( preProcessor: suspend () -> Unit = {}, postProcessor: suspend (Boolean) -> Unit = {} ) { - if (BuildConfig.FLAVOR != "fdroid") { + if (notFdroid) { viewModelScope.launch { preProcessor() appRepository.checkUpdate().let {