diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt index 1d6bb0ced..c1d2a0af4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt @@ -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, diff --git a/app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/BookmarksRepository.kt b/app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/BookmarksRepository.kt index 9ba79d92b..8162e4016 100644 --- a/app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/BookmarksRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/bookmarks/domain/BookmarksRepository.kt @@ -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, ) { diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 86180d8f8..d3a7f6f7c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -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() diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt index 93a13b52e..4130a98be 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt @@ -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(null) diff --git a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt b/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt index 6b14c5bc0..ba7e6b445 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt @@ -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 - } } diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt index cc0875da5..08f5577bd 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt @@ -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>() 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()) diff --git a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt index 66af005bd..34df167b6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt @@ -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 { + suspend fun getList(offset: Int, limit: Int): List { 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) { diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/domain/ChaptersLoader.kt b/app/src/main/java/org/koitharu/kotatsu/reader/domain/ChaptersLoader.kt index 7d3649268..ecd163e36 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/domain/ChaptersLoader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/domain/ChaptersLoader.kt @@ -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, ) { diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt b/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt index c92ce338f..0d19b47e9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt @@ -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>() 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() } diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index 2c3f63a87..3a7c6b786 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -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 get() = chaptersLoader.chapters - private val chaptersLoader = ChaptersLoader(mangaRepositoryFactory) - val readerMode = MutableLiveData() val onPageSaved = SingleLiveEvent() val onShowToast = SingleLiveEvent() diff --git a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt index 140ed3917..6ab02557c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt @@ -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, diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/domain/ShelfRepository.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/domain/ShelfRepository.kt index 7ecfa7dbf..5da6628e2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/shelf/domain/ShelfRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/shelf/domain/ShelfRepository.kt @@ -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, diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt index c1038f83e..ee1253901 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt @@ -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, ) { diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/RetainedLifecycleCoroutineScope.kt b/app/src/main/java/org/koitharu/kotatsu/utils/RetainedLifecycleCoroutineScope.kt new file mode 100644 index 000000000..73cbf290f --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/RetainedLifecycleCoroutineScope.kt @@ -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) + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CoroutineExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CoroutineExt.kt index dd4907134..27e9ca459 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CoroutineExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CoroutineExt.kt @@ -5,4 +5,4 @@ import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.lifecycleScope val processLifecycleScope: LifecycleCoroutineScope - inline get() = ProcessLifecycleOwner.get().lifecycleScope \ No newline at end of file + inline get() = ProcessLifecycleOwner.get().lifecycleScope diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt index 8370d01c1..44113f458 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt @@ -51,10 +51,10 @@ fun Fragment.addMenuProvider(provider: MenuProvider) { suspend fun Fragment.awaitViewLifecycle(): LifecycleOwner = suspendCancellableCoroutine { cont -> val liveData = viewLifecycleOwnerLiveData val observer = object : Observer { - 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) } } }