Update components scope

This commit is contained in:
Koitharu
2023-03-13 19:14:17 +02:00
parent b4e0704a3a
commit efc4bbacb5
16 changed files with 70 additions and 47 deletions

View File

@@ -30,10 +30,12 @@ import java.io.File
import java.io.InputStream
import java.util.zip.ZipFile
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.roundToInt
private const val MIN_WEBTOON_RATIO = 2
@Singleton
class MangaDataRepository @Inject constructor(
private val okHttpClient: OkHttpClient,
private val db: MangaDatabase,

View File

@@ -17,7 +17,9 @@ import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.mapItems
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import javax.inject.Singleton
@Singleton
class BookmarksRepository @Inject constructor(
private val db: MangaDatabase,
) {

View File

@@ -7,7 +7,6 @@ import android.text.style.ForegroundColorSpan
import androidx.core.text.getSpans
import androidx.core.text.parseAsHtml
import androidx.lifecycle.LiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.asFlow
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
@@ -27,12 +26,9 @@ import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.base.domain.MangaIntent
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.details.domain.BranchComparator
@@ -58,27 +54,17 @@ import javax.inject.Inject
@HiltViewModel
class DetailsViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val historyRepository: HistoryRepository,
favouritesRepository: FavouritesRepository,
private val localMangaRepository: LocalMangaRepository,
trackingRepository: TrackingRepository,
mangaDataRepository: MangaDataRepository,
private val bookmarksRepository: BookmarksRepository,
private val settings: AppSettings,
private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
private val imageGetter: Html.ImageGetter,
mangaRepositoryFactory: MangaRepository.Factory,
private val delegate: MangaDetailsDelegate,
) : BaseViewModel() {
private val delegate = MangaDetailsDelegate(
intent = MangaIntent(savedStateHandle),
mangaDataRepository = mangaDataRepository,
historyRepository = historyRepository,
localMangaRepository = localMangaRepository,
mangaRepositoryFactory = mangaRepositoryFactory,
)
private var loadingJob: Job
val onShowToast = SingleLiveEvent<Int>()

View File

@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.details.ui
import androidx.lifecycle.SavedStateHandle
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.koitharu.kotatsu.base.domain.MangaDataRepository
@@ -17,15 +19,17 @@ import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import javax.inject.Inject
class MangaDetailsDelegate(
private val intent: MangaIntent,
@ViewModelScoped
class MangaDetailsDelegate @Inject constructor(
savedStateHandle: SavedStateHandle,
private val mangaDataRepository: MangaDataRepository,
private val historyRepository: HistoryRepository,
private val localMangaRepository: LocalMangaRepository,
private val mangaRepositoryFactory: MangaRepository.Factory,
) {
private val intent = MangaIntent(savedStateHandle)
private val mangaData = MutableStateFlow(intent.manga)
val selectedBranch = MutableStateFlow<String?>(null)

View File

@@ -1,17 +1,18 @@
package org.koitharu.kotatsu.download.domain
import android.app.Service
import android.content.Context
import android.webkit.MimeTypeMap
import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope
import coil.ImageLoader
import coil.request.ImageRequest
import coil.size.Scale
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.RetainedLifecycle
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.scopes.ServiceScoped
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay
@@ -35,19 +36,22 @@ import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.utils.RetainedLifecycleCoroutineScope
import org.koitharu.kotatsu.utils.ext.copyToSuspending
import org.koitharu.kotatsu.utils.ext.deleteAwait
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import org.koitharu.kotatsu.utils.progress.PausingProgressJob
import java.io.File
import javax.inject.Inject
private const val MAX_FAILSAFE_ATTEMPTS = 2
private const val DOWNLOAD_ERROR_DELAY = 500L
private const val SLOWDOWN_DELAY = 200L
class DownloadManager @AssistedInject constructor(
@Assisted private val coroutineScope: CoroutineScope,
@ServiceScoped
class DownloadManager @Inject constructor(
service: Service,
@ApplicationContext private val context: Context,
private val imageLoader: ImageLoader,
private val okHttp: OkHttpClient,
@@ -64,6 +68,7 @@ class DownloadManager @AssistedInject constructor(
androidx.core.R.dimen.compat_notification_large_icon_max_height,
)
private val semaphore = Semaphore(settings.downloadsParallelism)
private val coroutineScope = (service as LifecycleService).lifecycleScope
fun downloadManga(
manga: Manga,
@@ -262,10 +267,4 @@ class DownloadManager @AssistedInject constructor(
} finally {
localMangaRepository.unlockManga(manga.id)
}
@AssistedFactory
interface Factory {
fun create(coroutineScope: CoroutineScope): DownloadManager
}
}

View File

@@ -44,12 +44,11 @@ import kotlin.collections.set
@AndroidEntryPoint
class DownloadService : BaseService() {
private lateinit var downloadManager: DownloadManager
private lateinit var downloadNotification: DownloadNotification
private lateinit var wakeLock: PowerManager.WakeLock
@Inject
lateinit var downloadManagerFactory: DownloadManager.Factory
lateinit var downloadManager: DownloadManager
private val jobs = LinkedHashMap<Int, PausingProgressJob<DownloadState>>()
private val jobCount = MutableStateFlow(0)
@@ -61,7 +60,6 @@ class DownloadService : BaseService() {
downloadNotification = DownloadNotification(this)
wakeLock = (applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager)
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "kotatsu:downloading")
downloadManager = downloadManagerFactory.create(lifecycleScope)
wakeLock.acquire(TimeUnit.HOURS.toMillis(8))
DownloadNotification.createChannel(this)
startForeground(DownloadNotification.ID_GROUP, downloadNotification.buildGroupNotification())

View File

@@ -27,9 +27,11 @@ import org.koitharu.kotatsu.scrobbling.common.domain.tryScrobble
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.ext.mapItems
import javax.inject.Inject
import javax.inject.Singleton
const val PROGRESS_NONE = -1f
@Singleton
class HistoryRepository @Inject constructor(
private val db: MangaDatabase,
private val trackingRepository: TrackingRepository,
@@ -37,7 +39,7 @@ class HistoryRepository @Inject constructor(
private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
) {
suspend fun getList(offset: Int, limit: Int = 20): List<Manga> {
suspend fun getList(offset: Int, limit: Int): List<Manga> {
val entities = db.historyDao.findAll(offset, limit)
return entities.map { it.manga.toManga(it.tags.toMangaTags()) }
}
@@ -135,7 +137,7 @@ class HistoryRepository @Inject constructor(
/**
* Try to replace one manga with another one
* Useful for replacing saved manga on deleting it with remove source
* Useful for replacing saved manga on deleting it with remote source
*/
suspend fun deleteOrSwap(manga: Manga, alternative: Manga?) {
if (alternative == null || db.mangaDao.update(alternative.toEntity()) <= 0) {

View File

@@ -1,16 +1,19 @@
package org.koitharu.kotatsu.reader.domain
import android.util.LongSparseArray
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import javax.inject.Inject
private const val PAGES_TRIM_THRESHOLD = 120
class ChaptersLoader(
@ViewModelScoped
class ChaptersLoader @Inject constructor(
private val mangaRepositoryFactory: MangaRepository.Factory,
) {

View File

@@ -9,13 +9,11 @@ import dagger.hilt.android.ActivityRetainedLifecycle
import dagger.hilt.android.lifecycle.RetainedLifecycle
import dagger.hilt.android.scopes.ActivityRetainedScoped
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -30,6 +28,7 @@ import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.utils.RetainedLifecycleCoroutineScope
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.withProgress
import org.koitharu.kotatsu.utils.progress.ProgressDeferred
@@ -57,7 +56,7 @@ class PageLoader @Inject constructor(
lifecycle.addOnClearedListener(this)
}
val loaderScope = CoroutineScope(SupervisorJob() + InternalErrorHandler() + Dispatchers.Default)
val loaderScope = RetainedLifecycleCoroutineScope(lifecycle) + InternalErrorHandler() + Dispatchers.Default
private val tasks = LongSparseArray<ProgressDeferred<File, Float>>()
private val convertLock = Mutex()
@@ -68,7 +67,6 @@ class PageLoader @Inject constructor(
private var prefetchQueueLimit = PREFETCH_LIMIT_DEFAULT // TODO adaptive
override fun onCleared() {
loaderScope.cancel()
synchronized(tasks) {
tasks.clear()
}

View File

@@ -70,6 +70,7 @@ class ReaderViewModel @Inject constructor(
private val settings: AppSettings,
private val pageSaveHelper: PageSaveHelper,
private val pageLoader: PageLoader,
private val chaptersLoader: ChaptersLoader,
) : BaseViewModel() {
private val intent = MangaIntent(savedStateHandle)
@@ -83,8 +84,6 @@ class ReaderViewModel @Inject constructor(
private val chapters: LongSparseArray<MangaChapter>
get() = chaptersLoader.chapters
private val chaptersLoader = ChaptersLoader(mangaRepositoryFactory)
val readerMode = MutableLiveData<ReaderMode>()
val onPageSaved = SingleLiveEvent<Uri?>()
val onShowToast = SingleLiveEvent<Int>()

View File

@@ -22,7 +22,9 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import javax.inject.Singleton
@Singleton
class MangaSearchRepository @Inject constructor(
private val settings: AppSettings,
private val db: MangaDatabase,

View File

@@ -24,7 +24,9 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ShelfRepository @Inject constructor(
private val localMangaRepository: LocalMangaRepository,
private val historyRepository: HistoryRepository,

View File

@@ -23,9 +23,11 @@ import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem
import java.util.Date
import javax.inject.Inject
import javax.inject.Singleton
private const val NO_ID = 0L
@Singleton
class TrackingRepository @Inject constructor(
private val db: MangaDatabase,
) {

View File

@@ -0,0 +1,24 @@
package org.koitharu.kotatsu.utils
import dagger.hilt.android.lifecycle.RetainedLifecycle
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlin.coroutines.CoroutineContext
class RetainedLifecycleCoroutineScope(
private val lifecycle: RetainedLifecycle,
) : CoroutineScope, RetainedLifecycle.OnClearedListener {
override val coroutineContext: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
init {
lifecycle.addOnClearedListener(this)
}
override fun onCleared() {
coroutineContext.cancel()
lifecycle.removeOnClearedListener(this)
}
}

View File

@@ -5,4 +5,4 @@ import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
val processLifecycleScope: LifecycleCoroutineScope
inline get() = ProcessLifecycleOwner.get().lifecycleScope
inline get() = ProcessLifecycleOwner.get().lifecycleScope

View File

@@ -51,10 +51,10 @@ fun Fragment.addMenuProvider(provider: MenuProvider) {
suspend fun Fragment.awaitViewLifecycle(): LifecycleOwner = suspendCancellableCoroutine { cont ->
val liveData = viewLifecycleOwnerLiveData
val observer = object : Observer<LifecycleOwner?> {
override fun onChanged(result: LifecycleOwner?) {
if (result != null) {
override fun onChanged(value: LifecycleOwner?) {
if (value != null) {
liveData.removeObserver(this)
cont.resume(result)
cont.resume(value)
}
}
}