Tracker tests and fixes
This commit is contained in:
@@ -149,6 +149,34 @@ class TrackerTest : KoinTest {
|
|||||||
assertEquals(0, repository.getNewChaptersCount(mangaFull.id))
|
assertEquals(0, repository.getNewChaptersCount(mangaFull.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun syncWithHistory() = runTest {
|
||||||
|
val mangaFull = loadManga("full.json")
|
||||||
|
val mangaFirst = loadManga("first_chapters.json")
|
||||||
|
tracker.deleteTrack(mangaFull.id)
|
||||||
|
|
||||||
|
tracker.checkUpdates(mangaFirst, commit = true).apply {
|
||||||
|
assertFalse(isValid)
|
||||||
|
assert(newChapters.isEmpty())
|
||||||
|
}
|
||||||
|
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
|
||||||
|
tracker.checkUpdates(mangaFull, commit = true).apply {
|
||||||
|
assertTrue(isValid)
|
||||||
|
assertEquals(3, newChapters.size)
|
||||||
|
}
|
||||||
|
assertEquals(3, repository.getNewChaptersCount(mangaFirst.id))
|
||||||
|
|
||||||
|
val chapter = requireNotNull(mangaFull.chapters).run { get(lastIndex - 1) }
|
||||||
|
repository.syncWithHistory(mangaFull, chapter.id)
|
||||||
|
|
||||||
|
assertEquals(1, repository.getNewChaptersCount(mangaFirst.id))
|
||||||
|
tracker.checkUpdates(mangaFull, commit = true).apply {
|
||||||
|
assertTrue(isValid)
|
||||||
|
assert(newChapters.isEmpty())
|
||||||
|
}
|
||||||
|
assertEquals(1, repository.getNewChaptersCount(mangaFirst.id))
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun loadManga(name: String): Manga {
|
private suspend fun loadManga(name: String): Manga {
|
||||||
val assets = InstrumentationRegistry.getInstrumentation().context.assets
|
val assets = InstrumentationRegistry.getInstrumentation().context.assets
|
||||||
val manga = assets.open("manga/$name").use {
|
val manga = assets.open("manga/$name").use {
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
package org.koitharu.kotatsu.details.ui
|
package org.koitharu.kotatsu.details.ui
|
||||||
|
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.LiveData
|
||||||
import kotlinx.coroutines.*
|
import androidx.lifecycle.asFlow
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import java.io.IOException
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||||
@@ -23,14 +30,13 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
|||||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class DetailsViewModel(
|
class DetailsViewModel(
|
||||||
intent: MangaIntent,
|
intent: MangaIntent,
|
||||||
private val historyRepository: HistoryRepository,
|
private val historyRepository: HistoryRepository,
|
||||||
favouritesRepository: FavouritesRepository,
|
favouritesRepository: FavouritesRepository,
|
||||||
private val localMangaRepository: LocalMangaRepository,
|
private val localMangaRepository: LocalMangaRepository,
|
||||||
private val trackingRepository: TrackingRepository,
|
trackingRepository: TrackingRepository,
|
||||||
mangaDataRepository: MangaDataRepository,
|
mangaDataRepository: MangaDataRepository,
|
||||||
private val bookmarksRepository: BookmarksRepository,
|
private val bookmarksRepository: BookmarksRepository,
|
||||||
private val settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
@@ -54,9 +60,8 @@ class DetailsViewModel(
|
|||||||
private val favourite = favouritesRepository.observeCategoriesIds(delegate.mangaId).map { it.isNotEmpty() }
|
private val favourite = favouritesRepository.observeCategoriesIds(delegate.mangaId).map { it.isNotEmpty() }
|
||||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
|
||||||
|
|
||||||
private val newChapters = viewModelScope.async(Dispatchers.Default) {
|
private val newChapters = trackingRepository.observeNewChaptersCount(delegate.mangaId)
|
||||||
trackingRepository.getNewChaptersCount(delegate.mangaId)
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0)
|
||||||
}
|
|
||||||
|
|
||||||
private val chaptersQuery = MutableStateFlow("")
|
private val chaptersQuery = MutableStateFlow("")
|
||||||
|
|
||||||
@@ -65,7 +70,7 @@ class DetailsViewModel(
|
|||||||
|
|
||||||
val manga = delegate.manga.filterNotNull().asLiveData(viewModelScope.coroutineContext)
|
val manga = delegate.manga.filterNotNull().asLiveData(viewModelScope.coroutineContext)
|
||||||
val favouriteCategories = favourite.asLiveData(viewModelScope.coroutineContext)
|
val favouriteCategories = favourite.asLiveData(viewModelScope.coroutineContext)
|
||||||
val newChaptersCount = liveData(viewModelScope.coroutineContext) { emit(newChapters.await()) }
|
val newChaptersCount = newChapters.asLiveData(viewModelScope.coroutineContext)
|
||||||
val readingHistory = history.asLiveData(viewModelScope.coroutineContext)
|
val readingHistory = history.asLiveData(viewModelScope.coroutineContext)
|
||||||
val isChaptersReversed = chaptersReversed.asLiveData(viewModelScope.coroutineContext)
|
val isChaptersReversed = chaptersReversed.asLiveData(viewModelScope.coroutineContext)
|
||||||
|
|
||||||
@@ -97,8 +102,9 @@ class DetailsViewModel(
|
|||||||
delegate.relatedManga,
|
delegate.relatedManga,
|
||||||
history,
|
history,
|
||||||
delegate.selectedBranch,
|
delegate.selectedBranch,
|
||||||
) { manga, related, history, branch ->
|
newChapters,
|
||||||
delegate.mapChapters(manga, related, history, newChapters.await(), branch)
|
) { manga, related, history, branch, news ->
|
||||||
|
delegate.mapChapters(manga, related, history, news, branch)
|
||||||
},
|
},
|
||||||
chaptersReversed,
|
chaptersReversed,
|
||||||
chaptersQuery,
|
chaptersQuery,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.koitharu.kotatsu.tracker.data
|
package org.koitharu.kotatsu.tracker.data
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
abstract class TracksDao {
|
abstract class TracksDao {
|
||||||
@@ -17,6 +18,9 @@ abstract class TracksDao {
|
|||||||
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
|
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
|
||||||
abstract suspend fun findNewChapters(mangaId: Long): Int?
|
abstract suspend fun findNewChapters(mangaId: Long): Int?
|
||||||
|
|
||||||
|
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
|
||||||
|
abstract fun observeNewChapters(mangaId: Long): Flow<Int?>
|
||||||
|
|
||||||
@Query("DELETE FROM tracks")
|
@Query("DELETE FROM tracks")
|
||||||
abstract suspend fun clear()
|
abstract suspend fun clear()
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package org.koitharu.kotatsu.tracker.domain
|
|||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||||
@@ -28,6 +30,10 @@ class TrackingRepository(
|
|||||||
return db.tracksDao.findNewChapters(mangaId) ?: 0
|
return db.tracksDao.findNewChapters(mangaId) ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun observeNewChaptersCount(mangaId: Long): Flow<Int> {
|
||||||
|
return db.tracksDao.observeNewChapters(mangaId).map { it ?: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getTracks(mangaList: Collection<Manga>): List<MangaTracking> {
|
suspend fun getTracks(mangaList: Collection<Manga>): List<MangaTracking> {
|
||||||
val ids = mangaList.mapToSet { it.id }
|
val ids = mangaList.mapToSet { it.id }
|
||||||
val tracks = db.tracksDao.findAll(ids).groupBy { it.mangaId }
|
val tracks = db.tracksDao.findAll(ids).groupBy { it.mangaId }
|
||||||
|
|||||||
@@ -7,4 +7,7 @@ class MangaUpdates(
|
|||||||
val manga: Manga,
|
val manga: Manga,
|
||||||
val newChapters: List<MangaChapter>,
|
val newChapters: List<MangaChapter>,
|
||||||
val isValid: Boolean,
|
val isValid: Boolean,
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
fun isNotEmpty() = newChapters.isNotEmpty()
|
||||||
|
}
|
||||||
@@ -25,7 +25,6 @@ import org.koitharu.kotatsu.tracker.work.TrackWorker
|
|||||||
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
||||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||||
import org.koitharu.kotatsu.utils.ext.measureHeight
|
import org.koitharu.kotatsu.utils.ext.measureHeight
|
||||||
import org.koitharu.kotatsu.utils.progress.Progress
|
|
||||||
|
|
||||||
class FeedFragment :
|
class FeedFragment :
|
||||||
BaseFragment<FragmentFeedBinding>(),
|
BaseFragment<FragmentFeedBinding>(),
|
||||||
@@ -35,7 +34,6 @@ class FeedFragment :
|
|||||||
private val viewModel by viewModel<FeedViewModel>()
|
private val viewModel by viewModel<FeedViewModel>()
|
||||||
|
|
||||||
private var feedAdapter: FeedAdapter? = null
|
private var feedAdapter: FeedAdapter? = null
|
||||||
private var updateStatusSnackbar: Snackbar? = null
|
|
||||||
private var paddingVertical = 0
|
private var paddingVertical = 0
|
||||||
private var paddingHorizontal = 0
|
private var paddingHorizontal = 0
|
||||||
|
|
||||||
@@ -60,6 +58,7 @@ class FeedFragment :
|
|||||||
)
|
)
|
||||||
addItemDecoration(decoration)
|
addItemDecoration(decoration)
|
||||||
}
|
}
|
||||||
|
binding.swipeRefreshLayout.isEnabled = false
|
||||||
addMenuProvider(FeedMenuProvider(binding.recyclerView, viewModel))
|
addMenuProvider(FeedMenuProvider(binding.recyclerView, viewModel))
|
||||||
|
|
||||||
viewModel.content.observe(viewLifecycleOwner, this::onListChanged)
|
viewModel.content.observe(viewLifecycleOwner, this::onListChanged)
|
||||||
@@ -67,13 +66,12 @@ class FeedFragment :
|
|||||||
viewModel.onFeedCleared.observe(viewLifecycleOwner) {
|
viewModel.onFeedCleared.observe(viewLifecycleOwner) {
|
||||||
onFeedCleared()
|
onFeedCleared()
|
||||||
}
|
}
|
||||||
TrackWorker.getProgressLiveData(view.context.applicationContext)
|
TrackWorker.getIsRunningLiveData(view.context.applicationContext)
|
||||||
.observe(viewLifecycleOwner, this::onUpdateProgressChanged)
|
.observe(viewLifecycleOwner, this::onIsTrackerRunningChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
feedAdapter = null
|
feedAdapter = null
|
||||||
updateStatusSnackbar = null
|
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,23 +113,8 @@ class FeedFragment :
|
|||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onUpdateProgressChanged(progress: Progress?) {
|
private fun onIsTrackerRunningChanged(isRunning: Boolean) {
|
||||||
if (progress == null) {
|
binding.swipeRefreshLayout.isRefreshing = isRunning
|
||||||
updateStatusSnackbar?.dismiss()
|
|
||||||
updateStatusSnackbar = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val summaryText = getString(
|
|
||||||
R.string.chapters_checking_progress,
|
|
||||||
progress.value + 1,
|
|
||||||
progress.total
|
|
||||||
)
|
|
||||||
updateStatusSnackbar?.setText(summaryText) ?: run {
|
|
||||||
val snackbar =
|
|
||||||
Snackbar.make(binding.recyclerView, summaryText, Snackbar.LENGTH_INDEFINITE)
|
|
||||||
updateStatusSnackbar = snackbar
|
|
||||||
snackbar.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScrolledToEnd() {
|
override fun onScrolledToEnd() {
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ import androidx.work.*
|
|||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
@@ -25,11 +24,11 @@ import org.koitharu.kotatsu.details.ui.DetailsActivity
|
|||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
import org.koitharu.kotatsu.tracker.domain.Tracker
|
import org.koitharu.kotatsu.tracker.domain.Tracker
|
||||||
|
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
|
||||||
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
||||||
import org.koitharu.kotatsu.utils.ext.referer
|
import org.koitharu.kotatsu.utils.ext.referer
|
||||||
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
|
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
|
||||||
import org.koitharu.kotatsu.utils.ext.trySetForeground
|
import org.koitharu.kotatsu.utils.ext.trySetForeground
|
||||||
import org.koitharu.kotatsu.utils.progress.Progress
|
|
||||||
|
|
||||||
class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||||
CoroutineWorker(context, workerParams),
|
CoroutineWorker(context, workerParams),
|
||||||
@@ -45,40 +44,59 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
|||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
if (!settings.isTrackerEnabled) {
|
if (!settings.isTrackerEnabled) {
|
||||||
return Result.success()
|
return Result.success(workDataOf(0, 0))
|
||||||
}
|
}
|
||||||
if (TAG in tags) { // not expedited
|
if (TAG in tags) { // not expedited
|
||||||
trySetForeground()
|
trySetForeground()
|
||||||
}
|
}
|
||||||
val tracks = tracker.getAllTracks()
|
val tracks = tracker.getAllTracks()
|
||||||
|
if (tracks.isEmpty()) {
|
||||||
|
return Result.success(workDataOf(0, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
val updates = checkUpdatesAsync(tracks)
|
||||||
|
val results = updates.awaitAll()
|
||||||
|
tracker.gc()
|
||||||
|
|
||||||
var success = 0
|
var success = 0
|
||||||
val workData = Data.Builder().putInt(DATA_TOTAL, tracks.size)
|
var failed = 0
|
||||||
for ((index, item) in tracks.withIndex()) {
|
results.forEach { x ->
|
||||||
val (track, channelId) = item
|
if (x == null) {
|
||||||
val updates = runCatching {
|
failed++
|
||||||
tracker.fetchUpdates(track, commit = true)
|
} else {
|
||||||
}.onSuccess {
|
|
||||||
success++
|
success++
|
||||||
}.getOrNull()
|
|
||||||
workData.putInt(DATA_PROGRESS, index)
|
|
||||||
setProgress(workData.build())
|
|
||||||
if (updates != null && updates.newChapters.isNotEmpty()) {
|
|
||||||
showNotification(
|
|
||||||
manga = updates.manga,
|
|
||||||
channelId = channelId,
|
|
||||||
newChapters = updates.newChapters,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tracker.gc()
|
val resultData = workDataOf(success, failed)
|
||||||
return if (success == 0) {
|
return if (success == 0 && failed != 0) {
|
||||||
Result.retry()
|
Result.failure(resultData)
|
||||||
} else {
|
} else {
|
||||||
Result.success()
|
Result.success(resultData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun checkUpdatesAsync(tracks: List<TrackingItem>): List<Deferred<MangaUpdates?>> {
|
||||||
|
val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM)
|
||||||
|
val deferredList = coroutineScope {
|
||||||
|
tracks.map { (track, channelId) ->
|
||||||
|
async(dispatcher) {
|
||||||
|
runCatching {
|
||||||
|
tracker.fetchUpdates(track, commit = true)
|
||||||
|
}.onSuccess { updates ->
|
||||||
|
if (updates.isValid && updates.isNotEmpty()) {
|
||||||
|
showNotification(
|
||||||
|
manga = updates.manga,
|
||||||
|
channelId = channelId,
|
||||||
|
newChapters = updates.newChapters,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deferredList
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun showNotification(manga: Manga, channelId: String?, newChapters: List<MangaChapter>) {
|
private suspend fun showNotification(manga: Manga, channelId: String?, newChapters: List<MangaChapter>) {
|
||||||
if (newChapters.isEmpty() || channelId == null) {
|
if (newChapters.isEmpty() || channelId == null) {
|
||||||
return
|
return
|
||||||
@@ -160,14 +178,22 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
|||||||
return ForegroundInfo(WORKER_NOTIFICATION_ID, notification)
|
return ForegroundInfo(WORKER_NOTIFICATION_ID, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun workDataOf(success: Int, failed: Int): Data {
|
||||||
|
return Data.Builder()
|
||||||
|
.putInt(DATA_KEY_SUCCESS, success)
|
||||||
|
.putInt(DATA_KEY_FAILED, failed)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val WORKER_CHANNEL_ID = "track_worker"
|
private const val WORKER_CHANNEL_ID = "track_worker"
|
||||||
private const val WORKER_NOTIFICATION_ID = 35
|
private const val WORKER_NOTIFICATION_ID = 35
|
||||||
private const val DATA_PROGRESS = "progress"
|
|
||||||
private const val DATA_TOTAL = "total"
|
|
||||||
private const val TAG = "tracking"
|
private const val TAG = "tracking"
|
||||||
private const val TAG_ONESHOT = "tracking_oneshot"
|
private const val TAG_ONESHOT = "tracking_oneshot"
|
||||||
|
private const val MAX_PARALLELISM = 4
|
||||||
|
private const val DATA_KEY_SUCCESS = "success"
|
||||||
|
private const val DATA_KEY_FAILED = "failed"
|
||||||
|
|
||||||
fun setup(context: Context) {
|
fun setup(context: Context) {
|
||||||
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
|
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
|
||||||
@@ -184,17 +210,16 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
|||||||
WorkManager.getInstance(context).enqueue(request)
|
WorkManager.getInstance(context).enqueue(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getProgressLiveData(context: Context): LiveData<Progress?> {
|
fun getIsRunningLiveData(context: Context): LiveData<Boolean> {
|
||||||
return WorkManager.getInstance(context).getWorkInfosByTagLiveData(TAG).map { list ->
|
val query = WorkQuery.Builder.fromTags(listOf(TAG, TAG_ONESHOT)).build()
|
||||||
list.find { work ->
|
return WorkManager.getInstance(context).getWorkInfosLiveData(query).map { works ->
|
||||||
work.state == WorkInfo.State.RUNNING
|
works.any { x -> x.state == WorkInfo.State.RUNNING }
|
||||||
}?.let { workInfo ->
|
|
||||||
Progress(
|
|
||||||
value = workInfo.progress.getInt(DATA_PROGRESS, 0),
|
|
||||||
total = workInfo.progress.getInt(DATA_TOTAL, -1)
|
|
||||||
).takeUnless { it.isIndeterminate }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getInfo(context: Context): List<WorkInfo> {
|
||||||
|
val query = WorkQuery.Builder.fromTags(listOf(TAG, TAG_ONESHOT)).build()
|
||||||
|
return WorkManager.getInstance(context).getWorkInfos(query).await().orEmpty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/swipeRefreshLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:clipToPadding="false"
|
|
||||||
android:orientation="vertical"
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:paddingLeft="@dimen/list_spacing"
|
android:id="@+id/recyclerView"
|
||||||
android:paddingRight="@dimen/list_spacing"
|
android:layout_width="match_parent"
|
||||||
android:paddingTop="@dimen/grid_spacing_outer"
|
android:layout_height="match_parent"
|
||||||
android:paddingBottom="@dimen/grid_spacing_outer"
|
android:clipToPadding="false"
|
||||||
app:fastScrollEnabled="true"
|
android:orientation="vertical"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
android:paddingLeft="@dimen/list_spacing"
|
||||||
tools:listitem="@layout/item_feed" />
|
android:paddingTop="@dimen/grid_spacing_outer"
|
||||||
|
android:paddingRight="@dimen/list_spacing"
|
||||||
|
android:paddingBottom="@dimen/grid_spacing_outer"
|
||||||
|
app:fastScrollEnabled="true"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/item_feed" />
|
||||||
|
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
Reference in New Issue
Block a user