diff --git a/app/build.gradle b/app/build.gradle index 14b037c..710c85c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d61d9fa..c3c90ef 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -23,6 +24,11 @@ + + \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/App.kt b/app/src/main/java/me/ash/reader/App.kt index f5d3855..a433564 100644 --- a/app/src/main/java/me/ash/reader/App.kt +++ b/app/src/main/java/me/ash/reader/App.kt @@ -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( + 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() } \ No newline at end of file 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 91b0751..0149284 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,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 - abstract suspend fun updateArticleInfo(article: Article) abstract suspend fun subscribe(feed: Feed, articles: List
) - abstract suspend fun sync( - context: Context, - accountDao: AccountDao, - articleDao: ArticleDao, - feedDao: FeedDao, - rssNetworkDataSource: RssNetworkDataSource - ) + abstract suspend fun sync() fun pullGroups(): Flow> { 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( - 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" + } } \ No newline at end of file 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 index d0c505e..ff7745d 100644 --- a/app/src/main/java/me/ash/reader/data/repository/FeverRssRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/FeverRssRepository.kt @@ -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, 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 8988612..a9d420e 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 @@ -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, diff --git a/app/src/main/java/me/ash/reader/ui/page/home/HomePage.kt b/app/src/main/java/me/ash/reader/ui/page/home/HomePage.kt index 67723d7..007bec8 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/HomePage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/HomePage.kt @@ -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) 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 3a5a3a8..ef7e6e0 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 @@ -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() } } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt index 892006a..89c8d07 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt @@ -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 } } 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 27b2ae0..0a2511d 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 @@ -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), diff --git a/gradle.properties b/gradle.properties index 75833fb..3c7a8bd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 \ No newline at end of file