Fixed auto sync

This commit is contained in:
Ash 2022-03-21 22:40:40 +08:00
parent 582e0f8148
commit c7c708d92a
11 changed files with 106 additions and 107 deletions

View File

@ -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"

View File

@ -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>

View File

@ -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 {
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)
}
rssRepository.get().doSync(true)
}
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()
}

View File

@ -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"
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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),

View File

@ -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