Migrate to Metro DI (#3608)

Assisted-by: Claude:claude-opus-5
This commit is contained in:
AntsyLich
2026-08-17 00:47:48 +06:00
committed by GitHub
parent 1e5b1dc5e7
commit b2015d1ef3
314 changed files with 2416 additions and 2010 deletions
@@ -3,11 +3,11 @@ package eu.kanade.core.util
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import androidx.compose.ui.platform.LocalContext
import mihon.app.di.appGraph
@Composable
fun ifSourcesLoaded(): Boolean {
return remember { Injekt.get<SourceManager>().isInitialized }.collectAsState().value
val context = LocalContext.current
return remember { context.appGraph.sourceManager.isInitialized }.collectAsState().value
}
@@ -1,209 +0,0 @@
package eu.kanade.domain
import eu.kanade.domain.chapter.interactor.GetAvailableScanlators
import eu.kanade.domain.chapter.interactor.SetReadStatus
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.download.interactor.DeleteDownload
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
import eu.kanade.domain.extension.interactor.GetExtensionSources
import eu.kanade.domain.extension.interactor.GetExtensionsByType
import eu.kanade.domain.extension.interactor.TrustExtension
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
import eu.kanade.domain.manga.interactor.SetExcludedScanlators
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.source.interactor.GetEnabledSources
import eu.kanade.domain.source.interactor.GetIncognitoState
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.domain.source.interactor.ToggleIncognito
import eu.kanade.domain.source.interactor.ToggleLanguage
import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.domain.source.interactor.ToggleSourcePin
import eu.kanade.domain.track.interactor.AddTracks
import eu.kanade.domain.track.interactor.RefreshTracks
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
import eu.kanade.domain.track.interactor.TrackChapter
import mihon.data.extension.repository.ExtensionStoreRepositoryImpl
import mihon.data.extension.service.ExtensionStoreService
import mihon.domain.chapter.interactor.FilterChaptersForDownload
import mihon.domain.extension.interactor.AddExtensionStore
import mihon.domain.extension.interactor.GetExtensionStoreCountAsFlow
import mihon.domain.extension.interactor.GetExtensionStores
import mihon.domain.extension.interactor.RemoveExtensionStore
import mihon.domain.extension.interactor.UpdateExtensionStores
import mihon.domain.extension.repository.ExtensionStoreRepository
import mihon.domain.migration.usecases.MigrateMangaUseCase
import mihon.domain.source.interactor.UpdateMangaFromRemote
import mihon.domain.upcoming.interactor.GetUpcomingManga
import tachiyomi.data.category.CategoryRepositoryImpl
import tachiyomi.data.chapter.ChapterRepositoryImpl
import tachiyomi.data.history.HistoryRepositoryImpl
import tachiyomi.data.manga.MangaRepositoryImpl
import tachiyomi.data.release.ReleaseServiceImpl
import tachiyomi.data.source.SourceRepositoryImpl
import tachiyomi.data.source.StubSourceRepositoryImpl
import tachiyomi.data.track.TrackRepositoryImpl
import tachiyomi.data.updates.UpdatesRepositoryImpl
import tachiyomi.domain.category.interactor.CreateCategoryWithName
import tachiyomi.domain.category.interactor.DeleteCategory
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.RenameCategory
import tachiyomi.domain.category.interactor.ReorderCategory
import tachiyomi.domain.category.interactor.ResetCategoryFlags
import tachiyomi.domain.category.interactor.SetDisplayMode
import tachiyomi.domain.category.interactor.SetMangaCategories
import tachiyomi.domain.category.interactor.SetSortModeForCategory
import tachiyomi.domain.category.repository.CategoryRepository
import tachiyomi.domain.chapter.interactor.GetBookmarkedChaptersByMangaId
import tachiyomi.domain.chapter.interactor.GetChapter
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
import tachiyomi.domain.chapter.interactor.UpdateChapter
import tachiyomi.domain.chapter.repository.ChapterRepository
import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.history.interactor.GetNextChapters
import tachiyomi.domain.history.interactor.GetTotalReadDuration
import tachiyomi.domain.history.interactor.RemoveHistory
import tachiyomi.domain.history.interactor.UpsertHistory
import tachiyomi.domain.history.repository.HistoryRepository
import tachiyomi.domain.manga.interactor.FetchInterval
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
import tachiyomi.domain.manga.interactor.GetFavorites
import tachiyomi.domain.manga.interactor.GetLibraryManga
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.interactor.ResetViewerFlags
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
import tachiyomi.domain.manga.interactor.UpdateMangaNotes
import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.domain.release.interactor.GetApplicationRelease
import tachiyomi.domain.release.service.ReleaseService
import tachiyomi.domain.source.interactor.GetRemoteManga
import tachiyomi.domain.source.interactor.GetSourcesWithNonLibraryManga
import tachiyomi.domain.source.repository.SourceRepository
import tachiyomi.domain.source.repository.StubSourceRepository
import tachiyomi.domain.track.interactor.DeleteTrack
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.GetTracksPerManga
import tachiyomi.domain.track.interactor.InsertTrack
import tachiyomi.domain.track.repository.TrackRepository
import tachiyomi.domain.updates.interactor.GetUpdates
import tachiyomi.domain.updates.repository.UpdatesRepository
import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addFactory
import uy.kohesive.injekt.api.addSingletonFactory
import uy.kohesive.injekt.api.get
class DomainModule : InjektModule {
override fun InjektRegistrar.registerInjectables() {
addSingletonFactory<CategoryRepository> { CategoryRepositoryImpl(get()) }
addFactory { GetCategories(get()) }
addFactory { ResetCategoryFlags(get(), get()) }
addFactory { SetDisplayMode(get()) }
addFactory { SetSortModeForCategory(get(), get()) }
addFactory { CreateCategoryWithName(get(), get()) }
addFactory { RenameCategory(get()) }
addFactory { ReorderCategory(get()) }
addFactory { DeleteCategory(get(), get(), get()) }
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
addFactory { GetDuplicateLibraryManga(get()) }
addFactory { GetFavorites(get()) }
addFactory { GetLibraryManga(get()) }
addFactory { GetMangaWithChapters(get(), get()) }
addFactory { GetMangaByUrlAndSourceId(get()) }
addFactory { GetManga(get()) }
addFactory { GetNextChapters(get(), get(), get()) }
addFactory { GetUpcomingManga(get()) }
addFactory { ResetViewerFlags(get()) }
addFactory { SetMangaChapterFlags(get()) }
addFactory { FetchInterval(get()) }
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
addFactory { SetMangaViewerFlags(get()) }
addFactory { NetworkToLocalManga(get()) }
addFactory { UpdateManga(get(), get()) }
addFactory { UpdateMangaNotes(get()) }
addFactory { SetMangaCategories(get()) }
addFactory { GetExcludedScanlators(get()) }
addFactory { SetExcludedScanlators(get()) }
addFactory {
MigrateMangaUseCase(
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(),
)
}
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
addFactory { GetApplicationRelease(get()) }
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
addFactory { TrackChapter(get(), get(), get(), get()) }
addFactory { AddTracks(get(), get(), get(), get()) }
addFactory { RefreshTracks(get(), get(), get(), get()) }
addFactory { DeleteTrack(get()) }
addFactory { GetTracksPerManga(get()) }
addFactory { GetTracks(get()) }
addFactory { InsertTrack(get()) }
addFactory { SyncChapterProgressWithTrack(get(), get(), get()) }
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
addFactory { GetChapter(get()) }
addFactory { GetChaptersByMangaId(get()) }
addFactory { GetBookmarkedChaptersByMangaId(get()) }
addFactory { GetChapterByUrlAndMangaId(get()) }
addFactory { UpdateChapter(get()) }
addFactory { SetReadStatus(get(), get(), get(), get()) }
addFactory { ShouldUpdateDbChapter() }
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
addFactory { GetAvailableScanlators(get()) }
addFactory { FilterChaptersForDownload(get(), get(), get()) }
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
addFactory { GetHistory(get()) }
addFactory { UpsertHistory(get()) }
addFactory { RemoveHistory(get()) }
addFactory { GetTotalReadDuration(get()) }
addFactory { DeleteDownload(get(), get()) }
addFactory { GetExtensionsByType(get(), get()) }
addFactory { GetExtensionSources(get()) }
addFactory { GetExtensionLanguages(get(), get()) }
addSingletonFactory<UpdatesRepository> { UpdatesRepositoryImpl(get()) }
addFactory { GetUpdates(get()) }
addSingletonFactory<SourceRepository> { SourceRepositoryImpl(get(), get()) }
addSingletonFactory<StubSourceRepository> { StubSourceRepositoryImpl(get()) }
addFactory { GetEnabledSources(get(), get()) }
addFactory { GetLanguagesWithSources(get(), get()) }
addFactory { GetRemoteManga(get()) }
addFactory { GetSourcesWithFavoriteCount(get(), get()) }
addFactory { GetSourcesWithNonLibraryManga(get()) }
addFactory { SetMigrateSorting(get()) }
addFactory { ToggleLanguage(get()) }
addFactory { ToggleSource(get()) }
addFactory { ToggleSourcePin(get()) }
addFactory { TrustExtension(get(), get()) }
addSingletonFactory { ExtensionStoreService(get(), get(), get()) }
addSingletonFactory<ExtensionStoreRepository> { ExtensionStoreRepositoryImpl(get(), get()) }
addFactory { AddExtensionStore(get()) }
addFactory { GetExtensionStoreCountAsFlow(get()) }
addFactory { GetExtensionStores(get()) }
addFactory { RemoveExtensionStore(get()) }
addFactory { UpdateExtensionStores(get()) }
addFactory { ToggleIncognito(get()) }
addFactory { GetIncognitoState(get(), get(), get()) }
addFactory { UpdateMangaFromRemote(get(), get(), get(), get(), get(), get(), get()) }
}
}
@@ -2,10 +2,15 @@ package eu.kanade.domain.base
import android.content.Context
import dev.icerock.moko.resources.StringResource
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.i18n.MR
@Inject
@SingleIn(AppScope::class)
class BasePreferences(
val context: Context,
preferenceStore: PreferenceStore,
@@ -1,9 +1,11 @@
package eu.kanade.domain.chapter.interactor
import dev.zacsweers.metro.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import tachiyomi.domain.chapter.repository.ChapterRepository
@Inject
class GetAvailableScanlators(
private val repository: ChapterRepository,
) {
@@ -1,5 +1,6 @@
package eu.kanade.domain.chapter.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.download.interactor.DeleteDownload
import logcat.LogPriority
import tachiyomi.core.common.util.lang.withNonCancellableContext
@@ -11,6 +12,7 @@ import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaRepository
@Inject
class SetReadStatus(
private val downloadPreferences: DownloadPreferences,
private val deleteDownload: DeleteDownload,
@@ -1,5 +1,6 @@
package eu.kanade.domain.chapter.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.chapter.model.copyFromSChapter
import eu.kanade.domain.chapter.model.toSChapter
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
@@ -29,6 +30,7 @@ import java.lang.Long.max
import java.util.TreeSet
import kotlin.time.Clock
@Inject
class SyncChaptersWithSource(
private val downloadManager: DownloadManager,
private val downloadProvider: DownloadProvider,
@@ -1,11 +1,13 @@
package eu.kanade.domain.download.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.download.DownloadManager
import tachiyomi.core.common.util.lang.withNonCancellableContext
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
@Inject
class DeleteDownload(
private val sourceManager: SourceManager,
private val downloadManager: DownloadManager,
@@ -1,11 +1,13 @@
package eu.kanade.domain.extension.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.util.system.LocaleHelper
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@Inject
class GetExtensionLanguages(
private val preferences: SourcePreferences,
private val extensionManager: ExtensionManager,
@@ -1,11 +1,13 @@
package eu.kanade.domain.extension.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.source.Source
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@Inject
class GetExtensionSources(
private val preferences: SourcePreferences,
) {
@@ -1,5 +1,6 @@
package eu.kanade.domain.extension.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.extension.model.Extensions
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.ExtensionManager
@@ -7,6 +8,7 @@ import eu.kanade.tachiyomi.extension.model.Extension
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@Inject
class GetExtensionsByType(
private val preferences: SourcePreferences,
private val extensionManager: ExtensionManager,
@@ -2,10 +2,12 @@ package eu.kanade.domain.extension.interactor
import android.content.pm.PackageInfo
import androidx.core.content.pm.PackageInfoCompat
import dev.zacsweers.metro.Inject
import eu.kanade.domain.source.service.SourcePreferences
import mihon.domain.extension.repository.ExtensionStoreRepository
import tachiyomi.core.common.preference.getAndSet
@Inject
class TrustExtension(
private val repository: ExtensionStoreRepository,
private val preferences: SourcePreferences,
@@ -1,11 +1,13 @@
package eu.kanade.domain.manga.interactor
import app.cash.sqldelight.async.coroutines.awaitAsList
import dev.zacsweers.metro.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import tachiyomi.data.Database
import tachiyomi.data.subscribeToList
@Inject
class GetExcludedScanlators(
private val database: Database,
) {
@@ -1,8 +1,10 @@
package eu.kanade.domain.manga.interactor
import app.cash.sqldelight.async.coroutines.awaitAsList
import dev.zacsweers.metro.Inject
import tachiyomi.data.Database
@Inject
class SetExcludedScanlators(
private val database: Database,
) {
@@ -1,10 +1,12 @@
package eu.kanade.domain.manga.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.repository.MangaRepository
@Inject
class SetMangaViewerFlags(
private val mangaRepository: MangaRepository,
) {
@@ -1,5 +1,6 @@
package eu.kanade.domain.manga.interactor
import dev.zacsweers.metro.Inject
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
@@ -9,6 +10,7 @@ import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.repository.MangaRepository
import kotlin.time.Clock
@Inject
class UpdateManga(
private val mangaRepository: MangaRepository,
private val fetchInterval: FetchInterval,
@@ -1,10 +1,11 @@
package eu.kanade.domain.manga.model
import eu.kanade.domain.base.BasePreferences
import android.content.Context
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import mihon.app.di.appGraph
import tachiyomi.core.common.preference.TriState
import tachiyomi.core.metadata.comicinfo.ComicInfo
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
@@ -22,7 +23,7 @@ val Manga.readerOrientation: Long
val Manga.downloadedFilter: TriState
get() {
if (Injekt.get<BasePreferences>().downloadedOnly.get()) return TriState.ENABLED_IS
if (Injekt.get<Context>().appGraph.basePreferences.downloadedOnly.get()) return TriState.ENABLED_IS
return when (downloadedFilterRaw) {
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
@@ -1,5 +1,6 @@
package eu.kanade.domain.source.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.source.service.SourcePreferences
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -10,6 +11,7 @@ import tachiyomi.domain.source.model.Source
import tachiyomi.domain.source.repository.SourceRepository
import tachiyomi.source.local.isLocal
@Inject
class GetEnabledSources(
private val repository: SourceRepository,
private val preferences: SourcePreferences,
@@ -1,5 +1,6 @@
package eu.kanade.domain.source.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.ExtensionManager
@@ -7,6 +8,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@Inject
class GetIncognitoState(
private val basePreferences: BasePreferences,
private val sourcePreferences: SourcePreferences,
@@ -1,5 +1,6 @@
package eu.kanade.domain.source.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.util.system.LocaleHelper
import kotlinx.coroutines.flow.Flow
@@ -8,6 +9,7 @@ import tachiyomi.domain.source.model.Source
import tachiyomi.domain.source.repository.SourceRepository
import java.util.SortedMap
@Inject
class GetLanguagesWithSources(
private val repository: SourceRepository,
private val preferences: SourcePreferences,
@@ -1,5 +1,6 @@
package eu.kanade.domain.source.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.source.service.SourcePreferences
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -9,6 +10,7 @@ import tachiyomi.domain.source.repository.SourceRepository
import tachiyomi.source.local.isLocal
import java.util.Collections
@Inject
class GetSourcesWithFavoriteCount(
private val repository: SourceRepository,
private val preferences: SourcePreferences,
@@ -1,7 +1,9 @@
package eu.kanade.domain.source.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.source.service.SourcePreferences
@Inject
class SetMigrateSorting(
private val preferences: SourcePreferences,
) {
@@ -1,8 +1,10 @@
package eu.kanade.domain.source.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.source.service.SourcePreferences
import tachiyomi.core.common.preference.getAndSet
@Inject
class ToggleIncognito(
private val preferences: SourcePreferences,
) {
@@ -1,8 +1,10 @@
package eu.kanade.domain.source.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.source.service.SourcePreferences
import tachiyomi.core.common.preference.getAndSet
@Inject
class ToggleLanguage(
val preferences: SourcePreferences,
) {
@@ -1,9 +1,11 @@
package eu.kanade.domain.source.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.source.service.SourcePreferences
import tachiyomi.core.common.preference.getAndSet
import tachiyomi.domain.source.model.Source
@Inject
class ToggleSource(
private val preferences: SourcePreferences,
) {
@@ -1,9 +1,11 @@
package eu.kanade.domain.source.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.source.service.SourcePreferences
import tachiyomi.core.common.preference.getAndSet
import tachiyomi.domain.source.model.Source
@Inject
class ToggleSourcePin(
private val preferences: SourcePreferences,
) {
@@ -1,16 +1,17 @@
package eu.kanade.domain.source.model
import android.content.Context
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.core.graphics.drawable.toBitmap
import eu.kanade.tachiyomi.extension.ExtensionManager
import mihon.app.di.appGraph
import tachiyomi.domain.source.model.Source
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
val Source.icon: ImageBitmap?
get() {
return Injekt.get<ExtensionManager>().getAppIconForSource(id)
return Injekt.get<Context>().appGraph.extensionManager.getAppIconForSource(id)
?.toBitmap()
?.asImageBitmap()
}
@@ -1,5 +1,8 @@
package eu.kanade.domain.source.service
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.tachiyomi.util.system.LocaleHelper
import mihon.domain.migration.models.MigrationFlag
@@ -9,6 +12,8 @@ import tachiyomi.core.common.preference.getEnum
import tachiyomi.core.common.preference.getLongArray
import tachiyomi.domain.library.model.LibraryDisplayMode
@Inject
@SingleIn(AppScope::class)
class SourcePreferences(
preferenceStore: PreferenceStore,
) {
@@ -1,5 +1,6 @@
package eu.kanade.domain.track.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.data.database.models.Track
@@ -17,14 +18,14 @@ import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.interactor.InsertTrack
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Inject
class AddTracks(
private val insertTrack: InsertTrack,
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
private val getChaptersByMangaId: GetChaptersByMangaId,
private val trackerManager: TrackerManager,
private val getHistory: GetHistory,
) {
// TODO: update all trackers based on common data
@@ -55,7 +56,7 @@ class AddTracks(
}
if (track.startDate <= 0) {
val firstReadChapterDate = Injekt.get<GetHistory>().await(mangaId)
val firstReadChapterDate = getHistory.await(mangaId)
.sortedBy { it.readAt }
.firstOrNull()
?.readAt
@@ -1,5 +1,6 @@
package eu.kanade.domain.track.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.data.track.Tracker
@@ -10,6 +11,7 @@ import kotlinx.coroutines.supervisorScope
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack
@Inject
class RefreshTracks(
private val getTracks: GetTracks,
private val trackerManager: TrackerManager,
@@ -1,5 +1,6 @@
package eu.kanade.domain.track.interactor
import dev.zacsweers.metro.Inject
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.Tracker
@@ -12,6 +13,7 @@ import tachiyomi.domain.track.interactor.InsertTrack
import tachiyomi.domain.track.model.Track
import kotlin.math.max
@Inject
class SyncChapterProgressWithTrack(
private val updateChapter: UpdateChapter,
private val insertTrack: InsertTrack,
@@ -1,6 +1,7 @@
package eu.kanade.domain.track.interactor
import android.content.Context
import dev.zacsweers.metro.Inject
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.domain.track.service.DelayedTrackingUpdateJob
@@ -14,6 +15,7 @@ import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack
@Inject
class TrackChapter(
private val getTracks: GetTracks,
private val trackerManager: TrackerManager,
@@ -8,30 +8,36 @@ import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters
import dev.zacsweers.metro.Inject
import eu.kanade.domain.track.interactor.TrackChapter
import eu.kanade.domain.track.store.DelayedTrackingStore
import eu.kanade.tachiyomi.util.system.workManager
import logcat.LogPriority
import mihon.app.di.AppGraph
import mihon.core.metro.metroGraph
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.track.interactor.GetTracks
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class DelayedTrackingUpdateJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
private val graph: AppGraph = context.metroGraph()
@Inject lateinit var getTracks: GetTracks
@Inject lateinit var trackChapter: TrackChapter
@Inject lateinit var delayedTrackingStore: DelayedTrackingStore
override suspend fun doWork(): Result {
graph.inject(this)
if (runAttemptCount > 3) {
return Result.failure()
}
val getTracks = Injekt.get<GetTracks>()
val trackChapter = Injekt.get<TrackChapter>()
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
withIOContext {
delayedTrackingStore.getItems()
.mapNotNull {
@@ -1,5 +1,8 @@
package eu.kanade.domain.track.service
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import eu.kanade.domain.track.model.AutoTrackState
import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.anilist.Anilist
@@ -8,6 +11,8 @@ import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.getEnum
@Inject
@SingleIn(AppScope::class)
class TrackPreferences(
private val preferenceStore: PreferenceStore,
) {
@@ -2,9 +2,14 @@ package eu.kanade.domain.track.store
import android.content.Context
import androidx.core.content.edit
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat
@Inject
@SingleIn(AppScope::class)
class DelayedTrackingStore(context: Context) {
/**
@@ -1,5 +1,8 @@
package eu.kanade.domain.ui
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import eu.kanade.domain.ui.model.AppTheme
import eu.kanade.domain.ui.model.TabletUiMode
import eu.kanade.domain.ui.model.ThemeMode
@@ -12,6 +15,8 @@ import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale
@Inject
@SingleIn(AppScope::class)
class UiPreferences(
preferenceStore: PreferenceStore,
) {
@@ -8,10 +8,9 @@ import eu.kanade.tachiyomi.util.lang.toRelativeString
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import mihon.app.di.appGraph
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Instant
@Composable
@@ -32,7 +31,7 @@ fun relativeDateText(
): String {
val context = LocalContext.current
val preferences = remember { Injekt.get<UiPreferences>() }
val preferences = remember { context.appGraph.uiPreferences }
val relativeTime = remember { preferences.relativeTime.get() }
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat.get()) }
@@ -9,14 +9,15 @@ import androidx.compose.material.icons.outlined.BugReport
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.PreviewLightDark
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.tachiyomi.util.CrashLogUtil
import kotlinx.coroutines.launch
import mihon.app.di.appGraph
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
@@ -29,6 +30,7 @@ fun CrashScreen(
) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val crashLogUtil = remember { context.appGraph.crashLogUtil }
InfoScreen(
icon = Icons.Outlined.BugReport,
@@ -37,7 +39,7 @@ fun CrashScreen(
acceptText = stringResource(MR.strings.pref_dump_crash_logs),
onAcceptClick = {
scope.launch {
CrashLogUtil(context).dumpLogs(exception)
crashLogUtil.dumpLogs(exception)
}
},
rejectText = stringResource(MR.strings.crash_screen_restart_application),
@@ -26,11 +26,12 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.manga.model.downloadedFilter
import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings
import mihon.app.di.appGraph
import tachiyomi.core.common.preference.TriState
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
@@ -40,8 +41,6 @@ import tachiyomi.presentation.core.components.SortItem
import tachiyomi.presentation.core.components.TriStateItem
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.theme.active
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Composable
fun ChapterSettingsDialog(
@@ -65,7 +64,8 @@ fun ChapterSettingsDialog(
)
}
val downloadedOnly = remember { Injekt.get<BasePreferences>().downloadedOnly.get() }
val context = LocalContext.current
val downloadedOnly = remember { context.appGraph.basePreferences.downloadedOnly.get() }
TabbedDialog(
onDismissRequest = onDismissRequest,
@@ -63,10 +63,10 @@ import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SManga
import mihon.app.di.appGraph
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaWithChapterCount
import tachiyomi.domain.source.model.StubSource
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.Badge
import tachiyomi.presentation.core.components.BadgeGroup
@@ -74,8 +74,6 @@ import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.secondaryItemAlpha
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Composable
fun DuplicateMangaDialog(
@@ -86,7 +84,8 @@ fun DuplicateMangaDialog(
onMigrate: (manga: Manga) -> Unit,
modifier: Modifier = Modifier,
) {
val sourceManager = remember { Injekt.get<SourceManager>() }
val context = LocalContext.current
val sourceManager = remember { context.appGraph.sourceManager }
val minHeight = LocalPreferenceMinHeight.current
val horizontalPadding = PaddingValues(horizontal = TabbedDialogPaddings.Horizontal)
val horizontalPaddingModifier = Modifier.padding(horizontalPadding)
@@ -30,6 +30,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.DpOffset
@@ -48,7 +49,6 @@ import coil3.imageLoader
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.size.Size
import eu.kanade.domain.base.BasePreferences
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.DropdownMenu
@@ -57,13 +57,12 @@ import eu.kanade.tachiyomi.data.coil.ImageDecoder
import eu.kanade.tachiyomi.data.coil.newDecoder
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
import kotlinx.coroutines.runBlocking
import mihon.app.di.appGraph
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.clickableNoIndication
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Composable
fun MangaCoverDialog(
@@ -75,7 +74,7 @@ fun MangaCoverDialog(
onEditClick: ((EditCoverAction) -> Unit)?,
onDismissRequest: () -> Unit,
) {
val useNewRenderer = Injekt.get<BasePreferences>().highQualityRenderer.get()
val useNewRenderer = LocalContext.current.appGraph.basePreferences.highQualityRenderer.get()
val view = LocalView.current
Dialog(
@@ -86,13 +86,13 @@ import coil3.request.crossfade
import com.mikepenz.markdown.model.markdownAnnotator
import com.mikepenz.markdown.model.markdownAnnotatorConfig
import com.mikepenz.markdown.utils.getUnescapedTextInNode
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.system.copyToClipboard
import kotlinx.datetime.TimeZone
import kotlinx.datetime.daysUntil
import mihon.app.di.appGraph
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.ast.findChildOfType
@@ -105,8 +105,6 @@ import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.clickableNoIndication
import tachiyomi.presentation.core.util.secondaryItemAlpha
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.math.roundToInt
import kotlin.time.Clock
import kotlin.time.Instant
@@ -619,7 +617,8 @@ private fun MangaSummary(
onEditNotesClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
val preferences = remember { Injekt.get<UiPreferences>() }
val context = LocalContext.current
val preferences = remember { context.appGraph.uiPreferences }
val loadImages = remember { preferences.imagesInDescription.get() }
val animProgress by animateFloatAsState(
targetValue = if (expanded) 1f else 0f,
@@ -25,6 +25,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -36,19 +37,16 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LocalLifecycleOwner
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
import eu.kanade.tachiyomi.util.system.telemetryIncluded
import mihon.app.di.appGraph
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import tachiyomi.presentation.core.util.secondaryItemAlpha
import uy.kohesive.injekt.injectLazy
internal class PermissionStep : OnboardingStep {
private val privacyPreferences: PrivacyPreferences by injectLazy()
private var notificationGranted by mutableStateOf(false)
private var batteryGranted by mutableStateOf(false)
@@ -57,6 +55,7 @@ internal class PermissionStep : OnboardingStep {
@Composable
override fun Content() {
val context = LocalContext.current
val privacyPreferences = remember { context.appGraph.privacyPreferences }
val lifecycleOwner = LocalLifecycleOwner.current
val installGranted = rememberRequestPackageInstallsPermissionState()
@@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -20,18 +21,14 @@ import androidx.compose.ui.unit.dp
import eu.kanade.presentation.more.settings.screen.SettingsDataScreen
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
import tachiyomi.domain.storage.service.StoragePreferences
import mihon.app.di.appGraph
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Button
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
internal class StorageStep : OnboardingStep {
private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory
private var _isComplete by mutableStateOf(false)
override val isComplete: Boolean
@@ -42,6 +39,7 @@ internal class StorageStep : OnboardingStep {
val context = LocalContext.current
val handler = LocalUriHandler.current
val storagePref = remember { context.appGraph.storagePreferences.baseStorageDirectory }
val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref)
Column(
@@ -3,22 +3,22 @@ package eu.kanade.presentation.more.onboarding
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import eu.kanade.domain.ui.UiPreferences
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
import mihon.app.di.appGraph
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
internal class ThemeStep : OnboardingStep {
override val isComplete: Boolean = true
private val uiPreferences: UiPreferences = Injekt.get()
@Composable
override fun Content() {
val context = LocalContext.current
val uiPreferences = remember { context.appGraph.uiPreferences }
val themeModePref = uiPreferences.themeMode
val themeMode by themeModePref.collectAsState()
@@ -25,13 +25,10 @@ import androidx.core.net.toUri
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.extension.interactor.TrustExtension
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen
import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.library.MetadataUpdateJob
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.network.PREF_DOH_360
import eu.kanade.tachiyomi.network.PREF_DOH_ADGUARD
@@ -46,26 +43,24 @@ import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9
import eu.kanade.tachiyomi.network.PREF_DOH_SHECAN
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
import eu.kanade.tachiyomi.util.system.powerManager
import eu.kanade.tachiyomi.util.system.setDefaultSettings
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.workManager
import kotlinx.coroutines.launch
import logcat.LogPriority
import mihon.app.di.appGraph
import okhttp3.Headers
import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.interactor.ResetViewerFlags
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
object SettingsAdvancedScreen : SearchableSettings {
@@ -80,9 +75,11 @@ object SettingsAdvancedScreen : SearchableSettings {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val basePreferences = remember { Injekt.get<BasePreferences>() }
val networkPreferences = remember { Injekt.get<NetworkPreferences>() }
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
val graph = remember { context.appGraph }
val basePreferences = remember { graph.basePreferences }
val networkPreferences = remember { graph.networkPreferences }
val libraryPreferences = remember { graph.libraryPreferences }
val crashLogUtil = remember { graph.crashLogUtil }
return listOf(
Preference.PreferenceItem.TextPreference(
@@ -90,7 +87,7 @@ object SettingsAdvancedScreen : SearchableSettings {
subtitle = stringResource(MR.strings.pref_dump_crash_logs_summary),
onClick = {
scope.launch {
CrashLogUtil(context).dumpLogs()
crashLogUtil.dumpLogs()
}
},
),
@@ -180,7 +177,7 @@ object SettingsAdvancedScreen : SearchableSettings {
title = stringResource(MR.strings.pref_invalidate_download_cache),
subtitle = stringResource(MR.strings.pref_invalidate_download_cache_summary),
onClick = {
Injekt.get<DownloadCache>().invalidateCache()
context.appGraph.downloadCache.invalidateCache()
context.toast(MR.strings.download_cache_invalidated)
},
),
@@ -198,7 +195,7 @@ object SettingsAdvancedScreen : SearchableSettings {
networkPreferences: NetworkPreferences,
): Preference.PreferenceGroup {
val context = LocalContext.current
val networkHelper = remember { Injekt.get<NetworkHelper>() }
val networkHelper = remember { context.appGraph.networkHelper }
val userAgentPref = networkPreferences.defaultUserAgent
val userAgent by userAgentPref.collectAsState()
@@ -295,14 +292,14 @@ object SettingsAdvancedScreen : SearchableSettings {
preferenceItems = listOf(
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_refresh_library_covers),
onClick = { MetadataUpdateJob.startNow(context) },
onClick = { MetadataUpdateJob.startNow(context.workManager) },
),
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_reset_viewer_flags),
subtitle = stringResource(MR.strings.pref_reset_viewer_flags_summary),
onClick = {
scope.launchNonCancellable {
val success = Injekt.get<ResetViewerFlags>().await()
val success = context.appGraph.resetViewerFlags.await()
withUIContext {
val message = if (success) {
MR.strings.pref_reset_viewer_flags_success
@@ -352,7 +349,7 @@ object SettingsAdvancedScreen : SearchableSettings {
val uriHandler = LocalUriHandler.current
val extensionInstallerPref = basePreferences.extensionInstaller
var shizukuMissing by rememberSaveable { mutableStateOf(false) }
val trustExtension = remember { Injekt.get<TrustExtension>() }
val trustExtension = remember { context.appGraph.trustExtension }
if (shizukuMissing) {
val dismiss = { shizukuMissing = false }
@@ -22,11 +22,10 @@ import eu.kanade.tachiyomi.util.system.toast
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toJavaLocalDateTime
import kotlinx.datetime.toLocalDateTime
import mihon.app.di.appGraph
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Clock
object SettingsAppearanceScreen : SearchableSettings {
@@ -37,7 +36,8 @@ object SettingsAppearanceScreen : SearchableSettings {
@Composable
override fun getPreferences(): List<Preference> {
val uiPreferences = remember { Injekt.get<UiPreferences>() }
val context = LocalContext.current
val uiPreferences = remember { context.appGraph.uiPreferences }
return listOf(
getThemeGroup(uiPreferences = uiPreferences),
@@ -9,17 +9,14 @@ import androidx.compose.ui.platform.LocalContext
import androidx.fragment.app.FragmentActivity
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.screen.browse.ExtensionStoresScreen
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
import mihon.domain.extension.interactor.GetExtensionStoreCountAsFlow
import mihon.app.di.appGraph
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
object SettingsBrowseScreen : SearchableSettings {
@@ -32,8 +29,8 @@ object SettingsBrowseScreen : SearchableSettings {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
val getExtensionStoreCountAsFlow = remember { Injekt.get<GetExtensionStoreCountAsFlow>() }
val sourcePreferences = remember { context.appGraph.sourcePreferences }
val getExtensionStoreCountAsFlow = remember { context.appGraph.getExtensionStoreCountAsFlow }
val reposCount by getExtensionStoreCountAsFlow().collectAsState(0)
@@ -51,30 +51,27 @@ import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
import eu.kanade.presentation.util.relativeTimeSpanString
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.export.LibraryExporter
import eu.kanade.tachiyomi.data.export.LibraryExporter.ExportOptions
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.workManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import logcat.LogPriority
import mihon.app.di.appGraph
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.storage.displayablePath
import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.interactor.GetFavorites
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.storage.service.StoragePreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.TextButton
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
object SettingsDataScreen : SearchableSettings {
@@ -98,8 +95,9 @@ object SettingsDataScreen : SearchableSettings {
@Composable
override fun getPreferences(): List<Preference> {
val backupPreferences = Injekt.get<BackupPreferences>()
val storagePreferences = Injekt.get<StoragePreferences>()
val context = LocalContext.current
val backupPreferences = remember { context.appGraph.backupPreferences }
val storagePreferences = remember { context.appGraph.storagePreferences }
return listOf(
getStorageLocationPref(storagePreferences = storagePreferences),
@@ -230,7 +228,7 @@ object SettingsDataScreen : SearchableSettings {
modifier = Modifier.fillMaxHeight(),
checked = false,
onCheckedChange = {
if (!BackupRestoreJob.isRunning(context)) {
if (!BackupRestoreJob.isRunning(context.workManager)) {
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
context.toast(MR.strings.restore_miui_warning)
}
@@ -279,9 +277,9 @@ object SettingsDataScreen : SearchableSettings {
private fun getDataGroup(): Preference.PreferenceGroup {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
val libraryPreferences = remember { context.appGraph.libraryPreferences }
val chapterCache = remember { Injekt.get<ChapterCache>() }
val chapterCache = remember { context.appGraph.chapterCache }
var cacheReadableSizeSema by remember { mutableIntStateOf(0) }
val cacheReadableSize = remember(cacheReadableSizeSema) { chapterCache.readableSize }
@@ -341,7 +339,7 @@ object SettingsDataScreen : SearchableSettings {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val getFavorites = remember { Injekt.get<GetFavorites>() }
val getFavorites = remember { context.appGraph.getFavorites }
var favorites by remember { mutableStateOf<List<Manga>>(emptyList()) }
LaunchedEffect(Unit) {
favorites = getFavorites.await()
@@ -8,19 +8,18 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.util.fastMap
import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
import tachiyomi.domain.category.interactor.GetCategories
import mihon.app.di.appGraph
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
object SettingsDownloadScreen : SearchableSettings {
@@ -30,10 +29,11 @@ object SettingsDownloadScreen : SearchableSettings {
@Composable
override fun getPreferences(): List<Preference> {
val getCategories = remember { Injekt.get<GetCategories>() }
val context = LocalContext.current
val getCategories = remember { context.appGraph.getCategories }
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
val downloadPreferences = remember { context.appGraph.downloadPreferences }
val parallelSourceLimit by downloadPreferences.parallelSourceLimit.collectAsState()
val parallelPageLimit by downloadPreferences.parallelPageLimit.collectAsState()
return listOf(
@@ -21,8 +21,7 @@ import eu.kanade.presentation.more.settings.widget.TriStateListDialog
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.ui.category.CategoryScreen
import kotlinx.coroutines.launch
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.ResetCategoryFlags
import mihon.app.di.appGraph
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING
@@ -38,8 +37,6 @@ import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
object SettingsLibraryScreen : SearchableSettings {
@@ -49,8 +46,9 @@ object SettingsLibraryScreen : SearchableSettings {
@Composable
override fun getPreferences(): List<Preference> {
val getCategories = remember { Injekt.get<GetCategories>() }
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
val context = LocalContext.current
val getCategories = remember { context.appGraph.getCategories }
val libraryPreferences = remember { context.appGraph.libraryPreferences }
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
return listOf(
@@ -66,6 +64,7 @@ object SettingsLibraryScreen : SearchableSettings {
allCategories: List<Category>,
libraryPreferences: LibraryPreferences,
): Preference.PreferenceGroup {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size
@@ -98,7 +97,7 @@ object SettingsLibraryScreen : SearchableSettings {
onValueChanged = {
if (!it) {
scope.launch {
Injekt.get<ResetCategoryFlags>().await()
context.appGraph.resetCategoryFlags.await()
}
}
true
@@ -172,7 +171,9 @@ object SettingsLibraryScreen : SearchableSettings {
enabled = autoUpdateInterval > 0,
onValueChanged = {
// Post to event looper to allow the preference to be updated.
ContextCompat.getMainExecutor(context).execute { LibraryUpdateJob.setupTask(context) }
ContextCompat.getMainExecutor(context).execute {
LibraryUpdateJob.setupTask(context)
}
true
},
),
@@ -4,18 +4,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
import mihon.app.di.appGraph
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.NumberFormat
object SettingsReaderScreen : SearchableSettings {
@@ -26,7 +26,8 @@ object SettingsReaderScreen : SearchableSettings {
@Composable
override fun getPreferences(): List<Preference> {
val readerPref = remember { Injekt.get<ReaderPreferences>() }
val context = LocalContext.current
val readerPref = remember { context.appGraph.readerPreferences }
return listOf(
Preference.PreferenceItem.ListPreference(
@@ -12,13 +12,12 @@ import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
import eu.kanade.tachiyomi.util.system.telemetryIncluded
import mihon.app.di.appGraph
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
object SettingsSecurityScreen : SearchableSettings {
@@ -28,8 +27,9 @@ object SettingsSecurityScreen : SearchableSettings {
@Composable
override fun getPreferences(): List<Preference> {
val securityPreferences = remember { Injekt.get<SecurityPreferences>() }
val privacyPreferences = remember { Injekt.get<PrivacyPreferences>() }
val context = LocalContext.current
val securityPreferences = remember { context.appGraph.securityPreferences }
val privacyPreferences = remember { context.appGraph.privacyPreferences }
return buildList(2) {
add(getSecurityGroup(securityPreferences))
if (!telemetryIncluded) return@buildList
@@ -44,11 +44,9 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.track.model.AutoTrackState
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi
import eu.kanade.tachiyomi.data.track.hikka.HikkaApi
@@ -57,14 +55,12 @@ import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi
import eu.kanade.tachiyomi.data.track.shikimori.ShikimoriApi
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast
import mihon.app.di.appGraph
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
object SettingsTrackingScreen : SearchableSettings {
@@ -86,9 +82,9 @@ object SettingsTrackingScreen : SearchableSettings {
@Composable
override fun getPreferences(): List<Preference> {
val context = LocalContext.current
val trackPreferences = remember { Injekt.get<TrackPreferences>() }
val trackerManager = remember { Injekt.get<TrackerManager>() }
val sourceManager = remember { Injekt.get<SourceManager>() }
val trackPreferences = remember { context.appGraph.trackPreferences }
val trackerManager = remember { context.appGraph.trackerManager }
val sourceManager = remember { context.appGraph.sourceManager }
var dialog by remember { mutableStateOf<Any?>(null) }
dialog?.run {
@@ -30,10 +30,8 @@ import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.util.LocalBackPress
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.isFossBuildType
@@ -44,6 +42,7 @@ import kotlinx.coroutines.launch
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import logcat.LogPriority
import mihon.app.di.appGraph
import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext
@@ -74,6 +73,7 @@ object AboutScreen : Screen() {
val handleBack = LocalBackPress.current
val navigator = LocalNavigator.currentOrThrow
var isCheckingUpdates by remember { mutableStateOf(false) }
val crashLogUtil = remember { context.appGraph.crashLogUtil }
Scaffold(
topBar = { scrollBehavior ->
@@ -98,7 +98,7 @@ object AboutScreen : Screen() {
title = stringResource(MR.strings.version),
subtitle = getVersionName(withBuildDate = true),
onPreferenceClick = {
val deviceInfo = CrashLogUtil(context).getDebugInfo()
val deviceInfo = crashLogUtil.getDebugInfo()
context.copyToClipboard("Debug information", deviceInfo)
},
)
@@ -217,7 +217,7 @@ object AboutScreen : Screen() {
onAvailableUpdate: (GetApplicationRelease.Result.NewUpdate) -> Unit,
onFinish: () -> Unit,
) {
val updateChecker = AppUpdateChecker()
val updateChecker = context.appGraph.updateChecker
withUIContext {
try {
when (val result = withIOContext { updateChecker.checkForUpdate(forceCheck = true) }) {
@@ -279,7 +279,7 @@ object AboutScreen : Screen() {
.toLocalDateTime(TimeZone.currentSystemDefault())
.toDateTimestampString(
UiPreferences.dateFormat(
Injekt.get<UiPreferences>().dateFormat.get(),
Injekt.get<Context>().appGraph.uiPreferences.dateFormat.get(),
),
)
} catch (_: Exception) {
@@ -32,9 +32,14 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastMap
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoMap
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.binding
import dev.zacsweers.metrox.viewmodel.ViewModelKey
import dev.zacsweers.metrox.viewmodel.metroViewModel
import eu.kanade.presentation.browse.components.SourceIcon
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
@@ -60,8 +65,6 @@ import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.LoadingScreen
import tachiyomi.presentation.core.util.selectedBackground
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ClearDatabaseScreen : Screen() {
@@ -69,7 +72,7 @@ class ClearDatabaseScreen : Screen() {
override fun Content() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val viewModel = viewModel<ClearDatabaseViewModel>()
val viewModel = metroViewModel<ClearDatabaseViewModel>()
val state by viewModel.state.collectAsState()
val scope = rememberCoroutineScope()
@@ -222,14 +225,17 @@ class ClearDatabaseScreen : Screen() {
}
}
class ClearDatabaseViewModel : ViewModel() {
@Inject
@ViewModelKey
@ContributesIntoMap(AppScope::class, binding = binding<ViewModel>())
class ClearDatabaseViewModel(
private val database: Database,
private val getSourcesWithNonLibraryManga: GetSourcesWithNonLibraryManga,
) : ViewModel() {
val state: StateFlow<ClearDatabaseViewModel.State>
field = MutableStateFlow<ClearDatabaseViewModel.State>(State.Loading)
private val getSourcesWithNonLibraryManga: GetSourcesWithNonLibraryManga = Injekt.get()
private val database: Database = Injekt.get()
init {
viewModelScope.launchIO {
getSourcesWithNonLibraryManga.subscribe()
@@ -8,6 +8,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.zacsweers.metrox.viewmodel.metroViewModel
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoreConfirmDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoreCreateDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoreDeleteDialog
@@ -26,7 +27,7 @@ class ExtensionStoresScreen(
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val viewModel = viewModel<ExtensionStoresViewModel>()
val viewModel = metroViewModel<ExtensionStoresViewModel>()
val state by viewModel.state.collectAsStateWithLifecycle()
LaunchedEffect(url) {
@@ -3,6 +3,11 @@ package eu.kanade.presentation.more.settings.screen.browse
import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoMap
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.binding
import dev.zacsweers.metrox.viewmodel.ViewModelKey
import eu.kanade.tachiyomi.extension.ExtensionManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@@ -20,16 +25,17 @@ import mihon.domain.extension.interactor.RemoveExtensionStore
import mihon.domain.extension.interactor.UpdateExtensionStores
import mihon.domain.extension.model.ExtensionStore
import tachiyomi.core.common.util.lang.launchIO
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Duration.Companion.seconds
@Inject
@ViewModelKey
@ContributesIntoMap(AppScope::class, binding = binding<ViewModel>())
class ExtensionStoresViewModel(
private val getExtensionStores: GetExtensionStores = Injekt.get(),
private val addExtensionStore: AddExtensionStore = Injekt.get(),
private val removeExtensionStore: RemoveExtensionStore = Injekt.get(),
private val updateExtensionStores: UpdateExtensionStores = Injekt.get(),
private val extensionManager: ExtensionManager = Injekt.get(),
private val getExtensionStores: GetExtensionStores,
private val addExtensionStore: AddExtensionStore,
private val removeExtensionStore: RemoveExtensionStore,
private val updateExtensionStores: UpdateExtensionStores,
private val extensionManager: ExtensionManager,
) : ViewModel() {
private val dialog = MutableStateFlow<ExtensionStoreDialog?>(null)
@@ -12,9 +12,14 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoMap
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.binding
import dev.zacsweers.metrox.viewmodel.ViewModelKey
import dev.zacsweers.metrox.viewmodel.metroViewModel
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.WarningBanner
import eu.kanade.presentation.util.Screen
@@ -39,7 +44,7 @@ class CreateBackupScreen : Screen() {
override fun Content() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val viewModel = viewModel<CreateBackupViewModel>()
val viewModel = metroViewModel<CreateBackupViewModel>()
val state by viewModel.state.collectAsState()
val chooseBackupDir = rememberLauncherForActivityResult(
@@ -73,7 +78,7 @@ class CreateBackupScreen : Screen() {
if (!BackupCreateJob.isManualJobRunning(context)) {
try {
chooseBackupDir.launch(BackupCreator.getFilename())
} catch (e: ActivityNotFoundException) {
} catch (_: ActivityNotFoundException) {
context.toast(MR.strings.file_picker_error)
}
} else {
@@ -121,6 +126,9 @@ class CreateBackupScreen : Screen() {
}
}
@Inject
@ViewModelKey
@ContributesIntoMap(AppScope::class, binding = binding<ViewModel>())
class CreateBackupViewModel : ViewModel() {
val state: StateFlow<CreateBackupViewModel.State>
@@ -1,6 +1,5 @@
package eu.kanade.presentation.more.settings.screen.data
import android.app.Application
import android.content.Context
import android.net.Uri
import androidx.compose.foundation.layout.Arrangement
@@ -21,12 +20,16 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.core.net.toUri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import dev.zacsweers.metro.ContributesIntoMap
import dev.zacsweers.metrox.viewmodel.ManualViewModelAssistedFactory
import dev.zacsweers.metrox.viewmodel.ManualViewModelAssistedFactoryKey
import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.WarningBanner
import eu.kanade.presentation.util.Screen
@@ -34,6 +37,7 @@ import eu.kanade.tachiyomi.data.backup.BackupFileValidator
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.workManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
@@ -44,8 +48,6 @@ import tachiyomi.presentation.core.components.SectionCard
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class RestoreBackupScreen(
private val uri: String,
@@ -54,12 +56,8 @@ class RestoreBackupScreen(
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val viewModel = viewModel<RestoreBackupViewModel>(
factory = RestoreBackupViewModel.Factory,
extras = CreationExtras {
set(RestoreBackupViewModel.URI_KEY, uri)
},
)
val viewModel =
assistedMetroViewModel<RestoreBackupViewModel, RestoreBackupViewModel.Factory> { create(uri = uri) }
val state by viewModel.state.collectAsState()
Scaffold(
@@ -175,25 +173,21 @@ class RestoreBackupScreen(
}
}
@AssistedInject
class RestoreBackupViewModel(
@Assisted private val uri: String,
private val backupFileValidator: BackupFileValidator,
private val context: Context,
private val uri: String,
) : ViewModel() {
val state: StateFlow<RestoreBackupViewModel.State>
field = MutableStateFlow<RestoreBackupViewModel.State>(State())
companion object {
val URI_KEY = CreationExtras.Key<String>()
val Factory = viewModelFactory {
initializer {
RestoreBackupViewModel(
context = Injekt.get<Application>(),
uri = get(URI_KEY)!!,
)
}
}
@AssistedFactory
@ManualViewModelAssistedFactoryKey
@ContributesIntoMap(AppScope::class)
interface Factory : ManualViewModelAssistedFactory {
fun create(uri: String): RestoreBackupViewModel
}
init {
@@ -210,7 +204,7 @@ class RestoreBackupViewModel(
fun startRestore() {
BackupRestoreJob.start(
context = context,
workManager = context.workManager,
uri = uri.toUri(),
options = state.value.options,
)
@@ -218,7 +212,7 @@ class RestoreBackupViewModel(
private fun validate(uri: Uri) {
val results = try {
BackupFileValidator(context).validate(uri)
backupFileValidator.validate(uri)
} catch (e: Exception) {
setError(
error = InvalidRestore(uri, e.message.toString()),
@@ -16,7 +16,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.profileinstaller.ProfileVerifier
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.PreferenceScaffold
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
@@ -26,11 +25,10 @@ import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.copyToClipboard
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
import mihon.app.di.appGraph
import mihon.core.common.FeatureFlags
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class DebugInfoScreen : Screen() {
@@ -62,7 +60,7 @@ class DebugInfoScreen : Screen() {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val installationIdPref = remember { Injekt.get<BasePreferences>().installationId }
val installationIdPref = remember { context.appGraph.basePreferences.installationId }
val installationId by installationIdPref.collectAsState()
return Preference.PreferenceGroup(
@@ -1,6 +1,5 @@
package eu.kanade.presentation.more.settings.screen.debug
import android.app.Application
import android.content.Context
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.PaddingValues
@@ -21,13 +20,15 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import androidx.work.WorkInfo
import androidx.work.WorkQuery
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoMap
import dev.zacsweers.metro.Inject
import dev.zacsweers.metrox.viewmodel.ViewModelKey
import dev.zacsweers.metrox.viewmodel.metroViewModel
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
@@ -40,11 +41,11 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import mihon.app.di.appGraph
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.plus
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Instant
@@ -59,7 +60,7 @@ class WorkerInfoScreen : Screen() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val viewModel = viewModel<WorkerInfoViewModel>(factory = WorkerInfoViewModel.Factory)
val viewModel = metroViewModel<WorkerInfoViewModel>()
val enqueued by viewModel.enqueued.collectAsState()
val finished by viewModel.finished.collectAsState()
val running by viewModel.running.collectAsState()
@@ -120,20 +121,13 @@ class WorkerInfoScreen : Screen() {
)
}
@Inject
@ViewModelKey
@ContributesIntoMap(AppScope::class)
class WorkerInfoViewModel(
context: Context,
private val context: Context,
) : ViewModel() {
companion object {
val Factory = viewModelFactory {
initializer {
WorkerInfoViewModel(
context = Injekt.get<Application>(),
)
}
}
}
private val workManager = context.workManager
val finished = workManager
@@ -169,7 +163,7 @@ class WorkerInfoScreen : Screen() {
.toLocalDateTime(TimeZone.currentSystemDefault())
.toDateTimestampString(
UiPreferences.dateFormat(
Injekt.get<UiPreferences>().dateFormat.get(),
context.appGraph.uiPreferences.dateFormat.get(),
),
)
appendLine("Next scheduled run: $timestamp")
@@ -1,5 +1,6 @@
package eu.kanade.presentation.reader
import android.content.Context
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
@@ -13,6 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import kotlinx.coroutines.delay
import mihon.app.di.appGraph
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -22,7 +24,7 @@ import kotlin.time.Duration.Companion.milliseconds
class DisplayRefreshHost {
internal var currentDisplayRefresh by mutableStateOf(false)
private val readerPreferences = Injekt.get<ReaderPreferences>()
private val readerPreferences = Injekt.get<Context>().appGraph.readerPreferences
internal val flashMillis = readerPreferences.flashDurationMillis
internal val flashMode = readerPreferences.flashColor
@@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.manga.model.readerOrientation
import eu.kanade.domain.manga.model.readingMode
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
@@ -16,6 +17,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsViewModel
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import eu.kanade.tachiyomi.ui.reader.viewer.webgpu.WebGpuViewer
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
import mihon.app.di.appGraph
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.HeadingItem
@@ -23,8 +25,6 @@ import tachiyomi.presentation.core.components.SettingsChipRow
import tachiyomi.presentation.core.components.SliderItem
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.NumberFormat
@Composable
@@ -43,7 +43,7 @@ internal fun ColumnScope.ReadingModePage(viewModel: ReaderSettingsViewModel) {
}
}
val default = Injekt.get<ReaderPreferences>().defaultReadingMode.get()
val default = LocalContext.current.appGraph.readerPreferences.defaultReadingMode.get()
val resolved = ReadingMode.fromPreference(
when {
readingMode == ReadingMode.DEFAULT -> default
@@ -7,7 +7,6 @@ import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.AppTheme
import eu.kanade.presentation.theme.colorscheme.BaseColorScheme
import eu.kanade.presentation.theme.colorscheme.CatppuccinColorScheme
@@ -25,8 +24,7 @@ import eu.kanade.presentation.theme.colorscheme.TidalWaveColorScheme
import eu.kanade.presentation.theme.colorscheme.TokyoNightColorScheme
import eu.kanade.presentation.theme.colorscheme.YinYangColorScheme
import eu.kanade.presentation.theme.colorscheme.YotsubaColorScheme
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import mihon.app.di.appGraph
@Composable
fun TachiyomiTheme(
@@ -34,7 +32,8 @@ fun TachiyomiTheme(
amoled: Boolean? = null,
content: @Composable () -> Unit,
) {
val uiPreferences = Injekt.get<UiPreferences>()
val context = LocalContext.current
val uiPreferences = remember { context.appGraph.uiPreferences }
BaseTachiyomiTheme(
appTheme = appTheme ?: uiPreferences.appTheme.get(),
isAmoled = amoled ?: uiPreferences.themeDarkAmoled.get(),
+66 -33
View File
@@ -22,21 +22,21 @@ import coil3.request.allowRgb565
import coil3.request.crossfade
import coil3.util.DebugLogger
import dev.mihon.injekt.patchInjekt
import eu.kanade.domain.DomainModule
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.createGraphFactory
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.crash.CrashActivity
import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher
import eu.kanade.tachiyomi.data.coil.ImageDecoder
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
import eu.kanade.tachiyomi.data.coil.MangaKeyer
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.di.AppModule
import eu.kanade.tachiyomi.di.PreferenceModule
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
@@ -44,6 +44,7 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.isDebugBuildType
import eu.kanade.tachiyomi.util.system.notify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
@@ -51,34 +52,68 @@ import kotlinx.coroutines.flow.onEach
import logcat.AndroidLogcatLogger
import logcat.LogPriority
import logcat.LogcatLogger
import mihon.app.di.AppGraph
import mihon.app.di.injekt.MetroInteropModule
import mihon.core.metro.GraphProvider
import mihon.core.migration.Migration
import mihon.core.migration.Migrator
import mihon.core.migration.migrations.migrations
import mihon.telemetry.TelemetryConfig
import org.conscrypt.Conscrypt
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import tachiyomi.presentation.widget.WidgetManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import uy.kohesive.injekt.api.addSingleton
import java.security.Security
class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factory {
class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factory, GraphProvider<AppGraph> {
private val basePreferences: BasePreferences by injectLazy()
private val privacyPreferences: PrivacyPreferences by injectLazy()
private val networkPreferences: NetworkPreferences by injectLazy()
override val graph: AppGraph by lazy {
createGraphFactory<AppGraph.Factory>().create(context = this, isDebugBuild = isDebugBuildType)
}
@Inject private lateinit var preferenceStore: PreferenceStore
@Inject private lateinit var basePreferences: BasePreferences
@Inject private lateinit var privacyPreferences: PrivacyPreferences
@Inject private lateinit var networkPreferences: NetworkPreferences
@Inject private lateinit var uiPreferences: UiPreferences
@Inject private lateinit var coverCache: CoverCache
@Inject private lateinit var networkHelper: NetworkHelper
@Inject private lateinit var sourceManager: SourceManager
@Inject private lateinit var widgetManager: WidgetManager
@Inject private lateinit var injektMetroInteropModule: MetroInteropModule
@Inject private lateinit var migrations: Set<Migration>
private val disableIncognitoReceiver = DisableIncognitoReceiver()
@SuppressLint("LaunchActivityFromNotification")
override fun onCreate() {
super<Application>.onCreate()
patchInjekt()
// Must run before the graph is built, since injecting dependencies initializes WebView and the
// suffix can't be set once a provider exists in the process. Secondary processes die otherwise.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val process = getProcessName()
if (packageName != process) WebView.setDataDirectorySuffix(process)
}
graph.inject(this)
setupInjekt()
TelemetryConfig.init(applicationContext)
GlobalExceptionHandler.initialize(applicationContext, CrashActivity::class.java)
@@ -88,16 +123,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
Security.insertProviderAt(Conscrypt.newProvider(), 1)
}
// Avoid potential crashes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val process = getProcessName()
if (packageName != process) WebView.setDataDirectorySuffix(process)
}
Injekt.importModule(PreferenceModule(this))
Injekt.importModule(AppModule(this))
Injekt.importModule(DomainModule())
setupNotificationChannels()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
@@ -143,10 +168,10 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
.onEach(TelemetryConfig::setCrashlyticsEnabled)
.launchIn(scope)
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode.get())
setAppCompatDelegateThemeMode(uiPreferences.themeMode.get())
// Updates widget update
WidgetManager(Injekt.get(), Injekt.get()).apply { init(scope) }
widgetManager.init(scope)
if (!LogcatLogger.isInstalled) {
val minLogPriority = when {
@@ -161,14 +186,22 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
initializeMigrator()
}
private fun setupInjekt() {
patchInjekt()
Injekt.addSingleton<Application>(this)
Injekt.addSingleton<Context>(this)
Injekt.importModule(injektMetroInteropModule)
}
private fun initializeMigrator() {
val preferenceStore = Injekt.get<PreferenceStore>()
val preference = preferenceStore.getInt(Preference.appStateKey("last_version_code"), 0)
logcat { "Migration from ${preference.get()} to ${BuildConfig.VERSION_CODE}" }
logcat {
"Migration from ${preference.get()} to ${BuildConfig.VERSION_CODE} with ${migrations.size} migration(s)"
}
Migrator.initialize(
old = preference.get(),
new = BuildConfig.VERSION_CODE,
migrations = migrations,
migrations = migrations.toList(),
onMigrationComplete = {
logcat { "Updating last version to ${BuildConfig.VERSION_CODE}" }
preference.set(BuildConfig.VERSION_CODE)
@@ -178,7 +211,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
override fun newImageLoader(context: Context): ImageLoader {
return ImageLoader.Builder(this).apply {
val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client }
val callFactoryLazy = lazy { networkHelper.client }
components {
// NetworkFetcher.Factory
add(OkHttpNetworkFetcherFactory(callFactoryLazy::value))
@@ -186,10 +219,10 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
add(ImageDecoder.Factory())
// Fetcher.Factory
add(BufferedSourceFetcher.Factory())
add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy))
add(MangaCoverFetcher.MangaFactory(callFactoryLazy))
add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy, coverCache, sourceManager))
add(MangaCoverFetcher.MangaFactory(callFactoryLazy, coverCache, sourceManager))
// Keyer
add(MangaCoverKeyer())
add(MangaCoverKeyer(coverCache))
add(MangaKeyer())
}
@@ -211,11 +244,11 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
}
override fun onStart(owner: LifecycleOwner) {
SecureActivityDelegate.onApplicationStart()
SecureActivityDelegate.onApplicationStart(this)
}
override fun onStop(owner: LifecycleOwner) {
SecureActivityDelegate.onApplicationStopped()
SecureActivityDelegate.onApplicationStopped(this)
}
override fun getPackageName(): String {
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.net.Uri
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.models.Backup
import kotlinx.serialization.SerializationException
import kotlinx.serialization.protobuf.ProtoBuf
@@ -10,13 +11,12 @@ import okio.gzip
import okio.source
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.IOException
@Inject
class BackupDecoder(
private val context: Context,
private val parser: ProtoBuf = Injekt.get(),
private val parser: ProtoBuf,
) {
/**
* Decode a potentially-gzipped backup.
@@ -1,17 +1,15 @@
package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.net.Uri
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.track.TrackerManager
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Inject
class BackupFileValidator(
private val context: Context,
private val sourceManager: SourceManager = Injekt.get(),
private val trackerManager: TrackerManager = Injekt.get(),
private val sourceManager: SourceManager,
private val trackerManager: TrackerManager,
private val backupDecoder: BackupDecoder,
) {
/**
@@ -21,7 +19,7 @@ class BackupFileValidator(
*/
fun validate(uri: Uri): Results {
val backup = try {
BackupDecoder(context).decode(uri)
backupDecoder.decode(uri)
} catch (e: Exception) {
throw IllegalStateException(e)
}
@@ -4,6 +4,7 @@ import android.content.Context
import android.graphics.BitmapFactory
import androidx.core.app.NotificationCompat
import com.hippo.unifile.UniFile
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
@@ -16,14 +17,14 @@ import tachiyomi.core.common.i18n.pluralStringResource
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.storage.displayablePath
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
import java.io.File
import kotlin.time.Duration.Companion.milliseconds
class BackupNotifier(private val context: Context) {
private val preferences: SecurityPreferences by injectLazy()
@Inject
class BackupNotifier(
private val context: Context,
private val preferences: SecurityPreferences,
) {
private val progressNotificationBuilder = context.notificationBuilder(
Notifications.CHANNEL_BACKUP_RESTORE_PROGRESS,
) {
@@ -16,6 +16,7 @@ import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.hippo.unifile.UniFile
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.BackupNotifier
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
import eu.kanade.tachiyomi.data.notification.Notifications
@@ -24,22 +25,33 @@ import eu.kanade.tachiyomi.util.system.isRunning
import eu.kanade.tachiyomi.util.system.setForegroundSafely
import eu.kanade.tachiyomi.util.system.workManager
import logcat.LogPriority
import mihon.app.di.AppGraph
import mihon.app.di.appGraph
import mihon.core.metro.metroGraph
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.storage.service.StorageManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class BackupCreateJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
private val notifier = BackupNotifier(context)
private val graph: AppGraph = context.metroGraph()
@Inject
private lateinit var backupCreatorFactory: BackupCreator.Factory
@Inject
private lateinit var storageManager: StorageManager
@Inject private lateinit var notifier: BackupNotifier
override suspend fun doWork(): Result {
graph.inject(this)
val isAutoBackup = inputData.getBoolean(IS_AUTO_BACKUP_KEY, true)
if (isAutoBackup && BackupRestoreJob.isRunning(context)) return Result.retry()
if (isAutoBackup && BackupRestoreJob.isRunning(context.workManager)) return Result.retry()
val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
?: getAutomaticBackupLocation()
@@ -51,7 +63,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
?: BackupOptions()
return try {
val location = BackupCreator(context, isAutoBackup).backup(uri, options)
val location = backupCreatorFactory.create(isAutoBackup = isAutoBackup).backup(uri, options)
if (!isAutoBackup) {
notifier.showBackupComplete(UniFile.fromUri(context, location.toUri())!!)
}
@@ -78,7 +90,6 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
}
private fun getAutomaticBackupLocation(): Uri? {
val storageManager = Injekt.get<StorageManager>()
return storageManager.getAutomaticBackupsDirectory()?.uri
}
@@ -88,7 +99,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
}
fun setupTask(context: Context, prefInterval: Int? = null) {
val backupPreferences = Injekt.get<BackupPreferences>()
val backupPreferences = context.appGraph.backupPreferences
val interval = prefInterval ?: backupPreferences.backupInterval.get()
if (interval > 0) {
val constraints = Constraints(
@@ -3,6 +3,9 @@ package eu.kanade.tachiyomi.data.backup.create
import android.content.Context
import android.net.Uri
import com.hippo.unifile.UniFile
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
import eu.kanade.tachiyomi.data.backup.create.creators.CategoriesBackupCreator
@@ -29,29 +32,31 @@ import tachiyomi.domain.manga.interactor.GetFavorites
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.time.Clock
@AssistedInject
class BackupCreator(
@Assisted private val isAutoBackup: Boolean,
private val context: Context,
private val isAutoBackup: Boolean,
private val parser: ProtoBuf = Injekt.get(),
private val getFavorites: GetFavorites = Injekt.get(),
private val backupPreferences: BackupPreferences = Injekt.get(),
private val mangaRepository: MangaRepository = Injekt.get(),
private val categoriesBackupCreator: CategoriesBackupCreator = CategoriesBackupCreator(),
private val mangaBackupCreator: MangaBackupCreator = MangaBackupCreator(),
private val preferenceBackupCreator: PreferenceBackupCreator = PreferenceBackupCreator(),
private val extensionStoresBackupCreator: ExtensionStoresBackupCreator = ExtensionStoresBackupCreator(),
private val sourcesBackupCreator: SourcesBackupCreator = SourcesBackupCreator(),
private val parser: ProtoBuf,
private val getFavorites: GetFavorites,
private val backupPreferences: BackupPreferences,
private val mangaRepository: MangaRepository,
private val categoriesBackupCreator: CategoriesBackupCreator,
private val mangaBackupCreator: MangaBackupCreator,
private val preferenceBackupCreator: PreferenceBackupCreator,
private val extensionStoresBackupCreator: ExtensionStoresBackupCreator,
private val sourcesBackupCreator: SourcesBackupCreator,
private val backupFileValidator: BackupFileValidator,
) {
@AssistedFactory
fun interface Factory {
fun create(isAutoBackup: Boolean): BackupCreator
}
suspend fun backup(uri: Uri, options: BackupOptions): String {
var file: UniFile? = null
@@ -105,7 +110,7 @@ class BackupCreator(
val fileUri = file.uri
// Make sure it's a valid backup file
BackupFileValidator(context).validate(fileUri)
backupFileValidator.validate(fileUri)
if (isAutoBackup) {
backupPreferences.lastAutoBackupTimestamp.set(Clock.System.now().toEpochMilliseconds())
@@ -1,14 +1,14 @@
package eu.kanade.tachiyomi.data.backup.create.creators
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.backupCategoryMapper
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Inject
class CategoriesBackupCreator(
private val getCategories: GetCategories = Injekt.get(),
private val getCategories: GetCategories,
) {
suspend operator fun invoke(): List<BackupCategory> {
@@ -1,13 +1,13 @@
package eu.kanade.tachiyomi.data.backup.create.creators
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionStore
import eu.kanade.tachiyomi.data.backup.models.backupExtensionStoreMapper
import mihon.domain.extension.interactor.GetExtensionStores
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Inject
class ExtensionStoresBackupCreator(
private val getExtensionStores: GetExtensionStores = Injekt.get(),
private val getExtensionStores: GetExtensionStores,
) {
suspend operator fun invoke(): List<BackupExtensionStore> {
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.backup.create.creators
import app.cash.sqldelight.async.coroutines.awaitAsList
import app.cash.sqldelight.async.coroutines.awaitAsOne
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.create.BackupOptions
import eu.kanade.tachiyomi.data.backup.models.BackupChapter
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
@@ -14,13 +15,12 @@ import tachiyomi.data.MemoColumnAdapter
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.manga.model.Manga
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Inject
class MangaBackupCreator(
private val database: Database = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getHistory: GetHistory = Injekt.get(),
private val database: Database,
private val getCategories: GetCategories,
private val getHistory: GetHistory,
) {
suspend operator fun invoke(mangas: List<Manga>, options: BackupOptions): List<BackupManga> {
@@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.data.backup.create.creators
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue
@@ -14,12 +15,11 @@ import eu.kanade.tachiyomi.source.sourcePreferences
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Inject
class PreferenceBackupCreator(
private val sourceManager: SourceManager = Injekt.get(),
private val preferenceStore: PreferenceStore = Injekt.get(),
private val sourceManager: SourceManager,
private val preferenceStore: PreferenceStore,
) {
fun createApp(includePrivatePreferences: Boolean): List<BackupPreference> {
@@ -1,14 +1,14 @@
package eu.kanade.tachiyomi.data.backup.create.creators
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.source.Source
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Inject
class SourcesBackupCreator(
private val sourceManager: SourceManager = Injekt.get(),
private val sourceManager: SourceManager,
) {
operator fun invoke(mangas: List<BackupManga>): List<BackupSource> {
@@ -9,16 +9,19 @@ import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.ForegroundInfo
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.BackupNotifier
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.isRunning
import eu.kanade.tachiyomi.util.system.setForegroundSafely
import eu.kanade.tachiyomi.util.system.workManager
import kotlinx.coroutines.CancellationException
import logcat.LogPriority
import mihon.app.di.AppGraph
import mihon.core.metro.metroGraph
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.system.logcat
import tachiyomi.i18n.MR
@@ -26,9 +29,15 @@ import tachiyomi.i18n.MR
class BackupRestoreJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
private val notifier = BackupNotifier(context)
private val graph: AppGraph = context.metroGraph()
@Inject private lateinit var backupRestorerFactory: BackupRestorer.Factory
@Inject private lateinit var notifier: BackupNotifier
override suspend fun doWork(): Result {
graph.inject(this)
val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
val options = inputData.getBooleanArray(OPTIONS_KEY)?.let { RestoreOptions.fromBooleanArray(it) }
@@ -41,7 +50,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
setForegroundSafely()
return try {
BackupRestorer(context, notifier, isSync).restore(uri, options)
backupRestorerFactory.create(notifier = notifier, isSync = isSync).restore(uri, options)
Result.success()
} catch (e: Exception) {
if (e is CancellationException) {
@@ -70,12 +79,12 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
}
companion object {
fun isRunning(context: Context): Boolean {
return context.workManager.isRunning(TAG)
fun isRunning(workManager: WorkManager): Boolean {
return workManager.isRunning(TAG)
}
fun start(
context: Context,
workManager: WorkManager,
uri: Uri,
options: RestoreOptions,
sync: Boolean = false,
@@ -89,11 +98,11 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
.addTag(TAG)
.setInputData(inputData)
.build()
context.workManager.enqueueUniqueWork(TAG, ExistingWorkPolicy.KEEP, request)
workManager.enqueueUniqueWork(TAG, ExistingWorkPolicy.KEEP, request)
}
fun stop(context: Context) {
context.workManager.cancelUniqueWork(TAG)
fun stop(workManager: WorkManager) {
workManager.cancelUniqueWork(TAG)
}
}
}
@@ -2,6 +2,9 @@ package eu.kanade.tachiyomi.data.backup.restore
import android.content.Context
import android.net.Uri
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import eu.kanade.tachiyomi.data.backup.BackupDecoder
import eu.kanade.tachiyomi.data.backup.BackupNotifier
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
@@ -24,8 +27,6 @@ import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.system.logcat
import tachiyomi.data.Database
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
@@ -36,18 +37,25 @@ import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.concurrent.atomics.incrementAndFetch
@OptIn(ExperimentalAtomicApi::class)
@AssistedInject
class BackupRestorer(
@Assisted private val notifier: BackupNotifier,
@Assisted private val isSync: Boolean,
private val context: Context,
private val notifier: BackupNotifier,
private val isSync: Boolean,
private val database: Database = Injekt.get(),
private val categoriesRestorer: CategoriesRestorer = CategoriesRestorer(),
private val preferenceRestorer: PreferenceRestorer = PreferenceRestorer(context),
private val extensionStoreRestorer: ExtensionStoreRestorer = ExtensionStoreRestorer(),
private val mangaRestorer: MangaRestorer = MangaRestorer(),
private val database: Database,
private val downloadCache: DownloadCache,
private val categoriesRestorer: CategoriesRestorer,
private val preferenceRestorer: PreferenceRestorer,
private val extensionStoreRestorer: ExtensionStoreRestorer,
private val mangaRestorer: MangaRestorer,
private val backupDecoder: BackupDecoder,
) {
@AssistedFactory
fun interface Factory {
fun create(notifier: BackupNotifier, isSync: Boolean): BackupRestorer
}
private var restoreAmount = 0
private val restoreProgress = AtomicInt(0)
private val errors = CopyOnWriteArrayList<Pair<Date, String>>()
@@ -65,7 +73,7 @@ class BackupRestorer(
// Invalidate download cache to ensure UI reflects any restored downloads
if (options.libraryEntries) {
try {
Injekt.get<DownloadCache>().invalidateCache()
downloadCache.invalidateCache()
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to invalidate download cache after restore" }
}
@@ -85,7 +93,7 @@ class BackupRestorer(
}
private suspend fun restoreFromFile(uri: Uri, options: RestoreOptions) {
val backup = BackupDecoder(context).decode(uri)
val backup = backupDecoder.decode(uri)
// Store source mapping for error messages
val backupMaps = backup.backupSources
@@ -1,16 +1,16 @@
package eu.kanade.tachiyomi.data.backup.restore.restorers
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import tachiyomi.data.Database
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.library.service.LibraryPreferences
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Inject
class CategoriesRestorer(
private val database: Database = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(),
private val database: Database,
private val getCategories: GetCategories,
private val libraryPreferences: LibraryPreferences,
) {
suspend operator fun invoke(backupCategories: List<BackupCategory>) {
@@ -1,12 +1,12 @@
package eu.kanade.tachiyomi.data.backup.restore.restorers
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionStore
import tachiyomi.data.Database
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Inject
class ExtensionStoreRestorer(
private val database: Database = Injekt.get(),
private val database: Database,
) {
suspend operator fun invoke(
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers
import app.cash.sqldelight.async.coroutines.awaitAsList
import app.cash.sqldelight.async.coroutines.awaitAsOne
import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull
import dev.zacsweers.metro.Inject
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupChapter
@@ -23,21 +24,20 @@ import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack
import tachiyomi.domain.track.model.Track
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Date
import kotlin.math.max
import kotlin.time.Clock
@Inject
class MangaRestorer(
private val database: Database = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getMangaByUrlAndSourceId: GetMangaByUrlAndSourceId = Injekt.get(),
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(),
fetchInterval: FetchInterval = Injekt.get(),
private val database: Database,
private val getCategories: GetCategories,
private val getMangaByUrlAndSourceId: GetMangaByUrlAndSourceId,
private val getChaptersByMangaId: GetChaptersByMangaId,
private val updateManga: UpdateManga,
private val getTracks: GetTracks,
private val insertTrack: InsertTrack,
fetchInterval: FetchInterval,
) {
private val timeZone = TimeZone.currentSystemDefault()
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers
import android.content.Context
import android.util.Log
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
@@ -21,13 +22,12 @@ import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.library.service.LibraryPreferences
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@Inject
class PreferenceRestorer(
private val context: Context,
private val getCategories: GetCategories = Injekt.get(),
private val preferenceStore: PreferenceStore = Injekt.get(),
private val getCategories: GetCategories,
private val preferenceStore: PreferenceStore,
) {
suspend fun restoreApp(
preferences: List<BackupPreference>,
@@ -45,7 +45,7 @@ class PreferenceRestorer(
suspend fun restoreSource(preferences: List<BackupSourcePreferences>) {
preferences.forEach {
val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey))
val sourcePrefs = AndroidPreferenceStore(sourcePreferences(it.sourceKey))
restorePreferences(it.prefs, sourcePrefs)
}
}
@@ -3,6 +3,9 @@ package eu.kanade.tachiyomi.data.cache
import android.content.Context
import android.text.format.Formatter
import com.jakewharton.disklrucache.DiskLruCache
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.saveTo
@@ -24,6 +27,8 @@ import java.io.IOException
*
* @param context the application context.
*/
@Inject
@SingleIn(AppScope::class)
class ChapterCache(
private val context: Context,
private val json: Json,
@@ -1,6 +1,9 @@
package eu.kanade.tachiyomi.data.cache
import android.content.Context
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import eu.kanade.tachiyomi.util.storage.DiskUtil
import tachiyomi.domain.manga.model.Manga
import java.io.File
@@ -15,6 +18,8 @@ import java.io.InputStream
* @param context the application context.
* @constructor creates an instance of the cover cache.
*/
@Inject
@SingleIn(AppScope::class)
class CoverCache(private val context: Context) {
companion object {
@@ -31,7 +31,6 @@ import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaCover
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.io.IOException
@@ -299,11 +298,10 @@ class MangaCoverFetcher(
class MangaFactory(
private val callFactoryLazy: Lazy<Call.Factory>,
private val coverCache: CoverCache,
private val sourceManager: SourceManager,
) : Fetcher.Factory<Manga> {
private val coverCache: CoverCache by injectLazy()
private val sourceManager: SourceManager by injectLazy()
override fun create(data: Manga, options: Options, imageLoader: ImageLoader): Fetcher {
return MangaCoverFetcher(
url = data.thumbnailUrl,
@@ -321,11 +319,10 @@ class MangaCoverFetcher(
class MangaCoverFactory(
private val callFactoryLazy: Lazy<Call.Factory>,
private val coverCache: CoverCache,
private val sourceManager: SourceManager,
) : Fetcher.Factory<MangaCover> {
private val coverCache: CoverCache by injectLazy()
private val sourceManager: SourceManager by injectLazy()
override fun create(data: MangaCover, options: Options, imageLoader: ImageLoader): Fetcher {
return MangaCoverFetcher(
url = data.url,
@@ -5,8 +5,6 @@ import coil3.request.Options
import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.tachiyomi.data.cache.CoverCache
import tachiyomi.domain.manga.model.MangaCover
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import tachiyomi.domain.manga.model.Manga as DomainManga
class MangaKeyer : Keyer<DomainManga> {
@@ -20,7 +18,7 @@ class MangaKeyer : Keyer<DomainManga> {
}
class MangaCoverKeyer(
private val coverCache: CoverCache = Injekt.get(),
private val coverCache: CoverCache,
) : Keyer<MangaCover> {
override fun key(data: MangaCover, options: Options): String {
return if (coverCache.getCustomCoverFile(data.mangaId).exists()) {
@@ -1,9 +1,11 @@
package eu.kanade.tachiyomi.data.download
import android.app.Application
import android.content.Context
import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.Source
import kotlinx.coroutines.CancellationException
@@ -61,12 +63,14 @@ import kotlin.time.Duration.Companion.seconds
* defined in [renewInterval] as we don't have any control over the filesystem and the user can
* delete the folders at any time without the app noticing.
*/
@Inject
@SingleIn(AppScope::class)
class DownloadCache(
private val context: Context,
private val provider: DownloadProvider = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(),
private val extensionManager: ExtensionManager = Injekt.get(),
private val storageManager: StorageManager = Injekt.get(),
private val provider: DownloadProvider,
private val sourceManager: SourceManager,
private val extensionManager: ExtensionManager,
private val storageManager: StorageManager,
) {
private val scope = CoroutineScope(Dispatchers.IO)
@@ -492,7 +496,7 @@ private object UniFileAsStringSerializer : KSerializer<UniFile?> {
override fun deserialize(decoder: Decoder): UniFile? {
return if (decoder.decodeNotNullMark()) {
UniFile.fromUri(Injekt.get<Application>(), decoder.decodeString().toUri())
UniFile.fromUri(Injekt.get<Context>(), decoder.decodeString().toUri())
} else {
decoder.decodeNull()
}
@@ -11,6 +11,7 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.NetworkState
@@ -24,9 +25,9 @@ import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import mihon.app.di.AppGraph
import mihon.core.metro.metroGraph
import tachiyomi.domain.download.service.DownloadPreferences
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/**
* This worker is used to manage the downloader. The system can decide to stop the worker, in
@@ -34,8 +35,15 @@ import uy.kohesive.injekt.api.get
*/
class DownloadJob(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
private val downloadManager: DownloadManager = Injekt.get()
private val downloadPreferences: DownloadPreferences = Injekt.get()
private val graph: AppGraph = context.metroGraph()
@Inject private lateinit var downloadManager: DownloadManager
@Inject private lateinit var downloadPreferences: DownloadPreferences
init {
graph.inject(this)
}
override suspend fun getForegroundInfo(): ForegroundInfo {
val notification = applicationContext.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
@@ -1,9 +1,13 @@
package eu.kanade.tachiyomi.data.download
import android.content.Context
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.drop
@@ -20,41 +24,37 @@ import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.chapter.interactor.GetChapter
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/**
* This class is used to manage chapter downloads in the application. It must be instantiated once
* and retrieved through dependency injection. You can use this class to queue new chapters or query
* downloaded chapters.
*/
@Inject
@SingleIn(AppScope::class)
class DownloadManager(
private val context: Context,
private val provider: DownloadProvider = Injekt.get(),
private val cache: DownloadCache = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(),
private val downloadPreferences: DownloadPreferences = Injekt.get(),
private val provider: DownloadProvider,
private val cache: DownloadCache,
private val getCategories: GetCategories,
private val getManga: GetManga,
private val getChapter: GetChapter,
private val sourceManager: SourceManager,
private val downloadPreferences: DownloadPreferences,
private val downloader: Downloader,
private val pendingDeleter: DownloadPendingDeleter,
) {
/**
* Downloader whose only task is to download chapters.
*/
private val downloader = Downloader(context, provider, cache)
val isRunning: Boolean
get() = downloader.isRunning
/**
* Queue to delay the deletion of a list of chapters until triggered.
*/
private val pendingDeleter = DownloadPendingDeleter(context)
val queueState
get() = downloader.queueState
@@ -107,7 +107,7 @@ class DownloadManager(
fun startDownloadNow(chapterId: Long) {
val existingDownload = getQueuedDownloadOrNull(chapterId)
// If not in queue try to start a new download
val toAdd = existingDownload ?: runBlocking { Download.fromChapterId(chapterId) } ?: return
val toAdd = existingDownload ?: runBlocking { downloadFromChapterId(chapterId) } ?: return
queueState.value.toMutableList().apply {
existingDownload?.let { remove(it) }
add(0, toAdd)
@@ -116,6 +116,14 @@ class DownloadManager(
startDownloads()
}
private suspend fun downloadFromChapterId(chapterId: Long): Download? {
val chapter = getChapter.await(chapterId) ?: return null
val manga = getManga.await(chapter.mangaId) ?: return null
val source = sourceManager.get(manga.source) as? HttpSource ?: return null
return Download(source, manga, chapter)
}
/**
* Reorders the download queue.
*
@@ -4,6 +4,7 @@ import android.app.PendingIntent
import android.content.Context
import android.graphics.BitmapFactory
import androidx.core.app.NotificationCompat
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.data.download.model.Download
@@ -16,7 +17,6 @@ import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notify
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
import java.util.regex.Pattern
/**
@@ -24,9 +24,11 @@ import java.util.regex.Pattern
*
* @param context context of application
*/
internal class DownloadNotifier(private val context: Context) {
private val preferences: SecurityPreferences by injectLazy()
@Inject
class DownloadNotifier(
private val context: Context,
private val preferences: SecurityPreferences,
) {
private val progressNotificationBuilder by lazy {
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
@@ -2,21 +2,24 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context
import androidx.core.content.edit
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.Manga
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/**
* Class used to keep a list of chapters for future deletion.
*
* @param context the application context.
*/
@Inject
@SingleIn(AppScope::class)
class DownloadPendingDeleter(
context: Context,
private val json: Json = Injekt.get(),
private val json: Json,
) {
/**
@@ -2,6 +2,9 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context
import com.hippo.unifile.UniFile
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.lang.Hash.md5
import eu.kanade.tachiyomi.util.storage.DiskUtil
@@ -14,8 +17,6 @@ import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.storage.service.StorageManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.IOException
/**
@@ -24,10 +25,12 @@ import java.io.IOException
*
* @param context the application context.
*/
@Inject
@SingleIn(AppScope::class)
class DownloadProvider(
private val context: Context,
private val storageManager: StorageManager = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(),
private val storageManager: StorageManager,
private val libraryPreferences: LibraryPreferences,
) {
private val downloadsDir: UniFile?
@@ -2,6 +2,9 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context
import androidx.core.content.edit
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.coroutines.runBlocking
@@ -11,18 +14,18 @@ import tachiyomi.domain.chapter.interactor.GetChapter
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/**
* This class is used to persist active downloads across application restarts.
*/
@Inject
@SingleIn(AppScope::class)
class DownloadStore(
context: Context,
private val sourceManager: SourceManager = Injekt.get(),
private val json: Json = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val getChapter: GetChapter = Injekt.get(),
private val sourceManager: SourceManager,
private val json: Json,
private val getManga: GetManga,
private val getChapter: GetChapter,
) {
/**
@@ -2,6 +2,9 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context
import com.hippo.unifile.UniFile
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import eu.kanade.domain.chapter.model.toSChapter
import eu.kanade.domain.manga.model.getComicInfo
import eu.kanade.tachiyomi.data.cache.ChapterCache
@@ -57,8 +60,6 @@ import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import java.util.Locale
import kotlin.time.Duration.Companion.seconds
@@ -68,34 +69,27 @@ import kotlin.time.Duration.Companion.seconds
*
* Its queue contains the list of chapters to download.
*/
@Inject
@SingleIn(AppScope::class)
class Downloader(
private val context: Context,
private val provider: DownloadProvider,
private val cache: DownloadCache,
private val sourceManager: SourceManager = Injekt.get(),
private val chapterCache: ChapterCache = Injekt.get(),
private val downloadPreferences: DownloadPreferences = Injekt.get(),
private val xml: XML = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(),
private val sourceManager: SourceManager,
private val chapterCache: ChapterCache,
private val downloadPreferences: DownloadPreferences,
private val xml: XML,
private val getCategories: GetCategories,
private val getTracks: GetTracks,
private val store: DownloadStore,
private val notifier: DownloadNotifier,
) {
/**
* Store for persisting downloads across restarts.
*/
private val store = DownloadStore(context)
/**
* Queue where active downloads are kept.
*/
private val _queueState = MutableStateFlow<List<Download>>(emptyList())
val queueState = _queueState.asStateFlow()
/**
* Notifier for the downloader state and progress.
*/
private val notifier by lazy { DownloadNotifier(context) }
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private var downloaderJob: Job? = null
@@ -10,13 +10,8 @@ import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import tachiyomi.domain.chapter.interactor.GetChapter
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Duration.Companion.milliseconds
data class Download(
@@ -71,19 +66,4 @@ data class Download(
DOWNLOADED(3),
ERROR(4),
}
companion object {
suspend fun fromChapterId(
chapterId: Long,
getChapter: GetChapter = Injekt.get(),
getManga: GetManga = Injekt.get(),
sourceManager: SourceManager = Injekt.get(),
): Download? {
val chapter = getChapter.await(chapterId) ?: return null
val manga = getManga.await(chapter.mangaId) ?: return null
val source = sourceManager.get(manga.source) as? HttpSource ?: return null
return Download(source, manga, chapter)
}
}
}
@@ -15,9 +15,11 @@ import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.source.model.SManga
@@ -38,6 +40,9 @@ import kotlinx.coroutines.sync.withPermit
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import logcat.LogPriority
import mihon.app.di.AppGraph
import mihon.app.di.appGraph
import mihon.core.metro.metroGraph
import mihon.domain.chapter.interactor.FilterChaptersForDownload
import mihon.domain.source.interactor.UpdateMangaFromRemote
import tachiyomi.core.common.i18n.stringResource
@@ -63,8 +68,6 @@ import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.model.SourceNotInstalledException
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit
@@ -78,24 +81,34 @@ import kotlin.time.Clock
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
private val sourceManager: SourceManager = Injekt.get()
private val libraryPreferences: LibraryPreferences = Injekt.get()
private val downloadManager: DownloadManager = Injekt.get()
private val getLibraryManga: GetLibraryManga = Injekt.get()
private val getManga: GetManga = Injekt.get()
private val fetchInterval: FetchInterval = Injekt.get()
private val filterChaptersForDownload: FilterChaptersForDownload = Injekt.get()
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get()
private val graph: AppGraph = context.metroGraph()
private val notifier = LibraryUpdateNotifier(context)
@Inject private lateinit var sourceManager: SourceManager
@Inject private lateinit var libraryPreferences: LibraryPreferences
@Inject private lateinit var downloadManager: DownloadManager
@Inject private lateinit var getLibraryManga: GetLibraryManga
@Inject private lateinit var getManga: GetManga
@Inject private lateinit var fetchInterval: FetchInterval
@Inject private lateinit var filterChaptersForDownload: FilterChaptersForDownload
@Inject private lateinit var updateMangaFromRemote: UpdateMangaFromRemote
@Inject private lateinit var notifier: LibraryUpdateNotifier
private var mangaToUpdate: List<LibraryManga> = mutableListOf()
override suspend fun doWork(): Result {
graph.inject(this)
if (tags.contains(WORK_NAME_AUTO)) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
val preferences = Injekt.get<LibraryPreferences>()
val restrictions = preferences.autoUpdateDeviceRestrictions.get()
val restrictions = libraryPreferences.autoUpdateDeviceRestrictions.get()
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
return Result.retry()
}
@@ -133,7 +146,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
}
override suspend fun getForegroundInfo(): ForegroundInfo {
val notifier = LibraryUpdateNotifier(context)
return ForegroundInfo(
Notifications.ID_LIBRARY_PROGRESS,
notifier.progressNotificationBuilder.build(),
@@ -420,7 +432,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
context: Context,
prefInterval: Int? = null,
) {
val preferences = Injekt.get<LibraryPreferences>()
val preferences = context.appGraph.libraryPreferences
val interval = prefInterval ?: preferences.autoUpdateInterval.get()
if (interval > 0) {
val restrictions = preferences.autoUpdateDeviceRestrictions.get()
@@ -469,11 +481,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
}
fun startNow(
context: Context,
workManager: WorkManager,
category: Category? = null,
): Boolean {
val wm = context.workManager
if (wm.isRunning(TAG)) {
if (workManager.isRunning(TAG)) {
// Already running either as a scheduled or manual job
return false
}
@@ -486,20 +497,20 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
.addTag(WORK_NAME_MANUAL)
.setInputData(inputData)
.build()
wm.enqueueUniqueWork(WORK_NAME_MANUAL, ExistingWorkPolicy.KEEP, request)
workManager.enqueueUniqueWork(WORK_NAME_MANUAL, ExistingWorkPolicy.KEEP, request)
return true
}
fun stop(context: Context) {
val wm = context.workManager
val workManager = context.workManager
val workQuery = WorkQuery.Builder.fromTags(listOf(TAG))
.addStates(listOf(WorkInfo.State.RUNNING))
.build()
wm.getWorkInfos(workQuery).get()
workManager.getWorkInfos(workQuery).get()
// Should only return one work but just in case
.forEach {
wm.cancelWorkById(it.id)
workManager.cancelWorkById(it.id)
// Re-enqueue cancelled scheduled work
if (it.tags.contains(WORK_NAME_AUTO)) {
@@ -14,6 +14,7 @@ import coil3.imageLoader
import coil3.request.ImageRequest
import coil3.request.transformations
import coil3.transform.CircleCropTransformation
import dev.zacsweers.metro.Inject
import eu.kanade.presentation.util.formatChapterNumber
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences
@@ -37,15 +38,14 @@ import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.math.RoundingMode
import java.text.NumberFormat
@Inject
class LibraryUpdateNotifier(
private val context: Context,
private val securityPreferences: SecurityPreferences = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(),
private val securityPreferences: SecurityPreferences,
private val sourceManager: SourceManager,
) {
private val percentFormatter = NumberFormat.getPercentInstance().apply {
@@ -7,13 +7,12 @@ import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.ForegroundInfo
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkQuery
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.isRunning
import eu.kanade.tachiyomi.util.system.setForegroundSafely
import eu.kanade.tachiyomi.util.system.workManager
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@@ -22,6 +21,8 @@ import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import logcat.LogPriority
import mihon.app.di.AppGraph
import mihon.core.metro.metroGraph
import mihon.domain.source.interactor.UpdateMangaFromRemote
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.logcat
@@ -29,8 +30,6 @@ import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.interactor.GetLibraryManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.concurrent.atomics.AtomicInt
import kotlin.concurrent.atomics.ExperimentalAtomicApi
@@ -40,15 +39,21 @@ import kotlin.concurrent.atomics.fetchAndIncrement
class MetadataUpdateJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
private val sourceManager: SourceManager = Injekt.get()
private val getLibraryManga: GetLibraryManga = Injekt.get()
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get()
private val graph: AppGraph = context.metroGraph()
private val notifier = LibraryUpdateNotifier(context)
@Inject private lateinit var sourceManager: SourceManager
@Inject private lateinit var getLibraryManga: GetLibraryManga
@Inject private lateinit var updateMangaFromRemote: UpdateMangaFromRemote
@Inject private lateinit var notifier: LibraryUpdateNotifier
private var mangaToUpdate: List<LibraryManga> = mutableListOf()
override suspend fun doWork(): Result {
graph.inject(this)
setForegroundSafely()
addMangaToQueue()
@@ -72,7 +77,6 @@ class MetadataUpdateJob(private val context: Context, workerParams: WorkerParame
}
override suspend fun getForegroundInfo(): ForegroundInfo {
val notifier = LibraryUpdateNotifier(context)
return ForegroundInfo(
Notifications.ID_LIBRARY_PROGRESS,
notifier.progressNotificationBuilder.build(),
@@ -168,9 +172,8 @@ class MetadataUpdateJob(private val context: Context, workerParams: WorkerParame
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60
fun startNow(context: Context): Boolean {
val wm = context.workManager
if (wm.isRunning(TAG)) {
fun startNow(workManager: WorkManager): Boolean {
if (workManager.isRunning(TAG)) {
// Already running either as a scheduled or manual job
return false
}
@@ -178,21 +181,9 @@ class MetadataUpdateJob(private val context: Context, workerParams: WorkerParame
.addTag(TAG)
.addTag(WORK_NAME_MANUAL)
.build()
wm.enqueueUniqueWork(WORK_NAME_MANUAL, ExistingWorkPolicy.KEEP, request)
workManager.enqueueUniqueWork(WORK_NAME_MANUAL, ExistingWorkPolicy.KEEP, request)
return true
}
fun stop(context: Context) {
val wm = context.workManager
val workQuery = WorkQuery.Builder.fromTags(listOf(TAG))
.addStates(listOf(WorkInfo.State.RUNNING))
.build()
wm.getWorkInfos(workQuery).get()
// Should only return one work but just in case
.forEach {
wm.cancelWorkById(it.id)
}
}
}
}
@@ -6,6 +6,7 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.net.toUri
import dev.zacsweers.metro.Inject
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
@@ -16,7 +17,9 @@ import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.workManager
import kotlinx.coroutines.runBlocking
import mihon.app.di.appGraph
import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.chapter.interactor.GetChapter
@@ -28,9 +31,6 @@ import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
/**
@@ -40,12 +40,21 @@ import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
*/
class NotificationReceiver : BroadcastReceiver() {
private val getManga: GetManga by injectLazy()
private val getChapter: GetChapter by injectLazy()
private val updateChapter: UpdateChapter by injectLazy()
private val downloadManager: DownloadManager by injectLazy()
@Inject private lateinit var getManga: GetManga
@Inject private lateinit var getChapter: GetChapter
@Inject private lateinit var updateChapter: UpdateChapter
@Inject private lateinit var downloadManager: DownloadManager
@Inject private lateinit var downloadPreferences: DownloadPreferences
@Inject private lateinit var sourceManager: SourceManager
override fun onReceive(context: Context, intent: Intent) {
context.appGraph.inject(this)
when (intent.action) {
// Dismiss notification
ACTION_DISMISS_NOTIFICATION -> dismissNotification(context, intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1))
@@ -161,7 +170,7 @@ class NotificationReceiver : BroadcastReceiver() {
* @param context context of application
*/
private fun cancelRestore(context: Context) {
BackupRestoreJob.stop(context)
BackupRestoreJob.stop(context.workManager)
}
/**
@@ -180,9 +189,6 @@ class NotificationReceiver : BroadcastReceiver() {
* @param mangaId id of manga
*/
private fun markAsRead(chapterUrls: Array<String>, mangaId: Long) {
val downloadPreferences: DownloadPreferences = Injekt.get()
val sourceManager: SourceManager = Injekt.get()
launchIO {
val toUpdate = chapterUrls.mapNotNull { getChapter.await(it, mangaId) }
.map {
@@ -11,6 +11,9 @@ import android.webkit.MimeTypeMap
import androidx.annotation.RequiresApi
import androidx.core.content.contentValuesOf
import androidx.core.net.toUri
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.cacheImageDir
import eu.kanade.tachiyomi.util.storage.getUriCompat
@@ -26,6 +29,8 @@ import java.io.File
import java.io.InputStream
import kotlin.time.Clock
@Inject
@SingleIn(AppScope::class)
class ImageSaver(
val context: Context,
) {
@@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.data.track
import android.app.Application
import android.content.Context
import androidx.annotation.CallSuper
import eu.kanade.domain.track.interactor.AddTracks
import eu.kanade.domain.track.model.toDomainTrack
@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import logcat.LogPriority
import mihon.app.di.appGraph
import okhttp3.OkHttpClient
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext
@@ -18,7 +19,6 @@ import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.track.interactor.InsertTrack
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import tachiyomi.domain.track.model.Track as DomainTrack
abstract class BaseTracker(
@@ -26,10 +26,14 @@ abstract class BaseTracker(
override val name: String,
) : Tracker {
val trackPreferences: TrackPreferences by injectLazy()
val networkService: NetworkHelper by injectLazy()
private val addTracks: AddTracks by injectLazy()
private val insertTrack: InsertTrack by injectLazy()
protected val appGraph get() = Injekt.get<Context>().appGraph
val trackPreferences: TrackPreferences by lazy { appGraph.trackPreferences }
val networkService: NetworkHelper by lazy { appGraph.networkHelper }
private val context: Context by lazy { appGraph.context }
private val addTracks: AddTracks by lazy { appGraph.addTracks }
private val insertTrack: InsertTrack by lazy { appGraph.insertTrack }
override val client: OkHttpClient
get() = networkService.client
@@ -83,7 +87,9 @@ abstract class BaseTracker(
try {
addTracks.bind(this, item, mangaId)
} catch (e: Throwable) {
withUIContext { Injekt.get<Application>().toast(e.message) }
withUIContext {
context.toast(e.message)
}
}
}
@@ -139,7 +145,9 @@ abstract class BaseTracker(
}
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to update remote track data id=$id" }
withUIContext { Injekt.get<Application>().toast(e.message) }
withUIContext {
context.toast(e.message)
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More