@@ -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())
|
||||
|
||||
+3
-3
@@ -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> {
|
||||
|
||||
+3
-3
@@ -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> {
|
||||
|
||||
+5
-5
@@ -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> {
|
||||
|
||||
+4
-4
@@ -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> {
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+5
-5
@@ -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>) {
|
||||
|
||||
+3
-3
@@ -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(
|
||||
|
||||
+10
-10
@@ -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()
|
||||
|
||||
+5
-5
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package eu.kanade.tachiyomi.data.track
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import eu.kanade.tachiyomi.data.track.bangumi.Bangumi
|
||||
import eu.kanade.tachiyomi.data.track.hikka.Hikka
|
||||
@@ -13,6 +16,8 @@ import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
|
||||
import eu.kanade.tachiyomi.data.track.suwayomi.Suwayomi
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
@Inject
|
||||
@SingleIn(AppScope::class)
|
||||
class TrackerManager {
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import kotlinx.serialization.json.Json
|
||||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
@@ -32,7 +31,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
private const val SEARCH_ID_PREFIX = "id:"
|
||||
}
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
private val json: Json by lazy { appGraph.json }
|
||||
|
||||
private val interceptor by lazy { AnilistInterceptor(this, getPassword()) }
|
||||
|
||||
@@ -42,7 +41,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
|
||||
override val supportsPrivateTracking: Boolean = true
|
||||
|
||||
private val scorePreference = trackPreferences.anilistScoreType
|
||||
private val scorePreference by lazy { trackPreferences.anilistScoreType }
|
||||
|
||||
init {
|
||||
// If the preference is an int from APIv1, logout user to force using APIv2
|
||||
@@ -135,7 +134,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
else -> "😊"
|
||||
}
|
||||
|
||||
else -> track.toApiScore()
|
||||
else -> track.toApiScore(appGraph.trackPreferences)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.data.track.anilist
|
||||
|
||||
import eu.kanade.domain.track.service.TrackPreferences
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
fun Track.toApiStatus() = when (status) {
|
||||
@@ -15,9 +14,7 @@ fun Track.toApiStatus() = when (status) {
|
||||
else -> throw NotImplementedError("Unknown status: $status")
|
||||
}
|
||||
|
||||
private val preferences: TrackPreferences by injectLazy()
|
||||
|
||||
fun DomainTrack.toApiScore(): String = when (preferences.anilistScoreType.get()) {
|
||||
fun DomainTrack.toApiScore(preferences: TrackPreferences): String = when (preferences.anilistScoreType.get()) {
|
||||
// 10 point
|
||||
"POINT_10" -> (score.toInt() / 10).toString()
|
||||
// 100 point
|
||||
|
||||
@@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.source.sourcePreferences
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.security.MessageDigest
|
||||
import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
@@ -29,7 +28,7 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
|
||||
private val interceptor by lazy { KavitaInterceptor(this) }
|
||||
val api by lazy { KavitaApi(client, interceptor) }
|
||||
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
private val sourceManager: SourceManager by lazy { appGraph.sourceManager }
|
||||
|
||||
override fun getLogo(): Int = R.drawable.brand_kavita
|
||||
|
||||
|
||||
@@ -21,10 +21,11 @@ class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
|
||||
const val COMPLETED = 3L
|
||||
}
|
||||
|
||||
override val client: OkHttpClient =
|
||||
override val client: OkHttpClient by lazy {
|
||||
networkService.client.newBuilder()
|
||||
.dns(Dns.SYSTEM) // don't use DNS over HTTPS as it breaks IP addressing
|
||||
.build()
|
||||
}
|
||||
|
||||
val api by lazy { KomgaApi(id, client) }
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
|
||||
class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker {
|
||||
|
||||
val api by lazy { SuwayomiApi(id) }
|
||||
val api by lazy { SuwayomiApi(id, appGraph.sourceManager) }
|
||||
|
||||
override fun getLogo() = R.drawable.brand_suwayomi
|
||||
|
||||
|
||||
@@ -23,11 +23,12 @@ import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.security.MessageDigest
|
||||
|
||||
class SuwayomiApi(private val trackId: Long) {
|
||||
class SuwayomiApi(
|
||||
private val trackId: Long,
|
||||
private val sourceManager: SourceManager,
|
||||
) {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
private val source: HttpSource by lazy { (sourceManager.get(sourceId) as HttpSource) }
|
||||
private val configurableSource: ConfigurableSource by lazy { (sourceManager.get(sourceId) as ConfigurableSource) }
|
||||
private val client: OkHttpClient by lazy { source.client }
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import dev.zacsweers.metro.Inject
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.util.system.isFossBuildType
|
||||
import eu.kanade.tachiyomi.util.system.isNightlyBuildType
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class AppUpdateChecker {
|
||||
|
||||
private val getApplicationRelease: GetApplicationRelease by injectLazy()
|
||||
@Inject
|
||||
class AppUpdateChecker(
|
||||
private val getApplicationRelease: GetApplicationRelease,
|
||||
) {
|
||||
|
||||
suspend fun checkForUpdate(forceCheck: Boolean = false): GetApplicationRelease.Result {
|
||||
// Disable app update checks for older Android versions that we're going to drop support for
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
package eu.kanade.tachiyomi.di
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteConfiguration
|
||||
import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteDatabaseType
|
||||
import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteDriver
|
||||
import com.eygraber.sqldelight.androidx.driver.FileProvider
|
||||
import eu.kanade.domain.track.store.DelayedTrackingStore
|
||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.download.DownloadCache
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
||||
import eu.kanade.tachiyomi.data.saver.ImageSaver
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.network.JavaScriptEngine
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.AndroidSourceManager
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import nl.adaptivity.xmlutil.XmlDeclMode
|
||||
import nl.adaptivity.xmlutil.core.XmlVersion
|
||||
import nl.adaptivity.xmlutil.serialization.XML
|
||||
import tachiyomi.core.common.storage.AndroidStorageFolderProvider
|
||||
import tachiyomi.data.Chapters
|
||||
import tachiyomi.data.Database
|
||||
import tachiyomi.data.DateColumnAdapter
|
||||
import tachiyomi.data.History
|
||||
import tachiyomi.data.Mangas
|
||||
import tachiyomi.data.MemoColumnAdapter
|
||||
import tachiyomi.data.StringListColumnAdapter
|
||||
import tachiyomi.data.UpdateStrategyColumnAdapter
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.domain.storage.service.StorageManager
|
||||
import tachiyomi.source.local.image.LocalCoverManager
|
||||
import tachiyomi.source.local.io.LocalSourceFileSystem
|
||||
import uy.kohesive.injekt.api.InjektModule
|
||||
import uy.kohesive.injekt.api.InjektRegistrar
|
||||
import uy.kohesive.injekt.api.addSingleton
|
||||
import uy.kohesive.injekt.api.addSingletonFactory
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
private val lock = Any()
|
||||
|
||||
class AppModule(val app: Application) : InjektModule {
|
||||
|
||||
private var sqlDriverRef: WeakReference<SqlDriver>? = null
|
||||
|
||||
override fun InjektRegistrar.registerInjectables() {
|
||||
addSingleton(app)
|
||||
addSingleton<Context>(app)
|
||||
|
||||
addSingletonFactory<SqlDriver> {
|
||||
synchronized(lock) {
|
||||
sqlDriverRef?.get()?.let { return@synchronized it }
|
||||
|
||||
AndroidxSqliteDriver(
|
||||
driver = BundledSQLiteDriver(),
|
||||
databaseType = AndroidxSqliteDatabaseType.FileProvider(app, "tachiyomi.db"),
|
||||
schema = Database.Schema,
|
||||
configuration = AndroidxSqliteConfiguration(
|
||||
isForeignKeyConstraintsEnabled = true,
|
||||
),
|
||||
)
|
||||
.also { sqlDriverRef = WeakReference(it) }
|
||||
}
|
||||
}
|
||||
addSingletonFactory {
|
||||
Database(
|
||||
driver = get(),
|
||||
historyAdapter = History.Adapter(
|
||||
last_readAdapter = DateColumnAdapter,
|
||||
),
|
||||
mangasAdapter = Mangas.Adapter(
|
||||
genreAdapter = StringListColumnAdapter,
|
||||
update_strategyAdapter = UpdateStrategyColumnAdapter,
|
||||
memoAdapter = MemoColumnAdapter,
|
||||
),
|
||||
chaptersAdapter = Chapters.Adapter(
|
||||
memoAdapter = MemoColumnAdapter,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
addSingletonFactory {
|
||||
Json {
|
||||
ignoreUnknownKeys = true
|
||||
explicitNulls = false
|
||||
}
|
||||
}
|
||||
addSingletonFactory<XML> {
|
||||
XML.v1 {
|
||||
policy {
|
||||
ignoreUnknownChildren()
|
||||
autoPolymorphic = true
|
||||
}
|
||||
xmlDeclMode = XmlDeclMode.Charset
|
||||
xmlVersion = XmlVersion.XML10
|
||||
setIndent(2)
|
||||
}
|
||||
}
|
||||
addSingletonFactory<ProtoBuf> {
|
||||
ProtoBuf
|
||||
}
|
||||
|
||||
addSingletonFactory { ChapterCache(app, get()) }
|
||||
addSingletonFactory { CoverCache(app) }
|
||||
|
||||
addSingletonFactory { NetworkHelper(app, get()) }
|
||||
addSingletonFactory { JavaScriptEngine(app) }
|
||||
|
||||
addSingletonFactory<SourceManager> { AndroidSourceManager(app, get(), get()) }
|
||||
addSingletonFactory { ExtensionManager(app) }
|
||||
|
||||
addSingletonFactory { DownloadProvider(app) }
|
||||
addSingletonFactory { DownloadManager(app) }
|
||||
addSingletonFactory { DownloadCache(app) }
|
||||
|
||||
addSingletonFactory { TrackerManager() }
|
||||
addSingletonFactory { DelayedTrackingStore(app) }
|
||||
|
||||
addSingletonFactory { ImageSaver(app) }
|
||||
|
||||
addSingletonFactory { AndroidStorageFolderProvider(app) }
|
||||
addSingletonFactory { LocalSourceFileSystem(get()) }
|
||||
addSingletonFactory { LocalCoverManager(app, get()) }
|
||||
addSingletonFactory { StorageManager(app, get()) }
|
||||
|
||||
// Asynchronously init expensive components for a faster cold start
|
||||
ContextCompat.getMainExecutor(app).execute {
|
||||
get<NetworkHelper>()
|
||||
|
||||
get<SourceManager>()
|
||||
|
||||
get<Database>()
|
||||
|
||||
get<DownloadManager>()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package eu.kanade.tachiyomi.di
|
||||
|
||||
import android.app.Application
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.domain.track.service.TrackPreferences
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
|
||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.util.system.isDebugBuildType
|
||||
import tachiyomi.core.common.preference.AndroidPreferenceStore
|
||||
import tachiyomi.core.common.preference.PreferenceStore
|
||||
import tachiyomi.core.common.storage.AndroidStorageFolderProvider
|
||||
import tachiyomi.domain.backup.service.BackupPreferences
|
||||
import tachiyomi.domain.download.service.DownloadPreferences
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.storage.service.StoragePreferences
|
||||
import tachiyomi.domain.upcoming.service.UpcomingPreferences
|
||||
import tachiyomi.domain.updates.service.UpdatesPreferences
|
||||
import uy.kohesive.injekt.api.InjektModule
|
||||
import uy.kohesive.injekt.api.InjektRegistrar
|
||||
import uy.kohesive.injekt.api.addSingletonFactory
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class PreferenceModule(val app: Application) : InjektModule {
|
||||
|
||||
override fun InjektRegistrar.registerInjectables() {
|
||||
addSingletonFactory<PreferenceStore> {
|
||||
AndroidPreferenceStore(app)
|
||||
}
|
||||
addSingletonFactory {
|
||||
NetworkPreferences(
|
||||
preferenceStore = get(),
|
||||
verboseLoggingDefault = isDebugBuildType,
|
||||
)
|
||||
}
|
||||
addSingletonFactory {
|
||||
SourcePreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
SecurityPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
PrivacyPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
LibraryPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
UpcomingPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
UpdatesPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
ReaderPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
TrackPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
DownloadPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
BackupPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
StoragePreferences(
|
||||
folderProvider = get<AndroidStorageFolderProvider>(),
|
||||
preferenceStore = get(),
|
||||
)
|
||||
}
|
||||
addSingletonFactory {
|
||||
UiPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
BasePreferences(app, get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@ package eu.kanade.tachiyomi.extension
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import eu.kanade.domain.extension.interactor.TrustExtension
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.extension.api.ExtensionApi
|
||||
@@ -30,8 +33,6 @@ import tachiyomi.core.common.util.lang.withUIContext
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.source.model.StubSource
|
||||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
@@ -41,10 +42,15 @@ import java.util.Locale
|
||||
* signature is trusted, otherwise the user will be prompted with a warning to trust it before being
|
||||
* loaded.
|
||||
*/
|
||||
@Inject
|
||||
@SingleIn(AppScope::class)
|
||||
class ExtensionManager(
|
||||
private val context: Context,
|
||||
private val preferences: SourcePreferences = Injekt.get(),
|
||||
private val trustExtension: TrustExtension = Injekt.get(),
|
||||
private val preferences: SourcePreferences,
|
||||
private val trustExtension: TrustExtension,
|
||||
private val api: ExtensionApi,
|
||||
private val installer: ExtensionInstaller,
|
||||
private val extensionUpdateNotifier: ExtensionUpdateNotifier,
|
||||
) {
|
||||
|
||||
val scope = CoroutineScope(SupervisorJob())
|
||||
@@ -52,16 +58,6 @@ class ExtensionManager(
|
||||
private val _isInitialized = MutableStateFlow(false)
|
||||
val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()
|
||||
|
||||
/**
|
||||
* API where all the available extensions can be found.
|
||||
*/
|
||||
private val api = ExtensionApi()
|
||||
|
||||
/**
|
||||
* The installer which installs, updates and uninstalls the extensions.
|
||||
*/
|
||||
private val installer by lazy { ExtensionInstaller(context) }
|
||||
|
||||
private val iconMap = mutableMapOf<String, Drawable>()
|
||||
|
||||
private val installedExtensionMapFlow = MutableStateFlow(emptyMap<String, Extension.Installed>())
|
||||
@@ -373,7 +369,7 @@ class ExtensionManager(
|
||||
val pendingUpdateCount = installedExtensionMapFlow.value.values.count { it.hasUpdate }
|
||||
preferences.extensionUpdatesCount.set(pendingUpdateCount)
|
||||
if (pendingUpdateCount == 0) {
|
||||
ExtensionUpdateNotifier(context).dismiss()
|
||||
extensionUpdateNotifier.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
package eu.kanade.tachiyomi.extension.api
|
||||
|
||||
import android.content.Context
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||
import mihon.domain.extension.interactor.UpdateExtensionStores
|
||||
import mihon.domain.extension.repository.ExtensionStoreRepository
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
internal class ExtensionApi {
|
||||
|
||||
private val repository: ExtensionStoreRepository by injectLazy()
|
||||
|
||||
private val updateExtensionStores: UpdateExtensionStores by injectLazy()
|
||||
@Inject
|
||||
@SingleIn(AppScope::class)
|
||||
class ExtensionApi(
|
||||
private val repository: ExtensionStoreRepository,
|
||||
private val updateExtensionStores: UpdateExtensionStores,
|
||||
private val extensionUpdateNotifier: ExtensionUpdateNotifier,
|
||||
) {
|
||||
|
||||
suspend fun findExtensions(): List<Extension.Available> {
|
||||
return withIOContext { repository.fetchExtensions() }
|
||||
@@ -41,7 +45,7 @@ internal class ExtensionApi {
|
||||
}
|
||||
|
||||
if (extensionsWithUpdate.isNotEmpty()) {
|
||||
ExtensionUpdateNotifier(context).promptUpdates(extensionsWithUpdate.map { it.name })
|
||||
extensionUpdateNotifier.promptUpdates(extensionsWithUpdate.map { it.name })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package eu.kanade.tachiyomi.extension.api
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
@@ -10,12 +13,12 @@ import eu.kanade.tachiyomi.util.system.cancelNotification
|
||||
import eu.kanade.tachiyomi.util.system.notify
|
||||
import tachiyomi.core.common.i18n.pluralStringResource
|
||||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
@Inject
|
||||
@SingleIn(AppScope::class)
|
||||
class ExtensionUpdateNotifier(
|
||||
private val context: Context,
|
||||
private val securityPreferences: SecurityPreferences = Injekt.get(),
|
||||
private val securityPreferences: SecurityPreferences,
|
||||
) {
|
||||
fun promptUpdates(names: List<String>) {
|
||||
context.notify(
|
||||
|
||||
@@ -3,12 +3,12 @@ package eu.kanade.tachiyomi.extension.util
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import dev.zacsweers.metro.Inject
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import mihon.app.di.appGraph
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
@@ -17,6 +17,8 @@ import kotlin.time.Duration.Companion.seconds
|
||||
*/
|
||||
class ExtensionInstallActivity : Activity() {
|
||||
|
||||
@Inject private lateinit var extensionManager: ExtensionManager
|
||||
|
||||
// MIUI package installer bug workaround
|
||||
private var ignoreUntil = 0L
|
||||
private var ignoreResult = false
|
||||
@@ -24,6 +26,7 @@ class ExtensionInstallActivity : Activity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
appGraph.inject(this)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||
@@ -71,7 +74,6 @@ class ExtensionInstallActivity : Activity() {
|
||||
|
||||
private fun checkInstallationResult(resultCode: Int) {
|
||||
val downloadId = intent.extras!!.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID)
|
||||
val extensionManager = Injekt.get<ExtensionManager>()
|
||||
val newStep = when (resultCode) {
|
||||
RESULT_OK -> InstallStep.Installed
|
||||
RESULT_CANCELED -> InstallStep.Idle
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import dev.zacsweers.metro.Inject
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.extension.installer.Installer
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
@@ -23,8 +24,6 @@ import logcat.LogPriority
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@@ -32,16 +31,19 @@ import java.io.File
|
||||
*
|
||||
* @param context The application context.
|
||||
*/
|
||||
internal class ExtensionInstaller(
|
||||
@Inject
|
||||
class ExtensionInstaller(
|
||||
private val context: Context,
|
||||
basePreferences: BasePreferences,
|
||||
networkHelper: NetworkHelper,
|
||||
) {
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
private val activeJobs = mutableMapOf<String, Job>()
|
||||
private val activeSteps = mutableMapOf<Long, MutableStateFlow<InstallStep>>()
|
||||
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller
|
||||
private val extensionInstaller = basePreferences.extensionInstaller
|
||||
|
||||
private val httpClient: OkHttpClient = Injekt.get<NetworkHelper>().client
|
||||
private val httpClient: OkHttpClient = networkHelper.client
|
||||
|
||||
/**
|
||||
* Adds the given extension to the downloads queue and returns an observable containing its
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import eu.kanade.domain.extension.interactor.TrustExtension
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
@@ -20,8 +19,8 @@ import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import logcat.LogPriority
|
||||
import mihon.app.di.appGraph
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@@ -40,12 +39,6 @@ import java.io.File
|
||||
*/
|
||||
internal object ExtensionLoader {
|
||||
|
||||
private val preferences: SourcePreferences by injectLazy()
|
||||
private val trustExtension: TrustExtension by injectLazy()
|
||||
private val loadNsfwSource by lazy {
|
||||
preferences.showNsfwSource.get()
|
||||
}
|
||||
|
||||
private const val EXTENSION_FEATURE = "tachiyomi.extension"
|
||||
private const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
|
||||
private const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory"
|
||||
@@ -228,6 +221,9 @@ internal object ExtensionLoader {
|
||||
* @param extensionInfo The extension to load.
|
||||
*/
|
||||
private suspend fun loadExtension(context: Context, extensionInfo: ExtensionInfo): LoadResult {
|
||||
val trustExtension: TrustExtension = context.appGraph.trustExtension
|
||||
val loadNsfwSource: Boolean = context.appGraph.sourcePreferences.showNsfwSource.get()
|
||||
|
||||
val pkgManager = context.packageManager
|
||||
val pkgInfo = extensionInfo.packageInfo
|
||||
val appInfo = pkgInfo.applicationInfo!!
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package eu.kanade.tachiyomi.source
|
||||
|
||||
import android.content.Context
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
@@ -20,22 +23,21 @@ import tachiyomi.domain.source.model.StubSource
|
||||
import tachiyomi.domain.source.repository.StubSourceRepository
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.source.local.LocalSource
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
@Inject
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class)
|
||||
class AndroidSourceManager(
|
||||
private val context: Context,
|
||||
private val extensionManager: ExtensionManager,
|
||||
private val sourceRepository: StubSourceRepository,
|
||||
private val localSource: LocalSource,
|
||||
private val downloadManager: Lazy<DownloadManager>,
|
||||
) : SourceManager {
|
||||
|
||||
private val _isInitialized = MutableStateFlow(false)
|
||||
override val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()
|
||||
|
||||
private val downloadManager: DownloadManager by injectLazy()
|
||||
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
|
||||
private val sourcesMapFlow = MutableStateFlow(ConcurrentHashMap<Long, Source>())
|
||||
@@ -51,13 +53,7 @@ class AndroidSourceManager(
|
||||
extensionManager.installedExtensionsFlow
|
||||
.collectLatest { extensions ->
|
||||
val mutableMap = ConcurrentHashMap<Long, Source>(
|
||||
mapOf(
|
||||
LocalSource.ID to LocalSource(
|
||||
context,
|
||||
Injekt.get(),
|
||||
Injekt.get(),
|
||||
),
|
||||
),
|
||||
mapOf(LocalSource.ID to localSource),
|
||||
)
|
||||
extensions.forEach { extension ->
|
||||
extension.sources.forEach {
|
||||
@@ -106,7 +102,7 @@ class AndroidSourceManager(
|
||||
if (dbSource == source) return@launch
|
||||
sourceRepository.upsertStubSource(source.id, source.lang, source.name)
|
||||
if (dbSource != null) {
|
||||
downloadManager.renameSource(dbSource, source)
|
||||
downloadManager.value.renameSource(dbSource, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package eu.kanade.tachiyomi.source
|
||||
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import android.content.Context
|
||||
import mihon.app.di.appGraph
|
||||
import tachiyomi.domain.source.model.StubSource
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
fun Source.getNameForMangaInfo(): String {
|
||||
val preferences = Injekt.get<SourcePreferences>()
|
||||
val preferences = Injekt.get<Context>().appGraph.sourcePreferences
|
||||
val enabledLanguages = preferences.enabledLanguages.get()
|
||||
.filterNot { it in listOf("all", "other") }
|
||||
val hasOneActiveLanguages = enabledLanguages.size == 1
|
||||
|
||||
@@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegateImpl
|
||||
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
|
||||
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegateImpl
|
||||
import eu.kanade.tachiyomi.util.system.prepareTabletUiContext
|
||||
import mihon.app.di.appGraph
|
||||
|
||||
open class BaseActivity :
|
||||
AppCompatActivity(),
|
||||
@@ -15,7 +16,8 @@ open class BaseActivity :
|
||||
ThemingDelegate by ThemingDelegateImpl() {
|
||||
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
super.attachBaseContext(newBase.prepareTabletUiContext())
|
||||
val uiPreferences = newBase.appGraph.uiPreferences
|
||||
super.attachBaseContext(newBase.prepareTabletUiContext(uiPreferences))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package eu.kanade.tachiyomi.ui.base.delegate
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||
import eu.kanade.tachiyomi.ui.security.UnlockActivity
|
||||
@@ -16,9 +18,9 @@ import eu.kanade.tachiyomi.util.view.setSecureScreen
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import mihon.app.di.appGraph
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
interface SecureActivityDelegate {
|
||||
fun registerSecureActivity(activity: AppCompatActivity)
|
||||
@@ -31,8 +33,8 @@ interface SecureActivityDelegate {
|
||||
*/
|
||||
var requireUnlock = true
|
||||
|
||||
fun onApplicationStopped() {
|
||||
val preferences = Injekt.get<SecurityPreferences>()
|
||||
fun onApplicationStopped(context: Context) {
|
||||
val preferences = context.appGraph.securityPreferences
|
||||
if (!preferences.useAuthenticator.get()) return
|
||||
|
||||
if (!AuthenticatorUtil.isAuthenticating) {
|
||||
@@ -48,8 +50,8 @@ interface SecureActivityDelegate {
|
||||
/**
|
||||
* Checks if unlock is needed when app comes foreground.
|
||||
*/
|
||||
fun onApplicationStart() {
|
||||
val preferences = Injekt.get<SecurityPreferences>()
|
||||
fun onApplicationStart(context: Context) {
|
||||
val preferences = context.appGraph.securityPreferences
|
||||
if (!preferences.useAuthenticator.get()) return
|
||||
|
||||
val lastClosedPref = preferences.lastAppClosed
|
||||
@@ -76,11 +78,13 @@ class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObser
|
||||
|
||||
private lateinit var activity: AppCompatActivity
|
||||
|
||||
private val preferences: BasePreferences by injectLazy()
|
||||
private val securityPreferences: SecurityPreferences by injectLazy()
|
||||
@Inject private lateinit var preferences: BasePreferences
|
||||
|
||||
@Inject private lateinit var securityPreferences: SecurityPreferences
|
||||
|
||||
override fun registerSecureActivity(activity: AppCompatActivity) {
|
||||
this.activity = activity
|
||||
Injekt.get<Context>().appGraph.inject(this)
|
||||
activity.lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package eu.kanade.tachiyomi.ui.base.delegate
|
||||
|
||||
import android.app.Activity
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.domain.ui.model.AppTheme
|
||||
import eu.kanade.tachiyomi.R
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import mihon.app.di.appGraph
|
||||
|
||||
interface ThemingDelegate {
|
||||
fun applyAppTheme(activity: Activity)
|
||||
@@ -22,7 +20,7 @@ interface ThemingDelegate {
|
||||
|
||||
class ThemingDelegateImpl : ThemingDelegate {
|
||||
override fun applyAppTheme(activity: Activity) {
|
||||
val uiPreferences = Injekt.get<UiPreferences>()
|
||||
val uiPreferences = activity.appGraph.uiPreferences
|
||||
ThemingDelegate.getThemeResIds(uiPreferences.appTheme.get(), uiPreferences.themeDarkAmoled.get())
|
||||
.forEach(activity::setTheme)
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import dev.zacsweers.metrox.viewmodel.metroViewModel
|
||||
import eu.kanade.presentation.components.TabbedScreen
|
||||
import eu.kanade.presentation.util.Tab
|
||||
import eu.kanade.tachiyomi.R
|
||||
@@ -58,7 +58,7 @@ data object BrowseTab : Tab {
|
||||
val context = LocalContext.current
|
||||
|
||||
// Hoisted for extensions tab's search bar
|
||||
val extensionsViewModel = viewModel<ExtensionsViewModel>()
|
||||
val extensionsViewModel = metroViewModel<ExtensionsViewModel>()
|
||||
val extensionsSearchQuery by extensionsViewModel.searchQuery.collectAsStateWithLifecycle()
|
||||
|
||||
val tabs = listOf(
|
||||
|
||||
@@ -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.browse.ExtensionFilterScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@@ -21,7 +22,7 @@ class ExtensionFilterScreen : Screen() {
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<ExtensionFilterViewModel>()
|
||||
val viewModel = metroViewModel<ExtensionFilterViewModel>()
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
if (state is ExtensionFilterState.Loading) {
|
||||
|
||||
+11
-5
@@ -3,6 +3,11 @@ package eu.kanade.tachiyomi.ui.browse.extension
|
||||
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.domain.extension.interactor.GetExtensionLanguages
|
||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
@@ -17,14 +22,15 @@ import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
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 ExtensionFilterViewModel(
|
||||
private val preferences: SourcePreferences = Injekt.get(),
|
||||
private val getExtensionLanguages: GetExtensionLanguages = Injekt.get(),
|
||||
private val toggleLanguage: ToggleLanguage = Injekt.get(),
|
||||
private val preferences: SourcePreferences,
|
||||
private val getExtensionLanguages: GetExtensionLanguages,
|
||||
private val toggleLanguage: ToggleLanguage,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _events: Channel<ExtensionFilterEvent> = Channel()
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.extension
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
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.domain.base.BasePreferences
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
@@ -33,21 +38,21 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.i18n.MR
|
||||
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 ExtensionsViewModel(
|
||||
private val preferences: SourcePreferences = Injekt.get(),
|
||||
basePreferences: BasePreferences = Injekt.get(),
|
||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||
private val getExtensions: GetExtensionsByType = Injekt.get(),
|
||||
private val preferences: SourcePreferences,
|
||||
basePreferences: BasePreferences,
|
||||
private val extensionManager: ExtensionManager,
|
||||
private val getExtensions: GetExtensionsByType,
|
||||
private val context: Context,
|
||||
) : ViewModel() {
|
||||
|
||||
private val currentDownloads = MutableStateFlow<Map<String, InstallStep>>(hashMapOf())
|
||||
|
||||
private val context = Injekt.get<Application>()
|
||||
|
||||
// Public so BrowseTab's search bar can observe it without subscribing to the whole state.
|
||||
val searchQuery: StateFlow<String?>
|
||||
field = MutableStateFlow(null)
|
||||
@@ -117,6 +122,7 @@ class ExtensionsViewModel(
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5.seconds), State())
|
||||
|
||||
init {
|
||||
|
||||
viewModelScope.launchIO { findAvailableExtensions() }
|
||||
}
|
||||
|
||||
|
||||
+5
-7
@@ -4,10 +4,10 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel
|
||||
import eu.kanade.presentation.browse.ExtensionDetailsScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import tachiyomi.i18n.MR
|
||||
@@ -20,12 +20,10 @@ data class ExtensionDetailsScreen(
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val viewModel = viewModel<ExtensionDetailsViewModel>(
|
||||
factory = ExtensionDetailsViewModel.Factory,
|
||||
extras = CreationExtras {
|
||||
set(ExtensionDetailsViewModel.PKG_NAME_KEY, pkgName)
|
||||
},
|
||||
)
|
||||
val viewModel =
|
||||
assistedMetroViewModel<ExtensionDetailsViewModel, ExtensionDetailsViewModel.Factory> {
|
||||
create(pkgName = pkgName)
|
||||
}
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
+20
-24
@@ -1,13 +1,16 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.extension.details
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
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 eu.kanade.domain.extension.interactor.ExtensionSourceItem
|
||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||
import eu.kanade.domain.source.interactor.ToggleIncognito
|
||||
@@ -32,32 +35,25 @@ import kotlinx.coroutines.flow.stateIn
|
||||
import logcat.LogPriority
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@AssistedInject
|
||||
class ExtensionDetailsViewModel(
|
||||
pkgName: String,
|
||||
@Assisted pkgName: String,
|
||||
private val context: Context,
|
||||
private val network: NetworkHelper = Injekt.get(),
|
||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||
private val getExtensionSources: GetExtensionSources = Injekt.get(),
|
||||
private val toggleSource: ToggleSource = Injekt.get(),
|
||||
private val toggleIncognito: ToggleIncognito = Injekt.get(),
|
||||
private val preferences: SourcePreferences = Injekt.get(),
|
||||
private val network: NetworkHelper,
|
||||
private val extensionManager: ExtensionManager,
|
||||
private val getExtensionSources: GetExtensionSources,
|
||||
private val toggleSource: ToggleSource,
|
||||
private val toggleIncognito: ToggleIncognito,
|
||||
private val preferences: SourcePreferences,
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
val PKG_NAME_KEY = CreationExtras.Key<String>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
ExtensionDetailsViewModel(
|
||||
pkgName = get(PKG_NAME_KEY)!!,
|
||||
context = Injekt.get<Application>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(pkgName: String): ExtensionDetailsViewModel
|
||||
}
|
||||
|
||||
val state: StateFlow<State> = extensionManager.installedExtensionsFlow
|
||||
|
||||
+6
-6
@@ -38,11 +38,9 @@ import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.sourcePreferences
|
||||
import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import mihon.app.di.appGraph
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class SourcePreferencesScreen(val sourceId: Long) : Screen() {
|
||||
|
||||
@@ -59,7 +57,7 @@ class SourcePreferencesScreen(val sourceId: Long) : Screen() {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppBar(
|
||||
title = Injekt.get<SourceManager>().getOrStub(sourceId).toString(),
|
||||
title = context.appGraph.sourceManager.getOrStub(sourceId).toString(),
|
||||
navigateUp = navigator::pop,
|
||||
scrollBehavior = it,
|
||||
)
|
||||
@@ -132,7 +130,9 @@ class SourcePreferencesFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private fun populateScreen(): PreferenceScreen {
|
||||
val sourceId = requireArguments().getLong(SOURCE_ID)
|
||||
val source = Injekt.get<SourceManager>().getOrStub(sourceId)
|
||||
val appGraph = requireContext().appGraph
|
||||
val source = appGraph.sourceManager.getOrStub(sourceId)
|
||||
val basePreferences = appGraph.basePreferences
|
||||
val sourceScreen = preferenceManager.createPreferenceScreen(requireContext())
|
||||
|
||||
if (source is ConfigurableSource) {
|
||||
@@ -152,7 +152,7 @@ class SourcePreferencesFragment : PreferenceFragmentCompat() {
|
||||
val setListener = pref.getOnBindEditTextListener()
|
||||
pref.setOnBindEditTextListener {
|
||||
setListener?.onBindEditText(it)
|
||||
it.setIncognito(lifecycleScope)
|
||||
it.setIncognito(basePreferences, lifecycleScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-7
@@ -19,10 +19,10 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
||||
import eu.kanade.presentation.util.Screen
|
||||
@@ -48,12 +48,8 @@ data class MigrateMangaScreen(
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<MigrateMangaViewModel>(
|
||||
factory = MigrateMangaViewModel.Factory,
|
||||
extras = CreationExtras {
|
||||
set(MigrateMangaViewModel.SOURCE_ID_KEY, sourceId)
|
||||
},
|
||||
)
|
||||
val viewModel =
|
||||
assistedMetroViewModel<MigrateMangaViewModel, MigrateMangaViewModel.Factory> { create(sourceId = sourceId) }
|
||||
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
|
||||
+16
-18
@@ -3,9 +3,13 @@ package eu.kanade.tachiyomi.ui.browse.migration.manga
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
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 eu.kanade.tachiyomi.source.Source
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
@@ -27,26 +31,20 @@ import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.manga.interactor.GetFavorites
|
||||
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.seconds
|
||||
|
||||
@AssistedInject
|
||||
class MigrateMangaViewModel(
|
||||
private val sourceId: Long,
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val getFavorites: GetFavorites = Injekt.get(),
|
||||
@Assisted private val sourceId: Long,
|
||||
private val sourceManager: SourceManager,
|
||||
private val getFavorites: GetFavorites,
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
val SOURCE_ID_KEY = CreationExtras.Key<Long>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
MigrateMangaViewModel(
|
||||
sourceId = get(SOURCE_ID_KEY)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(sourceId: Long): MigrateMangaViewModel
|
||||
}
|
||||
|
||||
private val _events: Channel<MigrationMangaEvent> = Channel()
|
||||
|
||||
+3
-8
@@ -3,10 +3,9 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel
|
||||
import eu.kanade.presentation.browse.MigrateSearchScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchViewModel
|
||||
@@ -20,12 +19,8 @@ class MigrateSearchScreen(private val mangaId: Long) : Screen() {
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val viewModel = viewModel<MigrateSearchViewModel>(
|
||||
factory = MigrateSearchViewModel.Factory,
|
||||
extras = CreationExtras {
|
||||
set(MigrateSearchViewModel.MANGA_ID_KEY, mangaId)
|
||||
},
|
||||
)
|
||||
val viewModel =
|
||||
assistedMetroViewModel<MigrateSearchViewModel, MigrateSearchViewModel.Factory> { create(mangaId = mangaId) }
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
MigrateSearchScreen(
|
||||
|
||||
+30
-21
@@ -1,36 +1,45 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.migration.search
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
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 eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
@AssistedInject
|
||||
class MigrateSearchViewModel(
|
||||
val mangaId: Long,
|
||||
getManga: GetManga = Injekt.get(),
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
||||
) : SearchViewModel() {
|
||||
|
||||
companion object {
|
||||
val MANGA_ID_KEY = CreationExtras.Key<Long>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
MigrateSearchViewModel(
|
||||
mangaId = get(MANGA_ID_KEY)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
@Assisted val mangaId: Long,
|
||||
sourcePreferences: SourcePreferences,
|
||||
extensionManager: ExtensionManager,
|
||||
networkToLocalManga: NetworkToLocalManga,
|
||||
getManga: GetManga,
|
||||
preferences: SourcePreferences,
|
||||
private val sourceManager: SourceManager,
|
||||
) : SearchViewModel(
|
||||
sourcePreferences = sourcePreferences,
|
||||
sourceManager = sourceManager,
|
||||
extensionManager = extensionManager,
|
||||
networkToLocalManga = networkToLocalManga,
|
||||
getManga = getManga,
|
||||
preferences = preferences,
|
||||
) {
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(mangaId: Long): MigrateSearchViewModel
|
||||
}
|
||||
|
||||
private val migrationSources by lazy { sourcePreferences.migrationSources.get() }
|
||||
|
||||
+5
-9
@@ -17,10 +17,9 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel
|
||||
import eu.kanade.core.util.ifSourcesLoaded
|
||||
import eu.kanade.presentation.browse.BrowseSourceContent
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
@@ -60,13 +59,10 @@ data class MigrateSourceSearchScreen(
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val viewModel = viewModel<BrowseSourceViewModel>(
|
||||
factory = BrowseSourceViewModel.Factory,
|
||||
extras = CreationExtras {
|
||||
set(BrowseSourceViewModel.SOURCE_ID_KEY, sourceId)
|
||||
set(BrowseSourceViewModel.LISTING_QUERY_KEY, query)
|
||||
},
|
||||
)
|
||||
val viewModel =
|
||||
assistedMetroViewModel<BrowseSourceViewModel, BrowseSourceViewModel.Factory> {
|
||||
create(sourceId = sourceId, listingQuery = query)
|
||||
}
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metrox.viewmodel.metroViewModel
|
||||
import eu.kanade.presentation.browse.MigrateSourceScreen
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.TabContent
|
||||
@@ -21,7 +22,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
fun Screen.migrateSourceTab(): TabContent {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<MigrateSourceViewModel>()
|
||||
val viewModel = metroViewModel<MigrateSourceViewModel>()
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
return TabContent(
|
||||
|
||||
+10
-4
@@ -3,6 +3,11 @@ package eu.kanade.tachiyomi.ui.browse.migration.sources
|
||||
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.domain.source.interactor.GetSourcesWithFavoriteCount
|
||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
@@ -20,13 +25,14 @@ import logcat.LogPriority
|
||||
import tachiyomi.core.common.preference.getAndSet
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.source.model.Source
|
||||
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 MigrateSourceViewModel(
|
||||
private val preferences: SourcePreferences = Injekt.get(),
|
||||
private val getSourcesWithFavoriteCount: GetSourcesWithFavoriteCount = Injekt.get(),
|
||||
private val preferences: SourcePreferences,
|
||||
private val getSourcesWithFavoriteCount: GetSourcesWithFavoriteCount,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _channel = Channel<Event>(Int.MAX_VALUE)
|
||||
|
||||
@@ -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.browse.SourcesFilterScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
@@ -19,7 +20,7 @@ class SourcesFilterScreen : Screen() {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<SourcesFilterViewModel>()
|
||||
val viewModel = metroViewModel<SourcesFilterViewModel>()
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
if (state is SourcesFilterViewModel.State.Loading) {
|
||||
|
||||
@@ -3,6 +3,11 @@ package eu.kanade.tachiyomi.ui.browse.source
|
||||
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.domain.source.interactor.GetLanguagesWithSources
|
||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||
import eu.kanade.domain.source.interactor.ToggleSource
|
||||
@@ -14,16 +19,17 @@ import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import tachiyomi.domain.source.model.Source
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.SortedMap
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Inject
|
||||
@ViewModelKey
|
||||
@ContributesIntoMap(AppScope::class, binding = binding<ViewModel>())
|
||||
class SourcesFilterViewModel(
|
||||
private val preferences: SourcePreferences = Injekt.get(),
|
||||
private val getLanguagesWithSources: GetLanguagesWithSources = Injekt.get(),
|
||||
private val toggleSource: ToggleSource = Injekt.get(),
|
||||
private val toggleLanguage: ToggleLanguage = Injekt.get(),
|
||||
private val preferences: SourcePreferences,
|
||||
private val getLanguagesWithSources: GetLanguagesWithSources,
|
||||
private val toggleSource: ToggleSource,
|
||||
private val toggleLanguage: ToggleLanguage,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<State> = combine(
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metrox.viewmodel.metroViewModel
|
||||
import eu.kanade.presentation.browse.SourceOptionsDialog
|
||||
import eu.kanade.presentation.browse.SourcesScreen
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
@@ -25,7 +26,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
@Composable
|
||||
fun Screen.sourcesTab(): TabContent {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<SourcesViewModel>()
|
||||
val viewModel = metroViewModel<SourcesViewModel>()
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
return TabContent(
|
||||
|
||||
@@ -3,6 +3,11 @@ package eu.kanade.tachiyomi.ui.browse.source
|
||||
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.domain.source.interactor.GetEnabledSources
|
||||
import eu.kanade.domain.source.interactor.ToggleSource
|
||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||
@@ -24,15 +29,16 @@ import logcat.LogPriority
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.source.model.Pin
|
||||
import tachiyomi.domain.source.model.Source
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.TreeMap
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Inject
|
||||
@ViewModelKey
|
||||
@ContributesIntoMap(AppScope::class, binding = binding<ViewModel>())
|
||||
class SourcesViewModel(
|
||||
private val getEnabledSources: GetEnabledSources = Injekt.get(),
|
||||
private val toggleSource: ToggleSource = Injekt.get(),
|
||||
private val toggleSourcePin: ToggleSourcePin = Injekt.get(),
|
||||
private val getEnabledSources: GetEnabledSources,
|
||||
private val toggleSource: ToggleSource,
|
||||
private val toggleSourcePin: ToggleSourcePin,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _events = Channel<Event>(Int.MAX_VALUE)
|
||||
|
||||
@@ -32,10 +32,9 @@ import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel
|
||||
import eu.kanade.core.util.ifSourcesLoaded
|
||||
import eu.kanade.presentation.browse.BrowseSourceContent
|
||||
import eu.kanade.presentation.browse.MissingSourceScreen
|
||||
@@ -82,13 +81,10 @@ data class BrowseSourceScreen(
|
||||
return
|
||||
}
|
||||
|
||||
val viewModel = viewModel<BrowseSourceViewModel>(
|
||||
factory = BrowseSourceViewModel.Factory,
|
||||
extras = CreationExtras {
|
||||
set(BrowseSourceViewModel.SOURCE_ID_KEY, sourceId)
|
||||
set(BrowseSourceViewModel.LISTING_QUERY_KEY, listingQuery)
|
||||
},
|
||||
)
|
||||
val viewModel =
|
||||
assistedMetroViewModel<BrowseSourceViewModel, BrowseSourceViewModel.Factory> {
|
||||
create(sourceId = sourceId, listingQuery = listingQuery)
|
||||
}
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
+28
-32
@@ -8,14 +8,18 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.paging.filter
|
||||
import androidx.paging.map
|
||||
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 eu.kanade.core.preference.asState
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.source.interactor.GetIncognitoState
|
||||
@@ -49,44 +53,36 @@ import tachiyomi.domain.manga.model.MangaWithChapterCount
|
||||
import tachiyomi.domain.manga.model.toMangaUpdate
|
||||
import tachiyomi.domain.source.interactor.GetRemoteManga
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.time.Clock
|
||||
import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter
|
||||
|
||||
@AssistedInject
|
||||
class BrowseSourceViewModel(
|
||||
private val sourceId: Long,
|
||||
listingQuery: String?,
|
||||
sourceManager: SourceManager = Injekt.get(),
|
||||
sourcePreferences: SourcePreferences = Injekt.get(),
|
||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
private val coverCache: CoverCache = Injekt.get(),
|
||||
private val getRemoteManga: GetRemoteManga = Injekt.get(),
|
||||
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
||||
private val getManga: GetManga = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val addTracks: AddTracks = Injekt.get(),
|
||||
getIncognitoState: GetIncognitoState = Injekt.get(),
|
||||
@Assisted private val sourceId: Long,
|
||||
@Assisted listingQuery: String?,
|
||||
sourceManager: SourceManager,
|
||||
sourcePreferences: SourcePreferences,
|
||||
private val libraryPreferences: LibraryPreferences,
|
||||
private val coverCache: CoverCache,
|
||||
private val getRemoteManga: GetRemoteManga,
|
||||
private val getDuplicateLibraryManga: GetDuplicateLibraryManga,
|
||||
private val getCategories: GetCategories,
|
||||
private val setMangaCategories: SetMangaCategories,
|
||||
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags,
|
||||
private val getManga: GetManga,
|
||||
private val updateManga: UpdateManga,
|
||||
private val addTracks: AddTracks,
|
||||
getIncognitoState: GetIncognitoState,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<BrowseSourceViewModel.State>
|
||||
field = MutableStateFlow<BrowseSourceViewModel.State>(State(Listing.valueOf(listingQuery)))
|
||||
|
||||
companion object {
|
||||
val SOURCE_ID_KEY = CreationExtras.Key<Long>()
|
||||
val LISTING_QUERY_KEY = CreationExtras.Key<String?>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
BrowseSourceViewModel(
|
||||
sourceId = get(SOURCE_ID_KEY)!!,
|
||||
listingQuery = get(LISTING_QUERY_KEY),
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(sourceId: Long, listingQuery: String?): BrowseSourceViewModel
|
||||
}
|
||||
|
||||
var displayMode by sourcePreferences.sourceDisplayMode.asState(viewModelScope)
|
||||
|
||||
+5
-9
@@ -7,10 +7,9 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel
|
||||
import eu.kanade.core.util.ifSourcesLoaded
|
||||
import eu.kanade.presentation.browse.GlobalSearchScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
@@ -32,13 +31,10 @@ class GlobalSearchScreen(
|
||||
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val viewModel = viewModel<GlobalSearchViewModel>(
|
||||
factory = GlobalSearchViewModel.Factory,
|
||||
extras = CreationExtras {
|
||||
set(GlobalSearchViewModel.INITIAL_QUERY_KEY, searchQuery)
|
||||
set(GlobalSearchViewModel.INITIAL_EXTENSION_FILTER_KEY, extensionFilter)
|
||||
},
|
||||
)
|
||||
val viewModel =
|
||||
assistedMetroViewModel<GlobalSearchViewModel, GlobalSearchViewModel.Factory> {
|
||||
create(initialQuery = searchQuery, initialExtensionFilter = extensionFilter)
|
||||
}
|
||||
val state by viewModel.state.collectAsState()
|
||||
var showSingleLoadingScreen by remember {
|
||||
mutableStateOf(searchQuery.isNotEmpty() && !extensionFilter.isNullOrEmpty() && state.total == 1)
|
||||
|
||||
+35
-18
@@ -1,27 +1,44 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source.globalsearch
|
||||
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
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 eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
|
||||
@AssistedInject
|
||||
class GlobalSearchViewModel(
|
||||
initialQuery: String,
|
||||
initialExtensionFilter: String?,
|
||||
) : SearchViewModel(State(searchQuery = initialQuery)) {
|
||||
@Assisted initialQuery: String,
|
||||
@Assisted initialExtensionFilter: String?,
|
||||
sourcePreferences: SourcePreferences,
|
||||
sourceManager: SourceManager,
|
||||
extensionManager: ExtensionManager,
|
||||
networkToLocalManga: NetworkToLocalManga,
|
||||
getManga: GetManga,
|
||||
preferences: SourcePreferences,
|
||||
) : SearchViewModel(
|
||||
initialState = State(searchQuery = initialQuery),
|
||||
sourcePreferences = sourcePreferences,
|
||||
sourceManager = sourceManager,
|
||||
extensionManager = extensionManager,
|
||||
networkToLocalManga = networkToLocalManga,
|
||||
getManga = getManga,
|
||||
preferences = preferences,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val INITIAL_QUERY_KEY = CreationExtras.Key<String>()
|
||||
val INITIAL_EXTENSION_FILTER_KEY = CreationExtras.Key<String?>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
GlobalSearchViewModel(
|
||||
initialQuery = get(INITIAL_QUERY_KEY)!!,
|
||||
initialExtensionFilter = get(INITIAL_EXTENSION_FILTER_KEY),
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(initialQuery: String, initialExtensionFilter: String?): GlobalSearchViewModel
|
||||
}
|
||||
|
||||
init {
|
||||
|
||||
+6
-8
@@ -27,18 +27,16 @@ import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||
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.Executors
|
||||
|
||||
abstract class SearchViewModel(
|
||||
initialState: State = State(),
|
||||
sourcePreferences: SourcePreferences = Injekt.get(),
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||
private val getManga: GetManga = Injekt.get(),
|
||||
private val preferences: SourcePreferences = Injekt.get(),
|
||||
sourcePreferences: SourcePreferences,
|
||||
private val sourceManager: SourceManager,
|
||||
private val extensionManager: ExtensionManager,
|
||||
private val networkToLocalManga: NetworkToLocalManga,
|
||||
private val getManga: GetManga,
|
||||
private val preferences: SourcePreferences,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<State>
|
||||
|
||||
@@ -9,6 +9,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.category.CategoryScreen
|
||||
import eu.kanade.presentation.category.components.CategoryCreateDialog
|
||||
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
||||
@@ -24,7 +25,7 @@ class CategoryScreen : Screen() {
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<CategoryViewModel>()
|
||||
val viewModel = metroViewModel<CategoryViewModel>()
|
||||
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
|
||||
@@ -4,6 +4,11 @@ import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
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 kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -21,16 +26,17 @@ import tachiyomi.domain.category.interactor.RenameCategory
|
||||
import tachiyomi.domain.category.interactor.ReorderCategory
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.i18n.MR
|
||||
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 CategoryViewModel(
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val createCategoryWithName: CreateCategoryWithName = Injekt.get(),
|
||||
private val deleteCategory: DeleteCategory = Injekt.get(),
|
||||
private val reorderCategory: ReorderCategory = Injekt.get(),
|
||||
private val renameCategory: RenameCategory = Injekt.get(),
|
||||
private val getCategories: GetCategories,
|
||||
private val createCategoryWithName: CreateCategoryWithName,
|
||||
private val deleteCategory: DeleteCategory,
|
||||
private val reorderCategory: ReorderCategory,
|
||||
private val renameCategory: RenameCategory,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _events: Channel<CategoryEvent> = Channel()
|
||||
|
||||
@@ -6,10 +6,9 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||
@@ -29,12 +28,7 @@ class DeepLinkScreen(
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val viewModel = viewModel<DeepLinkViewModel>(
|
||||
factory = DeepLinkViewModel.Factory,
|
||||
extras = CreationExtras {
|
||||
set(DeepLinkViewModel.QUERY_KEY, query)
|
||||
},
|
||||
)
|
||||
val viewModel = assistedMetroViewModel<DeepLinkViewModel, DeepLinkViewModel.Factory> { create(query = query) }
|
||||
val state by viewModel.state.collectAsState()
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
|
||||
@@ -3,9 +3,13 @@ package eu.kanade.tachiyomi.ui.deeplink
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
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 eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.online.ResolvableSource
|
||||
@@ -21,30 +25,24 @@ import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
@AssistedInject
|
||||
class DeepLinkViewModel(
|
||||
query: String,
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||
private val getChapterByUrlAndMangaId: GetChapterByUrlAndMangaId = Injekt.get(),
|
||||
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get(),
|
||||
@Assisted query: String,
|
||||
private val sourceManager: SourceManager,
|
||||
private val networkToLocalManga: NetworkToLocalManga,
|
||||
private val getChapterByUrlAndMangaId: GetChapterByUrlAndMangaId,
|
||||
private val updateMangaFromRemote: UpdateMangaFromRemote,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<DeepLinkViewModel.State>
|
||||
field = MutableStateFlow<DeepLinkViewModel.State>(State.Loading)
|
||||
|
||||
companion object {
|
||||
val QUERY_KEY = CreationExtras.Key<String>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
DeepLinkViewModel(
|
||||
query = get(QUERY_KEY)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(query: String): DeepLinkViewModel
|
||||
}
|
||||
|
||||
init {
|
||||
|
||||
@@ -45,6 +45,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metrox.viewmodel.metroViewModel
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.components.DropdownMenu
|
||||
@@ -65,7 +66,7 @@ object DownloadQueueScreen : Screen() {
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val scope = rememberCoroutineScope()
|
||||
val viewModel = viewModel<DownloadQueueViewModel>()
|
||||
val viewModel = metroViewModel<DownloadQueueViewModel>()
|
||||
val downloadList by viewModel.state.collectAsStateWithLifecycle()
|
||||
val downloadCount by remember {
|
||||
derivedStateOf { downloadList.sumOf { it.subItems.size } }
|
||||
|
||||
@@ -3,6 +3,10 @@ package eu.kanade.tachiyomi.ui.download
|
||||
import android.view.MenuItem
|
||||
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.metrox.viewmodel.ViewModelKey
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
@@ -20,13 +24,14 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Inject
|
||||
@ViewModelKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
class DownloadQueueViewModel(
|
||||
private val downloadManager: DownloadManager = Injekt.get(),
|
||||
private val downloadManager: DownloadManager,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<List<DownloadHeaderItem>> = downloadManager.queueState
|
||||
|
||||
@@ -16,6 +16,7 @@ import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import dev.zacsweers.metrox.viewmodel.metroViewModel
|
||||
import eu.kanade.presentation.category.components.ChangeCategoryDialog
|
||||
import eu.kanade.presentation.history.HistoryScreen
|
||||
import eu.kanade.presentation.history.components.HistoryDeleteAllDialog
|
||||
@@ -62,7 +63,7 @@ data object HistoryTab : Tab {
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
val viewModel = viewModel<HistoryViewModel>()
|
||||
val viewModel = metroViewModel<HistoryViewModel>()
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
HistoryScreen(
|
||||
|
||||
@@ -4,6 +4,11 @@ import androidx.compose.material3.SnackbarHostState
|
||||
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.core.util.insertSeparators
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.track.interactor.AddTracks
|
||||
@@ -46,25 +51,27 @@ import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaWithChapterCount
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
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 HistoryViewModel(
|
||||
private val addTracks: AddTracks = Injekt.get(),
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
|
||||
private val getHistory: GetHistory = Injekt.get(),
|
||||
private val getManga: GetManga = Injekt.get(),
|
||||
private val getNextChapters: GetNextChapters = Injekt.get(),
|
||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
private val removeHistory: RemoveHistory = Injekt.get(),
|
||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val addTracks: AddTracks,
|
||||
private val getCategories: GetCategories,
|
||||
private val getDuplicateLibraryManga: GetDuplicateLibraryManga,
|
||||
private val getHistory: GetHistory,
|
||||
private val getManga: GetManga,
|
||||
private val getNextChapters: GetNextChapters,
|
||||
private val libraryPreferences: LibraryPreferences,
|
||||
private val removeHistory: RemoveHistory,
|
||||
private val setMangaCategories: SetMangaCategories,
|
||||
private val updateManga: UpdateManga,
|
||||
private val sourceManager: SourceManager,
|
||||
) : ViewModel() {
|
||||
|
||||
val snackbarHostState: SnackbarHostState = SnackbarHostState()
|
||||
|
||||
private val _events: Channel<Event> = Channel(Channel.UNLIMITED)
|
||||
val events: Flow<Event> = _events.receiveAsFlow()
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -33,7 +34,6 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.TabNavigator
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.presentation.util.isTabletUi
|
||||
import eu.kanade.tachiyomi.ui.browse.BrowseTab
|
||||
@@ -48,16 +48,14 @@ import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import mihon.app.di.appGraph
|
||||
import soup.compose.material.motion.animation.materialFadeThroughIn
|
||||
import soup.compose.material.motion.animation.materialFadeThroughOut
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.NavigationBar
|
||||
import tachiyomi.presentation.core.components.material.NavigationRail
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
object HomeScreen : Screen() {
|
||||
|
||||
@@ -235,12 +233,13 @@ object HomeScreen : Screen() {
|
||||
|
||||
@Composable
|
||||
private fun NavigationIconItem(tab: eu.kanade.presentation.util.Tab) {
|
||||
val context = LocalContext.current
|
||||
BadgedBox(
|
||||
badge = {
|
||||
when {
|
||||
tab is UpdatesTab -> {
|
||||
val count by produceState(initialValue = 0) {
|
||||
val pref = Injekt.get<LibraryPreferences>()
|
||||
val pref = context.appGraph.libraryPreferences
|
||||
combine(
|
||||
pref.newShowUpdatesCount.changes(),
|
||||
pref.newUpdatesCount.changes(),
|
||||
@@ -263,7 +262,7 @@ object HomeScreen : Screen() {
|
||||
}
|
||||
BrowseTab::class.isInstance(tab) -> {
|
||||
val count by produceState(initialValue = 0) {
|
||||
Injekt.get<SourcePreferences>().extensionUpdatesCount.changes()
|
||||
context.appGraph.sourcePreferences.extensionUpdatesCount.changes()
|
||||
.collectLatest { value = it }
|
||||
}
|
||||
if (count > 0) {
|
||||
|
||||
@@ -2,6 +2,10 @@ package eu.kanade.tachiyomi.ui.library
|
||||
|
||||
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.metrox.viewmodel.ViewModelKey
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -16,16 +20,17 @@ import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
import tachiyomi.domain.library.model.LibrarySort
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Inject
|
||||
@ViewModelKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
class LibrarySettingsViewModel(
|
||||
val preferences: BasePreferences = Injekt.get(),
|
||||
val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
private val setDisplayMode: SetDisplayMode = Injekt.get(),
|
||||
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
|
||||
trackerManager: TrackerManager = Injekt.get(),
|
||||
val preferences: BasePreferences,
|
||||
val libraryPreferences: LibraryPreferences,
|
||||
private val setDisplayMode: SetDisplayMode,
|
||||
private val setSortModeForCategory: SetSortModeForCategory,
|
||||
trackerManager: TrackerManager,
|
||||
) : ViewModel() {
|
||||
|
||||
val trackersFlow = trackerManager.loggedInTrackersFlow()
|
||||
|
||||
@@ -27,6 +27,7 @@ import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import dev.zacsweers.metrox.viewmodel.metroViewModel
|
||||
import eu.kanade.presentation.category.components.ChangeCategoryDialog
|
||||
import eu.kanade.presentation.library.DeleteLibraryMangaDialog
|
||||
import eu.kanade.presentation.library.LibrarySettingsDialog
|
||||
@@ -43,6 +44,7 @@ import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.util.system.workManager
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
@@ -86,14 +88,14 @@ data object LibraryTab : Tab {
|
||||
val scope = rememberCoroutineScope()
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
val viewModel = viewModel<LibraryViewModel>()
|
||||
val settingsViewModel = viewModel<LibrarySettingsViewModel>()
|
||||
val viewModel = metroViewModel<LibraryViewModel>()
|
||||
val settingsViewModel = metroViewModel<LibrarySettingsViewModel>()
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
val onClickRefresh: (Category?) -> Boolean = { category ->
|
||||
val started = LibraryUpdateJob.startNow(context, category)
|
||||
val started = LibraryUpdateJob.startNow(context.workManager, category)
|
||||
scope.launch {
|
||||
val msgRes = when {
|
||||
!started -> MR.strings.update_already_running
|
||||
|
||||
@@ -6,6 +6,11 @@ import androidx.compose.ui.util.fastFilter
|
||||
import androidx.compose.ui.util.fastMap
|
||||
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.core.preference.PreferenceMutableState
|
||||
import eu.kanade.core.preference.asState
|
||||
import eu.kanade.core.util.fastFilterNot
|
||||
@@ -65,28 +70,29 @@ import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.domain.track.interactor.GetTracksPerManga
|
||||
import tachiyomi.domain.track.model.Track
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Inject
|
||||
@ViewModelKey
|
||||
@ContributesIntoMap(AppScope::class, binding = binding<ViewModel>())
|
||||
class LibraryViewModel(
|
||||
private val getLibraryManga: GetLibraryManga = Injekt.get(),
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val getTracksPerManga: GetTracksPerManga = Injekt.get(),
|
||||
private val getNextChapters: GetNextChapters = Injekt.get(),
|
||||
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
|
||||
private val getBookmarkedChaptersByMangaId: GetBookmarkedChaptersByMangaId = Injekt.get(),
|
||||
private val setReadStatus: SetReadStatus = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||
private val preferences: BasePreferences = Injekt.get(),
|
||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
private val coverCache: CoverCache = Injekt.get(),
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val downloadManager: DownloadManager = Injekt.get(),
|
||||
private val downloadCache: DownloadCache = Injekt.get(),
|
||||
private val trackerManager: TrackerManager = Injekt.get(),
|
||||
private val getLibraryManga: GetLibraryManga,
|
||||
private val getCategories: GetCategories,
|
||||
private val getTracksPerManga: GetTracksPerManga,
|
||||
private val getNextChapters: GetNextChapters,
|
||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||
private val getBookmarkedChaptersByMangaId: GetBookmarkedChaptersByMangaId,
|
||||
private val setReadStatus: SetReadStatus,
|
||||
private val updateManga: UpdateManga,
|
||||
private val setMangaCategories: SetMangaCategories,
|
||||
private val preferences: BasePreferences,
|
||||
private val libraryPreferences: LibraryPreferences,
|
||||
private val coverCache: CoverCache,
|
||||
private val sourceManager: SourceManager,
|
||||
private val downloadManager: DownloadManager,
|
||||
private val downloadCache: DownloadCache,
|
||||
private val trackerManager: TrackerManager,
|
||||
) : ViewModel() {
|
||||
|
||||
private val searchQuery = MutableStateFlow<String?>(null)
|
||||
|
||||
@@ -67,6 +67,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metro.Inject
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.source.interactor.GetIncognitoState
|
||||
import eu.kanade.presentation.components.AdaptiveSheet
|
||||
@@ -81,7 +82,6 @@ import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
|
||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||
import eu.kanade.tachiyomi.data.download.DownloadCache
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||
import eu.kanade.tachiyomi.extension.api.ExtensionApi
|
||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||
@@ -106,6 +106,9 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import mihon.app.di.AppGraph
|
||||
import mihon.app.di.appGraph
|
||||
import mihon.core.metro.metroGraph
|
||||
import mihon.core.migration.Migrator
|
||||
import mihon.feature.support.SupportUsScreen
|
||||
import tachiyomi.core.common.Constants
|
||||
@@ -118,7 +121,6 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Instant
|
||||
@@ -126,13 +128,19 @@ import kotlin.time.times
|
||||
|
||||
class MainActivity : BaseActivity() {
|
||||
|
||||
private val libraryPreferences: LibraryPreferences by injectLazy()
|
||||
private val preferences: BasePreferences by injectLazy()
|
||||
private val graph: AppGraph by lazy { metroGraph() }
|
||||
|
||||
private val downloadCache: DownloadCache by injectLazy()
|
||||
private val chapterCache: ChapterCache by injectLazy()
|
||||
@Inject private lateinit var libraryPreferences: LibraryPreferences
|
||||
|
||||
private val getIncognitoState: GetIncognitoState by injectLazy()
|
||||
@Inject private lateinit var preferences: BasePreferences
|
||||
|
||||
@Inject private lateinit var downloadCache: DownloadCache
|
||||
|
||||
@Inject private lateinit var chapterCache: ChapterCache
|
||||
|
||||
@Inject private lateinit var getIncognitoState: GetIncognitoState
|
||||
|
||||
@Inject private lateinit var extensionApi: ExtensionApi
|
||||
|
||||
// To be checked by splash screen. If true then splash screen will be removed.
|
||||
var ready = false
|
||||
@@ -144,6 +152,7 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
graph.inject(this)
|
||||
val isLaunch = savedInstanceState == null
|
||||
|
||||
// Prevent splash screen showing up on configuration changes
|
||||
@@ -311,7 +320,7 @@ class MainActivity : BaseActivity() {
|
||||
LaunchedEffect(Unit) {
|
||||
if (updaterEnabled) {
|
||||
try {
|
||||
val result = AppUpdateChecker().checkForUpdate()
|
||||
val result = context.appGraph.updateChecker.checkForUpdate()
|
||||
if (result is GetApplicationRelease.Result.NewUpdate) {
|
||||
val updateScreen = NewUpdateScreen(
|
||||
versionName = result.release.version,
|
||||
@@ -330,7 +339,7 @@ class MainActivity : BaseActivity() {
|
||||
// Extensions updates
|
||||
LaunchedEffect(Unit) {
|
||||
try {
|
||||
ExtensionApi().checkForUpdates(context)
|
||||
extensionApi.checkForUpdates(context)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
||||
|
||||
@@ -5,13 +5,17 @@ import android.net.Uri
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import coil3.asDrawable
|
||||
import coil3.imageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.size.Size
|
||||
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 eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.saver.Image
|
||||
@@ -36,30 +40,25 @@ import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import tachiyomi.source.local.image.LocalCoverManager
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@AssistedInject
|
||||
class MangaCoverViewModel(
|
||||
private val mangaId: Long,
|
||||
private val getManga: GetManga = Injekt.get(),
|
||||
private val imageSaver: ImageSaver = Injekt.get(),
|
||||
private val coverCache: CoverCache = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
|
||||
@Assisted private val mangaId: Long,
|
||||
private val getManga: GetManga,
|
||||
private val imageSaver: ImageSaver,
|
||||
private val coverCache: CoverCache,
|
||||
private val updateManga: UpdateManga,
|
||||
private val coverManager: LocalCoverManager,
|
||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
val MANGA_ID_KEY = CreationExtras.Key<Long>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
MangaCoverViewModel(
|
||||
mangaId = get(MANGA_ID_KEY)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(mangaId: Long): MangaCoverViewModel
|
||||
}
|
||||
|
||||
val state: StateFlow<Manga?> = getManga.subscribe(mangaId)
|
||||
@@ -140,7 +139,7 @@ class MangaCoverViewModel(
|
||||
viewModelScope.launchIO {
|
||||
context.contentResolver.openInputStream(data)?.use {
|
||||
try {
|
||||
manga.editCover(Injekt.get(), it, updateManga, coverCache)
|
||||
manga.editCover(coverManager, it, updateManga, coverCache)
|
||||
notifyCoverUpdated(context)
|
||||
} catch (e: Exception) {
|
||||
notifyFailedCoverUpdate(context, e)
|
||||
|
||||
@@ -17,11 +17,10 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel
|
||||
import eu.kanade.core.util.ifSourcesLoaded
|
||||
import eu.kanade.domain.manga.model.hasCustomCover
|
||||
import eu.kanade.domain.manga.model.toSManga
|
||||
@@ -83,13 +82,10 @@ class MangaScreen(
|
||||
val context = LocalContext.current
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val viewModel = viewModel<MangaViewModel>(
|
||||
factory = MangaViewModel.Factory,
|
||||
extras = CreationExtras {
|
||||
set(MangaViewModel.MANGA_ID_KEY, mangaId)
|
||||
set(MangaViewModel.IS_FROM_SOURCE_KEY, fromSource)
|
||||
},
|
||||
)
|
||||
val viewModel =
|
||||
assistedMetroViewModel<MangaViewModel, MangaViewModel.Factory> {
|
||||
create(mangaId = mangaId, isFromSource = fromSource)
|
||||
}
|
||||
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
@@ -243,28 +239,26 @@ class MangaScreen(
|
||||
)
|
||||
}
|
||||
MangaViewModel.Dialog.FullCover -> {
|
||||
val sm = viewModel<MangaCoverViewModel>(
|
||||
factory = MangaCoverViewModel.Factory,
|
||||
extras = CreationExtras {
|
||||
set(MangaCoverViewModel.MANGA_ID_KEY, successState.manga.id)
|
||||
},
|
||||
)
|
||||
val manga by sm.state.collectAsStateWithLifecycle()
|
||||
val vm =
|
||||
assistedMetroViewModel<MangaCoverViewModel, MangaCoverViewModel.Factory> {
|
||||
create(mangaId = mangaId)
|
||||
}
|
||||
val manga by vm.state.collectAsStateWithLifecycle()
|
||||
if (manga != null) {
|
||||
val getContent = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
|
||||
if (it == null) return@rememberLauncherForActivityResult
|
||||
sm.editCover(context, it)
|
||||
vm.editCover(context, it)
|
||||
}
|
||||
MangaCoverDialog(
|
||||
manga = manga!!,
|
||||
snackbarHostState = sm.snackbarHostState,
|
||||
snackbarHostState = vm.snackbarHostState,
|
||||
isCustomCover = remember(manga) { manga!!.hasCustomCover() },
|
||||
onShareClick = { sm.shareCover(context) },
|
||||
onSaveClick = { sm.saveCover(context) },
|
||||
onShareClick = { vm.shareCover(context) },
|
||||
onSaveClick = { vm.saveCover(context) },
|
||||
onEditClick = {
|
||||
when (it) {
|
||||
EditCoverAction.EDIT -> getContent.launch("image/*")
|
||||
EditCoverAction.DELETE -> sm.deleteCustomCover(context)
|
||||
EditCoverAction.DELETE -> vm.deleteCustomCover(context)
|
||||
}
|
||||
},
|
||||
onDismissRequest = onDismissRequest,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.kanade.tachiyomi.ui.manga
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
@@ -10,9 +9,13 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.util.fastAny
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
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 eu.kanade.core.preference.asState
|
||||
import eu.kanade.core.util.addOrRemove
|
||||
import eu.kanade.core.util.insertSeparators
|
||||
@@ -31,6 +34,7 @@ import eu.kanade.domain.track.service.TrackPreferences
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import eu.kanade.presentation.manga.components.ChapterDownloadAction
|
||||
import eu.kanade.presentation.util.formattedMessage
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.download.DownloadCache
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
@@ -85,60 +89,54 @@ import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.domain.track.interactor.GetTracks
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.math.floor
|
||||
|
||||
@AssistedInject
|
||||
class MangaViewModel(
|
||||
@Assisted private val mangaId: Long,
|
||||
@Assisted private val isFromSource: Boolean,
|
||||
private val context: Context,
|
||||
private val mangaId: Long,
|
||||
private val isFromSource: Boolean,
|
||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
trackPreferences: TrackPreferences = Injekt.get(),
|
||||
readerPreferences: ReaderPreferences = Injekt.get(),
|
||||
private val trackerManager: TrackerManager = Injekt.get(),
|
||||
private val trackChapter: TrackChapter = Injekt.get(),
|
||||
private val downloadManager: DownloadManager = Injekt.get(),
|
||||
private val downloadCache: DownloadCache = Injekt.get(),
|
||||
private val getMangaAndChapters: GetMangaWithChapters = Injekt.get(),
|
||||
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
|
||||
private val getAvailableScanlators: GetAvailableScanlators = Injekt.get(),
|
||||
private val getExcludedScanlators: GetExcludedScanlators = Injekt.get(),
|
||||
private val setExcludedScanlators: SetExcludedScanlators = Injekt.get(),
|
||||
private val setMangaChapterFlags: SetMangaChapterFlags = Injekt.get(),
|
||||
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
||||
private val setReadStatus: SetReadStatus = Injekt.get(),
|
||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val getTracks: GetTracks = Injekt.get(),
|
||||
private val addTracks: AddTracks = Injekt.get(),
|
||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||
private val mangaRepository: MangaRepository = Injekt.get(),
|
||||
private val filterChaptersForDownload: FilterChaptersForDownload = Injekt.get(),
|
||||
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get(),
|
||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||
private val libraryPreferences: LibraryPreferences,
|
||||
trackPreferences: TrackPreferences,
|
||||
readerPreferences: ReaderPreferences,
|
||||
private val trackerManager: TrackerManager,
|
||||
private val trackChapter: TrackChapter,
|
||||
private val downloadManager: DownloadManager,
|
||||
private val downloadCache: DownloadCache,
|
||||
private val getMangaAndChapters: GetMangaWithChapters,
|
||||
private val getDuplicateLibraryManga: GetDuplicateLibraryManga,
|
||||
private val getAvailableScanlators: GetAvailableScanlators,
|
||||
private val getExcludedScanlators: GetExcludedScanlators,
|
||||
private val setExcludedScanlators: SetExcludedScanlators,
|
||||
private val setMangaChapterFlags: SetMangaChapterFlags,
|
||||
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags,
|
||||
private val setReadStatus: SetReadStatus,
|
||||
private val updateChapter: UpdateChapter,
|
||||
private val updateManga: UpdateManga,
|
||||
private val getCategories: GetCategories,
|
||||
private val getTracks: GetTracks,
|
||||
private val addTracks: AddTracks,
|
||||
private val setMangaCategories: SetMangaCategories,
|
||||
private val mangaRepository: MangaRepository,
|
||||
private val filterChaptersForDownload: FilterChaptersForDownload,
|
||||
private val updateMangaFromRemote: UpdateMangaFromRemote,
|
||||
private val sourceManager: SourceManager,
|
||||
private val refreshTracks: RefreshTracks,
|
||||
private val coverCache: CoverCache,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<State>
|
||||
field = MutableStateFlow<State>(State.Loading)
|
||||
val state: StateFlow<MangaViewModel.State>
|
||||
field = MutableStateFlow<MangaViewModel.State>(State.Loading)
|
||||
|
||||
companion object {
|
||||
val MANGA_ID_KEY = CreationExtras.Key<Long>()
|
||||
|
||||
val IS_FROM_SOURCE_KEY = CreationExtras.Key<Boolean>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
MangaViewModel(
|
||||
context = Injekt.get<Application>(),
|
||||
mangaId = get(MANGA_ID_KEY)!!,
|
||||
isFromSource = get(IS_FROM_SOURCE_KEY)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(mangaId: Long, isFromSource: Boolean): MangaViewModel
|
||||
}
|
||||
|
||||
val snackbarHostState: SnackbarHostState = SnackbarHostState()
|
||||
|
||||
private val successState: State.Success?
|
||||
get() = state.value as? State.Success
|
||||
|
||||
@@ -236,7 +234,7 @@ class MangaViewModel(
|
||||
state.update {
|
||||
State.Success(
|
||||
manga = manga,
|
||||
source = Injekt.get<SourceManager>().getOrStub(manga.source),
|
||||
source = sourceManager.getOrStub(manga.source),
|
||||
isFromSource = isFromSource,
|
||||
chapters = chapters,
|
||||
availableScanlators = getAvailableScanlators.await(mangaId),
|
||||
@@ -348,7 +346,7 @@ class MangaViewModel(
|
||||
// Remove from library
|
||||
if (updateManga.awaitUpdateFavorite(manga.id, false)) {
|
||||
// Remove covers and update last modified in db
|
||||
if (manga.removeCovers() != manga) {
|
||||
if (manga.removeCovers(coverCache) != manga) {
|
||||
updateManga.awaitUpdateCoverLastModified(manga.id)
|
||||
}
|
||||
withUIContext { onRemoved() }
|
||||
@@ -777,9 +775,7 @@ class MangaViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshTrackers(
|
||||
refreshTracks: RefreshTracks = Injekt.get(),
|
||||
) {
|
||||
private suspend fun refreshTrackers() {
|
||||
refreshTracks.await(mangaId)
|
||||
.filter { it.first != null }
|
||||
.forEach { (track, e) ->
|
||||
|
||||
@@ -6,12 +6,16 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.manga.MangaNotesScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -20,8 +24,6 @@ import kotlinx.coroutines.flow.update
|
||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||
import tachiyomi.domain.manga.interactor.UpdateMangaNotes
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class MangaNotesScreen(
|
||||
private val manga: Manga,
|
||||
@@ -30,12 +32,7 @@ class MangaNotesScreen(
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val viewModel = viewModel<Model>(
|
||||
factory = Model.Factory,
|
||||
extras = CreationExtras {
|
||||
set(Model.MANGA_KEY, manga)
|
||||
},
|
||||
)
|
||||
val viewModel = assistedMetroViewModel<Model, Model.Factory> { create(manga = manga) }
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
MangaNotesScreen(
|
||||
@@ -45,24 +42,20 @@ class MangaNotesScreen(
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedInject
|
||||
class Model(
|
||||
private val manga: Manga,
|
||||
private val updateMangaNotes: UpdateMangaNotes = Injekt.get(),
|
||||
@Assisted private val manga: Manga,
|
||||
private val updateMangaNotes: UpdateMangaNotes,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<State>
|
||||
field = MutableStateFlow<State>(State(manga, manga.notes))
|
||||
|
||||
companion object {
|
||||
val MANGA_KEY = CreationExtras.Key<Manga>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
Model(
|
||||
manga = get(MANGA_KEY)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(manga: Manga): Model
|
||||
}
|
||||
|
||||
fun updateNotes(content: String) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.track
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -32,14 +31,18 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
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.domain.track.interactor.RefreshTracks
|
||||
import eu.kanade.domain.track.model.toDbTrack
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
@@ -52,7 +55,6 @@ import eu.kanade.presentation.track.TrackerSearch
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||
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.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||
@@ -70,6 +72,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.i18n.stringResource
|
||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
@@ -85,8 +88,6 @@ import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||
import tachiyomi.presentation.core.components.material.AlertDialogContent
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
@@ -100,15 +101,9 @@ data class TrackInfoDialogHomeScreen(
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
val viewModel = viewModel<Model>(
|
||||
factory = Model.Factory,
|
||||
extras = CreationExtras {
|
||||
set(Model.MANGA_ID_KEY, mangaId)
|
||||
set(Model.SOURCE_ID_KEY, sourceId)
|
||||
},
|
||||
)
|
||||
val viewModel = assistedMetroViewModel<Model, Model.Factory> { create(mangaId = mangaId, sourceId = sourceId) }
|
||||
|
||||
val dateFormat = remember { UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat.get()) }
|
||||
val dateFormat = remember { UiPreferences.dateFormat(context.appGraph.uiPreferences.dateFormat.get()) }
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
TrackInfoDialogHome(
|
||||
@@ -202,27 +197,26 @@ data class TrackInfoDialogHomeScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedInject
|
||||
class Model(
|
||||
private val mangaId: Long,
|
||||
private val sourceId: Long,
|
||||
private val getTracks: GetTracks = Injekt.get(),
|
||||
@Assisted private val mangaId: Long,
|
||||
@Assisted private val sourceId: Long,
|
||||
private val context: Context,
|
||||
private val getTracks: GetTracks,
|
||||
private val getManga: GetManga,
|
||||
private val trackerManager: TrackerManager,
|
||||
private val sourceManager: SourceManager,
|
||||
private val refreshTracks: RefreshTracks,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<Model.State>
|
||||
field = MutableStateFlow<Model.State>(State())
|
||||
|
||||
companion object {
|
||||
val MANGA_ID_KEY = CreationExtras.Key<Long>()
|
||||
val SOURCE_ID_KEY = CreationExtras.Key<Long>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
Model(
|
||||
mangaId = get(MANGA_ID_KEY)!!,
|
||||
sourceId = get(SOURCE_ID_KEY)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(mangaId: Long, sourceId: Long): Model
|
||||
}
|
||||
|
||||
init {
|
||||
@@ -242,20 +236,19 @@ data class TrackInfoDialogHomeScreen(
|
||||
fun registerEnhancedTracking(item: TrackItem) {
|
||||
item.tracker as EnhancedTracker
|
||||
viewModelScope.launchNonCancellable {
|
||||
val manga = Injekt.get<GetManga>().await(mangaId) ?: return@launchNonCancellable
|
||||
val manga = getManga.await(mangaId) ?: return@launchNonCancellable
|
||||
try {
|
||||
val matchResult = item.tracker.match(manga) ?: throw Exception()
|
||||
item.tracker.register(matchResult, mangaId)
|
||||
} catch (_: Exception) {
|
||||
withUIContext { Injekt.get<Application>().toast(MR.strings.error_no_match) }
|
||||
withUIContext {
|
||||
context.toast(MR.strings.error_no_match)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshTrackers() {
|
||||
val refreshTracks = Injekt.get<RefreshTracks>()
|
||||
val context = Injekt.get<Application>()
|
||||
|
||||
refreshTracks.await(mangaId)
|
||||
.filter { it.first != null }
|
||||
.forEach { (track, e) ->
|
||||
@@ -281,8 +274,8 @@ data class TrackInfoDialogHomeScreen(
|
||||
}
|
||||
|
||||
private fun List<Track>.mapToTrackItem(): List<TrackItem> {
|
||||
val loggedInTrackers = Injekt.get<TrackerManager>().loggedInTrackers()
|
||||
val source = Injekt.get<SourceManager>().getOrStub(sourceId)
|
||||
val loggedInTrackers = trackerManager.loggedInTrackers()
|
||||
val source = sourceManager.getOrStub(sourceId)
|
||||
return loggedInTrackers
|
||||
// Map to TrackItem
|
||||
.map { service -> TrackItem(find { it.trackerId == service.id }, service) }
|
||||
@@ -297,7 +290,7 @@ data class TrackInfoDialogHomeScreen(
|
||||
}
|
||||
}
|
||||
|
||||
private data class TrackStatusSelectorScreen(
|
||||
data class TrackStatusSelectorScreen(
|
||||
private val track: Track,
|
||||
private val serviceId: Long,
|
||||
) : Screen() {
|
||||
@@ -305,13 +298,7 @@ private data class TrackStatusSelectorScreen(
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<Model>(
|
||||
factory = Model.Factory,
|
||||
extras = CreationExtras {
|
||||
set(Model.TRACK_KEY, track)
|
||||
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||
},
|
||||
)
|
||||
val viewModel = assistedMetroViewModel<Model, Model.Factory> { create(track = track, trackerId = serviceId) }
|
||||
val state by viewModel.state.collectAsState()
|
||||
TrackStatusSelector(
|
||||
selection = state.selection,
|
||||
@@ -325,28 +312,25 @@ private data class TrackStatusSelectorScreen(
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedInject
|
||||
class Model(
|
||||
private val track: Track,
|
||||
private val tracker: Tracker,
|
||||
@Assisted private val track: Track,
|
||||
@Assisted private val trackerId: Long,
|
||||
trackerManager: TrackerManager,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<Model.State>
|
||||
field = MutableStateFlow<Model.State>(State(track.status))
|
||||
|
||||
companion object {
|
||||
val TRACK_KEY = CreationExtras.Key<Track>()
|
||||
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
Model(
|
||||
track = get(TRACK_KEY)!!,
|
||||
tracker = get(TRACKER_KEY)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(track: Track, trackerId: Long): Model
|
||||
}
|
||||
|
||||
val tracker = trackerManager.get(trackerId)!!
|
||||
|
||||
fun getSelections(): Map<Long, StringResource?> {
|
||||
return tracker.getStatusList().associateWith { tracker.getStatus(it) }
|
||||
}
|
||||
@@ -368,7 +352,7 @@ private data class TrackStatusSelectorScreen(
|
||||
}
|
||||
}
|
||||
|
||||
private data class TrackChapterSelectorScreen(
|
||||
data class TrackChapterSelectorScreen(
|
||||
private val track: Track,
|
||||
private val serviceId: Long,
|
||||
) : Screen() {
|
||||
@@ -376,13 +360,7 @@ private data class TrackChapterSelectorScreen(
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<Model>(
|
||||
factory = Model.Factory,
|
||||
extras = CreationExtras {
|
||||
set(Model.TRACK_KEY, track)
|
||||
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||
},
|
||||
)
|
||||
val viewModel = assistedMetroViewModel<Model, Model.Factory> { create(track = track, trackerId = serviceId) }
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
TrackChapterSelector(
|
||||
@@ -397,28 +375,25 @@ private data class TrackChapterSelectorScreen(
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedInject
|
||||
class Model(
|
||||
private val track: Track,
|
||||
private val tracker: Tracker,
|
||||
@Assisted private val track: Track,
|
||||
@Assisted private val trackerId: Long,
|
||||
trackerManager: TrackerManager,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<Model.State>
|
||||
field = MutableStateFlow<Model.State>(State(track.lastChapterRead.toInt()))
|
||||
|
||||
companion object {
|
||||
val TRACK_KEY = CreationExtras.Key<Track>()
|
||||
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
Model(
|
||||
track = get(TRACK_KEY)!!,
|
||||
tracker = get(TRACKER_KEY)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(track: Track, trackerId: Long): Model
|
||||
}
|
||||
|
||||
val tracker = trackerManager.get(trackerId)!!
|
||||
|
||||
fun getRange(): Iterable<Int> {
|
||||
val endRange = if (track.totalChapters > 0) {
|
||||
track.totalChapters
|
||||
@@ -445,7 +420,7 @@ private data class TrackChapterSelectorScreen(
|
||||
}
|
||||
}
|
||||
|
||||
private data class TrackScoreSelectorScreen(
|
||||
data class TrackScoreSelectorScreen(
|
||||
private val track: Track,
|
||||
private val serviceId: Long,
|
||||
) : Screen() {
|
||||
@@ -453,13 +428,7 @@ private data class TrackScoreSelectorScreen(
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<Model>(
|
||||
factory = Model.Factory,
|
||||
extras = CreationExtras {
|
||||
set(Model.TRACK_KEY, track)
|
||||
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||
},
|
||||
)
|
||||
val viewModel = assistedMetroViewModel<Model, Model.Factory> { create(track = track, trackerId = serviceId) }
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
TrackScoreSelector(
|
||||
@@ -474,26 +443,27 @@ private data class TrackScoreSelectorScreen(
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedInject
|
||||
class Model(
|
||||
private val track: Track,
|
||||
private val tracker: Tracker,
|
||||
@Assisted private val track: Track,
|
||||
@Assisted private val trackerId: Long,
|
||||
trackerManager: TrackerManager,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<Model.State>
|
||||
field = MutableStateFlow<Model.State>(State(tracker.displayScore(track)))
|
||||
field = MutableStateFlow<Model.State>(State(""))
|
||||
|
||||
companion object {
|
||||
val TRACK_KEY = CreationExtras.Key<Track>()
|
||||
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(track: Track, trackerId: Long): Model
|
||||
}
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
Model(
|
||||
track = get(TRACK_KEY)!!,
|
||||
tracker = get(TRACKER_KEY)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
val tracker = trackerManager.get(trackerId)!!
|
||||
|
||||
init {
|
||||
tracker.displayScore(track).let(::setSelection)
|
||||
}
|
||||
|
||||
fun getSelections(): List<String> {
|
||||
@@ -517,7 +487,7 @@ private data class TrackScoreSelectorScreen(
|
||||
}
|
||||
}
|
||||
|
||||
private data class TrackDateSelectorScreen(
|
||||
data class TrackDateSelectorScreen(
|
||||
private val track: Track,
|
||||
private val serviceId: Long,
|
||||
private val start: Boolean,
|
||||
@@ -573,14 +543,9 @@ private data class TrackDateSelectorScreen(
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<Model>(
|
||||
factory = Model.Factory,
|
||||
extras = CreationExtras {
|
||||
set(Model.TRACK_KEY, track)
|
||||
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||
set(Model.START_KEY, start)
|
||||
},
|
||||
)
|
||||
val viewModel = assistedMetroViewModel<Model, Model.Factory> {
|
||||
create(track = track, trackerId = serviceId, start = start)
|
||||
}
|
||||
|
||||
val canRemove = if (start) {
|
||||
track.startDate > 0
|
||||
@@ -604,28 +569,23 @@ private data class TrackDateSelectorScreen(
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedInject
|
||||
class Model(
|
||||
private val track: Track,
|
||||
private val tracker: Tracker,
|
||||
private val start: Boolean,
|
||||
@Assisted private val track: Track,
|
||||
@Assisted private val trackerId: Long,
|
||||
@Assisted private val start: Boolean,
|
||||
trackerManager: TrackerManager,
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
val TRACK_KEY = CreationExtras.Key<Track>()
|
||||
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||
val START_KEY = CreationExtras.Key<Boolean>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
Model(
|
||||
track = get(TRACK_KEY) as Track,
|
||||
tracker = get(TRACKER_KEY) as Tracker,
|
||||
start = get(START_KEY) as Boolean,
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(track: Track, trackerId: Long, start: Boolean): Model
|
||||
}
|
||||
|
||||
private val tracker = trackerManager.get(trackerId)!!
|
||||
|
||||
// In UTC
|
||||
val initialSelection: Long
|
||||
get() {
|
||||
@@ -654,7 +614,7 @@ private data class TrackDateSelectorScreen(
|
||||
}
|
||||
}
|
||||
|
||||
private data class TrackDateRemoverScreen(
|
||||
data class TrackDateRemoverScreen(
|
||||
private val track: Track,
|
||||
private val serviceId: Long,
|
||||
private val start: Boolean,
|
||||
@@ -663,14 +623,9 @@ private data class TrackDateRemoverScreen(
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<Model>(
|
||||
factory = Model.Factory,
|
||||
extras = CreationExtras {
|
||||
set(Model.TRACK_KEY, track)
|
||||
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||
set(Model.START_KEY, start)
|
||||
},
|
||||
)
|
||||
val viewModel = assistedMetroViewModel<Model, Model.Factory> {
|
||||
create(track = track, trackerId = serviceId, start = start)
|
||||
}
|
||||
AlertDialogContent(
|
||||
modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars),
|
||||
icon = {
|
||||
@@ -720,28 +675,23 @@ private data class TrackDateRemoverScreen(
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedInject
|
||||
class Model(
|
||||
private val track: Track,
|
||||
private val tracker: Tracker,
|
||||
private val start: Boolean,
|
||||
@Assisted private val track: Track,
|
||||
@Assisted private val trackerId: Long,
|
||||
@Assisted private val start: Boolean,
|
||||
trackerManager: TrackerManager,
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
val TRACK_KEY = CreationExtras.Key<Track>()
|
||||
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||
val START_KEY = CreationExtras.Key<Boolean>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
Model(
|
||||
track = get(TRACK_KEY) as Track,
|
||||
tracker = get(TRACKER_KEY) as Tracker,
|
||||
start = get(START_KEY) as Boolean,
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(track: Track, trackerId: Long, start: Boolean): Model
|
||||
}
|
||||
|
||||
private val tracker = trackerManager.get(trackerId)!!
|
||||
|
||||
fun getServiceName() = tracker.name
|
||||
|
||||
fun removeDate() {
|
||||
@@ -766,15 +716,14 @@ data class TrackerSearchScreen(
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<Model>(
|
||||
factory = Model.Factory,
|
||||
extras = CreationExtras {
|
||||
set(Model.MANGA_ID_KEY, mangaId)
|
||||
set(Model.CURRENT_URL_KEY, currentUrl)
|
||||
set(Model.INITIAL_QUERY_KEY, initialQuery)
|
||||
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||
},
|
||||
)
|
||||
val viewModel = assistedMetroViewModel<Model, Model.Factory> {
|
||||
create(
|
||||
mangaId = mangaId,
|
||||
currentUrl = currentUrl,
|
||||
initialQuery = initialQuery,
|
||||
trackerId = serviceId,
|
||||
)
|
||||
}
|
||||
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
@@ -796,34 +745,32 @@ data class TrackerSearchScreen(
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedInject
|
||||
class Model(
|
||||
private val mangaId: Long,
|
||||
private val currentUrl: String?,
|
||||
initialQuery: String,
|
||||
private val tracker: Tracker,
|
||||
@Assisted private val mangaId: Long,
|
||||
@Assisted private val currentUrl: String?,
|
||||
@Assisted initialQuery: String,
|
||||
@Assisted private val trackerId: Long,
|
||||
trackerManager: TrackerManager,
|
||||
) : ViewModel() {
|
||||
|
||||
val state: StateFlow<Model.State>
|
||||
field = MutableStateFlow<Model.State>(State())
|
||||
|
||||
companion object {
|
||||
val MANGA_ID_KEY = CreationExtras.Key<Long>()
|
||||
val CURRENT_URL_KEY = CreationExtras.Key<String?>()
|
||||
val INITIAL_QUERY_KEY = CreationExtras.Key<String>()
|
||||
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
Model(
|
||||
mangaId = get(MANGA_ID_KEY)!!,
|
||||
currentUrl = get(CURRENT_URL_KEY),
|
||||
initialQuery = get(INITIAL_QUERY_KEY)!!,
|
||||
tracker = get(TRACKER_KEY)!!,
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(
|
||||
mangaId: Long,
|
||||
currentUrl: String?,
|
||||
initialQuery: String,
|
||||
trackerId: Long,
|
||||
): Model
|
||||
}
|
||||
|
||||
private val tracker = trackerManager.get(trackerId)!!
|
||||
|
||||
val supportsPrivateTracking = tracker.supportsPrivateTracking
|
||||
|
||||
init {
|
||||
@@ -871,7 +818,7 @@ data class TrackerSearchScreen(
|
||||
}
|
||||
}
|
||||
|
||||
private data class TrackerRemoveScreen(
|
||||
data class TrackerRemoveScreen(
|
||||
private val mangaId: Long,
|
||||
private val track: Track,
|
||||
private val serviceId: Long,
|
||||
@@ -880,14 +827,9 @@ private data class TrackerRemoveScreen(
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<Model>(
|
||||
factory = Model.Factory,
|
||||
extras = CreationExtras {
|
||||
set(Model.MANGA_ID_KEY, mangaId)
|
||||
set(Model.TRACK_KEY, track)
|
||||
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||
},
|
||||
)
|
||||
val viewModel = assistedMetroViewModel<Model, Model.Factory> {
|
||||
create(mangaId = mangaId, track = track, trackerId = serviceId)
|
||||
}
|
||||
val serviceName = viewModel.getName()
|
||||
var removeRemoteTrack by remember { mutableStateOf(false) }
|
||||
AlertDialogContent(
|
||||
@@ -950,29 +892,24 @@ private data class TrackerRemoveScreen(
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedInject
|
||||
class Model(
|
||||
private val mangaId: Long,
|
||||
private val track: Track,
|
||||
private val tracker: Tracker,
|
||||
private val deleteTrack: DeleteTrack = Injekt.get(),
|
||||
@Assisted private val mangaId: Long,
|
||||
@Assisted private val track: Track,
|
||||
@Assisted private val trackerId: Long,
|
||||
private val deleteTrack: DeleteTrack,
|
||||
trackerManager: TrackerManager,
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
val MANGA_ID_KEY = CreationExtras.Key<Long>()
|
||||
val TRACK_KEY = CreationExtras.Key<Track>()
|
||||
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||
|
||||
val Factory = viewModelFactory {
|
||||
initializer {
|
||||
Model(
|
||||
mangaId = get(MANGA_ID_KEY) as Long,
|
||||
track = get(TRACK_KEY) as Track,
|
||||
tracker = get(TRACKER_KEY) as Tracker,
|
||||
)
|
||||
}
|
||||
}
|
||||
@AssistedFactory
|
||||
@ManualViewModelAssistedFactoryKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
interface Factory : ManualViewModelAssistedFactory {
|
||||
fun create(mangaId: Long, track: Track, trackerId: Long): Model
|
||||
}
|
||||
|
||||
private val tracker = trackerManager.get(trackerId)!!
|
||||
|
||||
fun getName() = tracker.name
|
||||
|
||||
fun isDeletable() = tracker is DeletableTracker
|
||||
|
||||
@@ -9,12 +9,16 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
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.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
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.core.preference.asState
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.presentation.more.MoreScreen
|
||||
@@ -34,8 +38,6 @@ import mihon.feature.support.SupportUsScreen
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
data object MoreTab : Tab {
|
||||
|
||||
@@ -58,7 +60,7 @@ data object MoreTab : Tab {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val viewModel = viewModel<MoreViewModel>()
|
||||
val viewModel = metroViewModel<MoreViewModel>()
|
||||
val downloadQueueState by viewModel.downloadQueueState.collectAsState()
|
||||
MoreScreen(
|
||||
downloadQueueStateProvider = { downloadQueueState },
|
||||
@@ -77,9 +79,12 @@ data object MoreTab : Tab {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject
|
||||
@ViewModelKey
|
||||
@ContributesIntoMap(AppScope::class)
|
||||
class MoreViewModel(
|
||||
private val downloadManager: DownloadManager = Injekt.get(),
|
||||
preferences: BasePreferences = Injekt.get(),
|
||||
private val downloadManager: DownloadManager,
|
||||
preferences: BasePreferences,
|
||||
) : ViewModel() {
|
||||
|
||||
var downloadedOnly by preferences.downloadedOnly.asState(viewModelScope)
|
||||
|
||||
@@ -4,10 +4,10 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import dev.zacsweers.metrox.viewmodel.assistedMetroViewModel
|
||||
import eu.kanade.presentation.more.NewUpdateScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
@@ -23,13 +23,10 @@ class NewUpdateScreen(
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
val viewModel = viewModel<NewUpdateScreenModel>(
|
||||
factory = NewUpdateScreenModel.Factory,
|
||||
extras = CreationExtras {
|
||||
set(NewUpdateScreenModel.CHANGELOG_INFO_KEY, changelogInfo)
|
||||
set(NewUpdateScreenModel.DOWNLOAD_LINK_KEY, downloadLink)
|
||||
},
|
||||
)
|
||||
val viewModel =
|
||||
assistedMetroViewModel<NewUpdateScreenModel, NewUpdateScreenModel.Factory> {
|
||||
create(changelogInfo = changelogInfo, downloadLink = downloadLink)
|
||||
}
|
||||
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user