Refactor data layer
This commit is contained in:
parent
ee55f671bc
commit
b33e0a7ac5
|
@ -5,9 +5,6 @@
|
|||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!-- Disable automatic updates in F-Droid -->
|
||||
<!-- <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />-->
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
|
|
|
@ -16,12 +16,9 @@ import me.ash.reader.data.source.AppNetworkDataSource
|
|||
import me.ash.reader.data.source.OpmlLocalDataSource
|
||||
import me.ash.reader.data.source.ReaderDatabase
|
||||
import me.ash.reader.ui.ext.*
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import org.conscrypt.Conscrypt
|
||||
import java.io.File
|
||||
import java.security.Security
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidApp
|
||||
|
@ -50,6 +47,9 @@ class App : Application(), Configuration.Provider {
|
|||
@Inject
|
||||
lateinit var rssHelper: RssHelper
|
||||
|
||||
@Inject
|
||||
lateinit var notificationHelper: NotificationHelper
|
||||
|
||||
@Inject
|
||||
lateinit var appRepository: AppRepository
|
||||
|
||||
|
@ -62,9 +62,6 @@ class App : Application(), Configuration.Provider {
|
|||
@Inject
|
||||
lateinit var localRssRepository: LocalRssRepository
|
||||
|
||||
// @Inject
|
||||
// lateinit var feverRssRepository: FeverRssRepository
|
||||
|
||||
@Inject
|
||||
lateinit var opmlRepository: OpmlRepository
|
||||
|
||||
|
@ -92,7 +89,7 @@ class App : Application(), Configuration.Provider {
|
|||
applicationScope.launch(dispatcherDefault) {
|
||||
accountInit()
|
||||
workerInit()
|
||||
if (BuildConfig.FLAVOR != "fdroid") {
|
||||
if (notFdroid) {
|
||||
checkUpdate()
|
||||
}
|
||||
}
|
||||
|
@ -128,28 +125,3 @@ class App : Application(), Configuration.Provider {
|
|||
.setMinimumLoggingLevel(android.util.Log.DEBUG)
|
||||
.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()
|
||||
}
|
|
@ -5,7 +5,7 @@ import androidx.room.*
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import me.ash.reader.data.entity.Article
|
||||
import me.ash.reader.data.entity.ArticleWithFeed
|
||||
import me.ash.reader.data.entity.ImportantCount
|
||||
import me.ash.reader.data.model.ImportantCount
|
||||
import java.util.*
|
||||
|
||||
@Dao
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
package me.ash.reader.data.entity
|
||||
|
||||
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<AssetsItem>? = 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,
|
||||
)
|
|
@ -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)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package me.ash.reader.data.entity
|
||||
package me.ash.reader.data.model
|
||||
|
||||
data class ImportantCount(
|
||||
val important: Int,
|
|
@ -0,0 +1,2 @@
|
|||
package me.ash.reader.data.model
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package me.ash.reader.data.entity
|
||||
package me.ash.reader.data.model
|
||||
|
||||
class Version(identifiers: List<String>) {
|
||||
private var major: Int = 0
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<out X509Certificate>?, authType: String?) {
|
||||
}
|
||||
|
||||
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
|
||||
}
|
||||
|
||||
val sslContext = SSLContext.getInstance("TLS")
|
||||
sslContext.init(null, arrayOf<TrustManager>(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(
|
||||
|
|
|
@ -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<SyncWorker>(
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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)) {
|
||||
|
|
|
@ -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<Article>) {
|
||||
// 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<Int, Int>()
|
||||
// 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<Article>()
|
||||
// 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 = ""
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -183,27 +183,4 @@ class RssHelper @Inject constructor(
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseDate(
|
||||
inputDate: String, patterns: Array<String> = 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
|
||||
}
|
||||
}
|
|
@ -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<SyncWorker>(
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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<LatestRelease>
|
||||
|
@ -93,3 +86,31 @@ fun ResponseBody.downloadToFileWithProgress(saveFile: File): Flow<Download> =
|
|||
}
|
||||
}
|
||||
}.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<AssetsItem>? = 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()
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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.*
|
||||
|
||||
|
@ -41,3 +42,26 @@ fun Date.formatAsString(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.parseToDate(
|
||||
patterns: Array<String> = 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
|
||||
}
|
|
@ -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)
|
||||
}
|
11
app/src/main/java/me/ash/reader/ui/ext/FlavorExt.kt
Normal file
11
app/src/main/java/me/ash/reader/ui/ext/FlavorExt.kt
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue
Block a user