Fixed auto sync
This commit is contained in:
parent
582e0f8148
commit
c7c708d92a
|
@ -54,6 +54,7 @@ dependencies {
|
|||
implementation("com.google.accompanist:accompanist-navigation-animation:0.24.3-alpha")
|
||||
implementation "androidx.datastore:datastore-preferences:1.0.0"
|
||||
implementation "com.airbnb.android:lottie-compose:4.2.2"
|
||||
implementation "androidx.hilt:hilt-work:1.0.0"
|
||||
implementation "androidx.work:work-runtime-ktx:2.8.0-alpha01"
|
||||
implementation "net.dankito.readability4j:readability4j:1.0.8"
|
||||
implementation "androidx.navigation:navigation-compose:2.5.0-alpha01"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="me.ash.reader">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
@ -23,6 +24,11 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
tools:node="remove">
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -1,22 +1,32 @@
|
|||
package me.ash.reader
|
||||
|
||||
import android.app.Application
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.*
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import me.ash.reader.data.repository.*
|
||||
import me.ash.reader.data.source.OpmlLocalDataSource
|
||||
import me.ash.reader.data.source.ReaderDatabase
|
||||
import me.ash.reader.data.source.RssNetworkDataSource
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
@HiltAndroidApp
|
||||
class App : Application() {
|
||||
class App : Application(), Configuration.Provider {
|
||||
@Inject
|
||||
lateinit var readerDatabase: ReaderDatabase
|
||||
|
||||
@Inject
|
||||
lateinit var workerFactory: HiltWorkerFactory
|
||||
|
||||
@Inject
|
||||
lateinit var workManager: WorkManager
|
||||
|
||||
@Inject
|
||||
lateinit var opmlLocalDataSource: OpmlLocalDataSource
|
||||
|
||||
|
@ -44,15 +54,43 @@ class App : Application() {
|
|||
@Inject
|
||||
lateinit var rssRepository: RssRepository
|
||||
|
||||
private val applicationScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
GlobalScope.launch {
|
||||
if (accountRepository.isNoAccount()) {
|
||||
val account = accountRepository.addDefaultAccount()
|
||||
applicationContext.dataStore.put(DataStoreKeys.CurrentAccountId, account.id!!)
|
||||
applicationContext.dataStore.put(DataStoreKeys.CurrentAccountType, account.type)
|
||||
}
|
||||
rssRepository.get().doSync(true)
|
||||
applicationScope.launch {
|
||||
accountInit()
|
||||
workerInit()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun accountInit() {
|
||||
if (accountRepository.isNoAccount()) {
|
||||
val account = accountRepository.addDefaultAccount()
|
||||
applicationContext.dataStore.put(DataStoreKeys.CurrentAccountId, account.id!!)
|
||||
applicationContext.dataStore.put(DataStoreKeys.CurrentAccountType, account.type)
|
||||
}
|
||||
}
|
||||
|
||||
private fun workerInit() {
|
||||
val repeatingRequest = PeriodicWorkRequestBuilder<SyncWorker>(
|
||||
15, TimeUnit.MINUTES
|
||||
).setConstraints(
|
||||
Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
).addTag(SyncWorker.WORK_NAME).build()
|
||||
|
||||
workManager.enqueueUniquePeriodicWork(
|
||||
SyncWorker.WORK_NAME,
|
||||
ExistingPeriodicWorkPolicy.REPLACE,
|
||||
repeatingRequest
|
||||
)
|
||||
}
|
||||
|
||||
override fun getWorkManagerConfiguration(): Configuration =
|
||||
Configuration.Builder()
|
||||
.setWorkerFactory(workerFactory)
|
||||
.setMinimumLoggingLevel(android.util.Log.DEBUG)
|
||||
.build()
|
||||
}
|
|
@ -2,11 +2,18 @@ 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 kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import me.ash.reader.DataStoreKeys
|
||||
import me.ash.reader.data.account.AccountDao
|
||||
import me.ash.reader.data.article.Article
|
||||
|
@ -18,12 +25,9 @@ import me.ash.reader.data.feed.FeedDao
|
|||
import me.ash.reader.data.group.Group
|
||||
import me.ash.reader.data.group.GroupDao
|
||||
import me.ash.reader.data.group.GroupWithFeed
|
||||
import me.ash.reader.data.source.ReaderDatabase
|
||||
import me.ash.reader.data.source.RssNetworkDataSource
|
||||
import me.ash.reader.dataStore
|
||||
import me.ash.reader.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class AbstractRssRepository constructor(
|
||||
private val context: Context,
|
||||
|
@ -43,19 +47,11 @@ abstract class AbstractRssRepository constructor(
|
|||
val isNotSyncing: Boolean = !isSyncing
|
||||
}
|
||||
|
||||
abstract fun getSyncState(): StateFlow<SyncState>
|
||||
|
||||
abstract suspend fun updateArticleInfo(article: Article)
|
||||
|
||||
abstract suspend fun subscribe(feed: Feed, articles: List<Article>)
|
||||
|
||||
abstract suspend fun sync(
|
||||
context: Context,
|
||||
accountDao: AccountDao,
|
||||
articleDao: ArticleDao,
|
||||
feedDao: FeedDao,
|
||||
rssNetworkDataSource: RssNetworkDataSource
|
||||
)
|
||||
abstract suspend fun sync()
|
||||
|
||||
fun pullGroups(): Flow<MutableList<Group>> {
|
||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId) ?: 0
|
||||
|
@ -135,46 +131,32 @@ abstract class AbstractRssRepository constructor(
|
|||
return workManager.getWorkInfosByTag("sync").get().size.toString()
|
||||
}
|
||||
|
||||
suspend fun doSync(isWork: Boolean? = false) {
|
||||
if (isWork == true) {
|
||||
workManager.cancelAllWork()
|
||||
val syncWorkerRequest: WorkRequest =
|
||||
PeriodicWorkRequestBuilder<SyncWorker>(
|
||||
15, TimeUnit.MINUTES
|
||||
).setConstraints(
|
||||
Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
).addTag("sync").build()
|
||||
workManager.enqueue(syncWorkerRequest)
|
||||
} else {
|
||||
sync(context, accountDao, articleDao, feedDao, rssNetworkDataSource)
|
||||
companion object {
|
||||
val mutex = Mutex()
|
||||
|
||||
private val _syncState = MutableStateFlow(SyncState())
|
||||
val syncState = _syncState.asStateFlow()
|
||||
|
||||
fun updateSyncState(function: (SyncState) -> SyncState) {
|
||||
_syncState.update(function)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
class SyncWorker(
|
||||
context: Context,
|
||||
workerParams: WorkerParameters,
|
||||
@HiltWorker
|
||||
class SyncWorker @AssistedInject constructor(
|
||||
@Assisted context: Context,
|
||||
@Assisted workerParams: WorkerParameters,
|
||||
private val rssRepository: RssRepository,
|
||||
) : CoroutineWorker(context, workerParams) {
|
||||
|
||||
@Inject
|
||||
lateinit var rssRepository: RssRepository
|
||||
|
||||
@Inject
|
||||
lateinit var rssNetworkDataSource: RssNetworkDataSource
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Log.i("RLog", "doWork: ")
|
||||
val db = ReaderDatabase.getInstance(applicationContext)
|
||||
rssRepository.get().sync(
|
||||
applicationContext,
|
||||
db.accountDao(),
|
||||
db.articleDao(),
|
||||
db.feedDao(),
|
||||
rssNetworkDataSource
|
||||
)
|
||||
rssRepository.get().sync()
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val WORK_NAME = "article.sync"
|
||||
}
|
||||
}
|
|
@ -4,9 +4,6 @@ import android.content.Context
|
|||
import android.util.Log
|
||||
import androidx.work.WorkManager
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import me.ash.reader.DataStoreKeys
|
||||
import me.ash.reader.data.account.AccountDao
|
||||
|
@ -33,18 +30,13 @@ class FeverRssRepository @Inject constructor(
|
|||
private val groupDao: GroupDao,
|
||||
private val rssHelper: RssHelper,
|
||||
private val feverApiDataSource: FeverApiDataSource,
|
||||
private val accountDao: AccountDao,
|
||||
rssNetworkDataSource: RssNetworkDataSource,
|
||||
accountDao: AccountDao,
|
||||
workManager: WorkManager,
|
||||
) : AbstractRssRepository(
|
||||
context, accountDao, articleDao, groupDao,
|
||||
feedDao, rssNetworkDataSource, workManager,
|
||||
) {
|
||||
private val mutex = Mutex()
|
||||
private val syncState = MutableStateFlow(SyncState())
|
||||
|
||||
override fun getSyncState() = syncState
|
||||
|
||||
override suspend fun updateArticleInfo(article: Article) {
|
||||
articleDao.update(article)
|
||||
}
|
||||
|
@ -56,18 +48,12 @@ class FeverRssRepository @Inject constructor(
|
|||
})
|
||||
}
|
||||
|
||||
override suspend fun sync(
|
||||
context: Context,
|
||||
accountDao: AccountDao,
|
||||
articleDao: ArticleDao,
|
||||
feedDao: FeedDao,
|
||||
rssNetworkDataSource: RssNetworkDataSource
|
||||
) {
|
||||
override suspend fun sync() {
|
||||
mutex.withLock {
|
||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)
|
||||
?: return
|
||||
|
||||
syncState.update {
|
||||
updateSyncState {
|
||||
it.copy(
|
||||
feedCount = 1,
|
||||
syncedCount = 1,
|
||||
|
@ -140,7 +126,7 @@ class FeverRssRepository @Inject constructor(
|
|||
accountDao.update(accountDao.queryById(accountId)!!.apply {
|
||||
updateAt = Date()
|
||||
})
|
||||
syncState.update {
|
||||
updateSyncState {
|
||||
it.copy(
|
||||
feedCount = 0,
|
||||
syncedCount = 0,
|
||||
|
|
|
@ -13,7 +13,6 @@ import androidx.core.content.ContextCompat.getSystemService
|
|||
import androidx.work.WorkManager
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import me.ash.reader.*
|
||||
import me.ash.reader.data.account.AccountDao
|
||||
|
@ -34,19 +33,14 @@ class LocalRssRepository @Inject constructor(
|
|||
private val articleDao: ArticleDao,
|
||||
private val feedDao: FeedDao,
|
||||
private val rssHelper: RssHelper,
|
||||
rssNetworkDataSource: RssNetworkDataSource,
|
||||
private val rssNetworkDataSource: RssNetworkDataSource,
|
||||
private val accountDao: AccountDao,
|
||||
groupDao: GroupDao,
|
||||
accountDao: AccountDao,
|
||||
workManager: WorkManager,
|
||||
) : AbstractRssRepository(
|
||||
context, accountDao, articleDao, groupDao,
|
||||
feedDao, rssNetworkDataSource, workManager,
|
||||
) {
|
||||
private val mutex = Mutex()
|
||||
private val syncState = MutableStateFlow(SyncState())
|
||||
|
||||
override fun getSyncState() = syncState
|
||||
|
||||
override suspend fun updateArticleInfo(article: Article) {
|
||||
articleDao.update(article)
|
||||
}
|
||||
|
@ -58,13 +52,7 @@ class LocalRssRepository @Inject constructor(
|
|||
})
|
||||
}
|
||||
|
||||
override suspend fun sync(
|
||||
context: Context,
|
||||
accountDao: AccountDao,
|
||||
articleDao: ArticleDao,
|
||||
feedDao: FeedDao,
|
||||
rssNetworkDataSource: RssNetworkDataSource
|
||||
) {
|
||||
override suspend fun sync() {
|
||||
mutex.withLock {
|
||||
val accountId = context.dataStore.get(DataStoreKeys.CurrentAccountId)
|
||||
?: return
|
||||
|
@ -98,10 +86,10 @@ class LocalRssRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
)
|
||||
syncState.update {
|
||||
updateSyncState {
|
||||
it.copy(
|
||||
feedCount = feeds.size,
|
||||
syncedCount = syncState.value.syncedCount + 1,
|
||||
syncedCount = it.syncedCount + 1,
|
||||
currentFeedName = feed.name
|
||||
)
|
||||
}
|
||||
|
@ -173,7 +161,7 @@ class LocalRssRepository @Inject constructor(
|
|||
}
|
||||
)
|
||||
}
|
||||
syncState.update {
|
||||
updateSyncState {
|
||||
it.copy(
|
||||
feedCount = 0,
|
||||
syncedCount = 0,
|
||||
|
|
|
@ -16,7 +16,6 @@ import androidx.navigation.NavHostController
|
|||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import me.ash.reader.ui.page.common.NotificationGroupName
|
||||
import me.ash.reader.ui.extension.collectAsStateValue
|
||||
import me.ash.reader.ui.extension.findActivity
|
||||
import me.ash.reader.ui.page.common.ExtraName
|
||||
|
@ -46,9 +45,9 @@ fun HomePage(
|
|||
intent.extras?.get(ExtraName.ARTICLE_ID)?.let {
|
||||
readViewModel.dispatch(ReadViewAction.ScrollToItem(2))
|
||||
scope.launch {
|
||||
val article =
|
||||
readViewModel.rssRepository.get().findArticleById(it.toString().toInt())
|
||||
?: return@launch
|
||||
val article = readViewModel
|
||||
.rssRepository.get()
|
||||
.findArticleById(it.toString().toInt()) ?: return@launch
|
||||
readViewModel.dispatch(ReadViewAction.InitData(article))
|
||||
if (article.feed.isFullContent) readViewModel.dispatch(ReadViewAction.RenderFullContent)
|
||||
else readViewModel.dispatch(ReadViewAction.RenderDescriptionContent)
|
||||
|
|
|
@ -15,6 +15,7 @@ import kotlinx.coroutines.launch
|
|||
import me.ash.reader.data.constant.Filter
|
||||
import me.ash.reader.data.feed.Feed
|
||||
import me.ash.reader.data.group.Group
|
||||
import me.ash.reader.data.repository.AbstractRssRepository
|
||||
import me.ash.reader.data.repository.RssRepository
|
||||
import me.ash.reader.ui.extension.animateScrollToPage
|
||||
import javax.inject.Inject
|
||||
|
@ -31,7 +32,7 @@ class HomeViewModel @Inject constructor(
|
|||
private val _filterState = MutableStateFlow(FilterState())
|
||||
val filterState = _filterState.asStateFlow()
|
||||
|
||||
val syncState = rssRepository.get().getSyncState()
|
||||
val syncState = AbstractRssRepository.syncState
|
||||
|
||||
fun dispatch(action: HomeViewAction) {
|
||||
when (action) {
|
||||
|
@ -47,7 +48,7 @@ class HomeViewModel @Inject constructor(
|
|||
|
||||
private fun sync(callback: () -> Unit = {}) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
rssRepository.get().doSync()
|
||||
rssRepository.get().sync()
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class FeedsViewModel @Inject constructor(
|
|||
private fun addFromFile(inputStream: InputStream) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
opmlRepository.saveToDatabase(inputStream)
|
||||
pullFeeds(isStarred = false, isUnread = false)
|
||||
rssRepository.get().sync()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,6 @@ class FeedsViewModel @Inject constructor(
|
|||
isStarred = filterState.filter.isStarred(),
|
||||
isUnread = filterState.filter.isUnread(),
|
||||
)
|
||||
_viewState
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package me.ash.reader.ui.page.home.flow
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
@ -77,7 +78,11 @@ fun FlowPage(
|
|||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = {}) {
|
||||
IconButton(onClick = {
|
||||
viewModel.dispatch(FlowViewAction.PeekSyncWork)
|
||||
Toast.makeText(context, viewState.syncWorkInfo.length.toString(), Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.DoneAll,
|
||||
contentDescription = stringResource(R.string.mark_all_as_read),
|
||||
|
|
|
@ -21,9 +21,3 @@ kotlin.code.style=official
|
|||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
|
||||
# Custom
|
||||
org.gradle.daemon=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.caching = true
|
Loading…
Reference in New Issue
Block a user