diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 08d81bbf4796..44ed9be67514 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -40,11 +40,6 @@ import exh.log.EHLogLevel import exh.ui.lock.LockActivityDelegate import io.realm.Realm import io.realm.RealmConfiguration -import java.io.File -import java.security.NoSuchAlgorithmException -import java.security.Security -import javax.net.ssl.SSLContext -import kotlin.concurrent.thread import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.conscrypt.Conscrypt @@ -52,9 +47,13 @@ import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.InjektScope import uy.kohesive.injekt.registry.default.DefaultRegistrar +import java.io.File +import java.security.NoSuchAlgorithmException +import java.security.Security +import javax.net.ssl.SSLContext +import kotlin.concurrent.thread open class App : Application(), LifecycleObserver { - override fun onCreate() { super.onCreate() if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree()) @@ -157,11 +156,12 @@ open class App : Application(), LifecycleObserver { // EXH private fun deleteOldMetadataRealm() { - val config = RealmConfiguration.Builder() - .name("gallery-metadata.realm") - .schemaVersion(3) - .deleteRealmIfMigrationNeeded() - .build() + val config = + RealmConfiguration.Builder() + .name("gallery-metadata.realm") + .schemaVersion(3) + .deleteRealmIfMigrationNeeded() + .build() Realm.deleteRealm(config) // Delete old paper db files @@ -182,37 +182,46 @@ open class App : Application(), LifecycleObserver { private fun setupExhLogging() { EHLogLevel.init(this) - val logLevel = if (EHLogLevel.shouldLog(EHLogLevel.EXTRA)) { - LogLevel.ALL - } else { - LogLevel.WARN - } + val logLevel = + if (EHLogLevel.shouldLog(EHLogLevel.EXTRA)) { + LogLevel.ALL + } else { + LogLevel.WARN + } - val logConfig = LogConfiguration.Builder() - .logLevel(logLevel) - .t() - .st(2) - .nb() - .build() + val logConfig = + LogConfiguration.Builder() + .logLevel(logLevel) + .t() + .st(2) + .nb() + .build() val printers = mutableListOf(AndroidPrinter()) - val logFolder = File( - Environment.getExternalStorageDirectory().absolutePath + File.separator + - getString(R.string.app_name), - "logs" - ) - - printers += FilePrinter - .Builder(logFolder.absolutePath) - .fileNameGenerator(object : DateFileNameGenerator() { - override fun generateFileName(logLevel: Int, timestamp: Long): String { - return super.generateFileName(logLevel, timestamp) + "-${BuildConfig.BUILD_TYPE}" - } - }) - .cleanStrategy(FileLastModifiedCleanStrategy(7.days.inMilliseconds.longValue)) - .backupStrategy(NeverBackupStrategy()) - .build() + val logFolder = + File( + Environment.getExternalStorageDirectory().absolutePath + File.separator + + getString(R.string.app_name), + "logs" + ) + + printers += + FilePrinter + .Builder(logFolder.absolutePath) + .fileNameGenerator( + object : DateFileNameGenerator() { + override fun generateFileName( + logLevel: Int, + timestamp: Long + ): String { + return super.generateFileName(logLevel, timestamp) + "-${BuildConfig.BUILD_TYPE}" + } + } + ) + .cleanStrategy(FileLastModifiedCleanStrategy(7.days.inMilliseconds.longValue)) + .backupStrategy(NeverBackupStrategy()) + .build() // Install Crashlytics in prod if (!BuildConfig.DEBUG) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppInfo.kt b/app/src/main/java/eu/kanade/tachiyomi/AppInfo.kt index 7ec80cdb6731..2321b5fb30b4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/AppInfo.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/AppInfo.kt @@ -7,5 +7,6 @@ package eu.kanade.tachiyomi */ object AppInfo { fun getVersionCode() = BuildConfig.VERSION_CODE + fun getVersionName() = BuildConfig.VERSION_NAME } diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt index 5878e1b54a03..33391b17937a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt @@ -25,7 +25,6 @@ import uy.kohesive.injekt.api.addSingletonFactory import uy.kohesive.injekt.api.get class AppModule(val app: Application) : InjektModule { - override fun InjektRegistrar.registerInjectables() { addSingleton(app) diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 7e6703b96924..bf44b24eb3cd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.extension.ExtensionUpdateJob import java.io.File object Migrations { - // TODO NATIVE TACHIYOMI MIGRATIONS ARE FUCKED UP DUE TO DIFFERING VERSION NUMBERS /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt index a55852a51ca8..7257a5c3369c 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt @@ -19,21 +19,23 @@ import rx.Observable import uy.kohesive.injekt.injectLazy abstract class AbstractBackupManager(protected val context: Context) { - internal val databaseHelper: DatabaseHelper by injectLazy() internal val sourceManager: SourceManager by injectLazy() internal val trackManager: TrackManager by injectLazy() protected val preferences: PreferencesHelper by injectLazy() - abstract fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? + abstract fun createBackup( + uri: Uri, + flags: Int, + isJob: Boolean + ): String? /** * Returns manga * * @return [Manga], null if not found */ - internal fun getMangaFromDatabase(manga: Manga): Manga? = - databaseHelper.getManga(manga.url, manga.source).executeAsBlocking() + internal fun getMangaFromDatabase(manga: Manga): Manga? = databaseHelper.getManga(manga.url, manga.source).executeAsBlocking() /** * [Observable] that fetches chapter information @@ -43,7 +45,12 @@ abstract class AbstractBackupManager(protected val context: Context) { * @param chapters list of chapters in the backup * @return [Observable] that contains manga */ - internal open fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List, throttleManager: EHentaiThrottleManager): Observable, List>> { + internal open fun restoreChapterFetchObservable( + source: Source, + manga: Manga, + chapters: List, + throttleManager: EHentaiThrottleManager + ): Observable, List>> { return ( if (source is EHentai) { source.fetchChapterList(manga, throttleManager::throttle) @@ -69,16 +76,14 @@ abstract class AbstractBackupManager(protected val context: Context) { * * @return [Manga] from library */ - protected fun getFavoriteManga(): List = - databaseHelper.getFavoriteMangas().executeAsBlocking() + protected fun getFavoriteManga(): List = databaseHelper.getFavoriteMangas().executeAsBlocking() /** * Inserts manga and returns id * * @return id of [Manga], null if not found */ - internal fun insertManga(manga: Manga): Long? = - databaseHelper.insertManga(manga).executeAsBlocking().insertedId() + internal fun insertManga(manga: Manga): Long? = databaseHelper.insertManga(manga).executeAsBlocking().insertedId() /** * Inserts list of chapters diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt index f1f82bc344a6..6f92bbf808cb 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt @@ -11,16 +11,15 @@ import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.util.chapter.NoChaptersException import exh.eh.EHentaiThrottleManager +import kotlinx.coroutines.Job +import rx.Observable +import uy.kohesive.injekt.injectLazy import java.io.File import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -import kotlinx.coroutines.Job -import rx.Observable -import uy.kohesive.injekt.injectLazy abstract class AbstractBackupRestore(protected val context: Context, protected val notifier: BackupNotifier) { - protected val db: DatabaseHelper by injectLazy() protected val trackManager: TrackManager by injectLazy() @@ -69,15 +68,20 @@ abstract class AbstractBackupRestore(protected val co * @param manga manga that needs updating * @return [Observable] that contains manga */ - internal fun chapterFetchObservable(source: Source, manga: Manga, chapters: List): Observable, List>> { + internal fun chapterFetchObservable( + source: Source, + manga: Manga, + chapters: List + ): Observable, List>> { return backupManager.restoreChapterFetchObservable(source, manga, chapters /* SY --> */, throttleManager /* SY <-- */) // If there's any error, return empty update and continue. .onErrorReturn { - val errorMessage = if (it is NoChaptersException) { - context.getString(R.string.no_chapters_error) - } else { - it.message - } + val errorMessage = + if (it is NoChaptersException) { + context.getString(R.string.no_chapters_error) + } else { + it.message + } errors.add(Date() to "${manga.title} - $errorMessage") Pair(emptyList(), emptyList()) } @@ -89,7 +93,10 @@ abstract class AbstractBackupRestore(protected val co * @param tracks list containing tracks from restore file. * @return [Observable] that contains updated track item */ - internal fun trackingFetchObservable(manga: Manga, tracks: List): Observable { + internal fun trackingFetchObservable( + manga: Manga, + tracks: List + ): Observable { return Observable.from(tracks) .flatMap { track -> val service = trackManager.getService(track.sync_id) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestoreValidator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestoreValidator.kt index 2dc959691ca0..53ae6e08622b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestoreValidator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestoreValidator.kt @@ -10,7 +10,10 @@ abstract class AbstractBackupRestoreValidator { protected val sourceManager: SourceManager by injectLazy() protected val trackManager: TrackManager by injectLazy() - abstract fun validate(context: Context, uri: Uri): Results + abstract fun validate( + context: Context, + uri: Uri + ): Results data class Results(val missingSources: List, val missingTrackers: List) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt index 1b396bc11b4c..fb0b03fda699 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.backup import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID object BackupConst { - private const val NAME = "BackupRestoreServices" const val EXTRA_URI = "$ID.$NAME.EXTRA_URI" const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt index 546b935e85d6..148042038cb3 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt @@ -19,7 +19,6 @@ import eu.kanade.tachiyomi.util.system.isServiceRunning * Service for backing up library information to a JSON file. */ class BackupCreateService : Service() { - companion object { // Filter options internal const val BACKUP_CATEGORY = 0x1 @@ -38,8 +37,7 @@ class BackupCreateService : Service() { * @param context the application context. * @return true if the service is running, false otherwise. */ - fun isRunning(context: Context): Boolean = - context.isServiceRunning(BackupCreateService::class.java) + fun isRunning(context: Context): Boolean = context.isServiceRunning(BackupCreateService::class.java) /** * Make a backup from library @@ -48,13 +46,19 @@ class BackupCreateService : Service() { * @param uri path of Uri * @param flags determines what to backup */ - fun start(context: Context, uri: Uri, flags: Int, type: Int) { + fun start( + context: Context, + uri: Uri, + flags: Int, + type: Int + ) { if (!isRunning(context)) { - val intent = Intent(context, BackupCreateService::class.java).apply { - putExtra(BackupConst.EXTRA_URI, uri) - putExtra(BackupConst.EXTRA_FLAGS, flags) - putExtra(BackupConst.EXTRA_TYPE, type) - } + val intent = + Intent(context, BackupCreateService::class.java).apply { + putExtra(BackupConst.EXTRA_URI, uri) + putExtra(BackupConst.EXTRA_FLAGS, flags) + putExtra(BackupConst.EXTRA_TYPE, type) + } ContextCompat.startForegroundService(context, intent) } } @@ -97,17 +101,22 @@ class BackupCreateService : Service() { */ override fun onBind(intent: Intent): IBinder? = null - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + override fun onStartCommand( + intent: Intent?, + flags: Int, + startId: Int + ): Int { if (intent == null) return START_NOT_STICKY try { val uri = intent.getParcelableExtra(BackupConst.EXTRA_URI)!! val backupFlags = intent.getIntExtra(BackupConst.EXTRA_FLAGS, 0) val backupType = intent.getIntExtra(BackupConst.EXTRA_TYPE, BackupConst.BACKUP_TYPE_LEGACY) - val backupManager = when (backupType) { - BackupConst.BACKUP_TYPE_FULL -> FullBackupManager(this) - else -> LegacyBackupManager(this) - } + val backupManager = + when (backupType) { + BackupConst.BACKUP_TYPE_FULL -> FullBackupManager(this) + else -> LegacyBackupManager(this) + } val backupFileUri = backupManager.createBackup(uri, backupFlags, false)?.toUri() val unifile = UniFile.fromUri(this, backupFileUri) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt index 1bbc6d206358..96d1bb3ab6cd 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt @@ -10,13 +10,12 @@ import androidx.work.WorkerParameters import eu.kanade.tachiyomi.data.backup.full.FullBackupManager import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import java.util.concurrent.TimeUnit import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.concurrent.TimeUnit class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { - override fun doWork(): Result { val preferences = Injekt.get() val backupManager = FullBackupManager(context) @@ -35,16 +34,22 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet companion object { private const val TAG = "BackupCreator" - fun setupTask(context: Context, prefInterval: Int? = null) { + fun setupTask( + context: Context, + prefInterval: Int? = null + ) { val preferences = Injekt.get() val interval = prefInterval ?: preferences.backupInterval().get() if (interval > 0) { - val request = PeriodicWorkRequestBuilder( - interval.toLong(), TimeUnit.HOURS, - 10, TimeUnit.MINUTES - ) - .addTag(TAG) - .build() + val request = + PeriodicWorkRequestBuilder( + interval.toLong(), + TimeUnit.HOURS, + 10, + TimeUnit.MINUTES + ) + .addTag(TAG) + .build() WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request) } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt index e46c9ef4ca0e..b73a44e15654 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt @@ -11,38 +11,40 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationManager +import uy.kohesive.injekt.injectLazy import java.io.File import java.util.concurrent.TimeUnit -import uy.kohesive.injekt.injectLazy class BackupNotifier(private val context: Context) { - private val preferences: PreferencesHelper by injectLazy() - private val progressNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE_PROGRESS) { - setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)) - setSmallIcon(R.drawable.ic_tachi) - setAutoCancel(false) - setOngoing(true) - } + private val progressNotificationBuilder = + context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE_PROGRESS) { + setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)) + setSmallIcon(R.drawable.ic_tachi) + setAutoCancel(false) + setOngoing(true) + } - private val completeNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE_COMPLETE) { - setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)) - setSmallIcon(R.drawable.ic_tachi) - setAutoCancel(false) - } + private val completeNotificationBuilder = + context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE_COMPLETE) { + setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)) + setSmallIcon(R.drawable.ic_tachi) + setAutoCancel(false) + } private fun NotificationCompat.Builder.show(id: Int) { context.notificationManager.notify(id, build()) } fun showBackupProgress(): NotificationCompat.Builder { - val builder = with(progressNotificationBuilder) { - setContentTitle(context.getString(R.string.creating_backup)) + val builder = + with(progressNotificationBuilder) { + setContentTitle(context.getString(R.string.creating_backup)) - setProgress(0, 0, true) - setOnlyAlertOnce(true) - } + setProgress(0, 0, true) + setOnlyAlertOnce(true) + } builder.show(Notifications.ID_BACKUP_PROGRESS) @@ -82,28 +84,33 @@ class BackupNotifier(private val context: Context) { } } - fun showRestoreProgress(content: String = "", progress: Int = 0, maxAmount: Int = 100): NotificationCompat.Builder { - val builder = with(progressNotificationBuilder) { - setContentTitle(context.getString(R.string.restoring_backup)) + fun showRestoreProgress( + content: String = "", + progress: Int = 0, + maxAmount: Int = 100 + ): NotificationCompat.Builder { + val builder = + with(progressNotificationBuilder) { + setContentTitle(context.getString(R.string.restoring_backup)) - if (!preferences.hideNotificationContent()) { - setContentText(content) - } + if (!preferences.hideNotificationContent()) { + setContentText(content) + } - setProgress(maxAmount, progress, false) - setOnlyAlertOnce(true) + setProgress(maxAmount, progress, false) + setOnlyAlertOnce(true) - // Clear old actions if they exist - if (mActions.isNotEmpty()) { - mActions.clear() - } + // Clear old actions if they exist + if (mActions.isNotEmpty()) { + mActions.clear() + } - addAction( - R.drawable.ic_close_24dp, - context.getString(R.string.action_stop), - NotificationReceiver.cancelRestorePendingBroadcast(context, Notifications.ID_RESTORE_PROGRESS) - ) - } + addAction( + R.drawable.ic_close_24dp, + context.getString(R.string.action_stop), + NotificationReceiver.cancelRestorePendingBroadcast(context, Notifications.ID_RESTORE_PROGRESS) + ) + } builder.show(Notifications.ID_RESTORE_PROGRESS) @@ -121,16 +128,23 @@ class BackupNotifier(private val context: Context) { } } - fun showRestoreComplete(time: Long, errorCount: Int, path: String?, file: String?) { + fun showRestoreComplete( + time: Long, + errorCount: Int, + path: String?, + file: String? + ) { context.notificationManager.cancel(Notifications.ID_RESTORE_PROGRESS) - val timeString = context.getString( - R.string.restore_duration, - TimeUnit.MILLISECONDS.toMinutes(time), - TimeUnit.MILLISECONDS.toSeconds(time) - TimeUnit.MINUTES.toSeconds( - TimeUnit.MILLISECONDS.toMinutes(time) + val timeString = + context.getString( + R.string.restore_duration, + TimeUnit.MILLISECONDS.toMinutes(time), + TimeUnit.MILLISECONDS.toSeconds(time) - + TimeUnit.MINUTES.toSeconds( + TimeUnit.MILLISECONDS.toMinutes(time) + ) ) - ) with(completeNotificationBuilder) { setContentTitle(context.getString(R.string.restore_completed)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt index 10320b602bcc..f61758bbc6ea 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt @@ -22,17 +22,14 @@ import timber.log.Timber * Restores backup. */ class BackupRestoreService : Service() { - companion object { - /** * Returns the status of the service. * * @param context the application context. * @return true if the service is running, false otherwise. */ - fun isRunning(context: Context): Boolean = - context.isServiceRunning(BackupRestoreService::class.java) + fun isRunning(context: Context): Boolean = context.isServiceRunning(BackupRestoreService::class.java) /** * Starts a service to restore a backup from Json @@ -40,13 +37,19 @@ class BackupRestoreService : Service() { * @param context context of application * @param uri path of Uri */ - fun start(context: Context, uri: Uri, mode: Int, online: Boolean?) { + fun start( + context: Context, + uri: Uri, + mode: Int, + online: Boolean? + ) { if (!isRunning(context)) { - val intent = Intent(context, BackupRestoreService::class.java).apply { - putExtra(BackupConst.EXTRA_URI, uri) - putExtra(BackupConst.EXTRA_MODE, mode) - online?.let { putExtra(BackupConst.EXTRA_TYPE, it) } - } + val intent = + Intent(context, BackupRestoreService::class.java).apply { + putExtra(BackupConst.EXTRA_URI, uri) + putExtra(BackupConst.EXTRA_MODE, mode) + online?.let { putExtra(BackupConst.EXTRA_TYPE, it) } + } ContextCompat.startForegroundService(context, intent) } } @@ -110,7 +113,11 @@ class BackupRestoreService : Service() { * @param startId the start id of this command. * @return the start value of the command. */ - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + override fun onStartCommand( + intent: Intent?, + flags: Int, + startId: Int + ): Int { val uri = intent?.getParcelableExtra(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY val mode = intent.getIntExtra(BackupConst.EXTRA_MODE, BackupConst.BACKUP_TYPE_FULL) val online = intent.getBooleanExtra(BackupConst.EXTRA_TYPE, true) @@ -118,22 +125,25 @@ class BackupRestoreService : Service() { // Cancel any previous job if needed. backupRestore?.job?.cancel() - backupRestore = when (mode) { - BackupConst.BACKUP_TYPE_FULL -> FullBackupRestore(this, notifier, online) - else -> LegacyBackupRestore(this, notifier) - } - val handler = CoroutineExceptionHandler { _, exception -> - Timber.e(exception) - backupRestore?.writeErrorLog() + backupRestore = + when (mode) { + BackupConst.BACKUP_TYPE_FULL -> FullBackupRestore(this, notifier, online) + else -> LegacyBackupRestore(this, notifier) + } + val handler = + CoroutineExceptionHandler { _, exception -> + Timber.e(exception) + backupRestore?.writeErrorLog() - notifier.showRestoreError(exception.message) - stopSelf(startId) - } - backupRestore?.job = GlobalScope.launch(handler) { - if (backupRestore?.restoreBackup(uri) == false) { - notifier.showRestoreError(getString(R.string.restoring_backup_canceled)) + notifier.showRestoreError(exception.message) + stopSelf(startId) + } + backupRestore?.job = + GlobalScope.launch(handler) { + if (backupRestore?.restoreBackup(uri) == false) { + notifier.showRestoreError(getString(R.string.restoring_backup_canceled)) + } } - } backupRestore?.job?.invokeOnCompletion { stopSelf(startId) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt index f1bbdfcaa7fe..499183a72320 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt @@ -37,7 +37,6 @@ import exh.metadata.metadata.base.getFlatMetadataForManga import exh.metadata.metadata.base.insertFlatMetadata import exh.savedsearches.JsonSavedSearch import exh.source.getMainSource -import kotlin.math.max import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString @@ -48,10 +47,10 @@ import okio.gzip import okio.sink import rx.Observable import timber.log.Timber +import kotlin.math.max @OptIn(ExperimentalSerializationApi::class) class FullBackupManager(context: Context) : AbstractBackupManager(context) { - val parser = ProtoBuf /** @@ -60,45 +59,51 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @param uri path of Uri * @param isJob backup called from job */ - override fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? { + override fun createBackup( + uri: Uri, + flags: Int, + isJob: Boolean + ): String? { // Create root object var backup: Backup? = null databaseHelper.inTransaction { val databaseManga = getFavoriteManga() - backup = Backup( - backupManga(databaseManga, flags), - backupCategories(), - backupExtensionInfo(databaseManga), - backupSavedSearches() - ) + backup = + Backup( + backupManga(databaseManga, flags), + backupCategories(), + backupExtensionInfo(databaseManga), + backupSavedSearches() + ) } try { - val file: UniFile = ( - if (isJob) { - // Get dir of file and create - var dir = UniFile.fromUri(context, uri) - dir = dir.createDirectory("automatic") - - // Delete older backups - val numberOfBackups = numberOfBackups() - val oldBackupRegex = Regex("""tachiyomi_full_\d+-\d+-\d+_\d+-\d+.proto.gz""") - val newBackupRegex = Regex("""tachiyomi_full_\d+-\d+-\d+_\d+-\d+.tachibk""") - dir.listFiles { _, filename -> (oldBackupRegex.matches(filename) || newBackupRegex.matches(filename)) } - .orEmpty() - .sortedByDescending { it.name } - .drop(numberOfBackups - 1) - .forEach { it.delete() } - - // Create new file to place backup - dir.createFile(BackupFull.getDefaultFilename()) - } else { - UniFile.fromUri(context, uri) - } - ) - ?: throw Exception("Couldn't create backup file") + val file: UniFile = + ( + if (isJob) { + // Get dir of file and create + var dir = UniFile.fromUri(context, uri) + dir = dir.createDirectory("automatic") + + // Delete older backups + val numberOfBackups = numberOfBackups() + val oldBackupRegex = Regex("""tachiyomi_full_\d+-\d+-\d+_\d+-\d+.proto.gz""") + val newBackupRegex = Regex("""tachiyomi_full_\d+-\d+-\d+_\d+-\d+.tachibk""") + dir.listFiles { _, filename -> (oldBackupRegex.matches(filename) || newBackupRegex.matches(filename)) } + .orEmpty() + .sortedByDescending { it.name } + .drop(numberOfBackups - 1) + .forEach { it.delete() } + + // Create new file to place backup + dir.createFile(BackupFull.getDefaultFilename()) + } else { + UniFile.fromUri(context, uri) + } + ) + ?: throw Exception("Couldn't create backup file") val byteArray = parser.encodeToByteArray(BackupSerializer, backup!!) file.openOutputStream().sink().gzip().buffer().use { it.write(byteArray) } @@ -109,7 +114,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { } } - private fun backupManga(mangas: List, flags: Int): List { + private fun backupManga( + mangas: List, + flags: Int + ): List { return mangas.map { backupMangaObject(it, flags) } @@ -137,6 +145,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { } // SY --> + /** * Backup the saved searches from sources * @@ -163,7 +172,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @param options options for the backup * @return [BackupManga] containing manga in a serializable form */ - private fun backupMangaObject(manga: Manga, options: Int): BackupManga { + private fun backupMangaObject( + manga: Manga, + options: Int + ): BackupManga { // Entry for this manga val mangaObject = BackupManga.copyFrom(manga) @@ -208,10 +220,11 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) { val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking() if (historyForManga.isNotEmpty()) { - val history = historyForManga.mapNotNull { history -> - val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url - url?.let { BackupHistory(url, history.last_read) } - } + val history = + historyForManga.mapNotNull { history -> + val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url + url?.let { BackupHistory(url, history.last_read) } + } if (history.isNotEmpty()) { mangaObject.history = history } @@ -221,7 +234,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { return mangaObject } - fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) { + fun restoreMangaNoFetch( + manga: Manga, + dbManga: Manga + ) { manga.id = dbManga.id manga.copyFrom(dbManga) insertManga(manga) @@ -234,7 +250,11 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @param manga manga that needs updating * @return [Observable] that contains manga */ - fun restoreMangaFetchObservable(source: Source?, manga: Manga, online: Boolean): Observable { + fun restoreMangaFetchObservable( + source: Source?, + manga: Manga, + online: Boolean + ): Observable { return if (online && source != null) { return runAsObservable({ val networkManga = source.getMangaDetails(manga.toMangaInfo()) @@ -293,7 +313,11 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @param manga the manga whose categories have to be restored. * @param categories the categories to restore. */ - internal fun restoreCategoriesForManga(manga: Manga, categories: List, backupCategories: List) { + internal fun restoreCategoriesForManga( + manga: Manga, + categories: List, + backupCategories: List + ) { val dbCategories = databaseHelper.getCategories().executeAsBlocking() val mangaCategoriesToUpdate = mutableListOf() categories.forEach { backupCategoryOrder -> @@ -334,9 +358,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { } else { // If not in database create databaseHelper.getChapter(url).executeAsBlocking()?.let { - val historyToAdd = History.create(it).apply { - last_read = lastRead - } + val historyToAdd = + History.create(it).apply { + last_read = lastRead + } historyToBeUpdated.add(historyToAdd) } } @@ -350,7 +375,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @param manga the manga whose sync have to be restored. * @param tracks the track list to restore. */ - internal fun restoreTrackForManga(manga: Manga, tracks: List) { + internal fun restoreTrackForManga( + manga: Manga, + tracks: List + ) { // Fix foreign keys with the current manga id tracks.map { it.manga_id = manga.id!! } @@ -397,7 +425,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * @param chapters list containing chapters that get restored * @return boolean answering if chapter fetch is not needed */ - internal fun restoreChaptersForManga(manga: Manga, chapters: List): Boolean { + internal fun restoreChaptersForManga( + manga: Manga, + chapters: List + ): Boolean { val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking() // Return if fetch is needed @@ -430,7 +461,10 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { return true } - internal fun restoreChaptersForMangaOffline(manga: Manga, chapters: List) { + internal fun restoreChaptersForMangaOffline( + manga: Manga, + chapters: List + ) { val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking() chapters.forEach { chapter -> @@ -458,36 +492,44 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { // SY --> internal fun restoreSavedSearches(backupSavedSearches: List) { - val currentSavedSearches = preferences.eh_savedSearches().get().map { - val sourceId = it.substringBefore(':').toLong() - val content = Json.decodeFromString(it.substringAfter(':')) - BackupSavedSearch( - content.name, - content.query, - content.filters.toString(), - sourceId - ) - } + val currentSavedSearches = + preferences.eh_savedSearches().get().map { + val sourceId = it.substringBefore(':').toLong() + val content = Json.decodeFromString(it.substringAfter(':')) + BackupSavedSearch( + content.name, + content.query, + content.filters.toString(), + sourceId + ) + } preferences.eh_savedSearches() .set( ( - backupSavedSearches.filter { backupSavedSearch -> currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } } + backupSavedSearches.filter { + backupSavedSearch -> + currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } + } .map { - "${it.source}:" + Json.encodeToString( - JsonSavedSearch( - it.name, - it.query, - Json.decodeFromString(it.filterList) + "${it.source}:" + + Json.encodeToString( + JsonSavedSearch( + it.name, + it.query, + Json.decodeFromString(it.filterList) + ) ) - ) } + preferences.eh_savedSearches().get() ) .toSet() ) } - internal fun restoreFlatMetadata(manga: Manga, backupFlatMetadata: BackupFlatMetadata) { + internal fun restoreFlatMetadata( + manga: Manga, + backupFlatMetadata: BackupFlatMetadata + ) { manga.id?.let { mangaId -> databaseHelper.getFlatMetadataForManga(mangaId).executeAsBlocking().let { if (it == null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt index e959c2c8444f..c5dc0f34da59 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt @@ -16,16 +16,19 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.source.Source import exh.EXHMigrations -import java.util.Date import kotlinx.serialization.ExperimentalSerializationApi import okio.buffer import okio.gzip import okio.source import rx.Observable +import java.util.Date @OptIn(ExperimentalSerializationApi::class) -class FullBackupRestore(context: Context, notifier: BackupNotifier, private val online: Boolean) : AbstractBackupRestore(context, notifier) { - +class FullBackupRestore( + context: Context, + notifier: BackupNotifier, + private val online: Boolean +) : AbstractBackupRestore(context, notifier) { override fun performRestore(uri: Uri): Boolean { // SY --> throttleManager.resetThrottle() @@ -81,7 +84,11 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val } // SY <-- - private fun restoreManga(backupManga: BackupManga, backupCategories: List, online: Boolean) { + private fun restoreManga( + backupManga: BackupManga, + backupCategories: List, + online: Boolean + ) { var manga = backupManga.getMangaImpl() val chapters = backupManga.getChaptersImpl() val categories = backupManga.categories @@ -221,7 +228,14 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val .subscribe() } - private fun restoreExtraForManga(manga: Manga, categories: List, history: List, tracks: List, backupCategories: List, flatMetadata: BackupFlatMetadata?) { + private fun restoreExtraForManga( + manga: Manga, + categories: List, + history: List, + tracks: List, + backupCategories: List, + flatMetadata: BackupFlatMetadata? + ) { // Restore categories backupManager.restoreCategoriesForManga(manga, categories, backupCategories) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestoreValidator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestoreValidator.kt index e482196ba9de..c69f8b7ca9f6 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestoreValidator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestoreValidator.kt @@ -18,7 +18,10 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() { * @throws Exception if manga cannot be found. * @return List of missing sources or missing trackers. */ - override fun validate(context: Context, uri: Uri): Results { + override fun validate( + context: Context, + uri: Uri + ): Results { val backupManager = FullBackupManager(context) val backupString = context.contentResolver.openInputStream(uri)!!.source().gzip().buffer().use { it.readByteArray() } @@ -29,20 +32,23 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() { } val sources = backup.backupSources.map { it.sourceId to it.name }.toMap() - val missingSources = sources - .filter { sourceManager.get(it.key) == null } - .values - .sorted() + val missingSources = + sources + .filter { sourceManager.get(it.key) == null } + .values + .sorted() - val trackers = backup.backupManga - .flatMap { it.tracking } - .map { it.syncId } - .distinct() - val missingTrackers = trackers - .mapNotNull { trackManager.getService(it) } - .filter { !it.isLogged } - .map { it.name } - .sorted() + val trackers = + backup.backupManga + .flatMap { it.tracking } + .map { it.syncId } + .distinct() + val missingTrackers = + trackers + .mapNotNull { trackManager.getService(it) } + .filter { !it.isLogged } + .map { it.name } + .sorted() return Results(missingSources, missingTrackers) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupTracking.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupTracking.kt index 07da8ea72541..8af9ebd3bdfb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupTracking.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupTracking.kt @@ -27,7 +27,6 @@ data class BackupTracking( @ProtoNumber(10) var startedReadingDate: Long = 0, // finishedReadingDate is called endReadTime in 1.x @ProtoNumber(11) var finishedReadingDate: Long = 0 - ) { fun getTrackingImpl(): TrackImpl { return TrackImpl().apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt index 06611fd530b7..8deaf8fcbafc 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt @@ -52,8 +52,6 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.util.lang.runAsObservable import exh.savedsearches.JsonSavedSearch -import java.lang.RuntimeException -import kotlin.math.max import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -61,9 +59,10 @@ import rx.Observable import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.lang.RuntimeException +import kotlin.math.max class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : AbstractBackupManager(context) { - var parserVersion: Int = version private set @@ -79,17 +78,18 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab parser = initParser() } - private fun initParser(): Gson = when (parserVersion) { - 2 -> - GsonBuilder() - .registerTypeAdapter(MangaTypeAdapter.build()) - .registerTypeHierarchyAdapter(ChapterTypeAdapter.build()) - .registerTypeAdapter(CategoryTypeAdapter.build()) - .registerTypeAdapter(HistoryTypeAdapter.build()) - .registerTypeHierarchyAdapter(TrackTypeAdapter.build()) - .create() - else -> throw Exception("Unknown backup version") - } + private fun initParser(): Gson = + when (parserVersion) { + 2 -> + GsonBuilder() + .registerTypeAdapter(MangaTypeAdapter.build()) + .registerTypeHierarchyAdapter(ChapterTypeAdapter.build()) + .registerTypeAdapter(CategoryTypeAdapter.build()) + .registerTypeAdapter(HistoryTypeAdapter.build()) + .registerTypeHierarchyAdapter(TrackTypeAdapter.build()) + .create() + else -> throw Exception("Unknown backup version") + } /** * Create backup Json file from database @@ -97,7 +97,11 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab * @param uri path of Uri * @param isJob backup called from job */ - override fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? { + override fun createBackup( + uri: Uri, + flags: Int, + isJob: Boolean + ): String? { // Create root object val root = JsonObject() @@ -148,28 +152,29 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab } try { - val file: UniFile = ( - if (isJob) { - // Get dir of file and create - var dir = UniFile.fromUri(context, uri) - dir = dir.createDirectory("automatic") - - // Delete older backups - val numberOfBackups = numberOfBackups() - val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""") - dir.listFiles { _, filename -> backupRegex.matches(filename) } - .orEmpty() - .sortedByDescending { it.name } - .drop(numberOfBackups - 1) - .forEach { it.delete() } - - // Create new file to place backup - dir.createFile(Backup.getDefaultFilename()) - } else { - UniFile.fromUri(context, uri) - } - ) - ?: throw Exception("Couldn't create backup file") + val file: UniFile = + ( + if (isJob) { + // Get dir of file and create + var dir = UniFile.fromUri(context, uri) + dir = dir.createDirectory("automatic") + + // Delete older backups + val numberOfBackups = numberOfBackups() + val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""") + dir.listFiles { _, filename -> backupRegex.matches(filename) } + .orEmpty() + .sortedByDescending { it.name } + .drop(numberOfBackups - 1) + .forEach { it.delete() } + + // Create new file to place backup + dir.createFile(Backup.getDefaultFilename()) + } else { + UniFile.fromUri(context, uri) + } + ) + ?: throw Exception("Couldn't create backup file") file.openOutputStream().bufferedWriter().use { parser.toJson(root, it) @@ -181,7 +186,10 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab } } - private fun backupExtensionInfo(root: JsonArray, extensions: Set) { + private fun backupExtensionInfo( + root: JsonArray, + extensions: Set + ) { extensions.sorted().forEach { root.add(it) } @@ -203,7 +211,10 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab * @param manga manga that gets converted * @return [JsonElement] containing manga information */ - internal fun backupMangaObject(manga: Manga, options: Int): JsonElement { + internal fun backupMangaObject( + manga: Manga, + options: Int + ): JsonElement { // Entry for this manga val entry = JsonObject() @@ -244,10 +255,11 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) { val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking() if (historyForManga.isNotEmpty()) { - val historyData = historyForManga.mapNotNull { history -> - val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url - url?.let { DHistory(url, history.last_read) } - } + val historyData = + historyForManga.mapNotNull { history -> + val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url + url?.let { DHistory(url, history.last_read) } + } val historyJson = parser.toJsonTree(historyData) if (historyJson.asJsonArray.size() > 0) { entry[HISTORY] = historyJson @@ -258,7 +270,10 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab return entry } - fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) { + fun restoreMangaNoFetch( + manga: Manga, + dbManga: Manga + ) { manga.id = dbManga.id manga.copyFrom(dbManga) manga.favorite = true @@ -272,7 +287,10 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab * @param manga manga that needs updating * @return [Observable] that contains manga */ - fun restoreMangaFetchObservable(source: Source, manga: Manga): Observable { + fun restoreMangaFetchObservable( + source: Source, + manga: Manga + ): Observable { return runAsObservable({ val networkManga = source.getMangaDetails(manga.toMangaInfo()) manga.copyFrom(networkManga.toSManga()) @@ -323,7 +341,10 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab * @param manga the manga whose categories have to be restored. * @param categories the categories to restore. */ - internal fun restoreCategoriesForManga(manga: Manga, categories: List) { + internal fun restoreCategoriesForManga( + manga: Manga, + categories: List + ) { val dbCategories = databaseHelper.getCategories().executeAsBlocking() val mangaCategoriesToUpdate = mutableListOf() for (backupCategoryStr in categories) { @@ -361,9 +382,10 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab } else { // If not in database create databaseHelper.getChapter(url).executeAsBlocking()?.let { - val historyToAdd = History.create(it).apply { - last_read = lastRead - } + val historyToAdd = + History.create(it).apply { + last_read = lastRead + } historyToBeUpdated.add(historyToAdd) } } @@ -377,7 +399,10 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab * @param manga the manga whose sync have to be restored. * @param tracks the track list to restore. */ - internal fun restoreTrackForManga(manga: Manga, tracks: List) { + internal fun restoreTrackForManga( + manga: Manga, + tracks: List + ) { // Fix foreign keys with the current manga id tracks.map { it.manga_id = manga.id!! } @@ -424,7 +449,10 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab * @param chapters list containing chapters that get restored * @return boolean answering if chapter fetch is not needed */ - internal fun restoreChaptersForManga(manga: Manga, chapters: List): Boolean { + internal fun restoreChaptersForManga( + manga: Manga, + chapters: List + ): Boolean { val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking() // Return if fetch is needed @@ -453,43 +481,47 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab internal fun restoreSavedSearches(jsonSavedSearches: JsonElement) { val backupSavedSearches = jsonSavedSearches.asString.split("***").toSet() - val newSavedSearches = backupSavedSearches.mapNotNull { - try { - val id = it.substringBefore(':').toLong() - val content = Json.decodeFromString(it.substringAfter(':')) - id to content - } catch (t: RuntimeException) { - // Load failed - Timber.e(t, "Failed to load saved search!") - t.printStackTrace() - null - } - }.toMutableList() + val newSavedSearches = + backupSavedSearches.mapNotNull { + try { + val id = it.substringBefore(':').toLong() + val content = Json.decodeFromString(it.substringAfter(':')) + id to content + } catch (t: RuntimeException) { + // Load failed + Timber.e(t, "Failed to load saved search!") + t.printStackTrace() + null + } + }.toMutableList() val currentSources = newSavedSearches.map { it.first }.toSet() - newSavedSearches += preferences.eh_savedSearches().get().mapNotNull { - try { - val id = it.substringBefore(':').toLong() - val content = Json.decodeFromString(it.substringAfter(':')) - id to content - } catch (t: RuntimeException) { - // Load failed - Timber.e(t, "Failed to load saved search!") - t.printStackTrace() - null - } - }.toMutableList() + newSavedSearches += + preferences.eh_savedSearches().get().mapNotNull { + try { + val id = it.substringBefore(':').toLong() + val content = Json.decodeFromString(it.substringAfter(':')) + id to content + } catch (t: RuntimeException) { + // Load failed + Timber.e(t, "Failed to load saved search!") + t.printStackTrace() + null + } + }.toMutableList() - val otherSerialized = preferences.eh_savedSearches().get().mapNotNull { - val sourceId = it.split(":")[0].toLongOrNull() ?: return@mapNotNull null - if (sourceId in currentSources) return@mapNotNull null - it - } + val otherSerialized = + preferences.eh_savedSearches().get().mapNotNull { + val sourceId = it.split(":")[0].toLongOrNull() ?: return@mapNotNull null + if (sourceId in currentSources) return@mapNotNull null + it + } - val newSerialized = newSavedSearches.map { - "${it.first}:" + Json.encodeToString(it.second) - } + val newSerialized = + newSavedSearches.map { + "${it.first}:" + Json.encodeToString(it.second) + } preferences.eh_savedSearches().set((otherSerialized + newSerialized).toSet()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt index 9f07c9a77a67..47c8e95754cd 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt @@ -22,11 +22,10 @@ import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.TrackImpl import eu.kanade.tachiyomi.source.Source import exh.EXHMigrations -import java.util.Date import rx.Observable +import java.util.Date class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBackupRestore(context, notifier) { - override fun performRestore(uri: Uri): Boolean { // SY --> throttleManager.resetThrottle() @@ -81,27 +80,33 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract // SY <-- private fun restoreManga(mangaJson: JsonObject) { - /* SY --> */ var /* SY <-- */ manga = backupManager.parser.fromJson( - mangaJson.get( - Backup.MANGA + // SY --> + var /* SY <-- */ manga = + backupManager.parser.fromJson( + mangaJson.get( + Backup.MANGA + ) + ) + val chapters = + backupManager.parser.fromJson>( + mangaJson.get(Backup.CHAPTERS) + ?: JsonArray() + ) + val categories = + backupManager.parser.fromJson>( + mangaJson.get(Backup.CATEGORIES) + ?: JsonArray() + ) + val history = + backupManager.parser.fromJson>( + mangaJson.get(Backup.HISTORY) + ?: JsonArray() + ) + val tracks = + backupManager.parser.fromJson>( + mangaJson.get(Backup.TRACK) + ?: JsonArray() ) - ) - val chapters = backupManager.parser.fromJson>( - mangaJson.get(Backup.CHAPTERS) - ?: JsonArray() - ) - val categories = backupManager.parser.fromJson>( - mangaJson.get(Backup.CATEGORIES) - ?: JsonArray() - ) - val history = backupManager.parser.fromJson>( - mangaJson.get(Backup.HISTORY) - ?: JsonArray() - ) - val tracks = backupManager.parser.fromJson>( - mangaJson.get(Backup.TRACK) - ?: JsonArray() - ) // EXH --> manga = EXHMigrations.migrateBackupEntry(manga) @@ -217,7 +222,12 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract .subscribe() } - private fun restoreExtraForManga(manga: Manga, categories: List, history: List, tracks: List) { + private fun restoreExtraForManga( + manga: Manga, + categories: List, + history: List, + tracks: List + ) { // Restore categories backupManager.restoreCategoriesForManga(manga, categories) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestoreValidator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestoreValidator.kt index f1b7cbf12dca..db815845871d 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestoreValidator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestoreValidator.kt @@ -16,7 +16,10 @@ class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() { * @throws Exception if version or manga cannot be found. * @return List of missing sources or missing trackers. */ - override fun validate(context: Context, uri: Uri): Results { + override fun validate( + context: Context, + uri: Uri + ): Results { val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader()) val json = JsonParser.parseReader(reader).asJsonObject @@ -32,21 +35,24 @@ class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() { } val sources = getSourceMapping(json) - val missingSources = sources - .filter { sourceManager.get(it.key) == null } - .values - .sorted() + val missingSources = + sources + .filter { sourceManager.get(it.key) == null } + .values + .sorted() - val trackers = mangas - .filter { it.asJsonObject.has("track") } - .flatMap { it.asJsonObject["track"].asJsonArray } - .map { it.asJsonObject["s"].asInt } - .distinct() - val missingTrackers = trackers - .mapNotNull { trackManager.getService(it) } - .filter { !it.isLogged } - .map { it.name } - .sorted() + val trackers = + mangas + .filter { it.asJsonObject.has("track") } + .flatMap { it.asJsonObject["track"].asJsonArray } + .map { it.asJsonObject["s"].asInt } + .distinct() + val missingTrackers = + trackers + .mapNotNull { trackManager.getService(it) } + .filter { !it.isLogged } + .map { it.name } + .sorted() return Results(missingSources, missingTrackers) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/CategoryTypeAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/CategoryTypeAdapter.kt index d346af19cffd..54986dae5be7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/CategoryTypeAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/CategoryTypeAdapter.kt @@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.data.database.models.CategoryImpl * JSON Serializer used to write / read [CategoryImpl] to / from json */ object CategoryTypeAdapter { - fun build(): TypeAdapter { return typeAdapter { write { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/ChapterTypeAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/ChapterTypeAdapter.kt index cacc8cb25b5d..f582bed8be0c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/ChapterTypeAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/ChapterTypeAdapter.kt @@ -9,7 +9,6 @@ import eu.kanade.tachiyomi.data.database.models.ChapterImpl * JSON Serializer used to write / read [ChapterImpl] to / from json */ object ChapterTypeAdapter { - private const val URL = "u" private const val READ = "r" private const val BOOKMARK = "b" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/HistoryTypeAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/HistoryTypeAdapter.kt index 4f7d5d9ff1b5..fb613abef525 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/HistoryTypeAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/HistoryTypeAdapter.kt @@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory * JSON Serializer used to write / read [DHistory] to / from json */ object HistoryTypeAdapter { - fun build(): TypeAdapter { return typeAdapter { write { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/MangaTypeAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/MangaTypeAdapter.kt index b902cbb5b721..c043b8411614 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/MangaTypeAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/MangaTypeAdapter.kt @@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.data.database.models.MangaImpl * JSON Serializer used to write / read [MangaImpl] to / from json */ object MangaTypeAdapter { - fun build(): TypeAdapter { return typeAdapter { write { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/TrackTypeAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/TrackTypeAdapter.kt index 84c0cd829d9b..c259a817dd3f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/TrackTypeAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/serializer/TrackTypeAdapter.kt @@ -9,7 +9,6 @@ import eu.kanade.tachiyomi.data.database.models.TrackImpl * JSON Serializer used to write / read [TrackImpl] to / from json */ object TrackTypeAdapter { - private const val SYNC = "s" private const val MEDIA = "r" private const val LIBRARY = "ml" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt index d83a73b3dcfb..1f97a7854d4a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt @@ -10,8 +10,6 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.saveTo -import java.io.File -import java.io.IOException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -22,6 +20,8 @@ import okio.buffer import okio.sink import rx.Observable import uy.kohesive.injekt.injectLazy +import java.io.File +import java.io.IOException /** * Class used to create chapter cache @@ -145,7 +145,10 @@ class ChapterCache(private val context: Context) { * @param chapter the chapter. * @param pages list of pages. */ - fun putPageListToCache(chapter: Chapter, pages: List) { + fun putPageListToCache( + chapter: Chapter, + pages: List + ) { // Convert list of pages to json string. val cachedValue = gson.toJson(pages) @@ -207,7 +210,10 @@ class ChapterCache(private val context: Context) { * @throws IOException image error. */ @Throws(IOException::class) - fun putImageToCache(imageUrl: String, response: Response) { + fun putImageToCache( + imageUrl: String, + response: Response + ) { // Initialize editor (edits the values for an entry). var editor: DiskLruCache.Editor? = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt index 4d1a006070f5..a574092434d5 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt @@ -17,7 +17,6 @@ import java.io.InputStream * @constructor creates an instance of the cover cache. */ class CoverCache(private val context: Context) { - companion object { private const val COVERS_DIR = "covers" private const val CUSTOM_COVERS_DIR = "covers/custom" @@ -60,7 +59,10 @@ class CoverCache(private val context: Context) { * @throws IOException if there's any error. */ @Throws(IOException::class) - fun setCustomCoverToCache(manga: Manga, inputStream: InputStream) { + fun setCustomCoverToCache( + manga: Manga, + inputStream: InputStream + ) { getCustomCoverFile(manga).outputStream().use { inputStream.copyTo(it) } @@ -73,7 +75,10 @@ class CoverCache(private val context: Context) { * @param deleteCustomCover whether the custom cover should be deleted. * @return number of files that were deleted. */ - fun deleteFromCache(manga: Manga, deleteCustomCover: Boolean = false): Int { + fun deleteFromCache( + manga: Manga, + deleteCustomCover: Boolean = false + ): Int { var deleted = 0 getCoverFile(manga)?.let { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseAdapter.kt index 2ae54be1ff4b..f10b694f7af5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseAdapter.kt @@ -3,30 +3,34 @@ package eu.kanade.tachiyomi.data.database import eu.kanade.tachiyomi.source.model.UpdateStrategy import java.util.Date -val dateAdapter = object : ColumnAdapter { - override fun decode(databaseValue: Long): Date = Date(databaseValue) - override fun encode(value: Date): Long = value.time -} - -private const val listOfStringsSeparator = ", " -val listOfStringsAdapter = object : ColumnAdapter, String> { - override fun decode(databaseValue: String) = - if (databaseValue.isEmpty()) { - emptyList() - } else { - databaseValue.split(listOfStringsSeparator) - } - override fun encode(value: List) = value.joinToString(separator = listOfStringsSeparator) -} +val dateAdapter = + object : ColumnAdapter { + override fun decode(databaseValue: Long): Date = Date(databaseValue) -val updateStrategyAdapter = object : ColumnAdapter { - private val enumValues by lazy { UpdateStrategy.values() } + override fun encode(value: Date): Long = value.time + } - override fun decode(databaseValue: Int): UpdateStrategy = - enumValues.getOrElse(databaseValue) { UpdateStrategy.ALWAYS_UPDATE } - - override fun encode(value: UpdateStrategy): Int = value.ordinal -} +private const val listOfStringsSeparator = ", " +val listOfStringsAdapter = + object : ColumnAdapter, String> { + override fun decode(databaseValue: String) = + if (databaseValue.isEmpty()) { + emptyList() + } else { + databaseValue.split(listOfStringsSeparator) + } + + override fun encode(value: List) = value.joinToString(separator = listOfStringsSeparator) + } + +val updateStrategyAdapter = + object : ColumnAdapter { + private val enumValues by lazy { UpdateStrategy.values() } + + override fun decode(databaseValue: Int): UpdateStrategy = enumValues.getOrElse(databaseValue) { UpdateStrategy.ALWAYS_UPDATE } + + override fun encode(value: UpdateStrategy): Int = value.ordinal + } interface ColumnAdapter { /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt index f5fbcc758ad1..b5b133eeab5f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt @@ -45,24 +45,25 @@ open class DatabaseHelper(context: Context) : SearchMetadataQueries, SearchTagQueries, SearchTitleQueries { + private val configuration = + SupportSQLiteOpenHelper.Configuration.builder(context) + .name(DbOpenCallback.DATABASE_NAME) + .callback(DbOpenCallback()) + .build() - private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context) - .name(DbOpenCallback.DATABASE_NAME) - .callback(DbOpenCallback()) - .build() - - override val db = DefaultStorIOSQLite.builder() - .sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration)) - .addTypeMapping(Manga::class.java, MangaTypeMapping()) - .addTypeMapping(Chapter::class.java, ChapterTypeMapping()) - .addTypeMapping(Track::class.java, TrackTypeMapping()) - .addTypeMapping(Category::class.java, CategoryTypeMapping()) - .addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping()) - .addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping()) - .addTypeMapping(History::class.java, HistoryTypeMapping()) - .addTypeMapping(SearchTag::class.java, SearchTagTypeMapping()) - .addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping()) - .build() + override val db = + DefaultStorIOSQLite.builder() + .sqliteOpenHelper(RequerySQLiteOpenHelperFactory().create(configuration)) + .addTypeMapping(Manga::class.java, MangaTypeMapping()) + .addTypeMapping(Chapter::class.java, ChapterTypeMapping()) + .addTypeMapping(Track::class.java, TrackTypeMapping()) + .addTypeMapping(Category::class.java, CategoryTypeMapping()) + .addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping()) + .addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping()) + .addTypeMapping(History::class.java, HistoryTypeMapping()) + .addTypeMapping(SearchTag::class.java, SearchTagTypeMapping()) + .addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping()) + .build() inline fun inTransaction(block: () -> Unit) = db.inTransaction(block) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt index 6184589e1c68..ac104d37e0e9 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt @@ -14,7 +14,6 @@ import exh.metadata.sql.tables.SearchTagTable import exh.metadata.sql.tables.SearchTitleTable class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) { - companion object { /** * Name of the database file. @@ -27,42 +26,47 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) { const val DATABASE_VERSION = 15 // [EXH + J2K DRAGNDROP + AZ MERGEDSOURES + DEV DATESORT + 1.4 EXTLIB] } - override fun onCreate(db: SupportSQLiteDatabase) = with(db) { - execSQL(MangaTable.createTableQuery) - execSQL(ChapterTable.createTableQuery) - execSQL(TrackTable.createTableQuery) - execSQL(CategoryTable.createTableQuery) - execSQL(MangaCategoryTable.createTableQuery) - execSQL(HistoryTable.createTableQuery) - // EXH --> - execSQL(SearchMetadataTable.createTableQuery) - execSQL(SearchTagTable.createTableQuery) - execSQL(SearchTitleTable.createTableQuery) - // EXH <-- - // AZ --> - execSQL(MergedTable.createTableQuery) - // AZ <-- + override fun onCreate(db: SupportSQLiteDatabase) = + with(db) { + execSQL(MangaTable.createTableQuery) + execSQL(ChapterTable.createTableQuery) + execSQL(TrackTable.createTableQuery) + execSQL(CategoryTable.createTableQuery) + execSQL(MangaCategoryTable.createTableQuery) + execSQL(HistoryTable.createTableQuery) + // EXH --> + execSQL(SearchMetadataTable.createTableQuery) + execSQL(SearchTagTable.createTableQuery) + execSQL(SearchTitleTable.createTableQuery) + // EXH <-- + // AZ --> + execSQL(MergedTable.createTableQuery) + // AZ <-- - // DB indexes - execSQL(MangaTable.createUrlIndexQuery) - execSQL(MangaTable.createLibraryIndexQuery) - execSQL(ChapterTable.createMangaIdIndexQuery) - execSQL(ChapterTable.createUnreadChaptersIndexQuery) - execSQL(HistoryTable.createChapterIdIndexQuery) - // EXH --> - db.execSQL(SearchMetadataTable.createUploaderIndexQuery) - db.execSQL(SearchMetadataTable.createIndexedExtraIndexQuery) - db.execSQL(SearchTagTable.createMangaIdIndexQuery) - db.execSQL(SearchTagTable.createNamespaceNameIndexQuery) - db.execSQL(SearchTitleTable.createMangaIdIndexQuery) - db.execSQL(SearchTitleTable.createTitleIndexQuery) - // EXH <-- - // AZ --> - execSQL(MergedTable.createIndexQuery) - // AZ <-- - } + // DB indexes + execSQL(MangaTable.createUrlIndexQuery) + execSQL(MangaTable.createLibraryIndexQuery) + execSQL(ChapterTable.createMangaIdIndexQuery) + execSQL(ChapterTable.createUnreadChaptersIndexQuery) + execSQL(HistoryTable.createChapterIdIndexQuery) + // EXH --> + db.execSQL(SearchMetadataTable.createUploaderIndexQuery) + db.execSQL(SearchMetadataTable.createIndexedExtraIndexQuery) + db.execSQL(SearchTagTable.createMangaIdIndexQuery) + db.execSQL(SearchTagTable.createNamespaceNameIndexQuery) + db.execSQL(SearchTitleTable.createMangaIdIndexQuery) + db.execSQL(SearchTitleTable.createTitleIndexQuery) + // EXH <-- + // AZ --> + execSQL(MergedTable.createIndexQuery) + // AZ <-- + } - override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { + override fun onUpgrade( + db: SupportSQLiteDatabase, + oldVersion: Int, + newVersion: Int + ) { if (oldVersion < 2) { db.execSQL(ChapterTable.sourceOrderUpdateQuery) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbProvider.kt index 4609852b968a..ff42ff75b6c7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbProvider.kt @@ -3,6 +3,5 @@ package eu.kanade.tachiyomi.data.database import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite interface DbProvider { - val db: DefaultStorIOSQLite } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt index 0434a8376fff..14b7c6ade9de 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/CategoryTypeMapping.kt @@ -25,45 +25,47 @@ class CategoryTypeMapping : SQLiteTypeMapping( ) class CategoryPutResolver : DefaultPutResolver() { + override fun mapToInsertQuery(obj: Category) = + InsertQuery.builder() + .table(TABLE) + .build() - override fun mapToInsertQuery(obj: Category) = InsertQuery.builder() - .table(TABLE) - .build() + override fun mapToUpdateQuery(obj: Category) = + UpdateQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() - override fun mapToUpdateQuery(obj: Category) = UpdateQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() - - override fun mapToContentValues(obj: Category) = ContentValues(4).apply { - put(COL_ID, obj.id) - put(COL_NAME, obj.name) - put(COL_ORDER, obj.order) - put(COL_FLAGS, obj.flags) - val orderString = obj.mangaOrder.joinToString("/") - put(COL_MANGA_ORDER, orderString) - } + override fun mapToContentValues(obj: Category) = + ContentValues(4).apply { + put(COL_ID, obj.id) + put(COL_NAME, obj.name) + put(COL_ORDER, obj.order) + put(COL_FLAGS, obj.flags) + val orderString = obj.mangaOrder.joinToString("/") + put(COL_MANGA_ORDER, orderString) + } } class CategoryGetResolver : DefaultGetResolver() { + override fun mapFromCursor(cursor: Cursor): Category = + CategoryImpl().apply { + id = cursor.getInt(cursor.getColumnIndex(COL_ID)) + name = cursor.getString(cursor.getColumnIndex(COL_NAME)) + order = cursor.getInt(cursor.getColumnIndex(COL_ORDER)) + flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS)) - override fun mapFromCursor(cursor: Cursor): Category = CategoryImpl().apply { - id = cursor.getInt(cursor.getColumnIndex(COL_ID)) - name = cursor.getString(cursor.getColumnIndex(COL_NAME)) - order = cursor.getInt(cursor.getColumnIndex(COL_ORDER)) - flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS)) - - val orderString = cursor.getString(cursor.getColumnIndex(COL_MANGA_ORDER)) - mangaOrder = orderString?.split("/")?.mapNotNull { it.toLongOrNull() } ?: emptyList() - } + val orderString = cursor.getString(cursor.getColumnIndex(COL_MANGA_ORDER)) + mangaOrder = orderString?.split("/")?.mapNotNull { it.toLongOrNull() } ?: emptyList() + } } class CategoryDeleteResolver : DefaultDeleteResolver() { - - override fun mapToDeleteQuery(obj: Category) = DeleteQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() + override fun mapToDeleteQuery(obj: Category) = + DeleteQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt index 9d5810e01269..1916acd6a81e 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt @@ -32,56 +32,58 @@ class ChapterTypeMapping : SQLiteTypeMapping( ) class ChapterPutResolver : DefaultPutResolver() { + override fun mapToInsertQuery(obj: Chapter) = + InsertQuery.builder() + .table(TABLE) + .build() - override fun mapToInsertQuery(obj: Chapter) = InsertQuery.builder() - .table(TABLE) - .build() + override fun mapToUpdateQuery(obj: Chapter) = + UpdateQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() - override fun mapToUpdateQuery(obj: Chapter) = UpdateQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() - - override fun mapToContentValues(obj: Chapter) = ContentValues(11).apply { - put(COL_ID, obj.id) - put(COL_MANGA_ID, obj.manga_id) - put(COL_URL, obj.url) - put(COL_NAME, obj.name) - put(COL_READ, obj.read) - put(COL_SCANLATOR, obj.scanlator) - put(COL_BOOKMARK, obj.bookmark) - put(COL_DATE_FETCH, obj.date_fetch) - put(COL_DATE_UPLOAD, obj.date_upload) - put(COL_LAST_PAGE_READ, obj.last_page_read) - put(COL_CHAPTER_NUMBER, obj.chapter_number) - put(COL_SOURCE_ORDER, obj.source_order) - } + override fun mapToContentValues(obj: Chapter) = + ContentValues(11).apply { + put(COL_ID, obj.id) + put(COL_MANGA_ID, obj.manga_id) + put(COL_URL, obj.url) + put(COL_NAME, obj.name) + put(COL_READ, obj.read) + put(COL_SCANLATOR, obj.scanlator) + put(COL_BOOKMARK, obj.bookmark) + put(COL_DATE_FETCH, obj.date_fetch) + put(COL_DATE_UPLOAD, obj.date_upload) + put(COL_LAST_PAGE_READ, obj.last_page_read) + put(COL_CHAPTER_NUMBER, obj.chapter_number) + put(COL_SOURCE_ORDER, obj.source_order) + } } class ChapterGetResolver : DefaultGetResolver() { - - override fun mapFromCursor(cursor: Cursor): Chapter = ChapterImpl().apply { - id = cursor.getLong(cursor.getColumnIndex(COL_ID)) - manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)) - url = cursor.getString(cursor.getColumnIndex(COL_URL)) - name = cursor.getString(cursor.getColumnIndex(COL_NAME)) - scanlator = cursor.getString(cursor.getColumnIndex(COL_SCANLATOR)) - read = cursor.getInt(cursor.getColumnIndex(COL_READ)) == 1 - bookmark = cursor.getInt(cursor.getColumnIndex(COL_BOOKMARK)) == 1 - date_fetch = cursor.getLong(cursor.getColumnIndex(COL_DATE_FETCH)) - date_upload = cursor.getLong(cursor.getColumnIndex(COL_DATE_UPLOAD)) - last_page_read = cursor.getInt(cursor.getColumnIndex(COL_LAST_PAGE_READ)) - chapter_number = cursor.getFloat(cursor.getColumnIndex(COL_CHAPTER_NUMBER)) - source_order = cursor.getInt(cursor.getColumnIndex(COL_SOURCE_ORDER)) - } + override fun mapFromCursor(cursor: Cursor): Chapter = + ChapterImpl().apply { + id = cursor.getLong(cursor.getColumnIndex(COL_ID)) + manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)) + url = cursor.getString(cursor.getColumnIndex(COL_URL)) + name = cursor.getString(cursor.getColumnIndex(COL_NAME)) + scanlator = cursor.getString(cursor.getColumnIndex(COL_SCANLATOR)) + read = cursor.getInt(cursor.getColumnIndex(COL_READ)) == 1 + bookmark = cursor.getInt(cursor.getColumnIndex(COL_BOOKMARK)) == 1 + date_fetch = cursor.getLong(cursor.getColumnIndex(COL_DATE_FETCH)) + date_upload = cursor.getLong(cursor.getColumnIndex(COL_DATE_UPLOAD)) + last_page_read = cursor.getInt(cursor.getColumnIndex(COL_LAST_PAGE_READ)) + chapter_number = cursor.getFloat(cursor.getColumnIndex(COL_CHAPTER_NUMBER)) + source_order = cursor.getInt(cursor.getColumnIndex(COL_SOURCE_ORDER)) + } } class ChapterDeleteResolver : DefaultDeleteResolver() { - - override fun mapToDeleteQuery(obj: Chapter) = DeleteQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() + override fun mapToDeleteQuery(obj: Chapter) = + DeleteQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt index e3da10169536..fe6fe2be19d0 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/HistoryTypeMapping.kt @@ -24,40 +24,42 @@ class HistoryTypeMapping : SQLiteTypeMapping( ) open class HistoryPutResolver : DefaultPutResolver() { + override fun mapToInsertQuery(obj: History) = + InsertQuery.builder() + .table(TABLE) + .build() - override fun mapToInsertQuery(obj: History) = InsertQuery.builder() - .table(TABLE) - .build() + override fun mapToUpdateQuery(obj: History) = + UpdateQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() - override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() - - override fun mapToContentValues(obj: History) = ContentValues(4).apply { - put(COL_ID, obj.id) - put(COL_CHAPTER_ID, obj.chapter_id) - put(COL_LAST_READ, obj.last_read) - put(COL_TIME_READ, obj.time_read) - } + override fun mapToContentValues(obj: History) = + ContentValues(4).apply { + put(COL_ID, obj.id) + put(COL_CHAPTER_ID, obj.chapter_id) + put(COL_LAST_READ, obj.last_read) + put(COL_TIME_READ, obj.time_read) + } } class HistoryGetResolver : DefaultGetResolver() { - - override fun mapFromCursor(cursor: Cursor): History = HistoryImpl().apply { - id = cursor.getLong(cursor.getColumnIndex(COL_ID)) - chapter_id = cursor.getLong(cursor.getColumnIndex(COL_CHAPTER_ID)) - last_read = cursor.getLong(cursor.getColumnIndex(COL_LAST_READ)) - time_read = cursor.getLong(cursor.getColumnIndex(COL_TIME_READ)) - } + override fun mapFromCursor(cursor: Cursor): History = + HistoryImpl().apply { + id = cursor.getLong(cursor.getColumnIndex(COL_ID)) + chapter_id = cursor.getLong(cursor.getColumnIndex(COL_CHAPTER_ID)) + last_read = cursor.getLong(cursor.getColumnIndex(COL_LAST_READ)) + time_read = cursor.getLong(cursor.getColumnIndex(COL_TIME_READ)) + } } class HistoryDeleteResolver : DefaultDeleteResolver() { - - override fun mapToDeleteQuery(obj: History) = DeleteQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() + override fun mapToDeleteQuery(obj: History) = + DeleteQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt index 24fff124cda3..eebf979c44f7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt @@ -22,38 +22,40 @@ class MangaCategoryTypeMapping : SQLiteTypeMapping( ) class MangaCategoryPutResolver : DefaultPutResolver() { - - override fun mapToInsertQuery(obj: MangaCategory) = InsertQuery.builder() - .table(TABLE) - .build() - - override fun mapToUpdateQuery(obj: MangaCategory) = UpdateQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() - - override fun mapToContentValues(obj: MangaCategory) = ContentValues(3).apply { - put(COL_ID, obj.id) - put(COL_MANGA_ID, obj.manga_id) - put(COL_CATEGORY_ID, obj.category_id) - } + override fun mapToInsertQuery(obj: MangaCategory) = + InsertQuery.builder() + .table(TABLE) + .build() + + override fun mapToUpdateQuery(obj: MangaCategory) = + UpdateQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() + + override fun mapToContentValues(obj: MangaCategory) = + ContentValues(3).apply { + put(COL_ID, obj.id) + put(COL_MANGA_ID, obj.manga_id) + put(COL_CATEGORY_ID, obj.category_id) + } } class MangaCategoryGetResolver : DefaultGetResolver() { - - override fun mapFromCursor(cursor: Cursor): MangaCategory = MangaCategory().apply { - id = cursor.getLong(cursor.getColumnIndex(COL_ID)) - manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)) - category_id = cursor.getInt(cursor.getColumnIndex(COL_CATEGORY_ID)) - } + override fun mapFromCursor(cursor: Cursor): MangaCategory = + MangaCategory().apply { + id = cursor.getLong(cursor.getColumnIndex(COL_ID)) + manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)) + category_id = cursor.getInt(cursor.getColumnIndex(COL_CATEGORY_ID)) + } } class MangaCategoryDeleteResolver : DefaultDeleteResolver() { - - override fun mapToDeleteQuery(obj: MangaCategory) = DeleteQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() + override fun mapToDeleteQuery(obj: MangaCategory) = + DeleteQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt index a39d7f844f96..fa4677d85815 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt @@ -39,41 +39,46 @@ class MangaTypeMapping : SQLiteTypeMapping( ) class MangaPutResolver : DefaultPutResolver() { + override fun mapToInsertQuery(obj: Manga) = + InsertQuery.builder() + .table(TABLE) + .build() - override fun mapToInsertQuery(obj: Manga) = InsertQuery.builder() - .table(TABLE) - .build() + override fun mapToUpdateQuery(obj: Manga) = + UpdateQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() - override fun mapToUpdateQuery(obj: Manga) = UpdateQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() - - override fun mapToContentValues(obj: Manga) = ContentValues(17).apply { - put(COL_ID, obj.id) - put(COL_SOURCE, obj.source) - put(COL_URL, obj.url) - put(COL_ARTIST, obj.artist) - put(COL_AUTHOR, obj.author) - put(COL_DESCRIPTION, obj.description) - put(COL_GENRE, obj.genre) - put(COL_TITLE, obj.title) - put(COL_STATUS, obj.status) - put(COL_THUMBNAIL_URL, obj.thumbnail_url) - put(COL_FAVORITE, obj.favorite) - put(COL_LAST_UPDATE, obj.last_update) - put(COL_INITIALIZED, obj.initialized) - put(COL_VIEWER, obj.viewer) - put(COL_CHAPTER_FLAGS, obj.chapter_flags) - put(COL_COVER_LAST_MODIFIED, obj.cover_last_modified) - put(COL_DATE_ADDED, obj.date_added) - put(COL_UPDATE_STRATEGY, obj.update_strategy.let(updateStrategyAdapter::encode)) - } + override fun mapToContentValues(obj: Manga) = + ContentValues(17).apply { + put(COL_ID, obj.id) + put(COL_SOURCE, obj.source) + put(COL_URL, obj.url) + put(COL_ARTIST, obj.artist) + put(COL_AUTHOR, obj.author) + put(COL_DESCRIPTION, obj.description) + put(COL_GENRE, obj.genre) + put(COL_TITLE, obj.title) + put(COL_STATUS, obj.status) + put(COL_THUMBNAIL_URL, obj.thumbnail_url) + put(COL_FAVORITE, obj.favorite) + put(COL_LAST_UPDATE, obj.last_update) + put(COL_INITIALIZED, obj.initialized) + put(COL_VIEWER, obj.viewer) + put(COL_CHAPTER_FLAGS, obj.chapter_flags) + put(COL_COVER_LAST_MODIFIED, obj.cover_last_modified) + put(COL_DATE_ADDED, obj.date_added) + put(COL_UPDATE_STRATEGY, obj.update_strategy.let(updateStrategyAdapter::encode)) + } } interface BaseMangaGetResolver { - fun mapBaseFromCursor(manga: Manga, cursor: Cursor) = manga.apply { + fun mapBaseFromCursor( + manga: Manga, + cursor: Cursor + ) = manga.apply { id = cursor.getLong(cursor.getColumnIndex(COL_ID)) source = cursor.getLong(cursor.getColumnIndex(COL_SOURCE)) url = cursor.getString(cursor.getColumnIndex(COL_URL)) @@ -91,24 +96,24 @@ interface BaseMangaGetResolver { chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS)) cover_last_modified = cursor.getLong(cursor.getColumnIndex(COL_COVER_LAST_MODIFIED)) date_added = cursor.getLong(cursor.getColumnIndex(COL_DATE_ADDED)) - update_strategy = cursor.getInt(cursor.getColumnIndex(COL_UPDATE_STRATEGY)).let( - updateStrategyAdapter::decode - ) + update_strategy = + cursor.getInt(cursor.getColumnIndex(COL_UPDATE_STRATEGY)).let( + updateStrategyAdapter::decode + ) } } open class MangaGetResolver : DefaultGetResolver(), BaseMangaGetResolver { - override fun mapFromCursor(cursor: Cursor): Manga { return mapBaseFromCursor(MangaImpl(), cursor) } } class MangaDeleteResolver : DefaultDeleteResolver() { - - override fun mapToDeleteQuery(obj: Manga) = DeleteQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() + override fun mapToDeleteQuery(obj: Manga) = + DeleteQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/TrackTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/TrackTypeMapping.kt index 94de567ad5e1..792cd2a0e39a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/TrackTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/TrackTypeMapping.kt @@ -33,58 +33,60 @@ class TrackTypeMapping : SQLiteTypeMapping( ) class TrackPutResolver : DefaultPutResolver() { + override fun mapToInsertQuery(obj: Track) = + InsertQuery.builder() + .table(TABLE) + .build() - override fun mapToInsertQuery(obj: Track) = InsertQuery.builder() - .table(TABLE) - .build() + override fun mapToUpdateQuery(obj: Track) = + UpdateQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() - override fun mapToUpdateQuery(obj: Track) = UpdateQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() - - override fun mapToContentValues(obj: Track) = ContentValues(10).apply { - put(COL_ID, obj.id) - put(COL_MANGA_ID, obj.manga_id) - put(COL_SYNC_ID, obj.sync_id) - put(COL_MEDIA_ID, obj.media_id) - put(COL_LIBRARY_ID, obj.library_id) - put(COL_TITLE, obj.title) - put(COL_LAST_CHAPTER_READ, obj.last_chapter_read) - put(COL_TOTAL_CHAPTERS, obj.total_chapters) - put(COL_STATUS, obj.status) - put(COL_TRACKING_URL, obj.tracking_url) - put(COL_SCORE, obj.score) - put(COL_START_DATE, obj.started_reading_date) - put(COL_FINISH_DATE, obj.finished_reading_date) - } + override fun mapToContentValues(obj: Track) = + ContentValues(10).apply { + put(COL_ID, obj.id) + put(COL_MANGA_ID, obj.manga_id) + put(COL_SYNC_ID, obj.sync_id) + put(COL_MEDIA_ID, obj.media_id) + put(COL_LIBRARY_ID, obj.library_id) + put(COL_TITLE, obj.title) + put(COL_LAST_CHAPTER_READ, obj.last_chapter_read) + put(COL_TOTAL_CHAPTERS, obj.total_chapters) + put(COL_STATUS, obj.status) + put(COL_TRACKING_URL, obj.tracking_url) + put(COL_SCORE, obj.score) + put(COL_START_DATE, obj.started_reading_date) + put(COL_FINISH_DATE, obj.finished_reading_date) + } } class TrackGetResolver : DefaultGetResolver() { - - override fun mapFromCursor(cursor: Cursor): Track = TrackImpl().apply { - id = cursor.getLong(cursor.getColumnIndex(COL_ID)) - manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)) - sync_id = cursor.getInt(cursor.getColumnIndex(COL_SYNC_ID)) - media_id = cursor.getInt(cursor.getColumnIndex(COL_MEDIA_ID)) - library_id = cursor.getLong(cursor.getColumnIndex(COL_LIBRARY_ID)) - title = cursor.getString(cursor.getColumnIndex(COL_TITLE)) - last_chapter_read = cursor.getInt(cursor.getColumnIndex(COL_LAST_CHAPTER_READ)) - total_chapters = cursor.getInt(cursor.getColumnIndex(COL_TOTAL_CHAPTERS)) - status = cursor.getInt(cursor.getColumnIndex(COL_STATUS)) - score = cursor.getFloat(cursor.getColumnIndex(COL_SCORE)) - tracking_url = cursor.getString(cursor.getColumnIndex(COL_TRACKING_URL)) - started_reading_date = cursor.getLong(cursor.getColumnIndex(COL_START_DATE)) - finished_reading_date = cursor.getLong(cursor.getColumnIndex(COL_FINISH_DATE)) - } + override fun mapFromCursor(cursor: Cursor): Track = + TrackImpl().apply { + id = cursor.getLong(cursor.getColumnIndex(COL_ID)) + manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)) + sync_id = cursor.getInt(cursor.getColumnIndex(COL_SYNC_ID)) + media_id = cursor.getInt(cursor.getColumnIndex(COL_MEDIA_ID)) + library_id = cursor.getLong(cursor.getColumnIndex(COL_LIBRARY_ID)) + title = cursor.getString(cursor.getColumnIndex(COL_TITLE)) + last_chapter_read = cursor.getInt(cursor.getColumnIndex(COL_LAST_CHAPTER_READ)) + total_chapters = cursor.getInt(cursor.getColumnIndex(COL_TOTAL_CHAPTERS)) + status = cursor.getInt(cursor.getColumnIndex(COL_STATUS)) + score = cursor.getFloat(cursor.getColumnIndex(COL_SCORE)) + tracking_url = cursor.getString(cursor.getColumnIndex(COL_TRACKING_URL)) + started_reading_date = cursor.getLong(cursor.getColumnIndex(COL_START_DATE)) + finished_reading_date = cursor.getLong(cursor.getColumnIndex(COL_FINISH_DATE)) + } } class TrackDeleteResolver : DefaultDeleteResolver() { - - override fun mapToDeleteQuery(obj: Track) = DeleteQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() + override fun mapToDeleteQuery(obj: Track) = + DeleteQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt index bf40f082560b..4bbcc452696c 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.database.models import java.io.Serializable interface Category : Serializable { - var id: Int? var name: String @@ -15,10 +14,10 @@ interface Category : Serializable { var mangaOrder: List companion object { - - fun create(name: String): Category = CategoryImpl().apply { - this.name = name - } + fun create(name: String): Category = + CategoryImpl().apply { + this.name = name + } fun createDefault(): Category = create("Default").apply { id = 0 } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt index 4e95fb3a62d7..be2324aa9cdd 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.database.models class CategoryImpl : Category { - override var id: Int? = null override lateinit var name: String diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt index 589ed671db8b..179fa64e93aa 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt @@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.source.model.SChapter import java.io.Serializable interface Chapter : SChapter, Serializable { - var id: Long? var manga_id: Long? @@ -23,9 +22,9 @@ interface Chapter : SChapter, Serializable { get() = chapter_number >= 0f companion object { - - fun create(): Chapter = ChapterImpl().apply { - chapter_number = -1f - } + fun create(): Chapter = + ChapterImpl().apply { + chapter_number = -1f + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt index a1a2d3f55edc..277cde338bab 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.database.models class ChapterImpl : Chapter { - override var id: Long? = null override var manga_id: Long? = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt index dff3bcb1556b..5d552d2b3221 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt @@ -6,7 +6,6 @@ import java.io.Serializable * Object containing the history statistics of a chapter */ interface History : Serializable { - /** * Id of history object. */ @@ -28,15 +27,15 @@ interface History : Serializable { var time_read: Long companion object { - /** * History constructor * * @param chapter chapter object * @return history object */ - fun create(chapter: Chapter): History = HistoryImpl().apply { - this.chapter_id = chapter.id!! - } + fun create(chapter: Chapter): History = + HistoryImpl().apply { + this.chapter_id = chapter.id!! + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/HistoryImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/HistoryImpl.kt index 94efcf2666b9..ea6a57327180 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/HistoryImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/HistoryImpl.kt @@ -4,7 +4,6 @@ package eu.kanade.tachiyomi.data.database.models * Object containing the history statistics of a chapter */ class HistoryImpl : History { - /** * Id of history object. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt index 35cf30c67a38..6a7dbbf1e589 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.database.models class LibraryManga : MangaImpl() { - var unread: Int = 0 var category: Int = 0 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt index 99045e64cd02..d32467ab25a5 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt @@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.source.model.SManga import tachiyomi.source.model.MangaInfo interface Manga : SManga { - var id: Long? var source: Long @@ -25,7 +24,10 @@ interface Manga : SManga { setFlags(order, SORT_MASK) } - private fun setFlags(flag: Int, mask: Int) { + private fun setFlags( + flag: Int, + mask: Int + ) { chapter_flags = chapter_flags and mask.inv() or (flag and mask) } @@ -59,7 +61,6 @@ interface Manga : SManga { set(sort) = setFlags(sort, SORTING_MASK) companion object { - const val SORT_DESC = 0x00000000 const val SORT_ASC = 0x00000001 const val SORT_MASK = 0x00000001 @@ -88,15 +89,21 @@ interface Manga : SManga { const val DISPLAY_NUMBER = 0x00100000 const val DISPLAY_MASK = 0x00100000 - fun create(source: Long): Manga = MangaImpl().apply { - this.source = source - } - - fun create(pathUrl: String, title: String, source: Long = 0): Manga = MangaImpl().apply { - url = pathUrl - this.title = title - this.source = source - } + fun create(source: Long): Manga = + MangaImpl().apply { + this.source = source + } + + fun create( + pathUrl: String, + title: String, + source: Long = 0 + ): Manga = + MangaImpl().apply { + url = pathUrl + this.title = title + this.source = source + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt index 22033708845e..8ab3d7864bb1 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaCategory.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.database.models class MangaCategory { - var id: Long? = null var manga_id: Long = 0 @@ -9,8 +8,10 @@ class MangaCategory { var category_id: Int = 0 companion object { - - fun create(manga: Manga, category: Category): MangaCategory { + fun create( + manga: Manga, + category: Category + ): MangaCategory { val mc = MangaCategory() mc.manga_id = manga.id!! mc.category_id = category.id!! diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt index 8400b4b1be4e..746081f3b2a4 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.database.models import eu.kanade.tachiyomi.source.model.UpdateStrategy open class MangaImpl : Manga { - override var id: Long? = null override var source: Long = -1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt index 0f3815c5446e..1f1cb9f74133 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Track.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.database.models import java.io.Serializable interface Track : Serializable { - var id: Long? var manga_id: Long @@ -39,8 +38,9 @@ interface Track : Serializable { } companion object { - fun create(serviceId: Int): Track = TrackImpl().apply { - sync_id = serviceId - } + fun create(serviceId: Int): Track = + TrackImpl().apply { + sync_id = serviceId + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt index 6f5991133cf9..36dbc0a26b59 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/TrackImpl.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.database.models class TrackImpl : Track { - override var id: Long? = null override var manga_id: Long = 0 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt index bf769b15457e..7593d5ef5b44 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt @@ -8,26 +8,27 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.tables.CategoryTable interface CategoryQueries : DbProvider { - - fun getCategories() = db.get() - .listOfObjects(Category::class.java) - .withQuery( - Query.builder() - .table(CategoryTable.TABLE) - .orderBy(CategoryTable.COL_ORDER) - .build() - ) - .prepare() - - fun getCategoriesForManga(manga: Manga) = db.get() - .listOfObjects(Category::class.java) - .withQuery( - RawQuery.builder() - .query(getCategoriesForMangaQuery()) - .args(manga.id) - .build() - ) - .prepare() + fun getCategories() = + db.get() + .listOfObjects(Category::class.java) + .withQuery( + Query.builder() + .table(CategoryTable.TABLE) + .orderBy(CategoryTable.COL_ORDER) + .build() + ) + .prepare() + + fun getCategoriesForManga(manga: Manga) = + db.get() + .listOfObjects(Category::class.java) + .withQuery( + RawQuery.builder() + .query(getCategoriesForMangaQuery()) + .args(manga.id) + .build() + ) + .prepare() fun insertCategory(category: Category) = db.put().`object`(category).prepare() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt index 106a4b699703..fb3aca66f275 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt @@ -14,64 +14,71 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable import java.util.Date interface ChapterQueries : DbProvider { - fun getChapters(manga: Manga) = getChaptersByMangaId(manga.id) - fun getChaptersByMangaId(mangaId: Long?) = db.get() - .listOfObjects(Chapter::class.java) - .withQuery( - Query.builder() - .table(ChapterTable.TABLE) - .where("${ChapterTable.COL_MANGA_ID} = ?") - .whereArgs(mangaId) - .build() - ) - .prepare() - - fun getChaptersByMergedMangaId(mangaId: Long) = db.get() - .listOfObjects(Chapter::class.java) - .withQuery( - RawQuery.builder() - .query(getMergedChaptersQuery(mangaId)) - .build() - ) - .prepare() - - fun getRecentChapters(date: Date) = db.get() - .listOfObjects(MangaChapter::class.java) - .withQuery( - RawQuery.builder() - .query(getRecentsQuery()) - .args(date.time) - .observesTables(ChapterTable.TABLE) - .build() - ) - .withGetResolver(MangaChapterGetResolver.INSTANCE) - .prepare() - - fun getChapter(id: Long) = db.get() - .`object`(Chapter::class.java) - .withQuery( - Query.builder() - .table(ChapterTable.TABLE) - .where("${ChapterTable.COL_ID} = ?") - .whereArgs(id) - .build() - ) - .prepare() - - fun getChapter(url: String) = db.get() - .`object`(Chapter::class.java) - .withQuery( - Query.builder() - .table(ChapterTable.TABLE) - .where("${ChapterTable.COL_URL} = ?") - .whereArgs(url) - .build() - ) - .prepare() - - fun getChapter(url: String, mangaId: Long) = db.get() + fun getChaptersByMangaId(mangaId: Long?) = + db.get() + .listOfObjects(Chapter::class.java) + .withQuery( + Query.builder() + .table(ChapterTable.TABLE) + .where("${ChapterTable.COL_MANGA_ID} = ?") + .whereArgs(mangaId) + .build() + ) + .prepare() + + fun getChaptersByMergedMangaId(mangaId: Long) = + db.get() + .listOfObjects(Chapter::class.java) + .withQuery( + RawQuery.builder() + .query(getMergedChaptersQuery(mangaId)) + .build() + ) + .prepare() + + fun getRecentChapters(date: Date) = + db.get() + .listOfObjects(MangaChapter::class.java) + .withQuery( + RawQuery.builder() + .query(getRecentsQuery()) + .args(date.time) + .observesTables(ChapterTable.TABLE) + .build() + ) + .withGetResolver(MangaChapterGetResolver.INSTANCE) + .prepare() + + fun getChapter(id: Long) = + db.get() + .`object`(Chapter::class.java) + .withQuery( + Query.builder() + .table(ChapterTable.TABLE) + .where("${ChapterTable.COL_ID} = ?") + .whereArgs(id) + .build() + ) + .prepare() + + fun getChapter(url: String) = + db.get() + .`object`(Chapter::class.java) + .withQuery( + Query.builder() + .table(ChapterTable.TABLE) + .where("${ChapterTable.COL_URL} = ?") + .whereArgs(url) + .build() + ) + .prepare() + + fun getChapter( + url: String, + mangaId: Long + ) = db.get() .`object`(Chapter::class.java) .withQuery( Query.builder() @@ -82,16 +89,17 @@ interface ChapterQueries : DbProvider { ) .prepare() - fun getChapters(url: String) = db.get() - .listOfObjects(Chapter::class.java) - .withQuery( - Query.builder() - .table(ChapterTable.TABLE) - .where("${ChapterTable.COL_URL} = ?") - .whereArgs(url) - .build() - ) - .prepare() + fun getChapters(url: String) = + db.get() + .listOfObjects(Chapter::class.java) + .withQuery( + Query.builder() + .table(ChapterTable.TABLE) + .where("${ChapterTable.COL_URL} = ?") + .whereArgs(url) + .build() + ) + .prepare() fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare() @@ -101,23 +109,27 @@ interface ChapterQueries : DbProvider { fun deleteChapters(chapters: List) = db.delete().objects(chapters).prepare() - fun updateChaptersBackup(chapters: List) = db.put() - .objects(chapters) - .withPutResolver(ChapterBackupPutResolver()) - .prepare() - - fun updateChapterProgress(chapter: Chapter) = db.put() - .`object`(chapter) - .withPutResolver(ChapterProgressPutResolver()) - .prepare() - - fun updateChaptersProgress(chapters: List) = db.put() - .objects(chapters) - .withPutResolver(ChapterProgressPutResolver()) - .prepare() - - fun fixChaptersSourceOrder(chapters: List) = db.put() - .objects(chapters) - .withPutResolver(ChapterSourceOrderPutResolver()) - .prepare() + fun updateChaptersBackup(chapters: List) = + db.put() + .objects(chapters) + .withPutResolver(ChapterBackupPutResolver()) + .prepare() + + fun updateChapterProgress(chapter: Chapter) = + db.put() + .`object`(chapter) + .withPutResolver(ChapterProgressPutResolver()) + .prepare() + + fun updateChaptersProgress(chapters: List) = + db.put() + .objects(chapters) + .withPutResolver(ChapterProgressPutResolver()) + .prepare() + + fun fixChaptersSourceOrder(chapters: List) = + db.put() + .objects(chapters) + .withPutResolver(ChapterSourceOrderPutResolver()) + .prepare() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt index 30ee1f798c37..3d918d481f82 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt @@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.data.database.tables.HistoryTable import java.util.Date interface HistoryQueries : DbProvider { - /** * Insert history into database * @param history object containing history information @@ -22,7 +21,11 @@ interface HistoryQueries : DbProvider { * Returns history of recent manga containing last read chapter * @param date recent date range */ - fun getRecentManga(date: Date, offset: Int = 0, search: String = "") = db.get() + fun getRecentManga( + date: Date, + offset: Int = 0, + search: String = "" + ) = db.get() .listOfObjects(MangaChapterHistory::class.java) .withQuery( RawQuery.builder() @@ -39,7 +42,11 @@ interface HistoryQueries : DbProvider { * @param date recent date range * @offset offset the db by */ - fun getRecentMangaLimit(date: Date, limit: Int = 0, search: String = "") = db.get() + fun getRecentMangaLimit( + date: Date, + limit: Int = 0, + search: String = "" + ) = db.get() .listOfObjects(MangaChapterHistory::class.java) .withQuery( RawQuery.builder() @@ -51,63 +58,69 @@ interface HistoryQueries : DbProvider { .withGetResolver(MangaChapterHistoryGetResolver.INSTANCE) .prepare() - fun getHistoryByMangaId(mangaId: Long) = db.get() - .listOfObjects(History::class.java) - .withQuery( - RawQuery.builder() - .query(getHistoryByMangaId()) - .args(mangaId) - .observesTables(HistoryTable.TABLE) - .build() - ) - .prepare() + fun getHistoryByMangaId(mangaId: Long) = + db.get() + .listOfObjects(History::class.java) + .withQuery( + RawQuery.builder() + .query(getHistoryByMangaId()) + .args(mangaId) + .observesTables(HistoryTable.TABLE) + .build() + ) + .prepare() - fun getHistoryByChapterUrl(chapterUrl: String) = db.get() - .`object`(History::class.java) - .withQuery( - RawQuery.builder() - .query(getHistoryByChapterUrl()) - .args(chapterUrl) - .observesTables(HistoryTable.TABLE) - .build() - ) - .prepare() + fun getHistoryByChapterUrl(chapterUrl: String) = + db.get() + .`object`(History::class.java) + .withQuery( + RawQuery.builder() + .query(getHistoryByChapterUrl()) + .args(chapterUrl) + .observesTables(HistoryTable.TABLE) + .build() + ) + .prepare() /** * Updates the history last read. * Inserts history object if not yet in database * @param history history object */ - fun updateHistoryLastRead(history: History) = db.put() - .`object`(history) - .withPutResolver(HistoryLastReadPutResolver()) - .prepare() + fun updateHistoryLastRead(history: History) = + db.put() + .`object`(history) + .withPutResolver(HistoryLastReadPutResolver()) + .prepare() /** * Updates the history last read. * Inserts history object if not yet in database * @param historyList history object list */ - fun updateHistoryLastRead(historyList: List) = db.put() - .objects(historyList) - .withPutResolver(HistoryLastReadPutResolver()) - .prepare() + fun updateHistoryLastRead(historyList: List) = + db.put() + .objects(historyList) + .withPutResolver(HistoryLastReadPutResolver()) + .prepare() - fun deleteHistory() = db.delete() - .byQuery( - DeleteQuery.builder() - .table(HistoryTable.TABLE) - .build() - ) - .prepare() + fun deleteHistory() = + db.delete() + .byQuery( + DeleteQuery.builder() + .table(HistoryTable.TABLE) + .build() + ) + .prepare() - fun deleteHistoryNoLastRead() = db.delete() - .byQuery( - DeleteQuery.builder() - .table(HistoryTable.TABLE) - .where("${HistoryTable.COL_LAST_READ} = ?") - .whereArgs(0) - .build() - ) - .prepare() + fun deleteHistoryNoLastRead() = + db.delete() + .byQuery( + DeleteQuery.builder() + .table(HistoryTable.TABLE) + .where("${HistoryTable.COL_LAST_READ} = ?") + .whereArgs(0) + .build() + ) + .prepare() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaCategoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaCategoryQueries.kt index 49b127403a8d..d9450e8bcbd2 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaCategoryQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaCategoryQueries.kt @@ -9,22 +9,25 @@ import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable interface MangaCategoryQueries : DbProvider { - fun insertMangaCategory(mangaCategory: MangaCategory) = db.put().`object`(mangaCategory).prepare() fun insertMangasCategories(mangasCategories: List) = db.put().objects(mangasCategories).prepare() - fun deleteOldMangasCategories(mangas: List) = db.delete() - .byQuery( - DeleteQuery.builder() - .table(MangaCategoryTable.TABLE) - .where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})") - .whereArgs(*mangas.map { it.id }.toTypedArray()) - .build() - ) - .prepare() + fun deleteOldMangasCategories(mangas: List) = + db.delete() + .byQuery( + DeleteQuery.builder() + .table(MangaCategoryTable.TABLE) + .where("${MangaCategoryTable.COL_MANGA_ID} IN (${Queries.placeholders(mangas.size)})") + .whereArgs(*mangas.map { it.id }.toTypedArray()) + .build() + ) + .prepare() - fun setMangaCategories(mangasCategories: List, mangas: List) { + fun setMangaCategories( + mangasCategories: List, + mangas: List + ) { db.inTransaction { mangas.chunked(100) { chunk -> deleteOldMangasCategories(chunk).executeAsBlocking() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt index a4707da3c001..177fb2f3bc4b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt @@ -20,40 +20,45 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable import exh.metadata.sql.tables.SearchMetadataTable interface MangaQueries : DbProvider { - - fun getMangas() = db.get() - .listOfObjects(Manga::class.java) - .withQuery( - Query.builder() - .table(MangaTable.TABLE) - .build() - ) - .prepare() - - fun getLibraryMangas() = db.get() - .listOfObjects(LibraryManga::class.java) - .withQuery( - RawQuery.builder() - .query(libraryQuery) - .observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE) - .build() - ) - .withGetResolver(LibraryMangaGetResolver.INSTANCE) - .prepare() - - fun getFavoriteMangas() = db.get() - .listOfObjects(Manga::class.java) - .withQuery( - Query.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_FAVORITE} = ?") - .whereArgs(1) - .orderBy(MangaTable.COL_TITLE) - .build() - ) - .prepare() - - fun getManga(url: String, sourceId: Long) = db.get() + fun getMangas() = + db.get() + .listOfObjects(Manga::class.java) + .withQuery( + Query.builder() + .table(MangaTable.TABLE) + .build() + ) + .prepare() + + fun getLibraryMangas() = + db.get() + .listOfObjects(LibraryManga::class.java) + .withQuery( + RawQuery.builder() + .query(libraryQuery) + .observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE) + .build() + ) + .withGetResolver(LibraryMangaGetResolver.INSTANCE) + .prepare() + + fun getFavoriteMangas() = + db.get() + .listOfObjects(Manga::class.java) + .withQuery( + Query.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_FAVORITE} = ?") + .whereArgs(1) + .orderBy(MangaTable.COL_TITLE) + .build() + ) + .prepare() + + fun getManga( + url: String, + sourceId: Long + ) = db.get() .`object`(Manga::class.java) .withQuery( Query.builder() @@ -64,159 +69,175 @@ interface MangaQueries : DbProvider { ) .prepare() - fun getManga(id: Long) = db.get() - .`object`(Manga::class.java) - .withQuery( - Query.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_ID} = ?") - .whereArgs(id) - .build() - ) - .prepare() - - fun getMergedMangas(id: Long) = db.get() - .listOfObjects(Manga::class.java) - .withQuery( - RawQuery.builder() - .query(getMergedMangaQuery(id)) - .build() - ) - .prepare() + fun getManga(id: Long) = + db.get() + .`object`(Manga::class.java) + .withQuery( + Query.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_ID} = ?") + .whereArgs(id) + .build() + ) + .prepare() + + fun getMergedMangas(id: Long) = + db.get() + .listOfObjects(Manga::class.java) + .withQuery( + RawQuery.builder() + .query(getMergedMangaQuery(id)) + .build() + ) + .prepare() fun insertManga(manga: Manga) = db.put().`object`(manga).prepare() fun insertMangas(mangas: List) = db.put().objects(mangas).prepare() - fun updateFlags(manga: Manga) = db.put() - .`object`(manga) - .withPutResolver(MangaFlagsPutResolver()) - .prepare() - - fun updateLastUpdated(manga: Manga) = db.put() - .`object`(manga) - .withPutResolver(MangaLastUpdatedPutResolver()) - .prepare() - - fun updateMangaFavorite(manga: Manga) = db.put() - .`object`(manga) - .withPutResolver(MangaFavoritePutResolver()) - .prepare() - - fun updateMangaViewer(manga: Manga) = db.put() - .`object`(manga) - .withPutResolver(MangaViewerPutResolver()) - .prepare() - - fun updateMangaTitle(manga: Manga) = db.put() - .`object`(manga) - .withPutResolver(MangaTitlePutResolver()) - .prepare() - - fun updateMangaCoverLastModified(manga: Manga) = db.put() - .`object`(manga) - .withPutResolver(MangaCoverLastModifiedPutResolver()) - .prepare() + fun updateFlags(manga: Manga) = + db.put() + .`object`(manga) + .withPutResolver(MangaFlagsPutResolver()) + .prepare() + + fun updateLastUpdated(manga: Manga) = + db.put() + .`object`(manga) + .withPutResolver(MangaLastUpdatedPutResolver()) + .prepare() + + fun updateMangaFavorite(manga: Manga) = + db.put() + .`object`(manga) + .withPutResolver(MangaFavoritePutResolver()) + .prepare() + + fun updateMangaViewer(manga: Manga) = + db.put() + .`object`(manga) + .withPutResolver(MangaViewerPutResolver()) + .prepare() + + fun updateMangaTitle(manga: Manga) = + db.put() + .`object`(manga) + .withPutResolver(MangaTitlePutResolver()) + .prepare() + + fun updateMangaCoverLastModified(manga: Manga) = + db.put() + .`object`(manga) + .withPutResolver(MangaCoverLastModifiedPutResolver()) + .prepare() fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare() fun deleteMangas(mangas: List) = db.delete().objects(mangas).prepare() - fun deleteMangasNotInLibrary() = db.delete() - .byQuery( - DeleteQuery.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_FAVORITE} = ?") - .whereArgs(0) - .build() - ) - .prepare() - - fun deleteMangas() = db.delete() - .byQuery( - DeleteQuery.builder() - .table(MangaTable.TABLE) - .build() - ) - .prepare() - - fun getLastReadManga() = db.get() - .listOfObjects(Manga::class.java) - .withQuery( - RawQuery.builder() - .query(getLastReadMangaQuery()) - .observesTables(MangaTable.TABLE) - .build() - ) - .prepare() - - fun getMangaWithMetadata() = db.get() - .listOfObjects(Manga::class.java) - .withQuery( - RawQuery.builder() - .query( - """ - SELECT ${MangaTable.TABLE}.* FROM ${MangaTable.TABLE} - INNER JOIN ${SearchMetadataTable.TABLE} - ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID} - ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID} - """.trimIndent() - ) - .build() - ) - .prepare() - - fun getFavoriteMangaWithMetadata() = db.get() - .listOfObjects(Manga::class.java) - .withQuery( - RawQuery.builder() - .query( - """ - SELECT ${MangaTable.TABLE}.* FROM ${MangaTable.TABLE} - INNER JOIN ${SearchMetadataTable.TABLE} - ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID} - WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1 - ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID} - """.trimIndent() - ) - .build() - ) - .prepare() - - fun getIdsOfFavoriteMangaWithMetadata() = db.get() - .cursor() - .withQuery( - RawQuery.builder() - .query( - """ - SELECT ${MangaTable.TABLE}.${MangaTable.COL_ID} FROM ${MangaTable.TABLE} - INNER JOIN ${SearchMetadataTable.TABLE} - ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID} - WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1 - ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID} - """.trimIndent() - ) - .build() - ) - .prepare() - - fun getTotalChapterManga() = db.get() - .listOfObjects(Manga::class.java) - .withQuery( - RawQuery.builder() - .query(getTotalChapterMangaQuery()) - .observesTables(MangaTable.TABLE) - .build() - ) - .prepare() - - fun getLatestChapterManga() = db.get() - .listOfObjects(Manga::class.java) - .withQuery( - RawQuery.builder() - .query(getLatestChapterMangaQuery()) - .observesTables(MangaTable.TABLE) - .build() - ) - .prepare() + fun deleteMangasNotInLibrary() = + db.delete() + .byQuery( + DeleteQuery.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_FAVORITE} = ?") + .whereArgs(0) + .build() + ) + .prepare() + + fun deleteMangas() = + db.delete() + .byQuery( + DeleteQuery.builder() + .table(MangaTable.TABLE) + .build() + ) + .prepare() + + fun getLastReadManga() = + db.get() + .listOfObjects(Manga::class.java) + .withQuery( + RawQuery.builder() + .query(getLastReadMangaQuery()) + .observesTables(MangaTable.TABLE) + .build() + ) + .prepare() + + fun getMangaWithMetadata() = + db.get() + .listOfObjects(Manga::class.java) + .withQuery( + RawQuery.builder() + .query( + """ + SELECT ${MangaTable.TABLE}.* FROM ${MangaTable.TABLE} + INNER JOIN ${SearchMetadataTable.TABLE} + ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID} + ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID} + """.trimIndent() + ) + .build() + ) + .prepare() + + fun getFavoriteMangaWithMetadata() = + db.get() + .listOfObjects(Manga::class.java) + .withQuery( + RawQuery.builder() + .query( + """ + SELECT ${MangaTable.TABLE}.* FROM ${MangaTable.TABLE} + INNER JOIN ${SearchMetadataTable.TABLE} + ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID} + WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1 + ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID} + """.trimIndent() + ) + .build() + ) + .prepare() + + fun getIdsOfFavoriteMangaWithMetadata() = + db.get() + .cursor() + .withQuery( + RawQuery.builder() + .query( + """ + SELECT ${MangaTable.TABLE}.${MangaTable.COL_ID} FROM ${MangaTable.TABLE} + INNER JOIN ${SearchMetadataTable.TABLE} + ON ${MangaTable.TABLE}.${MangaTable.COL_ID} = ${SearchMetadataTable.TABLE}.${SearchMetadataTable.COL_MANGA_ID} + WHERE ${MangaTable.TABLE}.${MangaTable.COL_FAVORITE} = 1 + ORDER BY ${MangaTable.TABLE}.${MangaTable.COL_ID} + """.trimIndent() + ) + .build() + ) + .prepare() + + fun getTotalChapterManga() = + db.get() + .listOfObjects(Manga::class.java) + .withQuery( + RawQuery.builder() + .query(getTotalChapterMangaQuery()) + .observesTables(MangaTable.TABLE) + .build() + ) + .prepare() + + fun getLatestChapterManga() = + db.get() + .listOfObjects(Manga::class.java) + .withQuery( + RawQuery.builder() + .query(getLatestChapterMangaQuery()) + .observesTables(MangaTable.TABLE) + .build() + ) + .prepare() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt index 7ffe29674fa2..f8d9363f511a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt @@ -78,8 +78,10 @@ fun getRecentsQuery() = * and are read after the given time period * @return return limit is 25 */ -fun getRecentMangasQuery(offset: Int = 0, search: String = "") = - """ +fun getRecentMangasQuery( + offset: Int = 0, + search: String = "" +) = """ SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.* FROM ${Manga.TABLE} JOIN ${Chapter.TABLE} @@ -105,8 +107,10 @@ fun getRecentMangasQuery(offset: Int = 0, search: String = "") = * The select statement returns all information of chapters that have the same id as the chapter in max_last_read * and are read after the given time period */ -fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") = - """ +fun getRecentMangasLimitQuery( + limit: Int = 25, + search: String = "" +) = """ SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.* FROM ${Manga.TABLE} JOIN ${Chapter.TABLE} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt index 7311fe40de19..4d609457cb8f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt @@ -9,23 +9,26 @@ import eu.kanade.tachiyomi.data.database.tables.TrackTable import eu.kanade.tachiyomi.data.track.TrackService interface TrackQueries : DbProvider { - - fun getTracks(manga: Manga) = db.get() - .listOfObjects(Track::class.java) - .withQuery( - Query.builder() - .table(TrackTable.TABLE) - .where("${TrackTable.COL_MANGA_ID} = ?") - .whereArgs(manga.id) - .build() - ) - .prepare() + fun getTracks(manga: Manga) = + db.get() + .listOfObjects(Track::class.java) + .withQuery( + Query.builder() + .table(TrackTable.TABLE) + .where("${TrackTable.COL_MANGA_ID} = ?") + .whereArgs(manga.id) + .build() + ) + .prepare() fun insertTrack(track: Track) = db.put().`object`(track).prepare() fun insertTracks(tracks: List) = db.put().objects(tracks).prepare() - fun deleteTrackForManga(manga: Manga, sync: TrackService) = db.delete() + fun deleteTrackForManga( + manga: Manga, + sync: TrackService + ) = db.delete() .byQuery( DeleteQuery.builder() .table(TrackTable.TABLE) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterBackupPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterBackupPutResolver.kt index 20008e07482d..02ae4a6ba4fd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterBackupPutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterBackupPutResolver.kt @@ -10,8 +10,10 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.tables.ChapterTable class ChapterBackupPutResolver : PutResolver() { - - override fun performPut(db: StorIOSQLite, chapter: Chapter) = db.inTransactionReturn { + override fun performPut( + db: StorIOSQLite, + chapter: Chapter + ) = db.inTransactionReturn { val updateQuery = mapToUpdateQuery(chapter) val contentValues = mapToContentValues(chapter) @@ -19,15 +21,17 @@ class ChapterBackupPutResolver : PutResolver() { PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) } - fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder() - .table(ChapterTable.TABLE) - .where("${ChapterTable.COL_URL} = ?") - .whereArgs(chapter.url) - .build() + fun mapToUpdateQuery(chapter: Chapter) = + UpdateQuery.builder() + .table(ChapterTable.TABLE) + .where("${ChapterTable.COL_URL} = ?") + .whereArgs(chapter.url) + .build() - fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply { - put(ChapterTable.COL_READ, chapter.read) - put(ChapterTable.COL_BOOKMARK, chapter.bookmark) - put(ChapterTable.COL_LAST_PAGE_READ, chapter.last_page_read) - } + fun mapToContentValues(chapter: Chapter) = + ContentValues(3).apply { + put(ChapterTable.COL_READ, chapter.read) + put(ChapterTable.COL_BOOKMARK, chapter.bookmark) + put(ChapterTable.COL_LAST_PAGE_READ, chapter.last_page_read) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterProgressPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterProgressPutResolver.kt index b2800551f871..e27764ddaa34 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterProgressPutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterProgressPutResolver.kt @@ -10,8 +10,10 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.tables.ChapterTable class ChapterProgressPutResolver : PutResolver() { - - override fun performPut(db: StorIOSQLite, chapter: Chapter) = db.inTransactionReturn { + override fun performPut( + db: StorIOSQLite, + chapter: Chapter + ) = db.inTransactionReturn { val updateQuery = mapToUpdateQuery(chapter) val contentValues = mapToContentValues(chapter) @@ -19,15 +21,17 @@ class ChapterProgressPutResolver : PutResolver() { PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) } - fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder() - .table(ChapterTable.TABLE) - .where("${ChapterTable.COL_ID} = ?") - .whereArgs(chapter.id) - .build() + fun mapToUpdateQuery(chapter: Chapter) = + UpdateQuery.builder() + .table(ChapterTable.TABLE) + .where("${ChapterTable.COL_ID} = ?") + .whereArgs(chapter.id) + .build() - fun mapToContentValues(chapter: Chapter) = ContentValues(3).apply { - put(ChapterTable.COL_READ, chapter.read) - put(ChapterTable.COL_BOOKMARK, chapter.bookmark) - put(ChapterTable.COL_LAST_PAGE_READ, chapter.last_page_read) - } + fun mapToContentValues(chapter: Chapter) = + ContentValues(3).apply { + put(ChapterTable.COL_READ, chapter.read) + put(ChapterTable.COL_BOOKMARK, chapter.bookmark) + put(ChapterTable.COL_LAST_PAGE_READ, chapter.last_page_read) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterSourceOrderPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterSourceOrderPutResolver.kt index fa0f3514c871..f32587ccef17 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterSourceOrderPutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterSourceOrderPutResolver.kt @@ -10,8 +10,10 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.tables.ChapterTable class ChapterSourceOrderPutResolver : PutResolver() { - - override fun performPut(db: StorIOSQLite, chapter: Chapter) = db.inTransactionReturn { + override fun performPut( + db: StorIOSQLite, + chapter: Chapter + ) = db.inTransactionReturn { val updateQuery = mapToUpdateQuery(chapter) val contentValues = mapToContentValues(chapter) @@ -19,13 +21,15 @@ class ChapterSourceOrderPutResolver : PutResolver() { PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) } - fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder() - .table(ChapterTable.TABLE) - .where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?") - .whereArgs(chapter.url, chapter.manga_id) - .build() + fun mapToUpdateQuery(chapter: Chapter) = + UpdateQuery.builder() + .table(ChapterTable.TABLE) + .where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?") + .whereArgs(chapter.url, chapter.manga_id) + .build() - fun mapToContentValues(chapter: Chapter) = ContentValues(1).apply { - put(ChapterTable.COL_SOURCE_ORDER, chapter.source_order) - } + fun mapToContentValues(chapter: Chapter) = + ContentValues(1).apply { + put(ChapterTable.COL_SOURCE_ORDER, chapter.source_order) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt index 32f73c0097a5..9952b4d5b5f4 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt @@ -12,52 +12,59 @@ import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.tables.HistoryTable class HistoryLastReadPutResolver : HistoryPutResolver() { - /** * Updates last_read time of chapter */ - override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn { - val updateQuery = mapToUpdateQuery(history) - - val cursor = db.lowLevel().query( - Query.builder() - .table(updateQuery.table()) - .where(updateQuery.where()) - .whereArgs(updateQuery.whereArgs()) - .build() - ) - - val putResult: PutResult - - putResult = cursor.use { putCursor -> - if (putCursor.count == 0) { - val insertQuery = mapToInsertQuery(history) - val insertedId = db.lowLevel().insert(insertQuery, mapToContentValues(history)) - PutResult.newInsertResult(insertedId, insertQuery.table()) - } else { - val numberOfRowsUpdated = db.lowLevel().update(updateQuery, mapToUpdateContentValues(history)) - PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) - } - } + override fun performPut( + @NonNull db: StorIOSQLite, + @NonNull history: History + ): PutResult = + db.inTransactionReturn { + val updateQuery = mapToUpdateQuery(history) + + val cursor = + db.lowLevel().query( + Query.builder() + .table(updateQuery.table()) + .where(updateQuery.where()) + .whereArgs(updateQuery.whereArgs()) + .build() + ) + + val putResult: PutResult - putResult - } + putResult = + cursor.use { putCursor -> + if (putCursor.count == 0) { + val insertQuery = mapToInsertQuery(history) + val insertedId = db.lowLevel().insert(insertQuery, mapToContentValues(history)) + PutResult.newInsertResult(insertedId, insertQuery.table()) + } else { + val numberOfRowsUpdated = db.lowLevel().update(updateQuery, mapToUpdateContentValues(history)) + PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) + } + } + + putResult + } /** * Creates update query * @param obj history object */ - override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder() - .table(HistoryTable.TABLE) - .where("${HistoryTable.COL_CHAPTER_ID} = ?") - .whereArgs(obj.chapter_id) - .build() + override fun mapToUpdateQuery(obj: History) = + UpdateQuery.builder() + .table(HistoryTable.TABLE) + .where("${HistoryTable.COL_CHAPTER_ID} = ?") + .whereArgs(obj.chapter_id) + .build() /** * Create content query * @param history object */ - fun mapToUpdateContentValues(history: History) = ContentValues(1).apply { - put(HistoryTable.COL_LAST_READ, history.last_read) - } + fun mapToUpdateContentValues(history: History) = + ContentValues(1).apply { + put(HistoryTable.COL_LAST_READ, history.last_read) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/LibraryMangaGetResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/LibraryMangaGetResolver.kt index aac8ead3e8eb..77f6ee28ddf9 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/LibraryMangaGetResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/LibraryMangaGetResolver.kt @@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.tables.MangaTable class LibraryMangaGetResolver : DefaultGetResolver(), BaseMangaGetResolver { - companion object { val INSTANCE = LibraryMangaGetResolver() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt index edd6a8983d09..74c2adfecae2 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.kt @@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.mappers.MangaGetResolver import eu.kanade.tachiyomi.data.database.models.MangaChapter class MangaChapterGetResolver : DefaultGetResolver() { - companion object { val INSTANCE = MangaChapterGetResolver() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaCoverLastModifiedPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaCoverLastModifiedPutResolver.kt index 98d6ba4a23e1..d202a7bc7afc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaCoverLastModifiedPutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaCoverLastModifiedPutResolver.kt @@ -10,8 +10,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.tables.MangaTable class MangaCoverLastModifiedPutResolver : PutResolver() { - - override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn { + override fun performPut( + db: StorIOSQLite, + manga: Manga + ) = db.inTransactionReturn { val updateQuery = mapToUpdateQuery(manga) val contentValues = mapToContentValues(manga) @@ -19,13 +21,15 @@ class MangaCoverLastModifiedPutResolver : PutResolver() { PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) } - fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_ID} = ?") - .whereArgs(manga.id) - .build() + fun mapToUpdateQuery(manga: Manga) = + UpdateQuery.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_ID} = ?") + .whereArgs(manga.id) + .build() - fun mapToContentValues(manga: Manga) = ContentValues(1).apply { - put(MangaTable.COL_COVER_LAST_MODIFIED, manga.cover_last_modified) - } + fun mapToContentValues(manga: Manga) = + ContentValues(1).apply { + put(MangaTable.COL_COVER_LAST_MODIFIED, manga.cover_last_modified) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFavoritePutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFavoritePutResolver.kt index 5c948a2c8eb5..adb893c6f8ec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFavoritePutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFavoritePutResolver.kt @@ -10,8 +10,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.tables.MangaTable class MangaFavoritePutResolver : PutResolver() { - - override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn { + override fun performPut( + db: StorIOSQLite, + manga: Manga + ) = db.inTransactionReturn { val updateQuery = mapToUpdateQuery(manga) val contentValues = mapToContentValues(manga) @@ -19,13 +21,15 @@ class MangaFavoritePutResolver : PutResolver() { PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) } - fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_ID} = ?") - .whereArgs(manga.id) - .build() + fun mapToUpdateQuery(manga: Manga) = + UpdateQuery.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_ID} = ?") + .whereArgs(manga.id) + .build() - fun mapToContentValues(manga: Manga) = ContentValues(1).apply { - put(MangaTable.COL_FAVORITE, manga.favorite) - } + fun mapToContentValues(manga: Manga) = + ContentValues(1).apply { + put(MangaTable.COL_FAVORITE, manga.favorite) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt index 0a30da7dab72..1327cc16b974 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt @@ -10,8 +10,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.tables.MangaTable class MangaFlagsPutResolver : PutResolver() { - - override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn { + override fun performPut( + db: StorIOSQLite, + manga: Manga + ) = db.inTransactionReturn { val updateQuery = mapToUpdateQuery(manga) val contentValues = mapToContentValues(manga) @@ -19,13 +21,15 @@ class MangaFlagsPutResolver : PutResolver() { PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) } - fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_ID} = ?") - .whereArgs(manga.id) - .build() + fun mapToUpdateQuery(manga: Manga) = + UpdateQuery.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_ID} = ?") + .whereArgs(manga.id) + .build() - fun mapToContentValues(manga: Manga) = ContentValues(1).apply { - put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags) - } + fun mapToContentValues(manga: Manga) = + ContentValues(1).apply { + put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaLastUpdatedPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaLastUpdatedPutResolver.kt index 6b33ed2554eb..8f7a5e531568 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaLastUpdatedPutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaLastUpdatedPutResolver.kt @@ -10,8 +10,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.tables.MangaTable class MangaLastUpdatedPutResolver : PutResolver() { - - override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn { + override fun performPut( + db: StorIOSQLite, + manga: Manga + ) = db.inTransactionReturn { val updateQuery = mapToUpdateQuery(manga) val contentValues = mapToContentValues(manga) @@ -19,13 +21,15 @@ class MangaLastUpdatedPutResolver : PutResolver() { PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) } - fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_ID} = ?") - .whereArgs(manga.id) - .build() + fun mapToUpdateQuery(manga: Manga) = + UpdateQuery.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_ID} = ?") + .whereArgs(manga.id) + .build() - fun mapToContentValues(manga: Manga) = ContentValues(1).apply { - put(MangaTable.COL_LAST_UPDATE, manga.last_update) - } + fun mapToContentValues(manga: Manga) = + ContentValues(1).apply { + put(MangaTable.COL_LAST_UPDATE, manga.last_update) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaTitlePutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaTitlePutResolver.kt index cb29c26d9514..d56a04240052 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaTitlePutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaTitlePutResolver.kt @@ -10,8 +10,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.tables.MangaTable class MangaTitlePutResolver : PutResolver() { - - override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn { + override fun performPut( + db: StorIOSQLite, + manga: Manga + ) = db.inTransactionReturn { val updateQuery = mapToUpdateQuery(manga) val contentValues = mapToContentValues(manga) @@ -19,13 +21,15 @@ class MangaTitlePutResolver : PutResolver() { PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) } - fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_ID} = ?") - .whereArgs(manga.id) - .build() + fun mapToUpdateQuery(manga: Manga) = + UpdateQuery.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_ID} = ?") + .whereArgs(manga.id) + .build() - fun mapToContentValues(manga: Manga) = ContentValues(1).apply { - put(MangaTable.COL_TITLE, manga.title) - } + fun mapToContentValues(manga: Manga) = + ContentValues(1).apply { + put(MangaTable.COL_TITLE, manga.title) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaUrlPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaUrlPutResolver.kt index 9cacff754e41..ee5147a83b7f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaUrlPutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaUrlPutResolver.kt @@ -11,8 +11,10 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable // [EXH] class MangaUrlPutResolver : PutResolver() { - - override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn { + override fun performPut( + db: StorIOSQLite, + manga: Manga + ) = db.inTransactionReturn { val updateQuery = mapToUpdateQuery(manga) val contentValues = mapToContentValues(manga) @@ -20,13 +22,15 @@ class MangaUrlPutResolver : PutResolver() { PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) } - fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_ID} = ?") - .whereArgs(manga.id) - .build() + fun mapToUpdateQuery(manga: Manga) = + UpdateQuery.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_ID} = ?") + .whereArgs(manga.id) + .build() - fun mapToContentValues(manga: Manga) = ContentValues(1).apply { - put(MangaTable.COL_URL, manga.url) - } + fun mapToContentValues(manga: Manga) = + ContentValues(1).apply { + put(MangaTable.COL_URL, manga.url) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaViewerPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaViewerPutResolver.kt index 13005d24c26f..6f11f30e95dc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaViewerPutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaViewerPutResolver.kt @@ -10,8 +10,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.tables.MangaTable class MangaViewerPutResolver : PutResolver() { - - override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn { + override fun performPut( + db: StorIOSQLite, + manga: Manga + ) = db.inTransactionReturn { val updateQuery = mapToUpdateQuery(manga) val contentValues = mapToContentValues(manga) @@ -19,13 +21,15 @@ class MangaViewerPutResolver : PutResolver() { PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) } - fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_ID} = ?") - .whereArgs(manga.id) - .build() + fun mapToUpdateQuery(manga: Manga) = + UpdateQuery.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_ID} = ?") + .whereArgs(manga.id) + .build() - fun mapToContentValues(manga: Manga) = ContentValues(1).apply { - put(MangaTable.COL_VIEWER, manga.viewer) - } + fun mapToContentValues(manga: Manga) = + ContentValues(1).apply { + put(MangaTable.COL_VIEWER, manga.viewer) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt index d3b9477b8a21..6a597226a0db 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.database.tables object CategoryTable { - const val TABLE = "categories" const val COL_ID = "_id" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt index 67047cc00463..84883f621fe2 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.database.tables object ChapterTable { - const val TABLE = "chapters" const val COL_ID = "_id" @@ -51,8 +50,9 @@ object ChapterTable { get() = "CREATE INDEX ${TABLE}_${COL_MANGA_ID}_index ON $TABLE($COL_MANGA_ID)" val createUnreadChaptersIndexQuery: String - get() = "CREATE INDEX ${TABLE}_unread_by_manga_index ON $TABLE($COL_MANGA_ID, $COL_READ) " + - "WHERE $COL_READ = 0" + get() = + "CREATE INDEX ${TABLE}_unread_by_manga_index ON $TABLE($COL_MANGA_ID, $COL_READ) " + + "WHERE $COL_READ = 0" val sourceOrderUpdateQuery: String get() = "ALTER TABLE $TABLE ADD COLUMN $COL_SOURCE_ORDER INTEGER DEFAULT 0" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt index 9d19544a4932..d328eb5266de 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/HistoryTable.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.database.tables object HistoryTable { - /** * Table name */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt index 578a85bbc9de..e7028d3ee806 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.database.tables object MangaCategoryTable { - const val TABLE = "mangas_categories" const val COL_ID = "_id" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt index 0979c0c059b2..126d26c80e0a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.database.tables object MangaTable { - const val TABLE = "mangas" const val COL_ID = "_id" @@ -71,8 +70,9 @@ object MangaTable { get() = "CREATE INDEX ${TABLE}_${COL_URL}_index ON $TABLE($COL_URL)" val createLibraryIndexQuery: String - get() = "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " + - "WHERE $COL_FAVORITE = 1" + get() = + "CREATE INDEX library_${COL_FAVORITE}_index ON $TABLE($COL_FAVORITE) " + + "WHERE $COL_FAVORITE = 1" val addCoverLastModified: String get() = "ALTER TABLE $TABLE ADD COLUMN $COL_COVER_LAST_MODIFIED LONG NOT NULL DEFAULT 0" @@ -84,11 +84,12 @@ object MangaTable { * Used with addDateAdded to populate it with the oldest chapter fetch date. */ val backfillDateAdded: String - get() = "UPDATE $TABLE SET $COL_DATE_ADDED = " + - "(SELECT MIN(${ChapterTable.COL_DATE_FETCH}) " + - "FROM $TABLE INNER JOIN ${ChapterTable.TABLE} " + - "ON $TABLE.$COL_ID = ${ChapterTable.TABLE}.${ChapterTable.COL_MANGA_ID} " + - "GROUP BY $TABLE.$COL_ID)" + get() = + "UPDATE $TABLE SET $COL_DATE_ADDED = " + + "(SELECT MIN(${ChapterTable.COL_DATE_FETCH}) " + + "FROM $TABLE INNER JOIN ${ChapterTable.TABLE} " + + "ON $TABLE.$COL_ID = ${ChapterTable.TABLE}.${ChapterTable.COL_MANGA_ID} " + + "GROUP BY $TABLE.$COL_ID)" val addUpdateStrategy: String get() = "ALTER TABLE $TABLE ADD COLUMN $COL_UPDATE_STRATEGY INTEGER NOT NULL DEFAULT 0" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MergedTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MergedTable.kt index 18ac16fb75d0..ed0cdc17a344 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MergedTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MergedTable.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.database.tables object MergedTable { - const val TABLE = "merged" const val COL_MERGE_ID = "mergeID" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt index c8dff441a452..4b23d6c36786 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/TrackTable.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.database.tables object TrackTable { - const val TABLE = "manga_sync" const val COL_ID = "_id" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt index eaccdcac0de8..27f8ef9ad7a2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt @@ -7,10 +7,10 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.SourceManager -import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.onEach import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.concurrent.TimeUnit /** * Cache where we dump the downloads directory from the filesystem. This class is needed because @@ -29,7 +29,6 @@ class DownloadCache( private val sourceManager: SourceManager, private val preferences: PreferencesHelper = Injekt.get() ) { - /** * The interval after which this cache should be invalidated. 1 hour shouldn't cause major * issues, as the cache is only used for UI feedback. @@ -69,7 +68,11 @@ class DownloadCache( * @param manga the manga of the chapter. * @param skipCache whether to skip the directory cache and check in the filesystem. */ - fun isChapterDownloaded(chapter: Chapter, manga: Manga, skipCache: Boolean): Boolean { + fun isChapterDownloaded( + chapter: Chapter, + manga: Manga, + skipCache: Boolean + ): Boolean { if (skipCache) { val source = sourceManager.get(manga.source) ?: return false return provider.findChapterDir(chapter, manga, source) != null @@ -126,27 +129,30 @@ class DownloadCache( val onlineSources = sourceManager.getVisibleOnlineSources() // SY <-- - val sourceDirs = rootDir.dir.listFiles() - .orEmpty() - .associate { it.name to SourceDirectory(it) } - .mapNotNullKeys { entry -> - onlineSources.find { provider.getSourceDirName(it) == entry.key }?.id - } + val sourceDirs = + rootDir.dir.listFiles() + .orEmpty() + .associate { it.name to SourceDirectory(it) } + .mapNotNullKeys { entry -> + onlineSources.find { provider.getSourceDirName(it) == entry.key }?.id + } rootDir.files = sourceDirs sourceDirs.values.forEach { sourceDir -> - val mangaDirs = sourceDir.dir.listFiles() - .orEmpty() - .associateNotNullKeys { it.name to MangaDirectory(it) } + val mangaDirs = + sourceDir.dir.listFiles() + .orEmpty() + .associateNotNullKeys { it.name to MangaDirectory(it) } sourceDir.files = mangaDirs mangaDirs.values.forEach { mangaDir -> - val chapterDirs = mangaDir.dir.listFiles() - .orEmpty() - .mapNotNull { it.name } - .toHashSet() + val chapterDirs = + mangaDir.dir.listFiles() + .orEmpty() + .mapNotNull { it.name } + .toHashSet() mangaDir.files = chapterDirs } @@ -161,7 +167,11 @@ class DownloadCache( * @param manga the manga of the chapter. */ @Synchronized - fun addChapter(chapterDirName: String, mangaUniFile: UniFile, manga: Manga) { + fun addChapter( + chapterDirName: String, + mangaUniFile: UniFile, + manga: Manga + ) { // Retrieve the cached source directory or cache a new one var sourceDir = rootDir.files[manga.source] if (sourceDir == null) { @@ -190,7 +200,10 @@ class DownloadCache( * @param manga the manga of the chapter. */ @Synchronized - fun removeChapter(chapter: Chapter, manga: Manga) { + fun removeChapter( + chapter: Chapter, + manga: Manga + ) { val sourceDir = rootDir.files[manga.source] ?: return val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] ?: return provider.getValidChapterDirNames(chapter).forEach { @@ -207,7 +220,10 @@ class DownloadCache( * @param manga the manga of the chapter. */ @Synchronized - fun removeChapters(chapters: List, manga: Manga) { + fun removeChapters( + chapters: List, + manga: Manga + ) { val sourceDir = rootDir.files[manga.source] ?: return val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] ?: return chapters.forEach { chapter -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index a8af6e17fc6f..2f95bd7c58d0 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -22,7 +22,6 @@ import uy.kohesive.injekt.injectLazy * @param context the application context. */ class DownloadManager(private val context: Context) { - /** * The sources manager. */ @@ -124,7 +123,11 @@ class DownloadManager(private val context: Context) { * @param chapters the list of chapters to enqueue. * @param autoStart whether to start the downloader after enqueing the chapters. */ - fun downloadChapters(manga: Manga, chapters: List, autoStart: Boolean = true) { + fun downloadChapters( + manga: Manga, + chapters: List, + autoStart: Boolean = true + ) { downloader.queueChapters(manga, chapters, autoStart) } @@ -136,7 +139,11 @@ class DownloadManager(private val context: Context) { * @param chapter the downloaded chapter. * @return an observable containing the list of pages from the chapter. */ - fun buildPageList(source: Source, manga: Manga, chapter: Chapter): Observable> { + fun buildPageList( + source: Source, + manga: Manga, + chapter: Chapter + ): Observable> { return buildPageList(provider.findChapterDir(chapter, manga, source)) } @@ -148,8 +155,9 @@ class DownloadManager(private val context: Context) { */ private fun buildPageList(chapterDir: UniFile?): Observable> { return Observable.fromCallable { - val files = chapterDir?.listFiles().orEmpty() - .filter { "image" in it.type.orEmpty() } + val files = + chapterDir?.listFiles().orEmpty() + .filter { "image" in it.type.orEmpty() } if (files.isEmpty()) { throw Exception("Page list is empty") @@ -169,7 +177,11 @@ class DownloadManager(private val context: Context) { * @param manga the manga of the chapter. * @param skipCache whether to skip the directory cache and check in the filesystem. */ - fun isChapterDownloaded(chapter: Chapter, manga: Manga, skipCache: Boolean = false): Boolean { + fun isChapterDownloaded( + chapter: Chapter, + manga: Manga, + skipCache: Boolean = false + ): Boolean { return cache.isChapterDownloaded(chapter, manga, skipCache) } @@ -198,7 +210,11 @@ class DownloadManager(private val context: Context) { * @param manga the manga of the chapters. * @param source the source of the chapters. */ - fun deleteChapters(chapters: List, manga: Manga, source: Source) { + fun deleteChapters( + chapters: List, + manga: Manga, + source: Source + ) { queue.remove(chapters) val chapterDirs = provider.findChapterDirs(chapters, manga, source) chapterDirs.forEach { it.delete() } @@ -214,7 +230,10 @@ class DownloadManager(private val context: Context) { * @param manga the manga to delete. * @param source the source of the manga. */ - fun deleteManga(manga: Manga, source: Source) { + fun deleteManga( + manga: Manga, + source: Source + ) { queue.remove(manga) provider.findMangaDir(manga, source)?.delete() cache.removeManga(manga) @@ -226,7 +245,10 @@ class DownloadManager(private val context: Context) { * @param chapters the list of chapters to delete. * @param manga the manga of the chapters. */ - fun enqueueDeleteChapters(chapters: List, manga: Manga) { + fun enqueueDeleteChapters( + chapters: List, + manga: Manga + ) { pendingDeleter.addChapters(chapters, manga) } @@ -249,15 +271,21 @@ class DownloadManager(private val context: Context) { * @param oldChapter the existing chapter with the old name. * @param newChapter the target chapter with the new name. */ - fun renameChapter(source: Source, manga: Manga, oldChapter: Chapter, newChapter: Chapter) { + fun renameChapter( + source: Source, + manga: Manga, + oldChapter: Chapter, + newChapter: Chapter + ) { val oldNames = provider.getValidChapterDirNames(oldChapter) val newName = provider.getChapterDirName(newChapter) val mangaDir = provider.getMangaDir(manga, source) // Assume there's only 1 version of the chapter name formats present - val oldFolder = oldNames.asSequence() - .mapNotNull { mangaDir.findFile(it) } - .firstOrNull() + val oldFolder = + oldNames.asSequence() + .mapNotNull { mangaDir.findFile(it) } + .firstOrNull() if (oldFolder?.renameTo(newName) == true) { cache.removeChapter(oldChapter, manga) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt index aef969c14bf4..65e1b841ecd4 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt @@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationManager -import java.util.regex.Pattern import uy.kohesive.injekt.injectLazy +import java.util.regex.Pattern /** * DownloadNotifier is used to show notifications when downloading one or multiple chapters. @@ -21,7 +21,6 @@ import uy.kohesive.injekt.injectLazy * @param context context of application */ internal class DownloadNotifier(private val context: Context) { - private val preferences: PreferencesHelper by injectLazy() private val progressNotificationBuilder by lazy { @@ -106,11 +105,12 @@ internal class DownloadNotifier(private val context: Context) { ) } - val downloadingProgressText = context.getString( - R.string.chapter_downloading_progress, - download.downloadedImages, - download.pages!!.size - ) + val downloadingProgressText = + context.getString( + R.string.chapter_downloading_progress, + download.downloadedImages, + download.pages!!.size + ) if (preferences.hideNotificationContent()) { setContentTitle(downloadingProgressText) @@ -214,7 +214,10 @@ internal class DownloadNotifier(private val context: Context) { * @param error string containing error information. * @param chapter string containing chapter title. */ - fun onError(error: String? = null, chapter: String? = null) { + fun onError( + error: String? = null, + chapter: String? = null + ) { // Create notification with(errorNotificationBuilder) { setContentTitle( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadPendingDeleter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadPendingDeleter.kt index 08b323ffc712..974d98b9534d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadPendingDeleter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadPendingDeleter.kt @@ -13,7 +13,6 @@ import uy.kohesive.injekt.injectLazy * @param context the application context. */ class DownloadPendingDeleter(context: Context) { - /** * Gson instance to encode and decode chapters. */ @@ -36,36 +35,40 @@ class DownloadPendingDeleter(context: Context) { * @param manga the manga of the chapters. */ @Synchronized - fun addChapters(chapters: List, manga: Manga) { + fun addChapters( + chapters: List, + manga: Manga + ) { val lastEntry = lastAddedEntry - val newEntry = if (lastEntry != null && lastEntry.manga.id == manga.id) { - // Append new chapters - val newChapters = lastEntry.chapters.addUniqueById(chapters) - - // If no chapters were added, do nothing - if (newChapters.size == lastEntry.chapters.size) return - - // Last entry matches the manga, reuse it to avoid decoding json from preferences - lastEntry.copy(chapters = newChapters) - } else { - val existingEntry = prefs.getString(manga.id!!.toString(), null) - if (existingEntry != null) { - // Existing entry found on preferences, decode json and add the new chapter - val savedEntry = gson.fromJson(existingEntry) - + val newEntry = + if (lastEntry != null && lastEntry.manga.id == manga.id) { // Append new chapters - val newChapters = savedEntry.chapters.addUniqueById(chapters) + val newChapters = lastEntry.chapters.addUniqueById(chapters) // If no chapters were added, do nothing - if (newChapters.size == savedEntry.chapters.size) return + if (newChapters.size == lastEntry.chapters.size) return - savedEntry.copy(chapters = newChapters) + // Last entry matches the manga, reuse it to avoid decoding json from preferences + lastEntry.copy(chapters = newChapters) } else { - // No entry has been found yet, create a new one - Entry(chapters.map { it.toEntry() }, manga.toEntry()) + val existingEntry = prefs.getString(manga.id!!.toString(), null) + if (existingEntry != null) { + // Existing entry found on preferences, decode json and add the new chapter + val savedEntry = gson.fromJson(existingEntry) + + // Append new chapters + val newChapters = savedEntry.chapters.addUniqueById(chapters) + + // If no chapters were added, do nothing + if (newChapters.size == savedEntry.chapters.size) return + + savedEntry.copy(chapters = newChapters) + } else { + // No entry has been found yet, create a new one + Entry(chapters.map { it.toEntry() }, manga.toEntry()) + } } - } // Save current state val json = gson.toJson(newEntry) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt index 82852f41e155..0d119a264b8f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt @@ -23,7 +23,6 @@ import uy.kohesive.injekt.injectLazy * @param context the application context. */ class DownloadProvider(private val context: Context) { - private val preferences: PreferencesHelper by injectLazy() private val scope = CoroutineScope(Job() + Dispatchers.Main) @@ -31,11 +30,12 @@ class DownloadProvider(private val context: Context) { /** * The root directory for downloads. */ - private var downloadsDir = preferences.downloadsDirectory().get().let { - val dir = UniFile.fromUri(context, Uri.parse(it)) - DiskUtil.createNoMediaFile(dir, context) - dir - } + private var downloadsDir = + preferences.downloadsDirectory().get().let { + val dir = UniFile.fromUri(context, Uri.parse(it)) + DiskUtil.createNoMediaFile(dir, context) + dir + } init { preferences.downloadsDirectory().asFlow() @@ -49,7 +49,10 @@ class DownloadProvider(private val context: Context) { * @param manga the manga to query. * @param source the source of the manga. */ - internal fun getMangaDir(manga: Manga, source: Source): UniFile { + internal fun getMangaDir( + manga: Manga, + source: Source + ): UniFile { try { return downloadsDir .createDirectory(getSourceDirName(source)) @@ -74,7 +77,10 @@ class DownloadProvider(private val context: Context) { * @param manga the manga to query. * @param source the source of the manga. */ - fun findMangaDir(manga: Manga, source: Source): UniFile? { + fun findMangaDir( + manga: Manga, + source: Source + ): UniFile? { val sourceDir = findSourceDir(source) return sourceDir?.findFile(getMangaDirName(manga)) } @@ -86,7 +92,11 @@ class DownloadProvider(private val context: Context) { * @param manga the manga of the chapter. * @param source the source of the chapter. */ - fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? { + fun findChapterDir( + chapter: Chapter, + manga: Manga, + source: Source + ): UniFile? { val mangaDir = findMangaDir(manga, source) return getValidChapterDirNames(chapter).asSequence() .mapNotNull { mangaDir?.findFile(it) } @@ -100,7 +110,11 @@ class DownloadProvider(private val context: Context) { * @param manga the manga of the chapter. * @param source the source of the chapter. */ - fun findChapterDirs(chapters: List, manga: Manga, source: Source): List { + fun findChapterDirs( + chapters: List, + manga: Manga, + source: Source + ): List { val mangaDir = findMangaDir(manga, source) ?: return emptyList() return chapters.mapNotNull { chapter -> getValidChapterDirNames(chapter).asSequence() @@ -149,7 +163,6 @@ class DownloadProvider(private val context: Context) { fun getValidChapterDirNames(chapter: Chapter): List { return listOf( getChapterDirName(chapter), - // Legacy chapter directory name used in v0.9.2 and before DiskUtil.buildValidFilename(chapter.name) ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt index 435bcd6575f7..f48f7047c183 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadService.kt @@ -33,9 +33,7 @@ import uy.kohesive.injekt.injectLazy * While the downloader is running, a wake lock will be held. */ class DownloadService : Service() { - companion object { - /** * Relay used to know when the service is running. */ @@ -103,7 +101,11 @@ class DownloadService : Service() { /** * Not used. */ - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + override fun onStartCommand( + intent: Intent?, + flags: Int, + startId: Int + ): Int { return START_NOT_STICKY } @@ -120,18 +122,19 @@ class DownloadService : Service() { * @see onNetworkStateChanged */ private fun listenNetworkChanges() { - subscriptions += ReactiveNetwork.observeNetworkConnectivity(applicationContext) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { state -> - onNetworkStateChanged(state) - }, - { - toast(R.string.download_queue_error) - stopSelf() - } - ) + subscriptions += + ReactiveNetwork.observeNetworkConnectivity(applicationContext) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { state -> + onNetworkStateChanged(state) + }, + { + toast(R.string.download_queue_error) + stopSelf() + } + ) } /** @@ -153,7 +156,7 @@ class DownloadService : Service() { downloadManager.stopDownloads(getString(R.string.download_notifier_no_network)) } else -> { - /* Do nothing */ + // Do nothing } } } @@ -162,13 +165,14 @@ class DownloadService : Service() { * Listens to downloader status. Enables or disables the wake lock depending on the status. */ private fun listenDownloaderState() { - subscriptions += downloadManager.runningRelay.subscribe { running -> - if (running) { - wakeLock.acquireIfNeeded() - } else { - wakeLock.releaseIfNeeded() + subscriptions += + downloadManager.runningRelay.subscribe { running -> + if (running) { + wakeLock.acquireIfNeeded() + } else { + wakeLock.releaseIfNeeded() + } } - } } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt index de9b48630aae..a604f0f8681e 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt @@ -18,7 +18,6 @@ class DownloadStore( context: Context, private val sourceManager: SourceManager ) { - /** * Preference file where active downloads are stored. */ @@ -76,18 +75,20 @@ class DownloadStore( * Returns the list of downloads to restore. It should be called in a background thread. */ fun restore(): List { - val objs = preferences.all - .mapNotNull { it.value as? String } - .mapNotNull { deserialize(it) } - .sortedBy { it.order } + val objs = + preferences.all + .mapNotNull { it.value as? String } + .mapNotNull { deserialize(it) } + .sortedBy { it.order } val downloads = mutableListOf() if (objs.isNotEmpty()) { val cachedManga = mutableMapOf() for ((mangaId, chapterId) in objs) { - val manga = cachedManga.getOrPut(mangaId) { - db.getManga(mangaId).executeAsBlocking() - } ?: continue + val manga = + cachedManga.getOrPut(mangaId) { + db.getManga(mangaId).executeAsBlocking() + } ?: continue val source = sourceManager.get(manga.source) as? HttpSource ?: continue val chapter = db.getChapter(chapterId).executeAsBlocking() ?: continue downloads.add(Download(source, manga, chapter)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index d73b75daf11e..adbf82af0041 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -24,7 +24,6 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.system.ImageUtil import exh.isEhBasedSource -import java.io.File import kotlinx.coroutines.async import okhttp3.Response import rx.Observable @@ -34,6 +33,7 @@ import rx.subscriptions.CompositeSubscription import timber.log.Timber import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.io.File /** * This class is the one in charge of downloading chapters. @@ -55,7 +55,6 @@ class Downloader( private val cache: DownloadCache, private val sourceManager: SourceManager ) { - private val preferences: PreferencesHelper by injectLazy() private val chapterCache: ChapterCache by injectLazy() @@ -189,29 +188,30 @@ class Downloader( subscriptions.clear() - subscriptions += downloadsRelay.concatMapIterable { it } - // Concurrently download from 5 different sources - .groupBy { it.source } - .flatMap( - { bySource -> - bySource.concatMap { download -> - downloadChapter(download).subscribeOn(Schedulers.io()) + subscriptions += + downloadsRelay.concatMapIterable { it } + // Concurrently download from 5 different sources + .groupBy { it.source } + .flatMap( + { bySource -> + bySource.concatMap { download -> + downloadChapter(download).subscribeOn(Schedulers.io()) + } + }, + 5 + ) + .onBackpressureLatest() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + completeDownload(it) + }, + { error -> + DownloadService.stop(context) + Timber.e(error) + notifier.onError(error.message) } - }, - 5 - ) - .onBackpressureLatest() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - completeDownload(it) - }, - { error -> - DownloadService.stop(context) - Timber.e(error) - notifier.onError(error.message) - } - ) + ) } /** @@ -232,24 +232,30 @@ class Downloader( * @param chapters the list of chapters to download. * @param autoStart whether to start the downloader after enqueing the chapters. */ - fun queueChapters(manga: Manga, chapters: List, autoStart: Boolean) = launchUI { + fun queueChapters( + manga: Manga, + chapters: List, + autoStart: Boolean + ) = launchUI { val source = sourceManager.get(manga.source) as? HttpSource ?: return@launchUI val wasEmpty = queue.isEmpty() // Called in background thread, the operation can be slow with SAF. - val chaptersWithoutDir = async { - chapters - // Filter out those already downloaded. - .filter { provider.findChapterDir(it, manga, source) == null } - // Add chapters to queue from the start. - .sortedByDescending { it.source_order } - } + val chaptersWithoutDir = + async { + chapters + // Filter out those already downloaded. + .filter { provider.findChapterDir(it, manga, source) == null } + // Add chapters to queue from the start. + .sortedByDescending { it.source_order } + } // Runs in main thread (synchronization needed). - val chaptersToQueue = chaptersWithoutDir.await() - // Filter out those already enqueued. - .filter { chapter -> queue.none { it.chapter.id == chapter.id } } - // Create a download for each one. - .map { Download(source, manga, it) } + val chaptersToQueue = + chaptersWithoutDir.await() + // Filter out those already enqueued. + .filter { chapter -> queue.none { it.chapter.id == chapter.id } } + // Create a download for each one. + .map { Download(source, manga, it) } if (chaptersToQueue.isNotEmpty()) { queue.addAll(chaptersToQueue) @@ -271,62 +277,64 @@ class Downloader( * * @param download the chapter to be downloaded. */ - private fun downloadChapter(download: Download): Observable = Observable.defer { - val mangaDir = provider.getMangaDir(download.manga, download.source) - - val availSpace = DiskUtil.getAvailableStorageSpace(mangaDir) - if (availSpace != -1L && availSpace < MIN_DISK_SPACE) { - download.status = Download.ERROR - notifier.onError(context.getString(R.string.download_insufficient_space), download.chapter.name) - return@defer Observable.just(download) - } + private fun downloadChapter(download: Download): Observable = + Observable.defer { + val mangaDir = provider.getMangaDir(download.manga, download.source) - val chapterDirname = provider.getChapterDirName(download.chapter) - val tmpDir = mangaDir.createDirectory(chapterDirname + TMP_DIR_SUFFIX) + val availSpace = DiskUtil.getAvailableStorageSpace(mangaDir) + if (availSpace != -1L && availSpace < MIN_DISK_SPACE) { + download.status = Download.ERROR + notifier.onError(context.getString(R.string.download_insufficient_space), download.chapter.name) + return@defer Observable.just(download) + } - val pageListObservable = if (download.pages == null) { - // Pull page list from network and add them to download object - download.source.fetchPageList(download.chapter) - .doOnNext { pages -> - if (pages.isEmpty()) { - throw Exception(context.getString(R.string.page_list_empty_error)) - } - download.pages = pages + val chapterDirname = provider.getChapterDirName(download.chapter) + val tmpDir = mangaDir.createDirectory(chapterDirname + TMP_DIR_SUFFIX) + + val pageListObservable = + if (download.pages == null) { + // Pull page list from network and add them to download object + download.source.fetchPageList(download.chapter) + .doOnNext { pages -> + if (pages.isEmpty()) { + throw Exception(context.getString(R.string.page_list_empty_error)) + } + download.pages = pages + } + } else { + // Or if the page list already exists, start from the file + Observable.just(download.pages!!) } - } else { - // Or if the page list already exists, start from the file - Observable.just(download.pages!!) - } - pageListObservable - .doOnNext { _ -> - // Delete all temporary (unfinished) files - tmpDir.listFiles() - ?.filter { it.name!!.endsWith(".tmp") } - ?.forEach { it.delete() } + pageListObservable + .doOnNext { _ -> + // Delete all temporary (unfinished) files + tmpDir.listFiles() + ?.filter { it.name!!.endsWith(".tmp") } + ?.forEach { it.delete() } - download.downloadedImages = 0 - download.status = Download.DOWNLOADING - } - // Get all the URLs to the source images, fetch pages if necessary - .flatMap { download.source.fetchAllImageUrlsFromPageList(it) } - // Start downloading images, consider we can have downloaded images already - // Concurrently do 5 pages at a time - .flatMap({ page -> getOrDownloadImage(page, download, tmpDir) }, 5) - .onBackpressureLatest() - // Do when page is downloaded. - .doOnNext { notifier.onProgressChange(download) } - .toList() - .map { download } - // Do after download completes - .doOnNext { ensureSuccessfulDownload(download, mangaDir, tmpDir, chapterDirname) } - // If the page list threw, it will resume here - .onErrorReturn { error -> - download.status = Download.ERROR - notifier.onError(error.message, download.chapter.name) - download - } - } + download.downloadedImages = 0 + download.status = Download.DOWNLOADING + } + // Get all the URLs to the source images, fetch pages if necessary + .flatMap { download.source.fetchAllImageUrlsFromPageList(it) } + // Start downloading images, consider we can have downloaded images already + // Concurrently do 5 pages at a time + .flatMap({ page -> getOrDownloadImage(page, download, tmpDir) }, 5) + .onBackpressureLatest() + // Do when page is downloaded. + .doOnNext { notifier.onProgressChange(download) } + .toList() + .map { download } + // Do after download completes + .doOnNext { ensureSuccessfulDownload(download, mangaDir, tmpDir, chapterDirname) } + // If the page list threw, it will resume here + .onErrorReturn { error -> + download.status = Download.ERROR + notifier.onError(error.message, download.chapter.name) + download + } + } /** * Returns the observable which gets the image from the filesystem if it exists or downloads it @@ -336,7 +344,11 @@ class Downloader( * @param download the download of the page. * @param tmpDir the temporary directory of the download. */ - private fun getOrDownloadImage(page: Page, download: Download, tmpDir: UniFile): Observable { + private fun getOrDownloadImage( + page: Page, + download: Download, + tmpDir: UniFile + ): Observable { // If the image URL is empty, do nothing if (page.imageUrl == null) { return Observable.just(page) @@ -352,11 +364,14 @@ class Downloader( val imageFile = tmpDir.listFiles()!!.find { it.name!!.startsWith("$filename.") } // If the image is already downloaded, do nothing. Otherwise download from network - val pageObservable = when { - imageFile != null -> Observable.just(imageFile) - chapterCache.isImageInCache(page.imageUrl!!) -> copyImageFromCache(chapterCache.getImageFile(page.imageUrl!!), tmpDir, filename) - else -> downloadImage(page, download.source, tmpDir, filename) - } + val pageObservable = + when { + imageFile != null -> Observable.just(imageFile) + chapterCache.isImageInCache( + page.imageUrl!! + ) -> copyImageFromCache(chapterCache.getImageFile(page.imageUrl!!), tmpDir, filename) + else -> downloadImage(page, download.source, tmpDir, filename) + } return pageObservable // When the image is ready, set image path, progress (just in case) and status @@ -383,14 +398,21 @@ class Downloader( * @param tmpDir the temporary directory of the download. * @param filename the filename of the image. */ - private fun downloadImage(page: Page, source: HttpSource, tmpDir: UniFile, filename: String): Observable { + private fun downloadImage( + page: Page, + source: HttpSource, + tmpDir: UniFile, + filename: String + ): Observable { page.status = Page.DOWNLOAD_IMAGE page.progress = 0 return /* SY --> If the source is E-Hentai request a new page if null */ Observable.just(Unit) .flatMap { if (page.imageUrl == null && source.isEhBasedSource()) { source.fetchImageUrl(page) - } else Observable.just(null) + } else { + Observable.just(null) + } } .doOnNext { imageUrl -> if (imageUrl != null) page.imageUrl = imageUrl @@ -426,7 +448,11 @@ class Downloader( * @param tmpDir the temporary directory of the download. * @param filename the filename of the image. */ - private fun copyImageFromCache(cacheFile: File, tmpDir: UniFile, filename: String): Observable { + private fun copyImageFromCache( + cacheFile: File, + tmpDir: UniFile, + filename: String + ): Observable { return Observable.just(cacheFile).map { val tmpFile = tmpDir.createFile("$filename.tmp") cacheFile.inputStream().use { input -> @@ -448,13 +474,17 @@ class Downloader( * @param response the network response of the image. * @param file the file where the image is already downloaded. */ - private fun getImageExtension(response: Response, file: UniFile): String { + private fun getImageExtension( + response: Response, + file: UniFile + ): String { // Read content type if available. - val mime = response.body.contentType()?.let { ct -> "${ct.type}/${ct.subtype}" } - // Else guess from the uri. - ?: context.contentResolver.getType(file.uri) - // Else read magic numbers. - ?: ImageUtil.findImageType { file.openInputStream() }?.mime + val mime = + response.body.contentType()?.let { ct -> "${ct.type}/${ct.subtype}" } + // Else guess from the uri. + ?: context.contentResolver.getType(file.uri) + // Else read magic numbers. + ?: ImageUtil.findImageType { file.openInputStream() }?.mime return MimeTypeMap.getSingleton().getExtensionFromMimeType(mime) ?: "jpg" } @@ -476,11 +506,12 @@ class Downloader( // Ensure that the chapter folder has all the images. val downloadedImages = tmpDir.listFiles().orEmpty().filterNot { it.name!!.endsWith(".tmp") } - download.status = if (downloadedImages.size == download.pages!!.size) { - Download.DOWNLOADED - } else { - Download.ERROR - } + download.status = + if (downloadedImages.size == download.pages!!.size) { + Download.DOWNLOADED + } else { + Download.ERROR + } // Only rename the directory if it's downloaded. if (download.status == Download.DOWNLOADED) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt index 5bd9b15bed51..b988d898889e 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt @@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.source.online.HttpSource import rx.subjects.PublishSubject class Download(val source: HttpSource, val manga: Manga, val chapter: Chapter) { - var pages: List? = null @Volatile diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt index 79dde6a6edd5..49165fce7f1b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt @@ -5,15 +5,14 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.DownloadStore import eu.kanade.tachiyomi.source.model.Page -import java.util.concurrent.CopyOnWriteArrayList import rx.Observable import rx.subjects.PublishSubject +import java.util.concurrent.CopyOnWriteArrayList class DownloadQueue( private val store: DownloadStore, private val queue: MutableList = CopyOnWriteArrayList() ) : List by queue { - private val statusSubject = PublishSubject.create() private val updatedRelay = PublishRelay.create() @@ -69,14 +68,14 @@ class DownloadQueue( updatedRelay.call(Unit) } - fun getActiveDownloads(): Observable = - Observable.from(this).filter { download -> download.status == Download.DOWNLOADING } + fun getActiveDownloads(): Observable = Observable.from(this).filter { download -> download.status == Download.DOWNLOADING } fun getStatusObservable(): Observable = statusSubject.onBackpressureBuffer() - fun getUpdatedObservable(): Observable> = updatedRelay.onBackpressureBuffer() - .startWith(Unit) - .map { this } + fun getUpdatedObservable(): Observable> = + updatedRelay.onBackpressureBuffer() + .startWith(Unit) + .map { this } private fun setPagesFor(download: Download) { if (download.status == Download.DOWNLOADED || download.status == Download.ERROR) { @@ -103,7 +102,10 @@ class DownloadQueue( .filter { it.status == Download.DOWNLOADING } } - private fun setPagesSubject(pages: List?, subject: PublishSubject?) { + private fun setPagesSubject( + pages: List?, + subject: PublishSubject? + ) { pages?.forEach { it.setStatusSubject(subject) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt index 91da7c558ae8..0383d63246b2 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt @@ -5,18 +5,20 @@ import android.util.Log import com.bumptech.glide.Priority import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.data.DataFetcher +import timber.log.Timber import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException import java.io.IOException import java.io.InputStream -import timber.log.Timber open class FileFetcher(private val filePath: String = "") : DataFetcher { - private var data: InputStream? = null - override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + override fun loadData( + priority: Priority, + callback: DataFetcher.DataCallback + ) { loadFromFile(callback) } @@ -24,7 +26,10 @@ open class FileFetcher(private val filePath: String = "") : DataFetcher) { + protected fun loadFromFile( + file: File, + callback: DataFetcher.DataCallback + ) { try { data = FileInputStream(file) } catch (e: FileNotFoundException) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaCustomCoverFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaCustomCoverFetcher.kt index 3d04f40c26c9..1d02ca085f27 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaCustomCoverFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaCustomCoverFetcher.kt @@ -12,8 +12,10 @@ open class LibraryMangaCustomCoverFetcher( private val manga: Manga, private val coverCache: CoverCache ) : FileFetcher() { - - override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + override fun loadData( + priority: Priority, + callback: DataFetcher.DataCallback + ) { getCustomCoverFile()?.let { loadFromFile(it, callback) } ?: callback.onLoadFailed(Exception("Custom cover file not found")) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaUrlFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaUrlFetcher.kt index 5d40dd6a4cb3..0a3ce13ad9f6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaUrlFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaUrlFetcher.kt @@ -22,8 +22,10 @@ class LibraryMangaUrlFetcher( private val manga: Manga, private val coverCache: CoverCache ) : LibraryMangaCustomCoverFetcher(manga, coverCache) { - - override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + override fun loadData( + priority: Priority, + callback: DataFetcher.DataCallback + ) { getCustomCoverFile()?.let { loadFromFile(it, callback) return @@ -44,12 +46,13 @@ class LibraryMangaUrlFetcher( val tmpFile = File(cover.path + ".tmp") try { // Retrieve destination stream, create parent folders if needed. - val output = try { - tmpFile.outputStream() - } catch (e: FileNotFoundException) { - tmpFile.parentFile!!.mkdirs() - tmpFile.outputStream() - } + val output = + try { + tmpFile.outputStream() + } catch (e: FileNotFoundException) { + tmpFile.parentFile!!.mkdirs() + tmpFile.outputStream() + } // Copy the file and rename to the original. data.use { output.use { data.copyTo(output) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaThumbnailModelLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaThumbnailModelLoader.kt index 3573246f5eac..32fb35f5e083 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaThumbnailModelLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaThumbnailModelLoader.kt @@ -15,10 +15,10 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.isLocal -import java.io.InputStream import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.io.InputStream /** * A class for loading a cover associated with a [Manga] that can be present in our own cache. @@ -32,7 +32,6 @@ import uy.kohesive.injekt.injectLazy * @param context the application context. */ class MangaThumbnailModelLoader : ModelLoader { - /** * Cover cache where persistent covers are stored. */ @@ -57,7 +56,6 @@ class MangaThumbnailModelLoader : ModelLoader { * Factory class for creating [MangaThumbnailModelLoader] instances. */ class Factory : ModelLoaderFactory { - override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { return MangaThumbnailModelLoader() } @@ -119,7 +117,10 @@ class MangaThumbnailModelLoader : ModelLoader { * * @param manga the model. */ - private fun getHeaders(manga: Manga, source: HttpSource?): Headers { + private fun getHeaders( + manga: Manga, + source: HttpSource? + ): Headers { if (source == null) return LazyHeaders.DEFAULT return cachedHeaders.getOrPut(manga.source) { @@ -133,7 +134,10 @@ class MangaThumbnailModelLoader : ModelLoader { } } - private inline fun LruCache.getOrPut(key: K, defaultValue: () -> V): V { + private inline fun LruCache.getOrPut( + key: K, + defaultValue: () -> V + ): V { val value = get(key) return if (value == null) { val answer = defaultValue() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/PassthroughModelLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/PassthroughModelLoader.kt index dd6d546f8c48..0dbc6160e28d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/PassthroughModelLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/PassthroughModelLoader.kt @@ -12,7 +12,6 @@ import java.io.IOException import java.io.InputStream class PassthroughModelLoader : ModelLoader { - override fun buildLoadData( model: InputStream, width: Int, @@ -27,7 +26,6 @@ class PassthroughModelLoader : ModelLoader { } class Fetcher(private val stream: InputStream) : DataFetcher { - override fun getDataClass(): Class { return InputStream::class.java } @@ -60,10 +58,7 @@ class PassthroughModelLoader : ModelLoader { * Factory class for creating [PassthroughModelLoader] instances. */ class Factory : ModelLoaderFactory { - - override fun build( - multiFactory: MultiModelLoaderFactory - ): ModelLoader { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { return PassthroughModelLoader() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt index f37be68ec923..673e6fb0383c 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt @@ -15,18 +15,20 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.module.AppGlideModule import com.bumptech.glide.request.RequestOptions import eu.kanade.tachiyomi.network.NetworkHelper -import java.io.InputStream -import java.nio.ByteBuffer import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.InputStream +import java.nio.ByteBuffer /** * Class used to update Glide module settings */ @GlideModule class TachiGlideModule : AppGlideModule() { - - override fun applyOptions(context: Context, builder: GlideBuilder) { + override fun applyOptions( + context: Context, + builder: GlideBuilder + ) { builder.setDiskCache(InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024)) builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565)) builder.setDefaultTransitionOptions( @@ -35,7 +37,11 @@ class TachiGlideModule : AppGlideModule() { ) } - override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + override fun registerComponents( + context: Context, + glide: Glide, + registry: Registry + ) { val networkFactory = OkHttpUrlLoader.Factory(Injekt.get().client) registry.replace( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiyomiImageDecoderGlideWrapper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiyomiImageDecoderGlideWrapper.kt index b598b23d2488..721e18b5035a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiyomiImageDecoderGlideWrapper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiyomiImageDecoderGlideWrapper.kt @@ -10,15 +10,17 @@ import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bumptech.glide.load.resource.bitmap.BitmapResource import com.bumptech.glide.load.resource.bitmap.Downsampler import eu.kanade.tachiyomi.util.system.ImageUtil +import tachiyomi.decoder.ImageDecoder import java.io.ByteArrayInputStream import java.io.InputStream import java.nio.ByteBuffer -import tachiyomi.decoder.ImageDecoder class TachiyomiImageDecoderGlideWrapper { class InputStreamDecoder(private val bitmapPool: BitmapPool) : ResourceDecoder { - - override fun handles(source: InputStream, options: Options): Boolean { + override fun handles( + source: InputStream, + options: Options + ): Boolean { return when (ImageUtil.findImageType(source)) { ImageUtil.ImageType.JXL, ImageUtil.ImageType.AVIF -> true ImageUtil.ImageType.HEIF -> Build.VERSION.SDK_INT < Build.VERSION_CODES.O @@ -26,7 +28,12 @@ class TachiyomiImageDecoderGlideWrapper { } } - override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource? { + override fun decode( + source: InputStream, + width: Int, + height: Int, + options: Options + ): Resource? { val decoder = ImageDecoder.newInstance(source) if (decoder == null || decoder.width == 0 || decoder.height == 0) { @@ -46,16 +53,22 @@ class TachiyomiImageDecoderGlideWrapper { } class ByteBufferDecoder(bitmapPool: BitmapPool) : ResourceDecoder { - private val streamDecoder = InputStreamDecoder(bitmapPool) - override fun handles(source: ByteBuffer, options: Options): Boolean { + override fun handles( + source: ByteBuffer, + options: Options + ): Boolean { val sourceCopy = ByteArray(source.remaining()) source.get(sourceCopy) return streamDecoder.handles(ByteArrayInputStream(sourceCopy), options) } - override fun decode(source: ByteBuffer, width: Int, height: Int, options: Options): Resource? = - streamDecoder.decode(ByteArrayInputStream(source.array()), width, height, options) + override fun decode( + source: ByteBuffer, + width: Int, + height: Int, + options: Options + ): Resource? = streamDecoder.decode(ByteArrayInputStream(source.array()), width, height, options) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt index 9e973103016b..d0ee6a6fe94c 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt @@ -9,13 +9,12 @@ import androidx.work.WorkManager import androidx.work.Worker import androidx.work.WorkerParameters import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import java.util.concurrent.TimeUnit import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.concurrent.TimeUnit class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { - override fun doWork(): Result { return if (LibraryUpdateService.start(context)) { Result.success() @@ -27,30 +26,38 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet companion object { private const val TAG = "LibraryUpdate" - fun setupTask(context: Context, prefInterval: Int? = null) { + fun setupTask( + context: Context, + prefInterval: Int? = null + ) { val preferences = Injekt.get() val interval = prefInterval ?: preferences.libraryUpdateInterval().get() if (interval > 0) { val restrictions = preferences.libraryUpdateRestriction()!! val acRestriction = "ac" in restrictions - val wifiRestriction = if ("wifi" in restrictions) { - NetworkType.UNMETERED - } else { - NetworkType.CONNECTED - } + val wifiRestriction = + if ("wifi" in restrictions) { + NetworkType.UNMETERED + } else { + NetworkType.CONNECTED + } - val constraints = Constraints.Builder() - .setRequiredNetworkType(wifiRestriction) - .setRequiresCharging(acRestriction) - .build() + val constraints = + Constraints.Builder() + .setRequiredNetworkType(wifiRestriction) + .setRequiresCharging(acRestriction) + .build() - val request = PeriodicWorkRequestBuilder( - interval.toLong(), TimeUnit.HOURS, - 10, TimeUnit.MINUTES - ) - .addTag(TAG) - .setConstraints(constraints) - .build() + val request = + PeriodicWorkRequestBuilder( + interval.toLong(), + TimeUnit.HOURS, + 10, + TimeUnit.MINUTES + ) + .addTag(TAG) + .setConstraints(constraints) + .build() WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request) } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index 0722165ee1c4..d81d27e8a467 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -22,12 +22,11 @@ import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.system.notification import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationManager +import uy.kohesive.injekt.injectLazy import java.text.DecimalFormat import java.text.DecimalFormatSymbols -import uy.kohesive.injekt.injectLazy class LibraryUpdateNotifier(private val context: Context) { - private val preferences: PreferencesHelper by injectLazy() /** @@ -65,12 +64,17 @@ class LibraryUpdateNotifier(private val context: Context) { * @param current the current progress. * @param total the total progress. */ - fun showProgressNotification(manga: Manga, current: Int, total: Int) { - val title = if (preferences.hideNotificationContent()) { - context.getString(R.string.notification_check_updates) - } else { - manga.title - } + fun showProgressNotification( + manga: Manga, + current: Int, + total: Int + ) { + val title = + if (preferences.hideNotificationContent()) { + context.getString(R.string.notification_check_updates) + } else { + manga.title + } context.notificationManager.notify( Notifications.ID_LIBRARY_PROGRESS, @@ -87,7 +91,10 @@ class LibraryUpdateNotifier(private val context: Context) { * @param errors List of entry titles that failed to update. * @param uri Uri for error log file containing all titles that failed. */ - fun showUpdateErrorNotification(errors: List, uri: Uri) { + fun showUpdateErrorNotification( + errors: List, + uri: Uri + ) { if (errors.isEmpty()) { return } @@ -133,7 +140,9 @@ class LibraryUpdateNotifier(private val context: Context) { if (updates.size == 1 && !preferences.hideNotificationContent()) { setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN)) } else { - setContentText(context.resources.getQuantityString(R.plurals.notification_new_chapters_summary, updates.size, updates.size)) + setContentText( + context.resources.getQuantityString(R.plurals.notification_new_chapters_summary, updates.size, updates.size) + ) if (!preferences.hideNotificationContent()) { setStyle( @@ -169,7 +178,10 @@ class LibraryUpdateNotifier(private val context: Context) { } } - private fun createNewChaptersNotification(manga: Manga, chapters: Array): Notification { + private fun createNewChaptersNotification( + manga: Manga, + chapters: Array + ): Notification { return context.notification(Notifications.CHANNEL_NEW_CHAPTERS) { setContentTitle(manga.title) @@ -194,18 +206,23 @@ class LibraryUpdateNotifier(private val context: Context) { // Mark chapters as read action addAction( - R.drawable.ic_glasses_black_24dp, context.getString(R.string.action_mark_as_read), + R.drawable.ic_glasses_black_24dp, + context.getString(R.string.action_mark_as_read), NotificationReceiver.markAsReadPendingBroadcast( context, - manga, chapters, Notifications.ID_NEW_CHAPTERS + manga, + chapters, + Notifications.ID_NEW_CHAPTERS ) ) // View chapters action addAction( - R.drawable.ic_book_24dp, context.getString(R.string.action_view_chapters), + R.drawable.ic_book_24dp, + context.getString(R.string.action_view_chapters), NotificationReceiver.openChapterPendingActivity( context, - manga, Notifications.ID_NEW_CHAPTERS + manga, + Notifications.ID_NEW_CHAPTERS ) ) } @@ -238,17 +255,19 @@ class LibraryUpdateNotifier(private val context: Context) { } private fun getNewChaptersDescription(chapters: Array): String { - val formatter = DecimalFormat( - "#.###", - DecimalFormatSymbols() - .apply { decimalSeparator = '.' } - ) + val formatter = + DecimalFormat( + "#.###", + DecimalFormatSymbols() + .apply { decimalSeparator = '.' } + ) - val displayableChapterNumbers = chapters - .filter { it.isRecognizedNumber } - .sortedBy { it.chapter_number } - .map { formatter.format(it.chapter_number) } - .toSet() + val displayableChapterNumbers = + chapters + .filter { it.isRecognizedNumber } + .sortedBy { it.chapter_number } + .map { formatter.format(it.chapter_number) } + .toSet() return when (displayableChapterNumbers.size) { // No sensible chapter numbers to show (i.e. no chapters have parsed chapter number) @@ -264,7 +283,11 @@ class LibraryUpdateNotifier(private val context: Context) { context.resources.getString(R.string.notification_chapters_single, displayableChapterNumbers.first()) } else { // "Chapter 2.5 and 10 more" - context.resources.getString(R.string.notification_chapters_single_and_more, displayableChapterNumbers.first(), remaining) + context.resources.getString( + R.string.notification_chapters_single_and_more, + displayableChapterNumbers.first(), + remaining + ) } } // Everything else (i.e. multiple parsed chapter numbers) @@ -274,7 +297,12 @@ class LibraryUpdateNotifier(private val context: Context) { // "Chapters 1, 2.5, 3, 4, 5 and 10 more" val remaining = displayableChapterNumbers.size - NOTIF_MAX_CHAPTERS val joinedChapterNumbers = displayableChapterNumbers.take(NOTIF_MAX_CHAPTERS).joinToString(", ") - context.resources.getQuantityString(R.plurals.notification_chapters_multiple_and_more, remaining, joinedChapterNumbers, remaining) + context.resources.getQuantityString( + R.plurals.notification_chapters_multiple_and_more, + remaining, + joinedChapterNumbers, + remaining + ) } else { // "Chapters 1, 2.5, 3" context.resources.getString(R.string.notification_chapters_multiple, displayableChapterNumbers.joinToString(", ")) @@ -287,10 +315,11 @@ class LibraryUpdateNotifier(private val context: Context) { * Returns an intent to open the main activity. */ private fun getNotificationIntent(): PendingIntent { - val intent = Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP - action = MainActivity.SHORTCUT_RECENTLY_UPDATED - } + val intent = + Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + action = MainActivity.SHORTCUT_RECENTLY_UPDATED + } return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateRanker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateRanker.kt index 172e1463e636..f411e1745558 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateRanker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateRanker.kt @@ -6,11 +6,11 @@ import eu.kanade.tachiyomi.data.database.models.Manga * This class will provide various functions to rank manga to efficiently schedule manga to update. */ object LibraryUpdateRanker { - - val rankingScheme = listOf( - (this::lexicographicRanking)(), - (this::latestFirstRanking)() - ) + val rankingScheme = + listOf( + (this::lexicographicRanking)(), + (this::latestFirstRanking)() + ) /** * Provides a total ordering over all the [Manga]s. diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index 273d21d6a3fc..ddaa651b14d8 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -33,15 +33,15 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.isServiceRunning import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES -import java.io.File -import java.util.ArrayList -import java.util.concurrent.atomic.AtomicInteger import rx.Observable import rx.Subscription import rx.schedulers.Schedulers import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.File +import java.util.ArrayList +import java.util.concurrent.atomic.AtomicInteger /** * This class will take care of updating the chapters of the manga from the library. It can be @@ -59,7 +59,6 @@ class LibraryUpdateService( val trackManager: TrackManager = Injekt.get(), val coverCache: CoverCache = Injekt.get() ) : Service() { - /** * Wake lock that will be held until the service is destroyed. */ @@ -83,7 +82,6 @@ class LibraryUpdateService( } companion object { - /** * Key for category to update. */ @@ -113,12 +111,17 @@ class LibraryUpdateService( * @param target defines what should be updated. * @return true if service newly started, false otherwise */ - fun start(context: Context, category: Category? = null, target: Target = Target.CHAPTERS): Boolean { + fun start( + context: Context, + category: Category? = null, + target: Target = Target.CHAPTERS + ): Boolean { if (!isRunning(context)) { - val intent = Intent(context, LibraryUpdateService::class.java).apply { - putExtra(KEY_TARGET, target) - category?.let { putExtra(KEY_CATEGORY, it.id) } - } + val intent = + Intent(context, LibraryUpdateService::class.java).apply { + putExtra(KEY_TARGET, target) + category?.let { putExtra(KEY_CATEGORY, it.id) } + } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { context.startService(intent) } else { @@ -181,41 +184,48 @@ class LibraryUpdateService( * @param startId the start id of this command. * @return the start value of the command. */ - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + override fun onStartCommand( + intent: Intent?, + flags: Int, + startId: Int + ): Int { if (intent == null) return START_NOT_STICKY - val target = intent.getSerializableExtra(KEY_TARGET) as? Target - ?: return START_NOT_STICKY + val target = + intent.getSerializableExtra(KEY_TARGET) as? Target + ?: return START_NOT_STICKY // Unsubscribe from any previous subscription if needed. subscription?.unsubscribe() // Update favorite manga. Destroy service when completed or in case of an error. - subscription = Observable - .defer { - val selectedScheme = preferences.libraryUpdatePrioritization().get() - val mangaList = getMangaToUpdate(intent, target) - .sortedWith(rankingScheme[selectedScheme]) - - // Update either chapter list or manga details. - when (target) { - Target.CHAPTERS -> updateChapterList(mangaList) - Target.COVERS -> updateCovers(mangaList) - Target.TRACKING -> updateTrackings(mangaList) - Target.DETAILS -> updateMangaMetadata(mangaList) - } - } - .subscribeOn(Schedulers.io()) - .subscribe( - { - }, - { - Timber.e(it) - stopSelf(startId) - }, - { - stopSelf(startId) + subscription = + Observable + .defer { + val selectedScheme = preferences.libraryUpdatePrioritization().get() + val mangaList = + getMangaToUpdate(intent, target) + .sortedWith(rankingScheme[selectedScheme]) + + // Update either chapter list or manga details. + when (target) { + Target.CHAPTERS -> updateChapterList(mangaList) + Target.COVERS -> updateCovers(mangaList) + Target.TRACKING -> updateTrackings(mangaList) + Target.DETAILS -> updateMangaMetadata(mangaList) + } } - ) + .subscribeOn(Schedulers.io()) + .subscribe( + { + }, + { + Timber.e(it) + stopSelf(startId) + }, + { + stopSelf(startId) + } + ) return START_REDELIVER_INTENT } @@ -227,21 +237,25 @@ class LibraryUpdateService( * @param target the target to update. * @return a list of manga to update */ - fun getMangaToUpdate(intent: Intent, target: Target): List { + fun getMangaToUpdate( + intent: Intent, + target: Target + ): List { val categoryId = intent.getIntExtra(KEY_CATEGORY, -1) - var listToUpdate = if (categoryId != -1) { - db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId } - } else { - val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt) - if (categoriesToUpdate.isNotEmpty()) { - db.getLibraryMangas().executeAsBlocking() - .filter { it.category in categoriesToUpdate } - .distinctBy { it.id } + var listToUpdate = + if (categoryId != -1) { + db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId } } else { - db.getLibraryMangas().executeAsBlocking().distinctBy { it.id } + val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt) + if (categoriesToUpdate.isNotEmpty()) { + db.getLibraryMangas().executeAsBlocking() + .filter { it.category in categoriesToUpdate } + .distinctBy { it.id } + } else { + db.getLibraryMangas().executeAsBlocking().distinctBy { it.id } + } } - } if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) { listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED } } @@ -333,7 +347,10 @@ class LibraryUpdateService( .map { manga -> manga.first } } - private fun downloadChapters(manga: Manga, chapters: List) { + private fun downloadChapters( + manga: Manga, + chapters: List + ) { // We don't want to start downloading while the library is updating, because websites // may don't like it and they could ban the user. downloadManager.downloadChapters(manga, chapters, false) @@ -420,8 +437,9 @@ class LibraryUpdateService( notifier.showProgressNotification(it, count++, mangaToUpdate.size) } .flatMap { manga -> - val source = sourceManager.get(manga.source) - ?: return@flatMap Observable.empty() + val source = + sourceManager.get(manga.source) + ?: return@flatMap Observable.empty() runAsObservable({ val networkManga = source.getMangaDetails(manga.toMangaInfo()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt index 3f74c375d8cd..ca9c8f386e28 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt @@ -19,10 +19,11 @@ object NotificationHandler { * @param context context of application */ internal fun openDownloadManagerPendingActivity(context: Context): PendingIntent { - val intent = Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT - action = MainActivity.SHORTCUT_DOWNLOADS - } + val intent = + Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT + action = MainActivity.SHORTCUT_DOWNLOADS + } return PendingIntent.getActivity(context, 0, intent, 0) } @@ -32,12 +33,16 @@ object NotificationHandler { * @param context context of application * @param file file containing image */ - internal fun openImagePendingActivity(context: Context, file: File): PendingIntent { - val intent = Intent(Intent.ACTION_VIEW).apply { - val uri = file.getUriCompat(context) - setDataAndType(uri, "image/*") - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION - } + internal fun openImagePendingActivity( + context: Context, + file: File + ): PendingIntent { + val intent = + Intent(Intent.ACTION_VIEW).apply { + val uri = file.getUriCompat(context) + setDataAndType(uri, "image/*") + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION + } return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } @@ -47,11 +52,15 @@ object NotificationHandler { * @param context context * @param uri uri of apk that is installed */ - fun installApkPendingActivity(context: Context, uri: Uri): PendingIntent { - val intent = Intent(Intent.ACTION_VIEW).apply { - setDataAndType(uri, ExtensionInstaller.APK_MIME) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION - } + fun installApkPendingActivity( + context: Context, + uri: Uri + ): PendingIntent { + val intent = + Intent(Intent.ACTION_VIEW).apply { + setDataAndType(uri, ExtensionInstaller.APK_MIME) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION + } return PendingIntent.getActivity(context, 0, intent, 0) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 5060cfbc85b5..0111c338d7fe 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -7,7 +7,6 @@ import android.content.Intent import android.net.Uri import android.os.Build import android.os.Handler -import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.BackupRestoreService import eu.kanade.tachiyomi.data.database.DatabaseHelper @@ -26,10 +25,11 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.toast -import java.io.File import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.io.File +import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID /** * Global [BroadcastReceiver] that runs on UI thread @@ -37,10 +37,12 @@ import uy.kohesive.injekt.injectLazy * NOTE: Use local broadcasts if possible. */ class NotificationReceiver : BroadcastReceiver() { - private val downloadManager: DownloadManager by injectLazy() - override fun onReceive(context: Context, intent: Intent) { + override fun onReceive( + context: Context, + intent: Intent + ) { when (intent.action) { // Dismiss notification ACTION_DISMISS_NOTIFICATION -> dismissNotification(context, intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)) @@ -74,10 +76,11 @@ class NotificationReceiver : BroadcastReceiver() { intent.getParcelableExtra(EXTRA_URI)!!, intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) ) - ACTION_CANCEL_RESTORE -> cancelRestore( - context, - intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) - ) + ACTION_CANCEL_RESTORE -> + cancelRestore( + context, + intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) + ) // Cancel library update and dismiss notification ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context, Notifications.ID_LIBRARY_PROGRESS) // Open reader activity @@ -108,7 +111,10 @@ class NotificationReceiver : BroadcastReceiver() { * * @param notificationId the id of the notification */ - private fun dismissNotification(context: Context, notificationId: Int) { + private fun dismissNotification( + context: Context, + notificationId: Int + ) { context.notificationManager.cancel(notificationId) } @@ -119,14 +125,19 @@ class NotificationReceiver : BroadcastReceiver() { * @param path path of file * @param notificationId id of notification */ - private fun shareImage(context: Context, path: String, notificationId: Int) { + private fun shareImage( + context: Context, + path: String, + notificationId: Int + ) { // Create intent - val intent = Intent(Intent.ACTION_SEND).apply { - val uri = File(path).getUriCompat(context) - putExtra(Intent.EXTRA_STREAM, uri) - type = "image/*" - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION - } + val intent = + Intent(Intent.ACTION_SEND).apply { + val uri = File(path).getUriCompat(context) + putExtra(Intent.EXTRA_STREAM, uri) + type = "image/*" + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION + } // Dismiss notification dismissNotification(context, notificationId) // Launch share activity @@ -140,12 +151,17 @@ class NotificationReceiver : BroadcastReceiver() { * @param path path of file * @param notificationId id of notification */ - private fun shareBackup(context: Context, uri: Uri, notificationId: Int) { - val sendIntent = Intent(Intent.ACTION_SEND).apply { - putExtra(Intent.EXTRA_STREAM, uri) - type = "application/json" - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION - } + private fun shareBackup( + context: Context, + uri: Uri, + notificationId: Int + ) { + val sendIntent = + Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_STREAM, uri) + type = "application/json" + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION + } // Dismiss notification dismissNotification(context, notificationId) // Launch share activity @@ -159,14 +175,19 @@ class NotificationReceiver : BroadcastReceiver() { * @param mangaId id of manga * @param chapterId id of chapter */ - private fun openChapter(context: Context, mangaId: Long, chapterId: Long) { + private fun openChapter( + context: Context, + mangaId: Long, + chapterId: Long + ) { val db = DatabaseHelper(context) val manga = db.getManga(mangaId).executeAsBlocking() val chapter = db.getChapter(chapterId).executeAsBlocking() if (manga != null && chapter != null) { - val intent = ReaderActivity.newIntent(context, manga, chapter).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP - } + val intent = + ReaderActivity.newIntent(context, manga, chapter).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + } context.startActivity(intent) } else { context.toast(context.getString(R.string.chapter_error)) @@ -179,7 +200,11 @@ class NotificationReceiver : BroadcastReceiver() { * @param path path of file * @param notificationId id of notification */ - private fun deleteImage(context: Context, path: String, notificationId: Int) { + private fun deleteImage( + context: Context, + path: String, + notificationId: Int + ) { // Dismiss notification dismissNotification(context, notificationId) @@ -196,7 +221,10 @@ class NotificationReceiver : BroadcastReceiver() { * @param context context of application * @param notificationId id of notification */ - private fun cancelRestore(context: Context, notificationId: Int) { + private fun cancelRestore( + context: Context, + notificationId: Int + ) { BackupRestoreService.stop(context) Handler().post { dismissNotification(context, notificationId) } } @@ -207,7 +235,10 @@ class NotificationReceiver : BroadcastReceiver() { * @param context context of application * @param notificationId id of notification */ - private fun cancelLibraryUpdate(context: Context, notificationId: Int) { + private fun cancelLibraryUpdate( + context: Context, + notificationId: Int + ) { LibraryUpdateService.stop(context) Handler().post { dismissNotification(context, notificationId) } } @@ -218,7 +249,10 @@ class NotificationReceiver : BroadcastReceiver() { * @param chapterUrls URLs of chapter to mark as read * @param mangaId id of manga */ - private fun markAsRead(chapterUrls: Array, mangaId: Long) { + private fun markAsRead( + chapterUrls: Array, + mangaId: Long + ) { val db: DatabaseHelper = Injekt.get() val preferences: PreferencesHelper = Injekt.get() val sourceManager: SourceManager = Injekt.get() @@ -310,9 +344,10 @@ class NotificationReceiver : BroadcastReceiver() { * @return [PendingIntent] */ internal fun resumeDownloadsPendingBroadcast(context: Context): PendingIntent { - val intent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_RESUME_DOWNLOADS - } + val intent = + Intent(context, NotificationReceiver::class.java).apply { + action = ACTION_RESUME_DOWNLOADS + } return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } @@ -323,9 +358,10 @@ class NotificationReceiver : BroadcastReceiver() { * @return [PendingIntent] */ internal fun pauseDownloadsPendingBroadcast(context: Context): PendingIntent { - val intent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_PAUSE_DOWNLOADS - } + val intent = + Intent(context, NotificationReceiver::class.java).apply { + action = ACTION_PAUSE_DOWNLOADS + } return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } @@ -336,9 +372,10 @@ class NotificationReceiver : BroadcastReceiver() { * @return [PendingIntent] */ internal fun clearDownloadsPendingBroadcast(context: Context): PendingIntent { - val intent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_CLEAR_DOWNLOADS - } + val intent = + Intent(context, NotificationReceiver::class.java).apply { + action = ACTION_CLEAR_DOWNLOADS + } return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } @@ -349,11 +386,15 @@ class NotificationReceiver : BroadcastReceiver() { * @param notificationId id of notification * @return [PendingIntent] */ - internal fun dismissNotificationPendingBroadcast(context: Context, notificationId: Int): PendingIntent { - val intent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_DISMISS_NOTIFICATION - putExtra(EXTRA_NOTIFICATION_ID, notificationId) - } + internal fun dismissNotificationPendingBroadcast( + context: Context, + notificationId: Int + ): PendingIntent { + val intent = + Intent(context, NotificationReceiver::class.java).apply { + action = ACTION_DISMISS_NOTIFICATION + putExtra(EXTRA_NOTIFICATION_ID, notificationId) + } return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } @@ -364,7 +405,11 @@ class NotificationReceiver : BroadcastReceiver() { * @param notificationId id of notification * @return [PendingIntent] */ - internal fun dismissNotification(context: Context, notificationId: Int, groupId: Int? = null) { + internal fun dismissNotification( + context: Context, + notificationId: Int, + groupId: Int? = null + ) { /* Group notifications always have at least 2 notifications: - Group summary notification @@ -374,14 +419,16 @@ class NotificationReceiver : BroadcastReceiver() { When programmatically dismissing this notification, the group notification is not automatically dismissed. */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - val groupKey = context.notificationManager.activeNotifications.find { - it.id == notificationId - }?.groupKey + val groupKey = + context.notificationManager.activeNotifications.find { + it.id == notificationId + }?.groupKey if (groupId != null && groupId != 0 && groupKey != null && groupKey.isNotEmpty()) { - val notifications = context.notificationManager.activeNotifications.filter { - it.groupKey == groupKey - } + val notifications = + context.notificationManager.activeNotifications.filter { + it.groupKey == groupKey + } if (notifications.size == 2) { context.notificationManager.cancel(groupId) @@ -401,12 +448,17 @@ class NotificationReceiver : BroadcastReceiver() { * @param notificationId id of notification * @return [PendingIntent] */ - internal fun shareImagePendingBroadcast(context: Context, path: String, notificationId: Int): PendingIntent { - val intent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_SHARE_IMAGE - putExtra(EXTRA_FILE_LOCATION, path) - putExtra(EXTRA_NOTIFICATION_ID, notificationId) - } + internal fun shareImagePendingBroadcast( + context: Context, + path: String, + notificationId: Int + ): PendingIntent { + val intent = + Intent(context, NotificationReceiver::class.java).apply { + action = ACTION_SHARE_IMAGE + putExtra(EXTRA_FILE_LOCATION, path) + putExtra(EXTRA_NOTIFICATION_ID, notificationId) + } return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } @@ -418,12 +470,17 @@ class NotificationReceiver : BroadcastReceiver() { * @param notificationId id of notification * @return [PendingIntent] */ - internal fun deleteImagePendingBroadcast(context: Context, path: String, notificationId: Int): PendingIntent { - val intent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_DELETE_IMAGE - putExtra(EXTRA_FILE_LOCATION, path) - putExtra(EXTRA_NOTIFICATION_ID, notificationId) - } + internal fun deleteImagePendingBroadcast( + context: Context, + path: String, + notificationId: Int + ): PendingIntent { + val intent = + Intent(context, NotificationReceiver::class.java).apply { + action = ACTION_DELETE_IMAGE + putExtra(EXTRA_FILE_LOCATION, path) + putExtra(EXTRA_NOTIFICATION_ID, notificationId) + } return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } @@ -434,7 +491,11 @@ class NotificationReceiver : BroadcastReceiver() { * @param manga manga of chapter * @param chapter chapter that needs to be opened */ - internal fun openChapterPendingActivity(context: Context, manga: Manga, chapter: Chapter): PendingIntent { + internal fun openChapterPendingActivity( + context: Context, + manga: Manga, + chapter: Chapter + ): PendingIntent { val newIntent = ReaderActivity.newIntent(context, manga, chapter) return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT) } @@ -445,7 +506,11 @@ class NotificationReceiver : BroadcastReceiver() { * @param context context of application * @param manga manga of chapter */ - internal fun openChapterPendingActivity(context: Context, manga: Manga, groupId: Int): PendingIntent { + internal fun openChapterPendingActivity( + context: Context, + manga: Manga, + groupId: Int + ): PendingIntent { val newIntent = Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) @@ -467,13 +532,14 @@ class NotificationReceiver : BroadcastReceiver() { chapters: Array, groupId: Int ): PendingIntent { - val newIntent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_MARK_AS_READ - putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray()) - putExtra(EXTRA_MANGA_ID, manga.id) - putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode()) - putExtra(EXTRA_GROUP_ID, groupId) - } + val newIntent = + Intent(context, NotificationReceiver::class.java).apply { + action = ACTION_MARK_AS_READ + putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray()) + putExtra(EXTRA_MANGA_ID, manga.id) + putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode()) + putExtra(EXTRA_GROUP_ID, groupId) + } return PendingIntent.getBroadcast(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT) } @@ -484,9 +550,10 @@ class NotificationReceiver : BroadcastReceiver() { * @return [PendingIntent] */ internal fun cancelLibraryUpdatePendingBroadcast(context: Context): PendingIntent { - val intent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_CANCEL_LIBRARY_UPDATE - } + val intent = + Intent(context, NotificationReceiver::class.java).apply { + action = ACTION_CANCEL_LIBRARY_UPDATE + } return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } @@ -497,10 +564,11 @@ class NotificationReceiver : BroadcastReceiver() { * @return [PendingIntent] */ internal fun openExtensionsPendingActivity(context: Context): PendingIntent { - val intent = Intent(context, MainActivity::class.java).apply { - action = MainActivity.SHORTCUT_EXTENSIONS - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - } + val intent = + Intent(context, MainActivity::class.java).apply { + action = MainActivity.SHORTCUT_EXTENSIONS + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + } return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } @@ -512,12 +580,17 @@ class NotificationReceiver : BroadcastReceiver() { * @param notificationId id of notification * @return [PendingIntent] */ - internal fun shareBackupPendingBroadcast(context: Context, uri: Uri, notificationId: Int): PendingIntent { - val intent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_SHARE_BACKUP - putExtra(EXTRA_URI, uri) - putExtra(EXTRA_NOTIFICATION_ID, notificationId) - } + internal fun shareBackupPendingBroadcast( + context: Context, + uri: Uri, + notificationId: Int + ): PendingIntent { + val intent = + Intent(context, NotificationReceiver::class.java).apply { + action = ACTION_SHARE_BACKUP + putExtra(EXTRA_URI, uri) + putExtra(EXTRA_NOTIFICATION_ID, notificationId) + } return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } @@ -528,12 +601,16 @@ class NotificationReceiver : BroadcastReceiver() { * @param uri uri of error log file * @return [PendingIntent] */ - internal fun openErrorLogPendingActivity(context: Context, uri: Uri): PendingIntent { - val intent = Intent().apply { - action = Intent.ACTION_VIEW - setDataAndType(uri, "text/plain") - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION - } + internal fun openErrorLogPendingActivity( + context: Context, + uri: Uri + ): PendingIntent { + val intent = + Intent().apply { + action = Intent.ACTION_VIEW + setDataAndType(uri, "text/plain") + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION + } return PendingIntent.getActivity(context, 0, intent, 0) } @@ -544,11 +621,15 @@ class NotificationReceiver : BroadcastReceiver() { * @param notificationId id of notification * @return [PendingIntent] */ - internal fun cancelRestorePendingBroadcast(context: Context, notificationId: Int): PendingIntent { - val intent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_CANCEL_RESTORE - putExtra(EXTRA_NOTIFICATION_ID, notificationId) - } + internal fun cancelRestorePendingBroadcast( + context: Context, + notificationId: Int + ): PendingIntent { + val intent = + Intent(context, NotificationReceiver::class.java).apply { + action = ACTION_CANCEL_RESTORE + putExtra(EXTRA_NOTIFICATION_ID, notificationId) + } return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt index ad5e3d588671..c87f1b86bbc8 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.util.system.notificationManager * Class to manage the basic information of all the notifications used in the app. */ object Notifications { - /** * Common notification channel and ids used anywhere. */ @@ -69,10 +68,11 @@ object Notifications { const val CHANNEL_CRASH_LOGS = "crash_logs_channel" const val ID_CRASH_LOGS = -601 - private val deprecatedChannels = listOf( - "downloader_channel", - "backup_restore_complete_channel" - ) + private val deprecatedChannels = + listOf( + "downloader_channel", + "backup_restore_complete_channel" + ) /** * Creates the notification channels introduced in Android Oreo. diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/EmptyPreferenceDataStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/EmptyPreferenceDataStore.kt index 2f2081a27bfe..1d8b346691df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/EmptyPreferenceDataStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/EmptyPreferenceDataStore.kt @@ -3,46 +3,81 @@ package eu.kanade.tachiyomi.data.preference import androidx.preference.PreferenceDataStore class EmptyPreferenceDataStore : PreferenceDataStore() { - - override fun getBoolean(key: String?, defValue: Boolean): Boolean { + override fun getBoolean( + key: String?, + defValue: Boolean + ): Boolean { return false } - override fun putBoolean(key: String?, value: Boolean) { + override fun putBoolean( + key: String?, + value: Boolean + ) { } - override fun getInt(key: String?, defValue: Int): Int { + override fun getInt( + key: String?, + defValue: Int + ): Int { return 0 } - override fun putInt(key: String?, value: Int) { + override fun putInt( + key: String?, + value: Int + ) { } - override fun getLong(key: String?, defValue: Long): Long { + override fun getLong( + key: String?, + defValue: Long + ): Long { return 0 } - override fun putLong(key: String?, value: Long) { + override fun putLong( + key: String?, + value: Long + ) { } - override fun getFloat(key: String?, defValue: Float): Float { + override fun getFloat( + key: String?, + defValue: Float + ): Float { return 0f } - override fun putFloat(key: String?, value: Float) { + override fun putFloat( + key: String?, + value: Float + ) { } - override fun getString(key: String?, defValue: String?): String? { + override fun getString( + key: String?, + defValue: String? + ): String? { return null } - override fun putString(key: String?, value: String?) { + override fun putString( + key: String?, + value: String? + ) { } - override fun getStringSet(key: String?, defValues: Set?): Set? { + override fun getStringSet( + key: String?, + defValues: Set? + ): Set? { return null } - override fun putStringSet(key: String?, values: Set?) { + override fun putStringSet( + key: String?, + values: Set? + ) { } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index f676a6378885..c0548e7c90e8 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -4,7 +4,6 @@ package eu.kanade.tachiyomi.data.preference * This class stores the keys for the preferences in the application. */ object PreferenceKeys { - const val themeMode = "pref_theme_mode_key" const val themeLight = "pref_theme_light_key" @@ -158,6 +157,7 @@ object PreferenceKeys { const val enableDoh = "enable_doh" const val hideLastUsedSource = "hide_last_used_source" + fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId" fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt index 65b69c00bdc3..a67baf11cf6a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt @@ -4,12 +4,11 @@ package eu.kanade.tachiyomi.data.preference * This class stores the values for the preferences in the application. */ object PreferenceValues { - // Keys are lowercase to match legacy string values enum class ThemeMode { light, dark, - system, + system } // Keys are lowercase to match legacy string values @@ -17,7 +16,7 @@ object PreferenceValues { default, blue, smoothie, - fumo, + fumo } // Keys are lowercase to match legacy string values @@ -25,13 +24,13 @@ object PreferenceValues { default, blue, amoled, - red, + red } enum class DisplayMode { COMPACT_GRID, COMFORTABLE_GRID, - LIST, + LIST } enum class NsfwAllowance { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 4183e3ed9e7f..a1d59427675a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -4,25 +4,25 @@ import android.content.Context import android.os.Environment import androidx.core.content.edit import androidx.preference.PreferenceManager -import com.f2prateek.rx.preferences.Preference as RxPreference import com.f2prateek.rx.preferences.RxSharedPreferences import com.tfcporciuncula.flow.FlowSharedPreferences import com.tfcporciuncula.flow.Preference import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys -import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode import eu.kanade.tachiyomi.data.preference.PreferenceValues.NsfwAllowance import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.anilist.Anilist import eu.kanade.tachiyomi.util.system.MiuiUtil +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onEach import java.io.File import java.text.DateFormat import java.text.SimpleDateFormat import java.util.Locale -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.onEach +import com.f2prateek.rx.preferences.Preference as RxPreference +import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys +import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values fun RxPreference.getOrDefault(): T = get() ?: defaultValue()!! @@ -35,22 +35,23 @@ fun Preference.asImmediateFlow(block: (value: T) -> Unit): Flow { @OptIn(ExperimentalCoroutinesApi::class) class PreferencesHelper(val context: Context) { - private val prefs = PreferenceManager.getDefaultSharedPreferences(context) private val rxPrefs = RxSharedPreferences.create(prefs) val flowPrefs = FlowSharedPreferences(prefs) - private val defaultDownloadsDir = File( - Environment.getExternalStorageDirectory().absolutePath + File.separator + - context.getString(R.string.app_name), - "downloads" - ).toURI() + private val defaultDownloadsDir = + File( + Environment.getExternalStorageDirectory().absolutePath + File.separator + + context.getString(R.string.app_name), + "downloads" + ).toURI() - private val defaultBackupDir = File( - Environment.getExternalStorageDirectory().absolutePath + File.separator + - context.getString(R.string.app_name), - "backup" - ).toURI() + private val defaultBackupDir = + File( + Environment.getExternalStorageDirectory().absolutePath + File.separator + + context.getString(R.string.app_name), + "backup" + ).toURI() fun startScreen() = prefs.getInt(Keys.startScreen, 1) @@ -146,7 +147,11 @@ class PreferencesHelper(val context: Context) { fun trackPassword(sync: TrackService) = prefs.getString(Keys.trackPassword(sync.id), "") - fun setTrackCredentials(sync: TrackService, username: String, password: String) { + fun setTrackCredentials( + sync: TrackService, + username: String, + password: String + ) { prefs.edit() .putString(Keys.trackUsername(sync.id), username) .putString(Keys.trackPassword(sync.id), password) @@ -159,10 +164,11 @@ class PreferencesHelper(val context: Context) { fun backupsDirectory() = flowPrefs.getString(Keys.backupDirectory, defaultBackupDir.toString()) - fun dateFormat(format: String = flowPrefs.getString(Keys.dateFormat, "").get()): DateFormat = when (format) { - "" -> DateFormat.getDateInstance(DateFormat.SHORT) - else -> SimpleDateFormat(format, Locale.getDefault()) - } + fun dateFormat(format: String = flowPrefs.getString(Keys.dateFormat, "").get()): DateFormat = + when (format) { + "" -> DateFormat.getDateInstance(DateFormat.SHORT) + else -> SimpleDateFormat(format, Locale.getDefault()) + } fun downloadsDirectory() = flowPrefs.getString(Keys.downloadsDirectory, defaultDownloadsDir.toString()) @@ -240,12 +246,18 @@ class PreferencesHelper(val context: Context) { fun enableDoh() = prefs.getBoolean(Keys.enableDoh, false) fun hideLastUsedSource() = flowPrefs.getBoolean(Keys.hideLastUsedSource, false) - fun extensionInstaller() = flowPrefs.getEnum( - Keys.extensionInstaller, - if (MiuiUtil.isMiui()) Values.ExtensionInstaller.LEGACY else Values.ExtensionInstaller.PACKAGEINSTALLER - ) - fun defaultUserAgent() = flowPrefs.getString(Keys.defaultUserAgent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44") + fun extensionInstaller() = + flowPrefs.getEnum( + Keys.extensionInstaller, + if (MiuiUtil.isMiui()) Values.ExtensionInstaller.LEGACY else Values.ExtensionInstaller.PACKAGEINSTALLER + ) + + fun defaultUserAgent() = + flowPrefs.getString( + Keys.defaultUserAgent, + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44" + ) // --> AZ J2K CHERRYPICKING @@ -299,11 +311,17 @@ class PreferencesHelper(val context: Context) { fun memberIdVal() = flowPrefs.getString("eh_ipb_member_id", "") fun passHashVal() = flowPrefs.getString("eh_ipb_pass_hash", "") + fun igneousVal() = flowPrefs.getString("eh_igneous", "") + fun eh_ehSettingsProfile() = flowPrefs.getInt(Keys.eh_ehSettingsProfile, -1) + fun eh_exhSettingsProfile() = flowPrefs.getInt(Keys.eh_exhSettingsProfile, -1) + fun eh_settingsKey() = flowPrefs.getString(Keys.eh_settingsKey, "") + fun eh_sessionCookie() = flowPrefs.getString(Keys.eh_sessionCookie, "") + fun eh_hathPerksCookies() = flowPrefs.getString(Keys.eh_hathPerksCookie, "") // Lock @@ -371,9 +389,17 @@ class PreferencesHelper(val context: Context) { fun eh_watchedListDefaultState() = flowPrefs.getBoolean(Keys.eh_watched_list_default_state, false) - fun eh_settingsLanguages() = flowPrefs.getString(Keys.eh_settings_languages, "false*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false") - - fun eh_EnabledCategories() = flowPrefs.getString(Keys.eh_enabled_categories, "false,false,false,false,false,false,false,false,false,false") + fun eh_settingsLanguages() = + flowPrefs.getString( + Keys.eh_settings_languages, + "false*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false" + ) + + fun eh_EnabledCategories() = + flowPrefs.getString( + Keys.eh_enabled_categories, + "false,false,false,false,false,false,false,false,false,false" + ) fun startReadingButton() = flowPrefs.getBoolean(Keys.startReadingButton, true) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/SharedPreferencesDataStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/SharedPreferencesDataStore.kt index 7c3295f21f1d..ea9af6f3e80c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/SharedPreferencesDataStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/SharedPreferencesDataStore.kt @@ -4,52 +4,87 @@ import android.content.SharedPreferences import androidx.preference.PreferenceDataStore class SharedPreferencesDataStore(private val prefs: SharedPreferences) : PreferenceDataStore() { - - override fun getBoolean(key: String?, defValue: Boolean): Boolean { + override fun getBoolean( + key: String?, + defValue: Boolean + ): Boolean { return prefs.getBoolean(key, defValue) } - override fun putBoolean(key: String?, value: Boolean) { + override fun putBoolean( + key: String?, + value: Boolean + ) { prefs.edit().putBoolean(key, value).apply() } - override fun getInt(key: String?, defValue: Int): Int { + override fun getInt( + key: String?, + defValue: Int + ): Int { return prefs.getInt(key, defValue) } - override fun putInt(key: String?, value: Int) { + override fun putInt( + key: String?, + value: Int + ) { prefs.edit().putInt(key, value).apply() } - override fun getLong(key: String?, defValue: Long): Long { + override fun getLong( + key: String?, + defValue: Long + ): Long { return prefs.getLong(key, defValue) } - override fun putLong(key: String?, value: Long) { + override fun putLong( + key: String?, + value: Long + ) { prefs.edit().putLong(key, value).apply() } - override fun getFloat(key: String?, defValue: Float): Float { + override fun getFloat( + key: String?, + defValue: Float + ): Float { return prefs.getFloat(key, defValue) } - override fun putFloat(key: String?, value: Float) { + override fun putFloat( + key: String?, + value: Float + ) { prefs.edit().putFloat(key, value).apply() } - override fun getString(key: String?, defValue: String?): String? { + override fun getString( + key: String?, + defValue: String? + ): String? { return prefs.getString(key, defValue) } - override fun putString(key: String?, value: String?) { + override fun putString( + key: String?, + value: String? + ) { prefs.edit().putString(key, value).apply() } - override fun getStringSet(key: String?, defValues: MutableSet?): MutableSet? { + override fun getStringSet( + key: String?, + defValues: MutableSet? + ): MutableSet? { return prefs.getStringSet(key, defValues) } - override fun putStringSet(key: String?, values: MutableSet?) { + override fun putStringSet( + key: String?, + values: MutableSet? + ) { prefs.edit().putStringSet(key, values).apply() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt index 173a6ffd6f0d..2a850a7fd0a7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt @@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList import eu.kanade.tachiyomi.data.track.shikimori.Shikimori class TrackManager(context: Context) { - companion object { const val MYANIMELIST = 1 const val ANILIST = 2 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt index 8c2c54da852a..e8b6db2652e2 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt @@ -12,7 +12,6 @@ import rx.Observable import uy.kohesive.injekt.injectLazy abstract class TrackService(val id: Int) { - val preferences: PreferencesHelper by injectLazy() val networkService: NetworkHelper by injectLazy() @@ -54,7 +53,10 @@ abstract class TrackService(val id: Int) { abstract fun refresh(track: Track): Observable - abstract fun login(username: String, password: String): Completable + abstract fun login( + username: String, + password: String + ): Completable @CallSuper open fun logout() { @@ -62,14 +64,18 @@ abstract class TrackService(val id: Int) { } open val isLogged: Boolean - get() = getUsername().isNotEmpty() && - getPassword().isNotEmpty() + get() = + getUsername().isNotEmpty() && + getPassword().isNotEmpty() fun getUsername() = preferences.trackUsername(this)!! fun getPassword() = preferences.trackPassword(this)!! - fun saveCredentials(username: String, password: String) { + fun saveCredentials( + username: String, + password: String + ) { preferences.setTrackCredentials(this, username, password) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt index 0ad37fa03aae..996a50639a9a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt @@ -12,7 +12,6 @@ import rx.Observable import uy.kohesive.injekt.injectLazy class Anilist(private val context: Context, id: Int) : TrackService(id) { - companion object { const val READING = 1 const val COMPLETED = 2 @@ -59,17 +58,18 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) { return listOf(READING, PLANNING, COMPLETED, REPEATING, PAUSED, DROPPED) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.reading) - PLANNING -> getString(R.string.plan_to_read) - COMPLETED -> getString(R.string.completed) - REPEATING -> getString(R.string.repeating) - PAUSED -> getString(R.string.paused) - DROPPED -> getString(R.string.dropped) - else -> "" + override fun getStatus(status: Int): String = + with(context) { + when (status) { + READING -> getString(R.string.reading) + PLANNING -> getString(R.string.plan_to_read) + COMPLETED -> getString(R.string.completed) + REPEATING -> getString(R.string.repeating) + PAUSED -> getString(R.string.paused) + DROPPED -> getString(R.string.dropped) + else -> "" + } } - } override fun getCompletionStatus(): Int = COMPLETED @@ -96,15 +96,17 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) { // 100 point POINT_100 -> index.toFloat() // 5 stars - POINT_5 -> when (index) { - 0 -> 0f - else -> index * 20f - 10f - } + POINT_5 -> + when (index) { + 0 -> 0f + else -> index * 20f - 10f + } // Smiley - POINT_3 -> when (index) { - 0 -> 0f - else -> index * 25f + 10f - } + POINT_3 -> + when (index) { + 0 -> 0f + else -> index * 25f + 10f + } // 10 point decimal POINT_10_DECIMAL -> index.toFloat() else -> throw Exception("Unknown score type") @@ -115,16 +117,18 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) { val score = track.score return when (scorePreference.get()) { - POINT_5 -> when (score) { - 0f -> "0 ★" - else -> "${((score + 10) / 20).toInt()} ★" - } - POINT_3 -> when { - score == 0f -> "0" - score <= 35 -> "😦" - score <= 60 -> "😐" - else -> "😊" - } + POINT_5 -> + when (score) { + 0f -> "0 ★" + else -> "${((score + 10) / 20).toInt()} ★" + } + POINT_3 -> + when { + score == 0f -> "0" + score <= 35 -> "😦" + score <= 60 -> "😐" + else -> "😊" + } else -> track.toAnilistScore() } } @@ -177,7 +181,10 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) { } } - override fun login(username: String, password: String) = login(password) + override fun login( + username: String, + password: String + ) = login(password) fun login(token: String): Completable { val oauth = api.createOAuth(token) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt index e38117883c6e..fab94fb93b4d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt @@ -12,15 +12,14 @@ import com.google.gson.JsonParser import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.network.asObservableSuccess -import java.util.Calendar import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import rx.Observable +import java.util.Calendar class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { - private val jsonMime = "application/json; charset=utf-8".toMediaTypeOrNull() private val authClient = client.newBuilder().addInterceptor(interceptor).build() @@ -33,21 +32,25 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { | status |} |} - |""".trimMargin() - val variables = jsonObject( - "mangaId" to track.media_id, - "progress" to track.last_chapter_read, - "status" to track.toAnilistStatus() - ) - val payload = jsonObject( - "query" to query, - "variables" to variables - ) + | + """.trimMargin() + val variables = + jsonObject( + "mangaId" to track.media_id, + "progress" to track.last_chapter_read, + "status" to track.toAnilistStatus() + ) + val payload = + jsonObject( + "query" to query, + "variables" to variables + ) val body = payload.toString().toRequestBody(jsonMime) - val request = Request.Builder() - .url(apiUrl) - .post(body) - .build() + val request = + Request.Builder() + .url(apiUrl) + .post(body) + .build() return authClient.newCall(request) .asObservableSuccess() .map { netResponse -> @@ -72,22 +75,26 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { |progress |} |} - |""".trimMargin() - val variables = jsonObject( - "listId" to track.library_id, - "progress" to track.last_chapter_read, - "status" to track.toAnilistStatus(), - "score" to track.score.toInt() - ) - val payload = jsonObject( - "query" to query, - "variables" to variables - ) + | + """.trimMargin() + val variables = + jsonObject( + "listId" to track.library_id, + "progress" to track.last_chapter_read, + "status" to track.toAnilistStatus(), + "score" to track.score.toInt() + ) + val payload = + jsonObject( + "query" to query, + "variables" to variables + ) val body = payload.toString().toRequestBody(jsonMime) - val request = Request.Builder() - .url(apiUrl) - .post(body) - .build() + val request = + Request.Builder() + .url(apiUrl) + .post(body) + .build() return authClient.newCall(request) .asObservableSuccess() .map { @@ -120,19 +127,23 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { |} |} |} - |""".trimMargin() - val variables = jsonObject( - "query" to search - ) - val payload = jsonObject( - "query" to query, - "variables" to variables - ) + | + """.trimMargin() + val variables = + jsonObject( + "query" to search + ) + val payload = + jsonObject( + "query" to query, + "variables" to variables + ) val body = payload.toString().toRequestBody(jsonMime) - val request = Request.Builder() - .url(apiUrl) - .post(body) - .build() + val request = + Request.Builder() + .url(apiUrl) + .post(body) + .build() return authClient.newCall(request) .asObservableSuccess() .map { netResponse -> @@ -149,7 +160,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { } } - fun findLibManga(track: Track, userid: Int): Observable { + fun findLibManga( + track: Track, + userid: Int + ): Observable { val query = """ |query (${'$'}id: Int!, ${'$'}manga_id: Int!) { @@ -180,20 +194,24 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { |} |} |} - |""".trimMargin() - val variables = jsonObject( - "id" to userid, - "manga_id" to track.media_id - ) - val payload = jsonObject( - "query" to query, - "variables" to variables - ) + | + """.trimMargin() + val variables = + jsonObject( + "id" to userid, + "manga_id" to track.media_id + ) + val payload = + jsonObject( + "query" to query, + "variables" to variables + ) val body = payload.toString().toRequestBody(jsonMime) - val request = Request.Builder() - .url(apiUrl) - .post(body) - .build() + val request = + Request.Builder() + .url(apiUrl) + .post(body) + .build() return authClient.newCall(request) .asObservableSuccess() .map { netResponse -> @@ -210,7 +228,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { } } - fun getLibManga(track: Track, userid: Int): Observable { + fun getLibManga( + track: Track, + userid: Int + ): Observable { return findLibManga(track, userid) .map { it ?: throw Exception("Could not find manga") } } @@ -230,15 +251,18 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { |} |} |} - |""".trimMargin() - val payload = jsonObject( - "query" to query - ) + | + """.trimMargin() + val payload = + jsonObject( + "query" to query + ) val body = payload.toString().toRequestBody(jsonMime) - val request = Request.Builder() - .url(apiUrl) - .post(body) - .build() + val request = + Request.Builder() + .url(apiUrl) + .post(body) + .build() return authClient.newCall(request) .asObservableSuccess() .map { netResponse -> @@ -254,30 +278,42 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { } private fun jsonToALManga(struct: JsonObject): ALManga { - val date = try { - val date = Calendar.getInstance() - date.set( - struct["startDate"]["year"].nullInt ?: 0, - ( - struct["startDate"]["month"].nullInt - ?: 0 - ) - 1, - struct["startDate"]["day"].nullInt ?: 0 - ) - date.timeInMillis - } catch (_: Exception) { - 0L - } + val date = + try { + val date = Calendar.getInstance() + date.set( + struct["startDate"]["year"].nullInt ?: 0, + ( + struct["startDate"]["month"].nullInt + ?: 0 + ) - 1, + struct["startDate"]["day"].nullInt ?: 0 + ) + date.timeInMillis + } catch (_: Exception) { + 0L + } return ALManga( - struct["id"].asInt, struct["title"]["romaji"].asString, struct["coverImage"]["large"].asString, - struct["description"].nullString.orEmpty(), struct["type"].asString, struct["status"].nullString.orEmpty(), - date, struct["chapters"].nullInt ?: 0 + struct["id"].asInt, + struct["title"]["romaji"].asString, + struct["coverImage"]["large"].asString, + struct["description"].nullString.orEmpty(), + struct["type"].asString, + struct["status"].nullString.orEmpty(), + date, + struct["chapters"].nullInt ?: 0 ) } private fun jsonToALUserManga(struct: JsonObject): ALUserManga { - return ALUserManga(struct["id"].asLong, struct["status"].asString, struct["scoreRaw"].asInt, struct["progress"].asInt, jsonToALManga(struct["media"].obj)) + return ALUserManga( + struct["id"].asLong, + struct["status"].asString, + struct["scoreRaw"].asInt, + struct["progress"].asInt, + jsonToALManga(struct["media"].obj) + ) } companion object { @@ -291,9 +327,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { return baseMangaUrl + mediaId } - fun authUrl() = Uri.parse("${baseUrl}oauth/authorize").buildUpon() - .appendQueryParameter("client_id", clientId) - .appendQueryParameter("response_type", "token") - .build() + fun authUrl() = + Uri.parse("${baseUrl}oauth/authorize").buildUpon() + .appendQueryParameter("client_id", clientId) + .appendQueryParameter("response_type", "token") + .build() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistInterceptor.kt index 07ac60111bf7..f14a667c44d3 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistInterceptor.kt @@ -4,7 +4,6 @@ import okhttp3.Interceptor import okhttp3.Response class AnilistInterceptor(val anilist: Anilist, private var token: String?) : Interceptor { - /** * OAuth object used for authenticated requests. * @@ -37,9 +36,10 @@ class AnilistInterceptor(val anilist: Anilist, private var token: String?) : Int } // Add the authorization header to the original request. - val authRequest = originalRequest.newBuilder() - .addHeader("Authorization", "Bearer ${oauth!!.access_token}") - .build() + val authRequest = + originalRequest.newBuilder() + .addHeader("Authorization", "Bearer ${oauth!!.access_token}") + .build() return chain.proceed(authRequest) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt index d2fb190472d3..496db9ba09bf 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt @@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.model.TrackSearch +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale -import uy.kohesive.injekt.injectLazy data class ALManga( val media_id: Int, @@ -18,25 +18,26 @@ data class ALManga( val start_date_fuzzy: Long, val total_chapters: Int ) { - - fun toTrack() = TrackSearch.create(TrackManager.ANILIST).apply { - media_id = this@ALManga.media_id - title = title_romaji - total_chapters = this@ALManga.total_chapters - cover_url = image_url_lge - summary = description ?: "" - tracking_url = AnilistApi.mangaUrl(media_id) - publishing_status = this@ALManga.publishing_status - publishing_type = type - if (start_date_fuzzy != 0L) { - start_date = try { - val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US) - outputDf.format(start_date_fuzzy) - } catch (e: Exception) { - "" + fun toTrack() = + TrackSearch.create(TrackManager.ANILIST).apply { + media_id = this@ALManga.media_id + title = title_romaji + total_chapters = this@ALManga.total_chapters + cover_url = image_url_lge + summary = description ?: "" + tracking_url = AnilistApi.mangaUrl(media_id) + publishing_status = this@ALManga.publishing_status + publishing_type = type + if (start_date_fuzzy != 0L) { + start_date = + try { + val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US) + outputDf.format(start_date_fuzzy) + } catch (e: Exception) { + "" + } } } - } } data class ALUserManga( @@ -46,61 +47,66 @@ data class ALUserManga( val chapters_read: Int, val manga: ALManga ) { + fun toTrack() = + Track.create(TrackManager.ANILIST).apply { + media_id = manga.media_id + status = toTrackStatus() + score = score_raw.toFloat() + last_chapter_read = chapters_read + library_id = this@ALUserManga.library_id + total_chapters = manga.total_chapters + } - fun toTrack() = Track.create(TrackManager.ANILIST).apply { - media_id = manga.media_id - status = toTrackStatus() - score = score_raw.toFloat() - last_chapter_read = chapters_read - library_id = this@ALUserManga.library_id - total_chapters = manga.total_chapters - } - - fun toTrackStatus() = when (list_status) { - "CURRENT" -> Anilist.READING - "COMPLETED" -> Anilist.COMPLETED - "PAUSED" -> Anilist.PAUSED - "DROPPED" -> Anilist.DROPPED - "PLANNING" -> Anilist.PLANNING - "REPEATING" -> Anilist.REPEATING - else -> throw NotImplementedError("Unknown status: $list_status") - } + fun toTrackStatus() = + when (list_status) { + "CURRENT" -> Anilist.READING + "COMPLETED" -> Anilist.COMPLETED + "PAUSED" -> Anilist.PAUSED + "DROPPED" -> Anilist.DROPPED + "PLANNING" -> Anilist.PLANNING + "REPEATING" -> Anilist.REPEATING + else -> throw NotImplementedError("Unknown status: $list_status") + } } -fun Track.toAnilistStatus() = when (status) { - Anilist.READING -> "CURRENT" - Anilist.COMPLETED -> "COMPLETED" - Anilist.PAUSED -> "PAUSED" - Anilist.DROPPED -> "DROPPED" - Anilist.PLANNING -> "PLANNING" - Anilist.REPEATING -> "REPEATING" - else -> throw NotImplementedError("Unknown status: $status") -} +fun Track.toAnilistStatus() = + when (status) { + Anilist.READING -> "CURRENT" + Anilist.COMPLETED -> "COMPLETED" + Anilist.PAUSED -> "PAUSED" + Anilist.DROPPED -> "DROPPED" + Anilist.PLANNING -> "PLANNING" + Anilist.REPEATING -> "REPEATING" + else -> throw NotImplementedError("Unknown status: $status") + } private val preferences: PreferencesHelper by injectLazy() -fun Track.toAnilistScore(): String = when (preferences.anilistScoreType().get()) { +fun Track.toAnilistScore(): String = + when (preferences.anilistScoreType().get()) { // 10 point - "POINT_10" -> (score.toInt() / 10).toString() + "POINT_10" -> (score.toInt() / 10).toString() // 100 point - "POINT_100" -> score.toInt().toString() + "POINT_100" -> score.toInt().toString() // 5 stars - "POINT_5" -> when { - score == 0f -> "0" - score < 30 -> "1" - score < 50 -> "2" - score < 70 -> "3" - score < 90 -> "4" - else -> "5" - } + "POINT_5" -> + when { + score == 0f -> "0" + score < 30 -> "1" + score < 50 -> "2" + score < 70 -> "3" + score < 90 -> "4" + else -> "5" + } // Smiley - "POINT_3" -> when { - score == 0f -> "0" - score <= 35 -> ":(" - score <= 60 -> ":|" - else -> ":)" - } + "POINT_3" -> + when { + score == 0f -> "0" + score <= 35 -> ":(" + score <= 60 -> ":|" + else -> ":)" + } // 10 point decimal - "POINT_10_DECIMAL" -> (score / 10).toString() - else -> throw NotImplementedError("Unknown score type") -} + "POINT_10_DECIMAL" -> (score / 10).toString() + else -> throw NotImplementedError("Unknown score type") + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/OAuth.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/OAuth.kt index 5df10fd51057..69916b254041 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/OAuth.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/OAuth.kt @@ -6,6 +6,5 @@ data class OAuth( val expires: Long, val expires_in: Long ) { - fun isExpired() = System.currentTimeMillis() > expires } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt index 99f1db58177e..addc5b6f720e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt @@ -12,7 +12,6 @@ import rx.Observable import uy.kohesive.injekt.injectLazy class Bangumi(private val context: Context, id: Int) : TrackService(id) { - override val name = "Bangumi" private val gson: Gson by injectLazy() @@ -85,20 +84,24 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) { return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLANNING) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.reading) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - DROPPED -> getString(R.string.dropped) - PLANNING -> getString(R.string.plan_to_read) - else -> "" + override fun getStatus(status: Int): String = + with(context) { + when (status) { + READING -> getString(R.string.reading) + COMPLETED -> getString(R.string.completed) + ON_HOLD -> getString(R.string.on_hold) + DROPPED -> getString(R.string.dropped) + PLANNING -> getString(R.string.plan_to_read) + else -> "" + } } - } override fun getCompletionStatus(): Int = COMPLETED - override fun login(username: String, password: String) = login(password) + override fun login( + username: String, + password: String + ) = login(password) fun login(code: String): Completable { return api.accessToken(code).map { oauth: OAuth? -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt index e6a2d522e0aa..d0fdcbe5c3d1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt @@ -11,28 +11,29 @@ import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.asObservableSuccess -import java.net.URLEncoder import okhttp3.CacheControl import okhttp3.FormBody import okhttp3.OkHttpClient import okhttp3.Request import rx.Observable import uy.kohesive.injekt.injectLazy +import java.net.URLEncoder class BangumiApi(private val client: OkHttpClient, interceptor: BangumiInterceptor) { - private val gson: Gson by injectLazy() private val authClient = client.newBuilder().addInterceptor(interceptor).build() fun addLibManga(track: Track): Observable { - val body = FormBody.Builder() - .add("rating", track.score.toInt().toString()) - .add("status", track.toBangumiStatus()) - .build() - val request = Request.Builder() - .url("$apiUrl/collection/${track.media_id}/update") - .post(body) - .build() + val body = + FormBody.Builder() + .add("rating", track.score.toInt().toString()) + .add("status", track.toBangumiStatus()) + .build() + val request = + Request.Builder() + .url("$apiUrl/collection/${track.media_id}/update") + .post(body) + .build() return authClient.newCall(request) .asObservableSuccess() .map { @@ -42,22 +43,26 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept fun updateLibManga(track: Track): Observable { // chapter update - val body = FormBody.Builder() - .add("watched_eps", track.last_chapter_read.toString()) - .build() - val request = Request.Builder() - .url("$apiUrl/subject/${track.media_id}/update/watched_eps") - .post(body) - .build() + val body = + FormBody.Builder() + .add("watched_eps", track.last_chapter_read.toString()) + .build() + val request = + Request.Builder() + .url("$apiUrl/subject/${track.media_id}/update/watched_eps") + .post(body) + .build() // read status update - val sbody = FormBody.Builder() - .add("status", track.toBangumiStatus()) - .build() - val srequest = Request.Builder() - .url("$apiUrl/collection/${track.media_id}/update") - .post(sbody) - .build() + val sbody = + FormBody.Builder() + .add("status", track.toBangumiStatus()) + .build() + val srequest = + Request.Builder() + .url("$apiUrl/collection/${track.media_id}/update") + .post(sbody) + .build() return authClient.newCall(srequest) .asObservableSuccess() .map { @@ -72,15 +77,17 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept } fun search(search: String): Observable> { - val url = Uri.parse( - "$apiUrl/search/subject/${URLEncoder.encode(search, Charsets.UTF_8.name())}" - ).buildUpon() - .appendQueryParameter("max_results", "20") - .build() - val request = Request.Builder() - .url(url.toString()) - .get() - .build() + val url = + Uri.parse( + "$apiUrl/search/subject/${URLEncoder.encode(search, Charsets.UTF_8.name())}" + ).buildUpon() + .appendQueryParameter("max_results", "20") + .build() + val request = + Request.Builder() + .url(url.toString()) + .get() + .build() return authClient.newCall(request) .asObservableSuccess() .map { netResponse -> @@ -110,15 +117,16 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept return Track.create(TrackManager.BANGUMI).apply { title = mangas["name"].asString media_id = mangas["id"].asInt - score = if (mangas["rating"] != null) { - if (mangas["rating"].isJsonObject) { - mangas["rating"].obj["score"].asFloat + score = + if (mangas["rating"] != null) { + if (mangas["rating"].isJsonObject) { + mangas["rating"].obj["score"].asFloat + } else { + 0f + } } else { 0f } - } else { - 0f - } status = Bangumi.DEFAULT_STATUS tracking_url = mangas["url"].asString } @@ -126,10 +134,11 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept fun findLibManga(track: Track): Observable { val urlMangas = "$apiUrl/subject/${track.media_id}" - val requestMangas = Request.Builder() - .url(urlMangas) - .get() - .build() + val requestMangas = + Request.Builder() + .url(urlMangas) + .get() + .build() return authClient.newCall(requestMangas) .asObservableSuccess() @@ -142,11 +151,12 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept fun statusLibManga(track: Track): Observable { val urlUserRead = "$apiUrl/collection/${track.media_id}" - val requestUserRead = Request.Builder() - .url(urlUserRead) - .cacheControl(CacheControl.FORCE_NETWORK) - .get() - .build() + val requestUserRead = + Request.Builder() + .url(urlUserRead) + .cacheControl(CacheControl.FORCE_NETWORK) + .get() + .build() // todo get user readed chapter here return authClient.newCall(requestUserRead) @@ -170,16 +180,18 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept } } - private fun accessTokenRequest(code: String) = POST( - oauthUrl, - body = FormBody.Builder() - .add("grant_type", "authorization_code") - .add("client_id", clientId) - .add("client_secret", clientSecret) - .add("code", code) - .add("redirect_uri", redirectUrl) - .build() - ) + private fun accessTokenRequest(code: String) = + POST( + oauthUrl, + body = + FormBody.Builder() + .add("grant_type", "authorization_code") + .add("client_id", clientId) + .add("client_secret", clientSecret) + .add("code", code) + .add("redirect_uri", redirectUrl) + .build() + ) companion object { private const val clientId = "bgm10555cda0762e80ca" @@ -204,15 +216,17 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept .appendQueryParameter("redirect_uri", redirectUrl) .build() - fun refreshTokenRequest(token: String) = POST( - oauthUrl, - body = FormBody.Builder() - .add("grant_type", "refresh_token") - .add("client_id", clientId) - .add("client_secret", clientSecret) - .add("refresh_token", token) - .add("redirect_uri", redirectUrl) - .build() - ) + fun refreshTokenRequest(token: String) = + POST( + oauthUrl, + body = + FormBody.Builder() + .add("grant_type", "refresh_token") + .add("client_id", clientId) + .add("client_secret", clientSecret) + .add("refresh_token", token) + .add("redirect_uri", redirectUrl) + .build() + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiInterceptor.kt index ff5584643187..67eb4155fdf9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiInterceptor.kt @@ -6,13 +6,15 @@ import okhttp3.Interceptor import okhttp3.Response class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor { - /** * OAuth object used for authenticated requests. */ private var oauth: OAuth? = bangumi.restoreToken() - fun addTocken(tocken: String, oidFormBody: FormBody): FormBody { + fun addTocken( + tocken: String, + oidFormBody: FormBody + ): FormBody { val newFormBody = FormBody.Builder() for (i in 0 until oidFormBody.size) { newFormBody.add(oidFormBody.name(i), oidFormBody.value(i)) @@ -35,29 +37,39 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor { } } - val authRequest = if (originalRequest.method == "GET") originalRequest.newBuilder() - .header("User-Agent", "Tachiyomi") - .url( - originalRequest.url.newBuilder() - .addQueryParameter("access_token", currAuth.access_token).build() - ) - .build() else originalRequest.newBuilder() - .post(addTocken(currAuth.access_token, originalRequest.body as FormBody)) - .header("User-Agent", "Tachiyomi") - .build() + val authRequest = + if (originalRequest.method == "GET") { + originalRequest.newBuilder() + .header("User-Agent", "Tachiyomi") + .url( + originalRequest.url.newBuilder() + .addQueryParameter("access_token", currAuth.access_token).build() + ) + .build() + } else { + originalRequest.newBuilder() + .post(addTocken(currAuth.access_token, originalRequest.body as FormBody)) + .header("User-Agent", "Tachiyomi") + .build() + } return chain.proceed(authRequest) } fun newAuth(oauth: OAuth?) { - this.oauth = if (oauth == null) null else OAuth( - oauth.access_token, - oauth.token_type, - System.currentTimeMillis() / 1000, - oauth.expires_in, - oauth.refresh_token, - this.oauth?.user_id - ) + this.oauth = + if (oauth == null) { + null + } else { + OAuth( + oauth.access_token, + oauth.token_type, + System.currentTimeMillis() / 1000, + oauth.expires_in, + oauth.refresh_token, + this.oauth?.user_id + ) + } bangumi.saveToken(oauth) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiModels.kt index 86dfe45ca98d..b31a024bb038 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiModels.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiModels.kt @@ -2,20 +2,22 @@ package eu.kanade.tachiyomi.data.track.bangumi import eu.kanade.tachiyomi.data.database.models.Track -fun Track.toBangumiStatus() = when (status) { - Bangumi.READING -> "do" - Bangumi.COMPLETED -> "collect" - Bangumi.ON_HOLD -> "on_hold" - Bangumi.DROPPED -> "dropped" - Bangumi.PLANNING -> "wish" - else -> throw NotImplementedError("Unknown status: $status") -} +fun Track.toBangumiStatus() = + when (status) { + Bangumi.READING -> "do" + Bangumi.COMPLETED -> "collect" + Bangumi.ON_HOLD -> "on_hold" + Bangumi.DROPPED -> "dropped" + Bangumi.PLANNING -> "wish" + else -> throw NotImplementedError("Unknown status: $status") + } -fun toTrackStatus(status: String) = when (status) { - "do" -> Bangumi.READING - "collect" -> Bangumi.COMPLETED - "on_hold" -> Bangumi.ON_HOLD - "dropped" -> Bangumi.DROPPED - "wish" -> Bangumi.PLANNING - else -> throw NotImplementedError("Unknown status: $status") -} +fun toTrackStatus(status: String) = + when (status) { + "do" -> Bangumi.READING + "collect" -> Bangumi.COMPLETED + "on_hold" -> Bangumi.ON_HOLD + "dropped" -> Bangumi.DROPPED + "wish" -> Bangumi.PLANNING + else -> throw NotImplementedError("Unknown status: $status") + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/OAuth.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/OAuth.kt index d3dfda20f1b5..7a51874927e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/OAuth.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/OAuth.kt @@ -8,7 +8,6 @@ data class OAuth( val refresh_token: String?, val user_id: Long? ) { - // Access token refresh before expired fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/job/DelayedTrackingStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/job/DelayedTrackingStore.kt index df8facb14abc..7a9af47514f8 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/job/DelayedTrackingStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/job/DelayedTrackingStore.kt @@ -7,7 +7,6 @@ import rx.Observable import timber.log.Timber class DelayedTrackingStore(context: Context) { - /** * Preference file where queued tracking updates are stored. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/job/DelayedTrackingUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/job/DelayedTrackingUpdateJob.kt index 4763ceef9e5b..1a9fa1c23b16 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/job/DelayedTrackingUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/job/DelayedTrackingUpdateJob.kt @@ -11,30 +11,30 @@ import androidx.work.WorkManager import androidx.work.WorkerParameters import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.track.TrackManager -import java.util.concurrent.TimeUnit import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.concurrent.TimeUnit class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { - override suspend fun doWork(): Result { val db = Injekt.get() val trackManager = Injekt.get() val delayedTrackingStore = Injekt.get() withContext(Dispatchers.IO) { - val tracks = delayedTrackingStore.getItems().mapNotNull { - val manga = db.getManga(it.mangaId).executeAsBlocking() ?: return@withContext - db.getTracks(manga).executeAsBlocking() - .find { track -> track.id == it.trackId } - ?.also { track -> - track.last_chapter_read = it.lastChapterRead.toInt() - } - } + val tracks = + delayedTrackingStore.getItems().mapNotNull { + val manga = db.getManga(it.mangaId).executeAsBlocking() ?: return@withContext + db.getTracks(manga).executeAsBlocking() + .find { track -> track.id == it.trackId } + ?.also { track -> + track.last_chapter_read = it.lastChapterRead.toInt() + } + } tracks.forEach { track -> try { @@ -58,15 +58,17 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters) private const val TAG = "DelayedTrackingUpdate" fun setupTask(context: Context) { - val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() + val constraints = + Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() - val request = OneTimeWorkRequestBuilder() - .setConstraints(constraints) - .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 20, TimeUnit.SECONDS) - .addTag(TAG) - .build() + val request = + OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 20, TimeUnit.SECONDS) + .addTag(TAG) + .build() WorkManager.getInstance(context) .enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt index 8b5ef74f1fe1..ef6bb95d822f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt @@ -7,13 +7,12 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.model.TrackSearch -import java.text.DecimalFormat import rx.Completable import rx.Observable import uy.kohesive.injekt.injectLazy +import java.text.DecimalFormat class Kitsu(private val context: Context, id: Int) : TrackService(id) { - companion object { const val READING = 1 const val COMPLETED = 2 @@ -41,16 +40,17 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) { return listOf(READING, PLAN_TO_READ, COMPLETED, ON_HOLD, DROPPED) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.currently_reading) - PLAN_TO_READ -> getString(R.string.want_to_read) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - DROPPED -> getString(R.string.dropped) - else -> "" + override fun getStatus(status: Int): String = + with(context) { + when (status) { + READING -> getString(R.string.currently_reading) + PLAN_TO_READ -> getString(R.string.want_to_read) + COMPLETED -> getString(R.string.completed) + ON_HOLD -> getString(R.string.on_hold) + DROPPED -> getString(R.string.dropped) + else -> "" + } } - } override fun getCompletionStatus(): Int = COMPLETED @@ -104,7 +104,10 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) { } } - override fun login(username: String, password: String): Completable { + override fun login( + username: String, + password: String + ): Completable { return api.login(username, password) .doOnNext { interceptor.newAuth(it) } .flatMap { api.getCurrentUser() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt index 63e55c65dafc..9f137d32c7c1 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt @@ -29,57 +29,69 @@ import retrofit2.http.Query import rx.Observable class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) { - private val authClient = client.newBuilder().addInterceptor(interceptor).build() - private val rest = Retrofit.Builder() - .baseUrl(baseUrl) - .client(authClient) - .addConverterFactory(GsonConverterFactory.create(GsonBuilder().serializeNulls().create())) - .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) - .build() - .create(Rest::class.java) - - private val searchRest = Retrofit.Builder() - .baseUrl(algoliaKeyUrl) - .client(authClient) - .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) - .build() - .create(SearchKeyRest::class.java) - - private val algoliaRest = Retrofit.Builder() - .baseUrl(algoliaUrl) - .client(client) - .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) - .build() - .create(AgoliaSearchRest::class.java) - - fun addLibManga(track: Track, userId: String): Observable { + private val rest = + Retrofit.Builder() + .baseUrl(baseUrl) + .client(authClient) + .addConverterFactory(GsonConverterFactory.create(GsonBuilder().serializeNulls().create())) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .build() + .create(Rest::class.java) + + private val searchRest = + Retrofit.Builder() + .baseUrl(algoliaKeyUrl) + .client(authClient) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .build() + .create(SearchKeyRest::class.java) + + private val algoliaRest = + Retrofit.Builder() + .baseUrl(algoliaUrl) + .client(client) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .build() + .create(AgoliaSearchRest::class.java) + + fun addLibManga( + track: Track, + userId: String + ): Observable { return Observable.defer { // @formatter:off - val data = jsonObject( - "type" to "libraryEntries", - "attributes" to jsonObject( - "status" to track.toKitsuStatus(), - "progress" to track.last_chapter_read - ), - "relationships" to jsonObject( - "user" to jsonObject( - "data" to jsonObject( - "id" to userId, - "type" to "users" - ) - ), - "media" to jsonObject( - "data" to jsonObject( - "id" to track.media_id, - "type" to "manga" + val data = + jsonObject( + "type" to "libraryEntries", + "attributes" to + jsonObject( + "status" to track.toKitsuStatus(), + "progress" to track.last_chapter_read + ), + "relationships" to + jsonObject( + "user" to + jsonObject( + "data" to + jsonObject( + "id" to userId, + "type" to "users" + ) + ), + "media" to + jsonObject( + "data" to + jsonObject( + "id" to track.media_id, + "type" to "manga" + ) + ) ) - ) ) - ) rest.addLibManga(jsonObject("data" to data)) .map { json -> @@ -92,15 +104,17 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) fun updateLibManga(track: Track): Observable { return Observable.defer { // @formatter:off - val data = jsonObject( - "type" to "libraryEntries", - "id" to track.media_id, - "attributes" to jsonObject( - "status" to track.toKitsuStatus(), - "progress" to track.last_chapter_read, - "ratingTwenty" to track.toKitsuScore() + val data = + jsonObject( + "type" to "libraryEntries", + "id" to track.media_id, + "attributes" to + jsonObject( + "status" to track.toKitsuStatus(), + "progress" to track.last_chapter_read, + "ratingTwenty" to track.toKitsuScore() + ) ) - ) // @formatter:on rest.updateLibManga(track.media_id, jsonObject("data" to data)) @@ -117,7 +131,10 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) } } - private fun algoliaSearch(key: String, query: String): Observable> { + private fun algoliaSearch( + key: String, + query: String + ): Observable> { val jsonObject = jsonObject("params" to "query=$query$algoliaFilter") return algoliaRest .getSearchQuery(algoliaAppId, key, jsonObject) @@ -129,7 +146,10 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) } } - fun findLibManga(track: Track, userId: String): Observable { + fun findLibManga( + track: Track, + userId: String + ): Observable { return rest.findLibManga(track.media_id, userId) .map { json -> val data = json["data"].array @@ -155,7 +175,10 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) } } - fun login(username: String, password: String): Observable { + fun login( + username: String, + password: String + ): Observable { return Retrofit.Builder() .baseUrl(loginUrl) .client(client) @@ -171,7 +194,6 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) } private interface Rest { - @Headers("Content-Type: application/vnd.api+json") @POST("library-entries") fun addLibManga( @@ -211,11 +233,14 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) private interface AgoliaSearchRest { @POST("query/") - fun getSearchQuery(@Header("X-Algolia-Application-Id") appid: String, @Header("X-Algolia-API-Key") key: String, @Body json: JsonObject): Observable + fun getSearchQuery( + @Header("X-Algolia-Application-Id") appid: String, + @Header("X-Algolia-API-Key") key: String, + @Body json: JsonObject + ): Observable } private interface LoginRest { - @FormUrlEncoded @POST("oauth/token") fun requestAccessToken( @@ -242,14 +267,16 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) return baseMangaUrl + remoteId } - fun refreshTokenRequest(token: String) = POST( - "${loginUrl}oauth/token", - body = FormBody.Builder() - .add("grant_type", "refresh_token") - .add("client_id", clientId) - .add("client_secret", clientSecret) - .add("refresh_token", token) - .build() - ) + fun refreshTokenRequest(token: String) = + POST( + "${loginUrl}oauth/token", + body = + FormBody.Builder() + .add("grant_type", "refresh_token") + .add("client_id", clientId) + .add("client_secret", clientSecret) + .add("refresh_token", token) + .build() + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt index 6439ad0cc562..08b8f58c6480 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuInterceptor.kt @@ -5,7 +5,6 @@ import okhttp3.Interceptor import okhttp3.Response class KitsuInterceptor(val kitsu: Kitsu, val gson: Gson) : Interceptor { - /** * OAuth object used for authenticated requests. */ @@ -29,11 +28,12 @@ class KitsuInterceptor(val kitsu: Kitsu, val gson: Gson) : Interceptor { } // Add the authorization header to the original request. - val authRequest = originalRequest.newBuilder() - .addHeader("Authorization", "Bearer ${oauth!!.access_token}") - .header("Accept", "application/vnd.api+json") - .header("Content-Type", "application/vnd.api+json") - .build() + val authRequest = + originalRequest.newBuilder() + .addHeader("Authorization", "Bearer ${oauth!!.access_token}") + .header("Accept", "application/vnd.api+json") + .header("Content-Type", "application/vnd.api+json") + .build() return chain.proceed(authRequest) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt index 48e5d387147c..5261cca5ac99 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt @@ -22,28 +22,31 @@ class KitsuSearchManga(obj: JsonObject) { val subType = obj.get("subtype").nullString val original = obj.get("posterImage").nullObj?.get("original")?.asString private val synopsis by obj.byString - private var startDate = obj.get("startDate").nullString?.let { - val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US) - outputDf.format(Date(it.toLong() * 1000)) - } + private var startDate = + obj.get("startDate").nullString?.let { + val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US) + outputDf.format(Date(it.toLong() * 1000)) + } private val endDate = obj.get("endDate").nullString @CallSuper - fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply { - media_id = this@KitsuSearchManga.id - title = canonicalTitle - total_chapters = chapterCount ?: 0 - cover_url = original ?: "" - summary = synopsis - tracking_url = KitsuApi.mangaUrl(media_id) - publishing_status = if (endDate == null) { - "Publishing" - } else { - "Finished" + fun toTrack() = + TrackSearch.create(TrackManager.KITSU).apply { + media_id = this@KitsuSearchManga.id + title = canonicalTitle + total_chapters = chapterCount ?: 0 + cover_url = original ?: "" + summary = synopsis + tracking_url = KitsuApi.mangaUrl(media_id) + publishing_status = + if (endDate == null) { + "Publishing" + } else { + "Finished" + } + publishing_type = subType ?: "" + start_date = startDate ?: "" } - publishing_type = subType ?: "" - start_date = startDate ?: "" - } } class KitsuLibManga(obj: JsonObject, manga: JsonObject) { @@ -59,39 +62,42 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) { private val ratingTwenty = obj["attributes"].obj.get("ratingTwenty").nullString val progress by obj["attributes"].byInt - fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply { - media_id = libraryId - title = canonicalTitle - total_chapters = chapterCount ?: 0 - cover_url = original - summary = synopsis - tracking_url = KitsuApi.mangaUrl(media_id) - publishing_status = this@KitsuLibManga.status - publishing_type = type - start_date = startDate - status = toTrackStatus() - score = ratingTwenty?.let { it.toInt() / 2f } ?: 0f - last_chapter_read = progress - } + fun toTrack() = + TrackSearch.create(TrackManager.KITSU).apply { + media_id = libraryId + title = canonicalTitle + total_chapters = chapterCount ?: 0 + cover_url = original + summary = synopsis + tracking_url = KitsuApi.mangaUrl(media_id) + publishing_status = this@KitsuLibManga.status + publishing_type = type + start_date = startDate + status = toTrackStatus() + score = ratingTwenty?.let { it.toInt() / 2f } ?: 0f + last_chapter_read = progress + } - private fun toTrackStatus() = when (status) { - "current" -> Kitsu.READING - "completed" -> Kitsu.COMPLETED - "on_hold" -> Kitsu.ON_HOLD - "dropped" -> Kitsu.DROPPED - "planned" -> Kitsu.PLAN_TO_READ - else -> throw Exception("Unknown status") - } + private fun toTrackStatus() = + when (status) { + "current" -> Kitsu.READING + "completed" -> Kitsu.COMPLETED + "on_hold" -> Kitsu.ON_HOLD + "dropped" -> Kitsu.DROPPED + "planned" -> Kitsu.PLAN_TO_READ + else -> throw Exception("Unknown status") + } } -fun Track.toKitsuStatus() = when (status) { - Kitsu.READING -> "current" - Kitsu.COMPLETED -> "completed" - Kitsu.ON_HOLD -> "on_hold" - Kitsu.DROPPED -> "dropped" - Kitsu.PLAN_TO_READ -> "planned" - else -> throw Exception("Unknown status") -} +fun Track.toKitsuStatus() = + when (status) { + Kitsu.READING -> "current" + Kitsu.COMPLETED -> "completed" + Kitsu.ON_HOLD -> "on_hold" + Kitsu.DROPPED -> "dropped" + Kitsu.PLAN_TO_READ -> "planned" + else -> throw Exception("Unknown status") + } fun Track.toKitsuScore(): String? { return if (score > 0) (score * 2).toInt().toString() else null diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/OAuth.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/OAuth.kt index a10981c51e10..c07ca5824d65 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/OAuth.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/OAuth.kt @@ -7,6 +7,5 @@ data class OAuth( val expires_in: Long, val refresh_token: String? ) { - fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt index 9035f555026b..25db6c1a6299 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/model/TrackSearch.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.track.model import eu.kanade.tachiyomi.data.database.models.Track class TrackSearch : Track { - override var id: Long? = null override var manga_id: Long = 0 @@ -59,8 +58,9 @@ class TrackSearch : Track { } companion object { - fun create(serviceId: Int): TrackSearch = TrackSearch().apply { - sync_id = serviceId - } + fun create(serviceId: Int): TrackSearch = + TrackSearch().apply { + sync_id = serviceId + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt index 839fb164a95b..63576e88b85a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt @@ -11,7 +11,6 @@ import rx.Completable import rx.Observable class MyAnimeList(private val context: Context, id: Int) : TrackService(id) { - companion object { const val READING = 1 const val COMPLETED = 2 @@ -43,16 +42,17 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) { return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.reading) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - DROPPED -> getString(R.string.dropped) - PLAN_TO_READ -> getString(R.string.plan_to_read) - else -> "" + override fun getStatus(status: Int): String = + with(context) { + when (status) { + READING -> getString(R.string.reading) + COMPLETED -> getString(R.string.completed) + ON_HOLD -> getString(R.string.on_hold) + DROPPED -> getString(R.string.dropped) + PLAN_TO_READ -> getString(R.string.plan_to_read) + else -> "" + } } - } override fun getCompletionStatus(): Int = COMPLETED @@ -102,7 +102,10 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) { fun login(csrfToken: String): Completable = login("myanimelist", csrfToken) - override fun login(username: String, password: String): Completable { + override fun login( + username: String, + password: String + ): Completable { return Observable.fromCallable { saveCSRF(password) } .doOnNext { saveCredentials(username, password) } .doOnError { logout() } @@ -121,9 +124,10 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) { } private val isAuthorized: Boolean - get() = super.isLogged && - getCSRF().isNotEmpty() && - checkCookies() + get() = + super.isLogged && + getCSRF().isNotEmpty() && + checkCookies() fun getCSRF(): String = preferences.trackToken(this).get() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt index 25cca12c006f..92402e45b2cb 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt @@ -12,13 +12,6 @@ import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.util.lang.toCalendar import eu.kanade.tachiyomi.util.selectInt import eu.kanade.tachiyomi.util.selectText -import java.io.BufferedReader -import java.io.InputStreamReader -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.GregorianCalendar -import java.util.Locale -import java.util.zip.GZIPInputStream import okhttp3.FormBody import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient @@ -31,9 +24,15 @@ import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.jsoup.parser.Parser import rx.Observable +import java.io.BufferedReader +import java.io.InputStreamReader +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.GregorianCalendar +import java.util.Locale +import java.util.zip.GZIPInputStream class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) { - private val authClient = client.newBuilder().addInterceptor(interceptor).build() fun search(query: String): Observable> { @@ -88,15 +87,16 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI return Observable.defer { // Get track data val response = authClient.newCall(GET(url = editPageUrl(track.media_id))).execute() - val editData = response.use { - val page = it.consumeBody().let { it1 -> Jsoup.parse(it1) } + val editData = + response.use { + val page = it.consumeBody().let { it1 -> Jsoup.parse(it1) } - // Extract track data from MAL page - extractDataFromEditPage(page).apply { - // Apply changes to the just fetched data - copyPersonalFrom(track) + // Extract track data from MAL page + extractDataFromEditPage(page).apply { + // Apply changes to the just fetched data + copyPersonalFrom(track) + } } - } // Update remote authClient.newCall(POST(url = editPageUrl(track.media_id), body = mangaEditPostBody(editData))) @@ -116,22 +116,23 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI if (it.priorResponse?.isRedirect != true) { val trackForm = it.consumeBody().let { it1 -> Jsoup.parse(it1) } - libTrack = Track.create(TrackManager.MYANIMELIST).apply { - last_chapter_read = - trackForm.select("#add_manga_num_read_chapters").`val`().toInt() - total_chapters = trackForm.select("#totalChap").text().toInt() - status = - trackForm.select("#add_manga_status > option[selected]").`val`() - .toInt() - score = - trackForm.select("#add_manga_score > option[selected]").`val`() - .toFloatOrNull() - ?: 0f - started_reading_date = - trackForm.searchDatePicker("#add_manga_start_date") - finished_reading_date = - trackForm.searchDatePicker("#add_manga_finish_date") - } + libTrack = + Track.create(TrackManager.MYANIMELIST).apply { + last_chapter_read = + trackForm.select("#add_manga_num_read_chapters").`val`().toInt() + total_chapters = trackForm.select("#totalChap").text().toInt() + status = + trackForm.select("#add_manga_status > option[selected]").`val`() + .toInt() + score = + trackForm.select("#add_manga_score > option[selected]").`val`() + .toFloatOrNull() + ?: 0f + started_reading_date = + trackForm.searchDatePicker("#add_manga_start_date") + finished_reading_date = + trackForm.searchDatePicker("#add_manga_finish_date") + } } } libTrack @@ -171,12 +172,13 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI return authClient.newCall(POST(url = exportListUrl(), body = exportPostBody())) .asObservable() .map { response -> - baseUrl + response.consumeBody().let { - Jsoup.parse(it) - .select("div.goodresult") - .select("a") - .attr("href") - } + baseUrl + + response.consumeBody().let { + Jsoup.parse(it) + .select("div.goodresult") + .select("a") + .attr("href") + } } } @@ -252,9 +254,10 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI private const val PREFIX_MY = "my:" private const val TD = "td" - fun loginUrl() = baseUrl.toUri().buildUpon() - .appendPath("login.php") - .toString() + fun loginUrl() = + baseUrl.toUri().buildUpon() + .appendPath("login.php") + .toString() private fun mangaUrl(remoteId: Int) = baseMangaUrl + remoteId @@ -272,19 +275,22 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .toString() } - private fun exportListUrl() = Uri.parse(baseUrl).buildUpon() - .appendPath("panel.php") - .appendQueryParameter("go", "export") - .toString() + private fun exportListUrl() = + Uri.parse(baseUrl).buildUpon() + .appendPath("panel.php") + .appendQueryParameter("go", "export") + .toString() - private fun editPageUrl(mediaId: Int) = Uri.parse(baseModifyListUrl).buildUpon() - .appendPath(mediaId.toString()) - .appendPath("edit") - .toString() + private fun editPageUrl(mediaId: Int) = + Uri.parse(baseModifyListUrl).buildUpon() + .appendPath(mediaId.toString()) + .appendPath("edit") + .toString() - private fun addUrl() = Uri.parse(baseModifyListUrl).buildUpon() - .appendPath("add.json") - .toString() + private fun addUrl() = + Uri.parse(baseModifyListUrl).buildUpon() + .appendPath("add.json") + .toString() private fun exportPostBody(): RequestBody { return FormBody.Builder() @@ -294,11 +300,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI } private fun mangaPostPayload(track: Track): RequestBody { - val body = JSONObject() - .put("manga_id", track.media_id) - .put("status", track.status) - .put("score", track.score) - .put("num_read_chapters", track.last_chapter_read) + val body = + JSONObject() + .put("manga_id", track.media_id) + .put("status", track.status) + .put("score", track.score) + .put("num_read_chapters", track.last_chapter_read) return body.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) } @@ -356,19 +363,22 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI private fun Element.searchTotalChapters() = if (select(TD)[4].text() == "-") 0 else select(TD)[4].text().toInt() - private fun Element.searchCoverUrl() = select("img") - .attr("data-src") - .split("\\?")[0] - .replace("/r/50x70/", "/") + private fun Element.searchCoverUrl() = + select("img") + .attr("data-src") + .split("\\?")[0] + .replace("/r/50x70/", "/") - private fun Element.searchMediaId() = select("div.picSurround") - .select("a").attr("id") - .replace("sarea", "") - .toInt() + private fun Element.searchMediaId() = + select("div.picSurround") + .select("a").attr("id") + .replace("sarea", "") + .toInt() - private fun Element.searchSummary() = select("div.pt4") - .first()!! - .ownText() + private fun Element.searchSummary() = + select("div.pt4") + .first()!! + .ownText() private fun Element.searchPublishingStatus() = if (select(TD).last()!!.text() == "-") "Publishing" else "Finished" @@ -376,83 +386,62 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI private fun Element.searchStartDate() = select(TD)[6].text() - private fun getStatus(status: String) = when (status) { - "Reading" -> 1 - "Completed" -> 2 - "On-Hold" -> 3 - "Dropped" -> 4 - "Plan to Read" -> 6 - else -> 1 - } + private fun getStatus(status: String) = + when (status) { + "Reading" -> 1 + "Completed" -> 2 + "On-Hold" -> 3 + "Dropped" -> 4 + "Plan to Read" -> 6 + else -> 1 + } } private class MyAnimeListEditData( // entry_id var entry_id: String, - // manga_id var manga_id: String, - // add_manga[status] var status: String, - // add_manga[num_read_volumes] var num_read_volumes: String, - // last_completed_vol var last_completed_vol: String, - // add_manga[num_read_chapters] var num_read_chapters: String, - // add_manga[score] var score: String, - // add_manga[start_date][month] var start_date_month: String, // [1-12] - // add_manga[start_date][day] var start_date_day: String, - // add_manga[start_date][year] var start_date_year: String, - // add_manga[finish_date][month] var finish_date_month: String, // [1-12] - // add_manga[finish_date][day] var finish_date_day: String, - // add_manga[finish_date][year] var finish_date_year: String, - // add_manga[tags] var tags: String, - // add_manga[priority] var priority: String, - // add_manga[storage_type] var storage_type: String, - // add_manga[num_retail_volumes] var num_retail_volumes: String, - // add_manga[num_read_times] var num_read_times: String, - // add_manga[reread_value] var reread_value: String, - // add_manga[comments] var comments: String, - // add_manga[is_asked_to_discuss] var is_asked_to_discuss: String, - // add_manga[sns_post_type] var sns_post_type: String, - // submitIt val submitIt: String = "0" ) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt index e8d61814bb0e..b88cfea72f79 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt @@ -9,7 +9,6 @@ import okio.Buffer import org.json.JSONObject class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { myanimelist.ensureLoggedIn() @@ -20,11 +19,12 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor private fun updateRequest(request: Request): Request { return request.body?.let { val contentType = it.contentType().toString() - val updatedBody = when { - contentType.contains("x-www-form-urlencoded") -> updateFormBody(it) - contentType.contains("json") -> updateJsonBody(it) - else -> it - } + val updatedBody = + when { + contentType.contains("x-www-form-urlencoded") -> updateFormBody(it) + contentType.contains("json") -> updateJsonBody(it) + else -> it + } request.newBuilder().post(updatedBody).build() } ?: request } @@ -39,13 +39,16 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor private fun updateFormBody(requestBody: RequestBody): RequestBody { val formString = bodyToString(requestBody) - return "$formString${if (formString.isNotEmpty()) "&" else ""}${MyAnimeListApi.CSRF}=${myanimelist.getCSRF()}".toRequestBody(requestBody.contentType()) + return "$formString${if (formString.isNotEmpty()) "&" else ""}${MyAnimeListApi.CSRF}=${myanimelist.getCSRF()}".toRequestBody( + requestBody.contentType() + ) } private fun updateJsonBody(requestBody: RequestBody): RequestBody { val jsonString = bodyToString(requestBody) - val newBody = JSONObject(jsonString) - .put(MyAnimeListApi.CSRF, myanimelist.getCSRF()) + val newBody = + JSONObject(jsonString) + .put(MyAnimeListApi.CSRF, myanimelist.getCSRF()) return newBody.toString().toRequestBody(requestBody.contentType()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/OAuth.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/OAuth.kt index 8c6f2a9822a7..e5be0d61f432 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/OAuth.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/OAuth.kt @@ -7,7 +7,6 @@ data class OAuth( val expires_in: Long, val refresh_token: String? ) { - // Access token lives 1 day fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt index 397559b11413..7e6388ab9cd7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt @@ -12,7 +12,6 @@ import rx.Observable import uy.kohesive.injekt.injectLazy class Shikimori(private val context: Context, id: Int) : TrackService(id) { - companion object { const val READING = 1 const val COMPLETED = 2 @@ -88,21 +87,25 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) { return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLANNING, REPEATING) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.reading) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - DROPPED -> getString(R.string.dropped) - PLANNING -> getString(R.string.plan_to_read) - REPEATING -> getString(R.string.repeating) - else -> "" + override fun getStatus(status: Int): String = + with(context) { + when (status) { + READING -> getString(R.string.reading) + COMPLETED -> getString(R.string.completed) + ON_HOLD -> getString(R.string.on_hold) + DROPPED -> getString(R.string.dropped) + PLANNING -> getString(R.string.plan_to_read) + REPEATING -> getString(R.string.repeating) + else -> "" + } } - } override fun getCompletionStatus(): Int = COMPLETED - override fun login(username: String, password: String) = login(password) + override fun login( + username: String, + password: String + ) = login(password) fun login(code: String): Completable { return api.accessToken(code).map { oauth: OAuth? -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt index 8467f6209d97..d371d1ce9664 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt @@ -23,27 +23,32 @@ import rx.Observable import uy.kohesive.injekt.injectLazy class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) { - private val gson: Gson by injectLazy() private val jsonime = "application/json; charset=utf-8".toMediaTypeOrNull() private val authClient = client.newBuilder().addInterceptor(interceptor).build() - fun addLibManga(track: Track, user_id: String): Observable { - val payload = jsonObject( - "user_rate" to jsonObject( - "user_id" to user_id, - "target_id" to track.media_id, - "target_type" to "Manga", - "chapters" to track.last_chapter_read, - "score" to track.score.toInt(), - "status" to track.toShikimoriStatus() + fun addLibManga( + track: Track, + user_id: String + ): Observable { + val payload = + jsonObject( + "user_rate" to + jsonObject( + "user_id" to user_id, + "target_id" to track.media_id, + "target_type" to "Manga", + "chapters" to track.last_chapter_read, + "score" to track.score.toInt(), + "status" to track.toShikimoriStatus() + ) ) - ) val body = payload.toString().toRequestBody(jsonime) - val request = Request.Builder() - .url("$apiUrl/v2/user_rates") - .post(body) - .build() + val request = + Request.Builder() + .url("$apiUrl/v2/user_rates") + .post(body) + .build() return authClient.newCall(request) .asObservableSuccess() .map { @@ -51,18 +56,23 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter } } - fun updateLibManga(track: Track, user_id: String): Observable = addLibManga(track, user_id) + fun updateLibManga( + track: Track, + user_id: String + ): Observable = addLibManga(track, user_id) fun search(search: String): Observable> { - val url = Uri.parse("$apiUrl/mangas").buildUpon() - .appendQueryParameter("order", "popularity") - .appendQueryParameter("search", search) - .appendQueryParameter("limit", "20") - .build() - val request = Request.Builder() - .url(url.toString()) - .get() - .build() + val url = + Uri.parse("$apiUrl/mangas").buildUpon() + .appendQueryParameter("order", "popularity") + .appendQueryParameter("search", search) + .appendQueryParameter("limit", "20") + .build() + val request = + Request.Builder() + .url(url.toString()) + .get() + .build() return authClient.newCall(request) .asObservableSuccess() .map { netResponse -> @@ -89,7 +99,10 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter } } - private fun jsonToTrack(obj: JsonObject, mangas: JsonObject): Track { + private fun jsonToTrack( + obj: JsonObject, + mangas: JsonObject + ): Track { return Track.create(TrackManager.SHIKIMORI).apply { title = mangas["name"].asString media_id = obj["id"].asInt @@ -101,24 +114,31 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter } } - fun findLibManga(track: Track, user_id: String): Observable { - val url = Uri.parse("$apiUrl/v2/user_rates").buildUpon() - .appendQueryParameter("user_id", user_id) - .appendQueryParameter("target_id", track.media_id.toString()) - .appendQueryParameter("target_type", "Manga") - .build() - val request = Request.Builder() - .url(url.toString()) - .get() - .build() - - val urlMangas = Uri.parse("$apiUrl/mangas").buildUpon() - .appendPath(track.media_id.toString()) - .build() - val requestMangas = Request.Builder() - .url(urlMangas.toString()) - .get() - .build() + fun findLibManga( + track: Track, + user_id: String + ): Observable { + val url = + Uri.parse("$apiUrl/v2/user_rates").buildUpon() + .appendQueryParameter("user_id", user_id) + .appendQueryParameter("target_id", track.media_id.toString()) + .appendQueryParameter("target_type", "Manga") + .build() + val request = + Request.Builder() + .url(url.toString()) + .get() + .build() + + val urlMangas = + Uri.parse("$apiUrl/mangas").buildUpon() + .appendPath(track.media_id.toString()) + .build() + val requestMangas = + Request.Builder() + .url(urlMangas.toString()) + .get() + .build() return authClient.newCall(requestMangas) .asObservableSuccess() .map { netResponse -> @@ -136,9 +156,10 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter if (response.size() > 1) { throw Exception("Too much mangas in response") } - val entry = response.map { - jsonToTrack(it.obj, mangas) - } + val entry = + response.map { + jsonToTrack(it.obj, mangas) + } entry.firstOrNull() } } @@ -159,16 +180,18 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter } } - private fun accessTokenRequest(code: String) = POST( - oauthUrl, - body = FormBody.Builder() - .add("grant_type", "authorization_code") - .add("client_id", clientId) - .add("client_secret", clientSecret) - .add("code", code) - .add("redirect_uri", redirectUrl) - .build() - ) + private fun accessTokenRequest(code: String) = + POST( + oauthUrl, + body = + FormBody.Builder() + .add("grant_type", "authorization_code") + .add("client_id", clientId) + .add("client_secret", clientSecret) + .add("code", code) + .add("redirect_uri", redirectUrl) + .build() + ) companion object { private const val clientId = "1aaf4cf232372708e98b5abc813d795b539c5a916dbbfe9ac61bf02a360832cc" @@ -193,14 +216,16 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter .appendQueryParameter("response_type", "code") .build() - fun refreshTokenRequest(token: String) = POST( - oauthUrl, - body = FormBody.Builder() - .add("grant_type", "refresh_token") - .add("client_id", clientId) - .add("client_secret", clientSecret) - .add("refresh_token", token) - .build() - ) + fun refreshTokenRequest(token: String) = + POST( + oauthUrl, + body = + FormBody.Builder() + .add("grant_type", "refresh_token") + .add("client_id", clientId) + .add("client_secret", clientSecret) + .add("refresh_token", token) + .build() + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt index 619d69a3628f..d9fd401ebbbc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt @@ -5,7 +5,6 @@ import okhttp3.Interceptor import okhttp3.Response class ShikimoriInterceptor(val shikimori: Shikimori, val gson: Gson) : Interceptor { - /** * OAuth object used for authenticated requests. */ @@ -28,10 +27,11 @@ class ShikimoriInterceptor(val shikimori: Shikimori, val gson: Gson) : Intercept } } // Add the authorization header to the original request. - val authRequest = originalRequest.newBuilder() - .addHeader("Authorization", "Bearer ${oauth!!.access_token}") - .header("User-Agent", "Tachiyomi") - .build() + val authRequest = + originalRequest.newBuilder() + .addHeader("Authorization", "Bearer ${oauth!!.access_token}") + .header("User-Agent", "Tachiyomi") + .build() return chain.proceed(authRequest) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriModels.kt index 78621577184d..d782f694a993 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriModels.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriModels.kt @@ -2,22 +2,24 @@ package eu.kanade.tachiyomi.data.track.shikimori import eu.kanade.tachiyomi.data.database.models.Track -fun Track.toShikimoriStatus() = when (status) { - Shikimori.READING -> "watching" - Shikimori.COMPLETED -> "completed" - Shikimori.ON_HOLD -> "on_hold" - Shikimori.DROPPED -> "dropped" - Shikimori.PLANNING -> "planned" - Shikimori.REPEATING -> "rewatching" - else -> throw NotImplementedError("Unknown status: $status") -} +fun Track.toShikimoriStatus() = + when (status) { + Shikimori.READING -> "watching" + Shikimori.COMPLETED -> "completed" + Shikimori.ON_HOLD -> "on_hold" + Shikimori.DROPPED -> "dropped" + Shikimori.PLANNING -> "planned" + Shikimori.REPEATING -> "rewatching" + else -> throw NotImplementedError("Unknown status: $status") + } -fun toTrackStatus(status: String) = when (status) { - "watching" -> Shikimori.READING - "completed" -> Shikimori.COMPLETED - "on_hold" -> Shikimori.ON_HOLD - "dropped" -> Shikimori.DROPPED - "planned" -> Shikimori.PLANNING - "rewatching" -> Shikimori.REPEATING - else -> throw NotImplementedError("Unknown status: $status") -} +fun toTrackStatus(status: String) = + when (status) { + "watching" -> Shikimori.READING + "completed" -> Shikimori.COMPLETED + "on_hold" -> Shikimori.ON_HOLD + "dropped" -> Shikimori.DROPPED + "planned" -> Shikimori.PLANNING + "rewatching" -> Shikimori.REPEATING + else -> throw NotImplementedError("Unknown status: $status") + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/Release.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/Release.kt index 61f2bd787010..2fcf4a384924 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/Release.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/Release.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.updater interface Release { - val info: String /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateChecker.kt index 9f1e8eac1bef..28ca9c6860a0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateChecker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateChecker.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.data.updater.devrepo.DevRepoUpdateChecker import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker abstract class UpdateChecker { - companion object { fun getUpdateChecker(): UpdateChecker { return if (BuildConfig.DEBUG) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateResult.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateResult.kt index a147c01df182..de5751c461d7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateResult.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateResult.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.data.updater abstract class UpdateResult { - open class NewUpdate(val release: T) : UpdateResult() + open class NoNewUpdate : UpdateResult() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt index f786afc1016c..15ab1f41dcb9 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt @@ -14,12 +14,11 @@ import androidx.work.WorkerParameters import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.system.notificationManager -import java.util.concurrent.TimeUnit import kotlinx.coroutines.runBlocking +import java.util.concurrent.TimeUnit class UpdaterJob(private val context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { - override fun doWork(): Result { return runBlocking { try { @@ -28,9 +27,10 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) : if (result is UpdateResult.NewUpdate<*>) { val url = result.release.downloadLink - val intent = Intent(context, UpdaterService::class.java).apply { - putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url) - } + val intent = + Intent(context, UpdaterService::class.java).apply { + putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url) + } NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON).update { setContentTitle(context.getString(R.string.app_name)) @@ -60,17 +60,21 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) : private const val TAG = "UpdateChecker" fun setupTask(context: Context) { - val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() + val constraints = + Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() - val request = PeriodicWorkRequestBuilder( - 3, TimeUnit.DAYS, - 3, TimeUnit.HOURS - ) - .addTag(TAG) - .setConstraints(constraints) - .build() + val request = + PeriodicWorkRequestBuilder( + 3, + TimeUnit.DAYS, + 3, + TimeUnit.HOURS + ) + .addTag(TAG) + .setConstraints(constraints) + .build() WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt index b054e6c86953..45e998d00343 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt @@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.util.system.notificationManager * @param context context of application. */ internal class UpdaterNotifier(private val context: Context) { - private val notificationBuilder = context.notificationBuilder(Notifications.CHANNEL_COMMON) /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt index bfc831e2f55e..a790ab34d3b6 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt @@ -20,12 +20,11 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.isServiceRunning -import java.io.File import timber.log.Timber import uy.kohesive.injekt.injectLazy +import java.io.File class UpdaterService : Service() { - private val network: NetworkHelper by injectLazy() /** @@ -49,7 +48,11 @@ class UpdaterService : Service() { */ override fun onBind(intent: Intent): IBinder? = null - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + override fun onStartCommand( + intent: Intent?, + flags: Int, + startId: Int + ): Int { if (intent == null) return START_NOT_STICKY val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return START_NOT_STICKY @@ -84,27 +87,35 @@ class UpdaterService : Service() { * * @param url url location of file */ - private suspend fun downloadApk(title: String, url: String) { + private suspend fun downloadApk( + title: String, + url: String + ) { // Show notification download starting. notifier.onDownloadStarted(title) - val progressListener = object : ProgressListener { - // Progress of the download - var savedProgress = 0 - - // Keep track of the last notification sent to avoid posting too many. - var lastTick = 0L - - override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { - val progress = (100 * (bytesRead.toFloat() / contentLength)).toInt() - val currentTime = System.currentTimeMillis() - if (progress > savedProgress && currentTime - 200 > lastTick) { - savedProgress = progress - lastTick = currentTime - notifier.onProgressChange(progress) + val progressListener = + object : ProgressListener { + // Progress of the download + var savedProgress = 0 + + // Keep track of the last notification sent to avoid posting too many. + var lastTick = 0L + + override fun update( + bytesRead: Long, + contentLength: Long, + done: Boolean + ) { + val progress = (100 * (bytesRead.toFloat() / contentLength)).toInt() + val currentTime = System.currentTimeMillis() + if (progress > savedProgress && currentTime - 200 > lastTick) { + savedProgress = progress + lastTick = currentTime + notifier.onProgressChange(progress) + } } } - } try { // Download the new update. @@ -127,7 +138,6 @@ class UpdaterService : Service() { } companion object { - internal const val EXTRA_DOWNLOAD_URL = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_URL" internal const val EXTRA_DOWNLOAD_TITLE = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_TITLE" @@ -137,8 +147,7 @@ class UpdaterService : Service() { * @param context the application context. * @return true if the service is running, false otherwise. */ - private fun isRunning(context: Context): Boolean = - context.isServiceRunning(UpdaterService::class.java) + private fun isRunning(context: Context): Boolean = context.isServiceRunning(UpdaterService::class.java) /** * Downloads a new update and let the user install the new version from a notification. @@ -146,12 +155,17 @@ class UpdaterService : Service() { * @param context the application context. * @param url the url to the new update. */ - fun start(context: Context, url: String, title: String = context.getString(R.string.app_name)) { + fun start( + context: Context, + url: String, + title: String = context.getString(R.string.app_name) + ) { if (!isRunning(context)) { - val intent = Intent(context, UpdaterService::class.java).apply { - putExtra(EXTRA_DOWNLOAD_TITLE, title) - putExtra(EXTRA_DOWNLOAD_URL, url) - } + val intent = + Intent(context, UpdaterService::class.java).apply { + putExtra(EXTRA_DOWNLOAD_TITLE, title) + putExtra(EXTRA_DOWNLOAD_URL, url) + } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { context.startService(intent) } else { @@ -166,10 +180,14 @@ class UpdaterService : Service() { * @param url the url to the new update. * @return [PendingIntent] */ - internal fun downloadApkPendingService(context: Context, url: String): PendingIntent { - val intent = Intent(context, UpdaterService::class.java).apply { - putExtra(EXTRA_DOWNLOAD_URL, url) - } + internal fun downloadApkPendingService( + context: Context, + url: String + ): PendingIntent { + val intent = + Intent(context, UpdaterService::class.java).apply { + putExtra(EXTRA_DOWNLOAD_URL, url) + } return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoRelease.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoRelease.kt index 0f2b1ac7517c..56c352934176 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoRelease.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoRelease.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.updater.devrepo import eu.kanade.tachiyomi.data.updater.Release class DevRepoRelease(override val info: String) : Release { - override val downloadLink: String get() = LATEST_URL diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoUpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoUpdateChecker.kt index c224ba4578fd..c300d1306a60 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoUpdateChecker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoUpdateChecker.kt @@ -13,7 +13,6 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class DevRepoUpdateChecker : UpdateChecker() { - private val client: OkHttpClient by lazy { Injekt.get().client.newBuilder() .followRedirects(false) @@ -25,9 +24,10 @@ class DevRepoUpdateChecker : UpdateChecker() { } override suspend fun checkForUpdate(): UpdateResult { - val response = withContext(Dispatchers.IO) { - client.newCall(GET(DevRepoRelease.LATEST_URL)).await() - } + val response = + withContext(Dispatchers.IO) { + client.newCall(GET(DevRepoRelease.LATEST_URL)).await() + } // Get latest repo version number from header in format "Location: tachiyomi-r1512.apk" val latestVersionNumber: String = versionRegex.find(response.header("Location")!!)!!.groupValues[1] diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoUpdateResult.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoUpdateResult.kt index 1b62201a851a..6c587c0838fa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoUpdateResult.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoUpdateResult.kt @@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.updater.devrepo import eu.kanade.tachiyomi.data.updater.UpdateResult sealed class DevRepoUpdateResult : UpdateResult() { - class NewUpdate(release: DevRepoRelease) : UpdateResult.NewUpdate(release) + class NoNewUpdate : UpdateResult.NoNewUpdate() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubRelease.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubRelease.kt index 09f1b37d0128..ae06c62088fe 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubRelease.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubRelease.kt @@ -16,7 +16,6 @@ class GithubRelease( @SerializedName("body") override val info: String, @SerializedName("assets") private val assets: List ) : Release { - /** * Get download link of latest release from the assets. * @return download link of latest release. @@ -28,5 +27,7 @@ class GithubRelease( * Assets class containing download url. * @param downloadLink download url. */ - inner class Assets(@SerializedName("browser_download_url") val downloadLink: String) + inner class Assets( + @SerializedName("browser_download_url") val downloadLink: String + ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubService.kt index 2ed1bcc7d8af..5324022d4ac1 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubService.kt @@ -11,14 +11,14 @@ import uy.kohesive.injekt.api.get * Used to connect with the GitHub API. */ interface GithubService { - companion object { fun create(): GithubService { - val restAdapter = Retrofit.Builder() - .baseUrl("https://api.github.com") - .addConverterFactory(GsonConverterFactory.create()) - .client(Injekt.get().client) - .build() + val restAdapter = + Retrofit.Builder() + .baseUrl("https://api.github.com") + .addConverterFactory(GsonConverterFactory.create()) + .client(Injekt.get().client) + .build() return restAdapter.create(GithubService::class.java) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateChecker.kt index 8f43eaa875f3..407cc3281b6b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateChecker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateChecker.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.data.updater.UpdateChecker import eu.kanade.tachiyomi.data.updater.UpdateResult class GithubUpdateChecker : UpdateChecker() { - private val service: GithubService = GithubService.create() override suspend fun checkForUpdate(): UpdateResult { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateResult.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateResult.kt index 8462f937ea3b..4412092c25a2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateResult.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateResult.kt @@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.updater.github import eu.kanade.tachiyomi.data.updater.UpdateResult sealed class GithubUpdateResult : UpdateResult() { - class NewUpdate(release: GithubRelease) : UpdateResult.NewUpdate(release) + class NoNewUpdate : UpdateResult.NoNewUpdate() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index c62e47faf14f..64c22da77f0d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -41,7 +41,6 @@ class ExtensionManager( private val context: Context, private val preferences: PreferencesHelper = Injekt.get() ) { - /** * API where all the available extensions can be found. */ @@ -134,18 +133,20 @@ class ExtensionManager( private fun initExtensions() { val extensions = ExtensionLoader.loadExtensions(context) - installedExtensions = extensions - .filterIsInstance() - .map { it.extension } + installedExtensions = + extensions + .filterIsInstance() + .map { it.extension } installedExtensions .flatMap { it.sources } // overwrite is needed until the bundled sources are removed .forEach { sourceManager.registerSource(it, true) } - untrustedExtensions = extensions - .filterIsInstance() - .map { it.extension } - .filterNotBlacklisted() + untrustedExtensions = + extensions + .filterIsInstance() + .map { it.extension } + .filterNotBlacklisted() } // EXH --> @@ -155,7 +156,9 @@ class ExtensionManager( if (it.isBlacklisted(blacklistEnabled)) { XLog.d("[EXH] Removing blacklisted extension: (name: %s, pkgName: %s)!", it.name, it.pkgName) false - } else true + } else { + true + } } } @@ -193,12 +196,13 @@ class ExtensionManager( */ fun findAvailableExtensions() { launchNow { - val extensions: List = try { - api.findExtensions().filterNotBlacklisted() - } catch (e: Exception) { - context.toast(e.message) - emptyList() - } + val extensions: List = + try { + api.findExtensions().filterNotBlacklisted() + } catch (e: Exception) { + context.toast(e.message) + emptyList() + } unalteredAvailableExtensions = extensions availableExtensions = extensions.filterNotBlacklisted() @@ -262,8 +266,9 @@ class ExtensionManager( * @param extension The extension to be updated. */ fun updateExtension(extension: Extension.Installed): Observable { - val availableExt = availableExtensions.find { it.pkgName == extension.pkgName } - ?: return Observable.empty() + val availableExt = + availableExtensions.find { it.pkgName == extension.pkgName } + ?: return Observable.empty() return installExtension(availableExt) } @@ -280,12 +285,18 @@ class ExtensionManager( installer.updateInstallStep(downloadId, InstallStep.Installing) } - fun setInstallationResult(downloadId: Long, result: Boolean) { + fun setInstallationResult( + downloadId: Long, + result: Boolean + ) { val step = if (result) InstallStep.Installed else InstallStep.Error installer.updateInstallStep(downloadId, step) } - fun updateInstallStep(downloadId: Long, step: InstallStep) { + fun updateInstallStep( + downloadId: Long, + step: InstallStep + ) { installer.updateInstallStep(downloadId, step) } @@ -390,7 +401,6 @@ class ExtensionManager( * Listener which receives events of the extensions being installed, updated or removed. */ private inner class InstallationListener : ExtensionInstallReceiver.Listener { - override fun onExtensionInstalled(extension: Extension.Installed) { registerNewExtension(extension.withUpdateCheck()) updatePendingUpdatesCount() diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt index d9d2ed398aa0..488e3d017d12 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt @@ -16,28 +16,29 @@ import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.util.system.notification -import java.util.concurrent.TimeUnit import kotlinx.coroutines.coroutineScope import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.concurrent.TimeUnit class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { + override suspend fun doWork(): Result = + coroutineScope { + val pendingUpdates = + try { + ExtensionGithubApi().checkForUpdates(context) + } catch (e: Exception) { + return@coroutineScope Result.failure() + } - override suspend fun doWork(): Result = coroutineScope { - val pendingUpdates = try { - ExtensionGithubApi().checkForUpdates(context) - } catch (e: Exception) { - return@coroutineScope Result.failure() - } + if (pendingUpdates.isNotEmpty()) { + createUpdateNotification(pendingUpdates.map { it.name }) + } - if (pendingUpdates.isNotEmpty()) { - createUpdateNotification(pendingUpdates.map { it.name }) + Result.success() } - Result.success() - } - private fun createUpdateNotification(names: List) { NotificationManagerCompat.from(context).apply { notify( @@ -64,21 +65,28 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam companion object { private const val TAG = "ExtensionUpdate" - fun setupTask(context: Context, forceAutoUpdateJob: Boolean? = null) { + fun setupTask( + context: Context, + forceAutoUpdateJob: Boolean? = null + ) { val preferences = Injekt.get() val autoUpdateJob = forceAutoUpdateJob ?: preferences.automaticExtUpdates().get() if (autoUpdateJob) { - val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() + val constraints = + Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() - val request = PeriodicWorkRequestBuilder( - 12, TimeUnit.HOURS, - 1, TimeUnit.HOURS - ) - .addTag(TAG) - .setConstraints(constraints) - .build() + val request = + PeriodicWorkRequestBuilder( + 12, + TimeUnit.HOURS, + 1, + TimeUnit.HOURS + ) + .addTag(TAG) + .setConstraints(constraints) + .build() WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request) } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt index b050cc34ccbf..3debf7b8ed72 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt @@ -6,15 +6,14 @@ import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.LoadResult import eu.kanade.tachiyomi.extension.util.ExtensionLoader import exh.source.BlacklistedSources -import java.util.Date import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import uy.kohesive.injekt.injectLazy +import java.util.Date internal class ExtensionGithubApi { - private val preferences: PreferencesHelper by injectLazy() suspend fun findExtensions(): List { @@ -45,11 +44,12 @@ internal class ExtensionGithubApi { val blacklistEnabled = preferences.eh_enableSourceBlacklist().get() // SY <-- - val installedExtensions = ExtensionLoader.loadExtensions(context) - .filterIsInstance() - .map { it.extension } - // SY --> - .filterNot { it.isBlacklisted(blacklistEnabled) } + val installedExtensions = + ExtensionLoader.loadExtensions(context) + .filterIsInstance() + .map { it.extension } + // SY --> + .filterNot { it.isBlacklisted(blacklistEnabled) } // SY <-- val extensionsWithUpdate = mutableListOf() @@ -66,7 +66,10 @@ internal class ExtensionGithubApi { return extensionsWithUpdate } - private fun parseResponse(json: JsonArray, repoUrl: String): List { + private fun parseResponse( + json: JsonArray, + repoUrl: String + ): List { return json .filter { element -> val versionName = element.jsonObject["version"]!!.jsonPrimitive.content @@ -94,9 +97,7 @@ internal class ExtensionGithubApi { return "${extension.repoUrl.substringBeforeLast("index.min.json")}apk/${extension.apkName}" } - private fun Extension.isBlacklisted( - blacklistEnabled: Boolean = preferences.eh_enableSourceBlacklist().get() - ): Boolean { + private fun Extension.isBlacklisted(blacklistEnabled: Boolean = preferences.eh_enableSourceBlacklist().get()): Boolean { return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS && blacklistEnabled } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubService.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubService.kt index 27067ce362ca..b3a37cc5ad11 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubService.kt @@ -15,21 +15,23 @@ import uy.kohesive.injekt.injectLazy * Used to get the extension repo listing from GitHub. */ interface ExtensionGithubService { - companion object { @OptIn(ExperimentalSerializationApi::class) fun create(): ExtensionGithubService { val network: NetworkHelper by injectLazy() - val adapter = Retrofit.Builder() - .baseUrl(ExtensionGithubApi.BASE_URL) - .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) - .client(network.client) - .build() + val adapter = + Retrofit.Builder() + .baseUrl(ExtensionGithubApi.BASE_URL) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .client(network.client) + .build() return adapter.create(ExtensionGithubService::class.java) } } @GET - suspend fun getRepo(@Url url: String): JsonArray + suspend fun getRepo( + @Url url: String + ): JsonArray } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt index 321dca4d7446..092281cb99ec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt @@ -10,26 +10,29 @@ import androidx.annotation.CallSuper import androidx.localbroadcastmanager.content.LocalBroadcastManager import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.model.InstallStep +import uy.kohesive.injekt.injectLazy import java.util.Collections import java.util.concurrent.atomic.AtomicReference -import uy.kohesive.injekt.injectLazy /** * Base implementation class for extension installer. To be used inside a foreground [Service]. */ abstract class Installer(private val service: Service) { - private val extensionManager: ExtensionManager by injectLazy() private var waitingInstall = AtomicReference(null) private val queue = Collections.synchronizedList(mutableListOf()) - private val cancelReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1).takeIf { it >= 0 } ?: return - cancelQueue(downloadId) + private val cancelReceiver = + object : BroadcastReceiver() { + override fun onReceive( + context: Context, + intent: Intent + ) { + val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1).takeIf { it >= 0 } ?: return + cancelQueue(downloadId) + } } - } /** * Installer readiness. If false, queue check will not run. @@ -44,7 +47,10 @@ abstract class Installer(private val service: Service) { * @param downloadId Download ID as known by [ExtensionManager] * @param uri Uri of APK to install */ - fun addToQueue(downloadId: Long, uri: Uri) { + fun addToQueue( + downloadId: Long, + uri: Uri + ) { queue.add(Entry(downloadId, uri)) checkQueue() } @@ -161,7 +167,10 @@ abstract class Installer(private val service: Service) { * * @param downloadId Download ID as known by [ExtensionManager] */ - fun cancelInstallQueue(context: Context, downloadId: Long) { + fun cancelInstallQueue( + context: Context, + downloadId: Long + ) { val intent = Intent(ACTION_CANCEL_QUEUE) intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId) LocalBroadcastManager.getInstance(context).sendBroadcast(intent) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/PackageInstallerInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/PackageInstallerInstaller.kt index 9dd03f261b19..a217eacf7d2e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/PackageInstallerInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/PackageInstallerInstaller.kt @@ -14,30 +14,33 @@ import eu.kanade.tachiyomi.util.system.getUriSize import timber.log.Timber class PackageInstallerInstaller(private val service: Service) : Installer(service) { - private val packageInstaller = service.packageManager.packageInstaller - private val packageActionReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)) { - PackageInstaller.STATUS_PENDING_USER_ACTION -> { - val userAction = intent.getParcelableExtra(Intent.EXTRA_INTENT) - if (userAction == null) { - Timber.e("Fatal error for $intent") - continueQueue(InstallStep.Error) - return + private val packageActionReceiver = + object : BroadcastReceiver() { + override fun onReceive( + context: Context, + intent: Intent + ) { + when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)) { + PackageInstaller.STATUS_PENDING_USER_ACTION -> { + val userAction = intent.getParcelableExtra(Intent.EXTRA_INTENT) + if (userAction == null) { + Timber.e("Fatal error for $intent") + continueQueue(InstallStep.Error) + return + } + userAction.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + service.startActivity(userAction) } - userAction.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - service.startActivity(userAction) - } - PackageInstaller.STATUS_FAILURE_ABORTED -> { - continueQueue(InstallStep.Idle) + PackageInstaller.STATUS_FAILURE_ABORTED -> { + continueQueue(InstallStep.Idle) + } + PackageInstaller.STATUS_SUCCESS -> continueQueue(InstallStep.Installed) + else -> continueQueue(InstallStep.Error) } - PackageInstaller.STATUS_SUCCESS -> continueQueue(InstallStep.Installed) - else -> continueQueue(InstallStep.Error) } } - } private var activeSession: Pair? = null @@ -65,12 +68,13 @@ class PackageInstallerInstaller(private val service: Service) : Installer(servic session.fsync(outputStream) } - val intentSender = PendingIntent.getBroadcast( - service, - activeSession!!.second, - Intent(INSTALL_ACTION), - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0 - ).intentSender + val intentSender = + PendingIntent.getBroadcast( + service, + activeSession!!.second, + Intent(INSTALL_ACTION), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0 + ).intentSender session.commit(intentSender) } } catch (e: Exception) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt index 9509053b04f8..7b50e26d563c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt @@ -8,8 +8,6 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.util.system.getUriSize import eu.kanade.tachiyomi.util.system.toast -import java.io.BufferedReader -import java.io.InputStream import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -17,29 +15,35 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import rikka.shizuku.Shizuku import timber.log.Timber +import java.io.BufferedReader +import java.io.InputStream class ShizukuInstaller(private val service: Service) : Installer(service) { - private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) - private val shizukuDeadListener = Shizuku.OnBinderDeadListener { - Timber.e("Shizuku was killed prematurely") - service.stopSelf() - } + private val shizukuDeadListener = + Shizuku.OnBinderDeadListener { + Timber.e("Shizuku was killed prematurely") + service.stopSelf() + } - private val shizukuPermissionListener = object : Shizuku.OnRequestPermissionResultListener { - override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) { - if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) { - if (grantResult == PackageManager.PERMISSION_GRANTED) { - ready = true - checkQueue() - } else { - service.stopSelf() + private val shizukuPermissionListener = + object : Shizuku.OnRequestPermissionResultListener { + override fun onRequestPermissionResult( + requestCode: Int, + grantResult: Int + ) { + if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) { + if (grantResult == PackageManager.PERMISSION_GRANTED) { + ready = true + checkQueue() + } else { + service.stopSelf() + } + Shizuku.removeRequestPermissionResultListener(this) } - Shizuku.removeRequestPermissionResultListener(this) } } - } override var ready = false @@ -51,12 +55,13 @@ class ShizukuInstaller(private val service: Service) : Installer(service) { try { val size = service.getUriSize(entry.uri) ?: throw IllegalStateException() service.contentResolver.openInputStream(entry.uri)!!.use { - val createCommand = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - val userId = Process.myUserHandle().hashCode() - "pm install-create --user $userId -r -i ${service.packageName} -S $size" - } else { - "pm install-create -r -i ${service.packageName} -S $size" - } + val createCommand = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + val userId = Process.myUserHandle().hashCode() + "pm install-create --user $userId -r -i ${service.packageName} -S $size" + } else { + "pm install-create -r -i ${service.packageName} -S $size" + } val createResult = exec(createCommand) sessionId = SESSION_ID_REGEX.find(createResult.out)?.value @@ -94,7 +99,10 @@ class ShizukuInstaller(private val service: Service) : Installer(service) { super.onDestroy() } - private fun exec(command: String, stdin: InputStream? = null): ShellResult { + private fun exec( + command: String, + stdin: InputStream? = null + ): ShellResult { @Suppress("DEPRECATION") val process = Shizuku.newProcess(arrayOf("sh", "-c", command), null, null) if (stdin != null) { @@ -109,20 +117,21 @@ class ShizukuInstaller(private val service: Service) : Installer(service) { init { Shizuku.addBinderDeadListener(shizukuDeadListener) - ready = if (Shizuku.pingBinder()) { - if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) { - true + ready = + if (Shizuku.pingBinder()) { + if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) { + true + } else { + Shizuku.addRequestPermissionResultListener(shizukuPermissionListener) + Shizuku.requestPermission(SHIZUKU_PERMISSION_REQUEST_CODE) + false + } } else { - Shizuku.addRequestPermissionResultListener(shizukuPermissionListener) - Shizuku.requestPermission(SHIZUKU_PERMISSION_REQUEST_CODE) + Timber.e("Shizuku is not ready to use.") + service.toast(R.string.ext_installer_shizuku_stopped) + service.stopSelf() false } - } else { - Timber.e("Shizuku is not ready to use.") - service.toast(R.string.ext_installer_shizuku_stopped) - service.stopSelf() - false - } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt index 318ed3b1580a..269488542f65 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension.model import eu.kanade.tachiyomi.source.Source sealed class Extension { - abstract val name: String abstract val pkgName: String abstract val versionName: String diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/model/InstallStep.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/model/InstallStep.kt index d1049689e21c..661326b3744f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/model/InstallStep.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/model/InstallStep.kt @@ -1,7 +1,13 @@ package eu.kanade.tachiyomi.extension.model enum class InstallStep { - Idle, Pending, Downloading, Installing, Installed, Error; + Idle, + Pending, + Downloading, + Installing, + Installed, + Error + ; fun isCompleted(): Boolean { return this == Installed || this == Error || this == Idle diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/model/LoadResult.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/model/LoadResult.kt index 0cf470fe85e7..1576fd5bd423 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/model/LoadResult.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/model/LoadResult.kt @@ -1,9 +1,10 @@ package eu.kanade.tachiyomi.extension.model sealed class LoadResult { - class Success(val extension: Extension.Installed) : LoadResult() + class Untrusted(val extension: Extension.Untrusted) : LoadResult() + class Error(val message: String? = null) : LoadResult() { constructor(exception: Throwable) : this(exception.message) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt index ce9ee8b42f3d..01ba72b57337 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt @@ -14,14 +14,14 @@ import uy.kohesive.injekt.api.get * with [startActivityForResult], which we need to update the UI. */ class ExtensionInstallActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE) - .setDataAndType(intent.data, intent.type) - .putExtra(Intent.EXTRA_RETURN_RESULT, true) - .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + val installIntent = + Intent(Intent.ACTION_INSTALL_PACKAGE) + .setDataAndType(intent.data, intent.type) + .putExtra(Intent.EXTRA_RETURN_RESULT, true) + .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) try { startActivityForResult(installIntent, INSTALL_REQUEST_CODE) @@ -32,7 +32,11 @@ class ExtensionInstallActivity : Activity() { } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + data: Intent? + ) { if (requestCode == INSTALL_REQUEST_CODE) { checkInstallationResult(resultCode) } @@ -42,11 +46,12 @@ class ExtensionInstallActivity : Activity() { private fun checkInstallationResult(resultCode: Int) { val downloadId = intent.extras!!.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID) val extensionManager = Injekt.get() - val newStep = when (resultCode) { - RESULT_OK -> InstallStep.Installed - RESULT_CANCELED -> InstallStep.Idle - else -> InstallStep.Error - } + val newStep = + when (resultCode) { + RESULT_OK -> InstallStep.Installed + RESULT_CANCELED -> InstallStep.Idle + else -> InstallStep.Error + } extensionManager.updateInstallStep(downloadId, newStep) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallReceiver.kt index e3cec0416d5b..bd29b7fb124b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallReceiver.kt @@ -20,7 +20,6 @@ import kotlinx.coroutines.async */ internal class ExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() { - /** * Registers this broadcast receiver */ @@ -32,27 +31,33 @@ internal class ExtensionInstallReceiver(private val listener: Listener) : * Returns the intent filter this receiver should subscribe to. */ private val filter - get() = IntentFilter().apply { - addAction(Intent.ACTION_PACKAGE_ADDED) - addAction(Intent.ACTION_PACKAGE_REPLACED) - addAction(Intent.ACTION_PACKAGE_REMOVED) - addDataScheme("package") - } + get() = + IntentFilter().apply { + addAction(Intent.ACTION_PACKAGE_ADDED) + addAction(Intent.ACTION_PACKAGE_REPLACED) + addAction(Intent.ACTION_PACKAGE_REMOVED) + addDataScheme("package") + } /** * Called when one of the events of the [filter] is received. When the package is an extension, * it's loaded in background and it notifies the [listener] when finished. */ - override fun onReceive(context: Context, intent: Intent?) { + override fun onReceive( + context: Context, + intent: Intent? + ) { if (intent == null) return when (intent.action) { Intent.ACTION_PACKAGE_ADDED -> { - if (!isReplacing(intent)) launchNow { - when (val result = getExtensionFromIntent(context, intent)) { - is LoadResult.Success -> listener.onExtensionInstalled(result.extension) - is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) - else -> {} + if (!isReplacing(intent)) { + launchNow { + when (val result = getExtensionFromIntent(context, intent)) { + is LoadResult.Success -> listener.onExtensionInstalled(result.extension) + is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} + } } } } @@ -93,10 +98,17 @@ internal class ExtensionInstallReceiver(private val listener: Listener) : * @param context The application context. * @param intent The intent containing the package name of the extension. */ - private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult { - val pkgName = getPackageNameFromIntent(intent) - ?: return LoadResult.Error("Package name not found") - return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await() + private suspend fun getExtensionFromIntent( + context: Context, + intent: Intent? + ): LoadResult { + val pkgName = + getPackageNameFromIntent(intent) + ?: return LoadResult.Error("Package name not found") + return GlobalScope.async( + Dispatchers.Default, + CoroutineStart.DEFAULT + ) { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await() } /** @@ -111,8 +123,11 @@ internal class ExtensionInstallReceiver(private val listener: Listener) : */ interface Listener { fun onExtensionInstalled(extension: Extension.Installed) + fun onExtensionUpdated(extension: Extension.Installed) + fun onExtensionUntrusted(extension: Extension.Untrusted) + fun onPackageUninstalled(pkgName: String) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallService.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallService.kt index f63fe9f4c6f8..1d0e95df96aa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallService.kt @@ -16,23 +16,27 @@ import eu.kanade.tachiyomi.util.system.notificationBuilder import timber.log.Timber class ExtensionInstallService : Service() { - private var installer: Installer? = null override fun onCreate() { super.onCreate() - val notification = notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) { - setSmallIcon(R.drawable.ic_tachi) - setAutoCancel(false) - setOngoing(true) - setShowWhen(false) - setContentTitle(getString(R.string.ext_install_service_notif)) - setProgress(100, 100, true) - }.build() + val notification = + notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) { + setSmallIcon(R.drawable.ic_tachi) + setAutoCancel(false) + setOngoing(true) + setShowWhen(false) + setContentTitle(getString(R.string.ext_install_service_notif)) + setProgress(100, 100, true) + }.build() startForeground(Notifications.ID_EXTENSION_INSTALLER, notification) } - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + override fun onStartCommand( + intent: Intent?, + flags: Int, + startId: Int + ): Int { val uri = intent?.data val id = intent?.getLongExtra(EXTRA_DOWNLOAD_ID, -1)?.takeIf { it != -1L } val installerUsed = intent?.getSerializableExtra(EXTRA_INSTALLER) as? PreferenceValues.ExtensionInstaller @@ -42,15 +46,16 @@ class ExtensionInstallService : Service() { } if (installer == null) { - installer = when (installerUsed) { - PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstaller(this) - PreferenceValues.ExtensionInstaller.SHIZUKU -> ShizukuInstaller(this) - else -> { - Timber.e("Not implemented for installer $installerUsed") - stopSelf() - return START_NOT_STICKY + installer = + when (installerUsed) { + PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstaller(this) + PreferenceValues.ExtensionInstaller.SHIZUKU -> ShizukuInstaller(this) + else -> { + Timber.e("Not implemented for installer $installerUsed") + stopSelf() + return START_NOT_STICKY + } } - } } installer!!.addToQueue(id, uri) return START_NOT_STICKY diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt index b1a8a4b217ae..67bd6b0229d7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt @@ -16,13 +16,13 @@ import eu.kanade.tachiyomi.extension.installer.Installer import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.util.storage.getUriCompat -import java.io.File -import java.util.concurrent.TimeUnit import rx.Observable import rx.android.schedulers.AndroidSchedulers import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.File +import java.util.concurrent.TimeUnit /** * The installer which installs, updates and uninstalls the extensions. @@ -30,7 +30,6 @@ import uy.kohesive.injekt.api.get * @param context The application context. */ internal class ExtensionInstaller(private val context: Context) { - /** * The system's download manager */ @@ -61,7 +60,10 @@ internal class ExtensionInstaller(private val context: Context) { * @param url The url of the apk. * @param extension The extension to install. */ - fun downloadAndInstall(url: String, extension: Extension) = Observable.defer { + fun downloadAndInstall( + url: String, + extension: Extension + ) = Observable.defer { val pkgName = extension.pkgName val oldDownload = activeDownloads[pkgName] @@ -73,11 +75,12 @@ internal class ExtensionInstaller(private val context: Context) { downloadReceiver.register() val downloadUri = Uri.parse(url) - val request = DownloadManager.Request(downloadUri) - .setTitle(extension.name) - .setMimeType(APK_MIME) - .setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, downloadUri.lastPathSegment) - .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + val request = + DownloadManager.Request(downloadUri) + .setTitle(extension.name) + .setMimeType(APK_MIME) + .setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, downloadUri.lastPathSegment) + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) val id = downloadManager.enqueue(request) activeDownloads[pkgName] = id @@ -130,13 +133,17 @@ internal class ExtensionInstaller(private val context: Context) { * * @param uri The uri of the extension to install. */ - fun installApk(downloadId: Long, uri: Uri) { + fun installApk( + downloadId: Long, + uri: Uri + ) { when (val installer = installerPref.get()) { PreferenceValues.ExtensionInstaller.LEGACY -> { - val intent = Intent(context, ExtensionInstallActivity::class.java) - .setDataAndType(uri, APK_MIME) - .putExtra(EXTRA_DOWNLOAD_ID, downloadId) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION) + val intent = + Intent(context, ExtensionInstallActivity::class.java) + .setDataAndType(uri, APK_MIME) + .putExtra(EXTRA_DOWNLOAD_ID, downloadId) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION) context.startActivity(intent) } @@ -163,8 +170,9 @@ internal class ExtensionInstaller(private val context: Context) { */ fun uninstallApk(pkgName: String) { val packageUri = Uri.parse("package:$pkgName") - val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + val intent = + Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(intent) } @@ -175,7 +183,10 @@ internal class ExtensionInstaller(private val context: Context) { * @param downloadId The id of the download. * @param step New install step. */ - fun updateInstallStep(downloadId: Long, step: InstallStep) { + fun updateInstallStep( + downloadId: Long, + step: InstallStep + ) { downloadsRelay.call(downloadId to step) } @@ -198,7 +209,6 @@ internal class ExtensionInstaller(private val context: Context) { * Receiver that listens to download status events. */ private inner class DownloadCompletionReceiver : BroadcastReceiver() { - /** * Whether this receiver is currently registered. */ @@ -229,7 +239,10 @@ internal class ExtensionInstaller(private val context: Context) { * Called when a download event is received. It looks for the download in the current active * downloads and notifies its installation step. */ - override fun onReceive(context: Context, intent: Intent?) { + override fun onReceive( + context: Context, + intent: Intent? + ) { val id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0) ?: return // Avoid events for downloads we didn't request @@ -247,9 +260,10 @@ internal class ExtensionInstaller(private val context: Context) { val query = DownloadManager.Query().setFilterById(id) downloadManager.query(query).use { cursor -> if (cursor.moveToFirst()) { - val localUri = cursor.getString( - cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI) - ).removePrefix(FILE_SCHEME) + val localUri = + cursor.getString( + cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI) + ).removePrefix(FILE_SCHEME) installApk(id, File(localUri).getUriCompat(context)) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt index cce9bcc44071..fa2ece80c78d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt @@ -24,7 +24,6 @@ import uy.kohesive.injekt.injectLazy */ @SuppressLint("PackageManagerGetSignatures") internal object ExtensionLoader { - private val preferences: PreferencesHelper by injectLazy() private val allowNsfwSource by lazy { preferences.allowNsfwSource().get() @@ -60,9 +59,10 @@ internal object ExtensionLoader { // Load each extension concurrently and wait for completion return runBlocking { - val deferred = extPkgs.map { - async { loadExtension(context, it.packageName, it) } - } + val deferred = + extPkgs.map { + async { loadExtension(context, it.packageName, it) } + } deferred.map { it.await() } } } @@ -71,13 +71,17 @@ internal object ExtensionLoader { * Attempts to load an extension from the given package name. It checks if the extension * contains the required feature flag before trying to load it. */ - fun loadExtensionFromPkgName(context: Context, pkgName: String): LoadResult { - val pkgInfo = try { - context.packageManager.getPackageInfo(pkgName, PACKAGE_FLAGS) - } catch (error: PackageManager.NameNotFoundException) { - // Unlikely, but the package may have been uninstalled at this point - return LoadResult.Error(error) - } + fun loadExtensionFromPkgName( + context: Context, + pkgName: String + ): LoadResult { + val pkgInfo = + try { + context.packageManager.getPackageInfo(pkgName, PACKAGE_FLAGS) + } catch (error: PackageManager.NameNotFoundException) { + // Unlikely, but the package may have been uninstalled at this point + return LoadResult.Error(error) + } if (!isPackageAnExtension(pkgInfo)) { return LoadResult.Error("Tried to load a package that wasn't a extension") } @@ -91,15 +95,20 @@ internal object ExtensionLoader { * @param pkgName The package name of the extension to load. * @param pkgInfo The package info of the extension. */ - private fun loadExtension(context: Context, pkgName: String, pkgInfo: PackageInfo): LoadResult { + private fun loadExtension( + context: Context, + pkgName: String, + pkgInfo: PackageInfo + ): LoadResult { val pkgManager = context.packageManager - val appInfo = try { - pkgManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA) - } catch (error: PackageManager.NameNotFoundException) { - // Unlikely, but the package may have been uninstalled at this point - return LoadResult.Error(error) - } + val appInfo = + try { + pkgManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA) + } catch (error: PackageManager.NameNotFoundException) { + // Unlikely, but the package may have been uninstalled at this point + return LoadResult.Error(error) + } val extName = pkgManager.getApplicationLabel(appInfo).toString().substringAfter("Tachiyomi: ") val versionName = pkgInfo.versionName @@ -114,10 +123,11 @@ internal object ExtensionLoader { // Validate lib version val libVersion = versionName.substringBeforeLast('.').toDouble() if (libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) { - val exception = Exception( - "Lib version is $libVersion, while only versions " + - "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed" - ) + val exception = + Exception( + "Lib version is $libVersion, while only versions " + + "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed" + ) Timber.w(exception) return LoadResult.Error(exception) } @@ -139,49 +149,53 @@ internal object ExtensionLoader { val classLoader = ChildFirstPathClassLoader(appInfo.sourceDir, null, context.classLoader) - val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!! - .split(";") - .map { - val sourceClass = it.trim() - if (sourceClass.startsWith(".")) { - pkgInfo.packageName + sourceClass - } else { - sourceClass + val sources = + appInfo.metaData.getString(METADATA_SOURCE_CLASS)!! + .split(";") + .map { + val sourceClass = it.trim() + if (sourceClass.startsWith(".")) { + pkgInfo.packageName + sourceClass + } else { + sourceClass + } } - } - .flatMap { - try { - when (val obj = Class.forName(it, false, classLoader).newInstance()) { - is Source -> listOf(obj) - is SourceFactory -> { - if (isSourceNsfw(obj)) { - emptyList() - } else { - obj.createSources() + .flatMap { + try { + when (val obj = Class.forName(it, false, classLoader).newInstance()) { + is Source -> listOf(obj) + is SourceFactory -> { + if (isSourceNsfw(obj)) { + emptyList() + } else { + obj.createSources() + } } + else -> throw Exception("Unknown source class type! ${obj.javaClass}") } - else -> throw Exception("Unknown source class type! ${obj.javaClass}") + } catch (e: Throwable) { + Timber.e(e, "Extension load error: $extName.") + return LoadResult.Error(e) } - } catch (e: Throwable) { - Timber.e(e, "Extension load error: $extName.") - return LoadResult.Error(e) } + .filter { !isSourceNsfw(it) } + + val langs = + sources.filterIsInstance() + .map { it.lang } + .toSet() + val lang = + when (langs.size) { + 0 -> "" + 1 -> langs.first() + else -> "all" } - .filter { !isSourceNsfw(it) } - - val langs = sources.filterIsInstance() - .map { it.lang } - .toSet() - val lang = when (langs.size) { - 0 -> "" - 1 -> langs.first() - else -> "all" - } - val extension = Extension.Installed( - extName, pkgName, versionName, versionCode, libVersion, lang, isNsfw, sources, - isUnofficial = false - ) + val extension = + Extension.Installed( + extName, pkgName, versionName, versionCode, libVersion, lang, isNsfw, sources, + isUnofficial = false + ) return LoadResult.Success(extension) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt b/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt index c93b217ee056..fbea268ad3ae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt @@ -6,10 +6,12 @@ import okhttp3.CookieJar import okhttp3.HttpUrl class AndroidCookieJar : CookieJar { - private val manager = CookieManager.getInstance() - override fun saveFromResponse(url: HttpUrl, cookies: List) { + override fun saveFromResponse( + url: HttpUrl, + cookies: List + ) { val urlString = url.toString() for (cookie in cookies) { @@ -31,7 +33,11 @@ class AndroidCookieJar : CookieJar { } } - fun remove(url: HttpUrl, cookieNames: List? = null, maxAge: Int = -1) { + fun remove( + url: HttpUrl, + cookieNames: List? = null, + maxAge: Int = -1 + ) { val urlString = url.toString() val cookies = manager.getCookie(urlString) ?: return diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/JavaScriptEngine.kt b/app/src/main/java/eu/kanade/tachiyomi/network/JavaScriptEngine.kt index d696decb56c9..068e2e414815 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/JavaScriptEngine.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/JavaScriptEngine.kt @@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.util.system.withIOContext * Util for evaluating JavaScript in sources. */ class JavaScriptEngine(context: Context) { - /** * Evaluate arbitrary JavaScript code and get the result as a primtive type * (e.g., String, Int). @@ -18,9 +17,10 @@ class JavaScriptEngine(context: Context) { * @return Result of JavaScript code as a primitive type. */ @Suppress("UNUSED", "UNCHECKED_CAST") - suspend fun evaluate(script: String): T = withIOContext { - QuickJs.create().use { - it.evaluate(script) as T + suspend fun evaluate(script: String): T = + withIOContext { + QuickJs.create().use { + it.evaluate(script) as T + } } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt index 772e27d0295d..0c020567543a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -7,9 +7,6 @@ import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor import eu.kanade.tachiyomi.network.interceptor.IgnoreGzipInterceptor import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor import exh.log.maybeInjectEHLogger -import java.io.File -import java.net.InetAddress -import java.util.concurrent.TimeUnit import okhttp3.Cache import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient @@ -17,9 +14,11 @@ import okhttp3.brotli.BrotliInterceptor import okhttp3.dnsoverhttps.DnsOverHttps import okhttp3.logging.HttpLoggingInterceptor import uy.kohesive.injekt.injectLazy +import java.io.File +import java.net.InetAddress +import java.util.concurrent.TimeUnit open class NetworkHelper(context: Context) { - private val preferences: PreferencesHelper by injectLazy() private val cacheDir = File(context.cacheDir, "network_cache") @@ -28,20 +27,23 @@ open class NetworkHelper(context: Context) { open val cookieManager = AndroidCookieJar() - /* SY --> */ open /* SY <-- */ val client by lazy { - val builder = OkHttpClient.Builder() - .cookieJar(cookieManager) - .cache(Cache(cacheDir, cacheSize)) - .connectTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .addNetworkInterceptor(IgnoreGzipInterceptor()) - .addNetworkInterceptor(BrotliInterceptor) - .maybeInjectEHLogger() + // SY --> + open /* SY <-- */ val client by lazy { + val builder = + OkHttpClient.Builder() + .cookieJar(cookieManager) + .cache(Cache(cacheDir, cacheSize)) + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .addNetworkInterceptor(IgnoreGzipInterceptor()) + .addNetworkInterceptor(BrotliInterceptor) + .maybeInjectEHLogger() if (BuildConfig.DEBUG) { - val httpLoggingInterceptor = HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.HEADERS - } + val httpLoggingInterceptor = + HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.HEADERS + } builder.addInterceptor(httpLoggingInterceptor) } @@ -69,11 +71,12 @@ open class NetworkHelper(context: Context) { builder.build() } - open val cloudflareClient = client.newBuilder() - .addInterceptor(UserAgentInterceptor()) - .addInterceptor(CloudflareInterceptor(context)) - .maybeInjectEHLogger() - .build() + open val cloudflareClient = + client.newBuilder() + .addInterceptor(UserAgentInterceptor()) + .addInterceptor(CloudflareInterceptor(context)) + .maybeInjectEHLogger() + .build() val defaultUserAgent by lazy { preferences.defaultUserAgent().get() diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt index f0908ecda5ad..2a4a30d815d7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -1,11 +1,6 @@ package eu.kanade.tachiyomi.network import exh.util.withRootCause -import java.io.IOException -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.coroutines.resumeWithException -import kotlin.reflect.KType -import kotlin.reflect.typeOf import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.serialization.KSerializer import kotlinx.serialization.json.Json @@ -21,6 +16,11 @@ import rx.Producer import rx.Subscription import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.IOException +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.coroutines.resumeWithException +import kotlin.reflect.KType +import kotlin.reflect.typeOf fun Call.asObservableWithAsyncStacktrace(): Observable> { // Record stacktrace at creation time for easier debugging @@ -32,36 +32,37 @@ fun Call.asObservableWithAsyncStacktrace(): Observable val call = clone() // Wrap the call in a helper which handles both unsubscription and backpressure. - val requestArbiter = object : AtomicBoolean(), Producer, Subscription { - val executed = AtomicBoolean(false) - - override fun request(n: Long) { - if (n == 0L || !compareAndSet(false, true)) return - - try { - val response = call.execute() - executed.set(true) - if (!subscriber.isUnsubscribed) { - subscriber.onNext(asyncStackTrace to response) - subscriber.onCompleted() - } - } catch (error: Throwable) { - if (!subscriber.isUnsubscribed) { - subscriber.onError(error.withRootCause(asyncStackTrace)) + val requestArbiter = + object : AtomicBoolean(), Producer, Subscription { + val executed = AtomicBoolean(false) + + override fun request(n: Long) { + if (n == 0L || !compareAndSet(false, true)) return + + try { + val response = call.execute() + executed.set(true) + if (!subscriber.isUnsubscribed) { + subscriber.onNext(asyncStackTrace to response) + subscriber.onCompleted() + } + } catch (error: Throwable) { + if (!subscriber.isUnsubscribed) { + subscriber.onError(error.withRootCause(asyncStackTrace)) + } } } - } - override fun unsubscribe() { - if (!executed.get()) { - call.cancel() + override fun unsubscribe() { + if (!executed.get()) { + call.cancel() + } } - } - override fun isUnsubscribed(): Boolean { - return call.isCanceled() + override fun isUnsubscribed(): Boolean { + return call.isCanceled() + } } - } subscriber.add(requestArbiter) subscriber.setProducer(requestArbiter) @@ -73,18 +74,25 @@ fun Call.asObservable() = asObservableWithAsyncStacktrace().map { it.second } // Based on https://github.com/gildor/kotlin-coroutines-okhttp private suspend fun Call.await(callStack: Array): Response { return suspendCancellableCoroutine { continuation -> - val callback = object : Callback { - override fun onResponse(call: Call, response: Response) { - continuation.resume(response) { response.body.close() } - } + val callback = + object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + continuation.resume(response) { response.body.close() } + } - override fun onFailure(call: Call, e: IOException) { - // Don't bother with resuming the continuation if it is already cancelled. - if (continuation.isCancelled) return - val exception = IOException(e).apply { stackTrace = callStack } - continuation.resumeWithException(e) + override fun onFailure( + call: Call, + e: IOException + ) { + // Don't bother with resuming the continuation if it is already cancelled. + if (continuation.isCancelled) return + val exception = IOException(e).apply { stackTrace = callStack } + continuation.resumeWithException(e) + } } - } enqueue(callback) @@ -112,25 +120,32 @@ suspend fun Call.awaitSuccess(): Response { } return response } + fun Call.asObservableSuccess(): Observable { return asObservableWithAsyncStacktrace().map { (asyncStacktrace, response) -> if (!response.isSuccessful) { response.close() throw Exception("HTTP error ${response.code}", asyncStacktrace) - } else response + } else { + response + } } } -fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: ProgressListener): Call { - val progressClient = newBuilder() - .cache(null) - .addNetworkInterceptor { chain -> - val originalResponse = chain.proceed(chain.request()) - originalResponse.newBuilder() - .body(ProgressResponseBody(originalResponse.body, listener)) - .build() - } - .build() +fun OkHttpClient.newCachelessCallWithProgress( + request: Request, + listener: ProgressListener +): Call { + val progressClient = + newBuilder() + .cache(null) + .addNetworkInterceptor { chain -> + val originalResponse = chain.proceed(chain.request()) + originalResponse.newBuilder() + .body(ProgressResponseBody(originalResponse.body, listener)) + .build() + } + .build() return progressClient.newCall(request) } @@ -140,7 +155,10 @@ inline fun Response.parseAs(): T { } @Suppress("UNCHECKED_CAST") -fun internalParseAs(type: KType, response: Response): T { +fun internalParseAs( + type: KType, + response: Response +): T { val deserializer = serializer(type) as KSerializer return response.body.source().use { Injekt.get().decodeFromBufferedSource(deserializer, it) diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt index 2e219895feef..2ab5a140a0cd 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt @@ -1,5 +1,9 @@ package eu.kanade.tachiyomi.network interface ProgressListener { - fun update(bytesRead: Long, contentLength: Long, done: Boolean) + fun update( + bytesRead: Long, + contentLength: Long, + done: Boolean + ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt index 071345eba7f5..4f8cad3ba055 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.network -import java.io.IOException import okhttp3.MediaType import okhttp3.ResponseBody import okio.Buffer @@ -8,9 +7,9 @@ import okio.BufferedSource import okio.ForwardingSource import okio.Source import okio.buffer +import java.io.IOException class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() { - private val bufferedSource: BufferedSource by lazy { source(responseBody.source()).buffer() } @@ -32,7 +31,10 @@ class ProgressResponseBody(private val responseBody: ResponseBody, private val p internal var totalBytesRead = 0L @Throws(IOException::class) - override fun read(sink: Buffer, byteCount: Long): Long { + override fun read( + sink: Buffer, + byteCount: Long + ): Long { val bytesRead = super.read(sink, byteCount) // read() returns the number of bytes read, or -1 if this source is exhausted. totalBytesRead += if (bytesRead != -1L) bytesRead else 0 diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt b/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt index 06fd450d0018..e2e0d9646772 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.network -import java.util.concurrent.TimeUnit.MINUTES import okhttp3.CacheControl import okhttp3.FormBody import okhttp3.Headers @@ -8,6 +7,7 @@ import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Request import okhttp3.RequestBody +import java.util.concurrent.TimeUnit.MINUTES private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build() private val DEFAULT_HEADERS = Headers.Builder().build() diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt index 65bc6e3e037d..95cecb036fa7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt @@ -14,18 +14,17 @@ import eu.kanade.tachiyomi.util.system.WebViewClientCompat import eu.kanade.tachiyomi.util.system.isOutdated import eu.kanade.tachiyomi.util.system.setDefaultSettings import eu.kanade.tachiyomi.util.system.toast -import java.io.IOException -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit import okhttp3.Cookie import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response import uy.kohesive.injekt.injectLazy +import java.io.IOException +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit class CloudflareInterceptor(private val context: Context) : Interceptor { - private val handler = Handler(Looper.getMainLooper()) private val networkHelper: NetworkHelper by injectLazy() @@ -54,8 +53,9 @@ class CloudflareInterceptor(private val context: Context) : Interceptor { try { response.close() networkHelper.cookieManager.remove(originalRequest.url, COOKIE_NAMES, 0) - val oldCookie = networkHelper.cookieManager.get(originalRequest.url) - .firstOrNull { it.name == "cf_clearance" } + val oldCookie = + networkHelper.cookieManager.get(originalRequest.url) + .firstOrNull { it.name == "cf_clearance" } resolveWithWebView(originalRequest, oldCookie) return chain.proceed(originalRequest) @@ -67,7 +67,10 @@ class CloudflareInterceptor(private val context: Context) : Interceptor { } @SuppressLint("SetJavaScriptEnabled") - private fun resolveWithWebView(request: Request, oldCookie: Cookie?) { + private fun resolveWithWebView( + request: Request, + oldCookie: Cookie? + ) { // We need to lock this thread until the WebView finds the challenge solution url, because // OkHttp doesn't support asynchronous interceptors. val latch = CountDownLatch(1) @@ -90,46 +93,50 @@ class CloudflareInterceptor(private val context: Context) : Interceptor { webview.settings.userAgentString = request.header("User-Agent") ?: networkHelper.defaultUserAgent - webview.webViewClient = object : WebViewClientCompat() { - override fun onPageFinished(view: WebView, url: String) { - fun isCloudFlareBypassed(): Boolean { - return networkHelper.cookieManager.get(origRequestUrl.toHttpUrl()) - .firstOrNull { it.name == "cf_clearance" } - .let { it != null && it != oldCookie } - } + webview.webViewClient = + object : WebViewClientCompat() { + override fun onPageFinished( + view: WebView, + url: String + ) { + fun isCloudFlareBypassed(): Boolean { + return networkHelper.cookieManager.get(origRequestUrl.toHttpUrl()) + .firstOrNull { it.name == "cf_clearance" } + .let { it != null && it != oldCookie } + } - if (isCloudFlareBypassed()) { - cloudflareBypassed = true - latch.countDown() - } + if (isCloudFlareBypassed()) { + cloudflareBypassed = true + latch.countDown() + } - // HTTP error codes are only received since M - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - url == origRequestUrl && !challengeFound - ) { - // The first request didn't return the challenge, abort. - latch.countDown() + // HTTP error codes are only received since M + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + url == origRequestUrl && !challengeFound + ) { + // The first request didn't return the challenge, abort. + latch.countDown() + } } - } - override fun onReceivedErrorCompat( - view: WebView, - errorCode: Int, - description: String?, - failingUrl: String, - isMainFrame: Boolean - ) { - if (isMainFrame) { - if (errorCode == 503) { - // Found the cloudflare challenge page. - challengeFound = true - } else { - // Unlock thread, the challenge wasn't found. - latch.countDown() + override fun onReceivedErrorCompat( + view: WebView, + errorCode: Int, + description: String?, + failingUrl: String, + isMainFrame: Boolean + ) { + if (isMainFrame) { + if (errorCode == 503) { + // Found the cloudflare challenge page. + challengeFound = true + } else { + // Unlock thread, the challenge wasn't found. + latch.countDown() + } } } } - } webView?.loadUrl(origRequestUrl, headers) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt index 0f058a312d5e..8c4b59b013d8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt @@ -1,10 +1,10 @@ package eu.kanade.tachiyomi.network.interceptor import android.os.SystemClock -import java.util.concurrent.TimeUnit import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response +import java.util.concurrent.TimeUnit /** * An OkHttp interceptor that handles rate limiting. @@ -31,25 +31,25 @@ private class RateLimitInterceptor( period: Long, unit: TimeUnit ) : Interceptor { - private val requestQueue = ArrayList(permits) private val rateLimitMillis = unit.toMillis(period) override fun intercept(chain: Interceptor.Chain): Response { synchronized(requestQueue) { val now = SystemClock.elapsedRealtime() - val waitTime = if (requestQueue.size < permits) { - 0 - } else { - val oldestReq = requestQueue[0] - val newestReq = requestQueue[permits - 1] - - if (newestReq - oldestReq > rateLimitMillis) { + val waitTime = + if (requestQueue.size < permits) { 0 } else { - oldestReq + rateLimitMillis - now // Remaining time + val oldestReq = requestQueue[0] + val newestReq = requestQueue[permits - 1] + + if (newestReq - oldestReq > rateLimitMillis) { + 0 + } else { + oldestReq + rateLimitMillis - now // Remaining time + } } - } if (requestQueue.size == permits) { requestQueue.removeAt(0) diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt index 4ab4ddb2b8b9..21180d4b1d2b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt @@ -1,11 +1,11 @@ package eu.kanade.tachiyomi.network.interceptor import android.os.SystemClock -import java.util.concurrent.TimeUnit import okhttp3.HttpUrl import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response +import java.util.concurrent.TimeUnit /** * An OkHttp interceptor that handles given url host's rate limiting. @@ -35,7 +35,6 @@ class SpecificHostRateLimitInterceptor( period: Long, unit: TimeUnit ) : Interceptor { - private val requestQueue = ArrayList(permits) private val rateLimitMillis = unit.toMillis(period) private val host = httpUrl.host @@ -46,18 +45,19 @@ class SpecificHostRateLimitInterceptor( } synchronized(requestQueue) { val now = SystemClock.elapsedRealtime() - val waitTime = if (requestQueue.size < permits) { - 0 - } else { - val oldestReq = requestQueue[0] - val newestReq = requestQueue[permits - 1] - - if (newestReq - oldestReq > rateLimitMillis) { + val waitTime = + if (requestQueue.size < permits) { 0 } else { - oldestReq + rateLimitMillis - now // Remaining time + val oldestReq = requestQueue[0] + val newestReq = requestQueue[permits - 1] + + if (newestReq - oldestReq > rateLimitMillis) { + 0 + } else { + oldestReq + rateLimitMillis - now // Remaining time + } } - } if (requestQueue.size == permits) { requestQueue.removeAt(0) diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt index e5d1c2656646..bba8016a1a05 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt @@ -6,18 +6,18 @@ import okhttp3.Response import uy.kohesive.injekt.injectLazy class UserAgentInterceptor : Interceptor { - private val networkHelper: NetworkHelper by injectLazy() override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() return if (originalRequest.header("User-Agent").isNullOrEmpty()) { - val newRequest = originalRequest - .newBuilder() - .removeHeader("User-Agent") - .addHeader("User-Agent", networkHelper.defaultUserAgent) - .build() + val newRequest = + originalRequest + .newBuilder() + .removeHeader("User-Agent") + .addHeader("User-Agent", networkHelper.defaultUserAgent) + .build() chain.proceed(newRequest) } else { chain.proceed(originalRequest) diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt index f9e416def693..41aea61023e1 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.source.model.MangasPage import rx.Observable interface CatalogueSource : Source { - /** * An ISO 639-1 compliant language code (two letters in lower case). */ @@ -30,7 +29,11 @@ interface CatalogueSource : Source { * @param query the search query. * @param filters the list of filters to apply. */ - fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable + fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList + ): Observable /** * Returns an observable containing a page with a list of latest manga updates. diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt index d1109ce6f663..ceef5d4bab78 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/ConfigurableSource.kt @@ -8,11 +8,9 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get interface ConfigurableSource : Source { - fun setupPreferenceScreen(screen: PreferenceScreen) } fun ConfigurableSource.preferenceKey(): String = "source_$id" -fun sourcePreferences(key: String): SharedPreferences = - Injekt.get().getSharedPreferences(key, Context.MODE_PRIVATE) +fun sourcePreferences(key: String): SharedPreferences = Injekt.get().getSharedPreferences(key, Context.MODE_PRIVATE) diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt index ca79bfb51191..5661e47bf54b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt @@ -22,16 +22,16 @@ import eu.kanade.tachiyomi.util.storage.EpubFileCompat import eu.kanade.tachiyomi.util.storage.openReadOnlyChannel import eu.kanade.tachiyomi.util.storage.toInputStream import eu.kanade.tachiyomi.util.system.ImageUtil +import org.apache.commons.compress.archivers.zip.ZipFile +import rx.Observable +import timber.log.Timber import java.io.File import java.io.FileInputStream import java.io.InputStream import java.util.Locale import java.util.concurrent.TimeUnit -import java.util.zip.ZipFile as ZipFileCompat import kotlin.Comparator -import org.apache.commons.compress.archivers.zip.ZipFile -import rx.Observable -import timber.log.Timber +import java.util.zip.ZipFile as ZipFileCompat class LocalSource(private val context: Context) : CatalogueSource { companion object { @@ -43,7 +43,11 @@ class LocalSource(private val context: Context) : CatalogueSource { private val LATEST_THRESHOLD = TimeUnit.MILLISECONDS.convert(7, TimeUnit.DAYS) const val ID = 0L - fun updateCover(context: Context, manga: SManga, input: InputStream): File? { + fun updateCover( + context: Context, + manga: SManga, + input: InputStream + ): File? { val dir = getBaseDirectories(context).firstOrNull() if (dir == null) { input.close() @@ -76,75 +80,83 @@ class LocalSource(private val context: Context) : CatalogueSource { override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", POPULAR_FILTERS) - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList + ): Observable { val baseDirs = getBaseDirectories(context) val time = if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L - var mangaDirs = baseDirs.mapNotNull { it.listFiles()?.toList() } - .flatten() - .filter { it.isDirectory } - .filterNot { it.name.startsWith('.') } - .filter { if (time == 0L) it.name.contains(query, ignoreCase = true) else it.lastModified() >= time } - .distinctBy { it.name } + var mangaDirs = + baseDirs.mapNotNull { it.listFiles()?.toList() } + .flatten() + .filter { it.isDirectory } + .filterNot { it.name.startsWith('.') } + .filter { if (time == 0L) it.name.contains(query, ignoreCase = true) else it.lastModified() >= time } + .distinctBy { it.name } val state = ((if (filters.isEmpty()) POPULAR_FILTERS else filters)[0] as OrderBy).state when (state?.index) { 0 -> { - mangaDirs = if (state.ascending) { - mangaDirs.sortedBy { it.name.lowercase(Locale.ENGLISH) } - } else { - mangaDirs.sortedByDescending { it.name.lowercase(Locale.ENGLISH) } - } + mangaDirs = + if (state.ascending) { + mangaDirs.sortedBy { it.name.lowercase(Locale.ENGLISH) } + } else { + mangaDirs.sortedByDescending { it.name.lowercase(Locale.ENGLISH) } + } } 1 -> { - mangaDirs = if (state.ascending) { - mangaDirs.sortedBy(File::lastModified) - } else { - mangaDirs.sortedByDescending(File::lastModified) - } + mangaDirs = + if (state.ascending) { + mangaDirs.sortedBy(File::lastModified) + } else { + mangaDirs.sortedByDescending(File::lastModified) + } } } - val mangas = mangaDirs.map { mangaDir -> - SManga.create().apply { - title = mangaDir.name - url = mangaDir.name - - // Try to find the cover - for (dir in baseDirs) { - val cover = File("${dir.absolutePath}/$url", COVER_NAME) - if (cover.exists()) { - thumbnail_url = cover.absolutePath - break + val mangas = + mangaDirs.map { mangaDir -> + SManga.create().apply { + title = mangaDir.name + url = mangaDir.name + + // Try to find the cover + for (dir in baseDirs) { + val cover = File("${dir.absolutePath}/$url", COVER_NAME) + if (cover.exists()) { + thumbnail_url = cover.absolutePath + break + } } - } - val chapters = runAsObservable({ getChapterList(this.toMangaInfo()).map { it.toSChapter() } }).toBlocking().first() - if (chapters.isNotEmpty()) { - val chapter = chapters.last() - val format = getFormat(chapter) - if (format is Format.Epub) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - EpubFile(format.file.openReadOnlyChannel(context)) - } else { - EpubFile(format.file) - }.use { epub -> - epub.fillMangaMetadata(this) + val chapters = runAsObservable({ getChapterList(this.toMangaInfo()).map { it.toSChapter() } }).toBlocking().first() + if (chapters.isNotEmpty()) { + val chapter = chapters.last() + val format = getFormat(chapter) + if (format is Format.Epub) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + EpubFile(format.file.openReadOnlyChannel(context)) + } else { + EpubFile(format.file) + }.use { epub -> + epub.fillMangaMetadata(this) + } } - } - // Copy the cover from the first chapter found. - if (thumbnail_url == null) { - try { - val dest = updateCover(chapter, this) - thumbnail_url = dest?.absolutePath - } catch (e: Exception) { - Timber.e(e) + // Copy the cover from the first chapter found. + if (thumbnail_url == null) { + try { + val dest = updateCover(chapter, this) + thumbnail_url = dest?.absolutePath + } catch (e: Exception) { + Timber.e(e) + } } } } } - } return Observable.just(MangasPage(mangas, false)) } @@ -171,44 +183,46 @@ class LocalSource(private val context: Context) : CatalogueSource { } override fun fetchChapterList(manga: SManga): Observable> { - val chapters = getBaseDirectories(context) - .asSequence() - .mapNotNull { File(it, manga.url).listFiles()?.toList() } - .flatten() - .filter { it.isDirectory || isSupportedFile(it.extension) } - .map { chapterFile -> - SChapter.create().apply { - url = "${manga.url}/${chapterFile.name}" - name = if (chapterFile.isDirectory) { - chapterFile.name - } else { - chapterFile.nameWithoutExtension - } - date_upload = chapterFile.lastModified() - - val format = getFormat(this) - if (format is Format.Epub) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - EpubFile(format.file.openReadOnlyChannel(context)) - } else { - EpubFile(format.file) - }.use { epub -> - epub.fillChapterMetadata(this) + val chapters = + getBaseDirectories(context) + .asSequence() + .mapNotNull { File(it, manga.url).listFiles()?.toList() } + .flatten() + .filter { it.isDirectory || isSupportedFile(it.extension) } + .map { chapterFile -> + SChapter.create().apply { + url = "${manga.url}/${chapterFile.name}" + name = + if (chapterFile.isDirectory) { + chapterFile.name + } else { + chapterFile.nameWithoutExtension + } + date_upload = chapterFile.lastModified() + + val format = getFormat(this) + if (format is Format.Epub) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + EpubFile(format.file.openReadOnlyChannel(context)) + } else { + EpubFile(format.file) + }.use { epub -> + epub.fillChapterMetadata(this) + } } - } - val chapNameCut = stripMangaTitle(name, manga.title) - if (chapNameCut.isNotEmpty()) name = chapNameCut - ChapterRecognition.parseChapterNumber(this, manga) - } - } - .sortedWith( - Comparator { c1, c2 -> - val c = c2.chapter_number.compareTo(c1.chapter_number) - if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c + val chapNameCut = stripMangaTitle(name, manga.title) + if (chapNameCut.isNotEmpty()) name = chapNameCut + ChapterRecognition.parseChapterNumber(this, manga) + } } - ) - .toList() + .sortedWith( + Comparator { c1, c2 -> + val c = c2.chapter_number.compareTo(c1.chapter_number) + if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c + } + ) + .toList() return Observable.just(chapters) } @@ -217,7 +231,10 @@ class LocalSource(private val context: Context) : CatalogueSource { * Strips the manga title from a chapter name, matching only based on alphanumeric and whitespace * characters. */ - private fun stripMangaTitle(chapterName: String, mangaTitle: String): String { + private fun stripMangaTitle( + chapterName: String, + mangaTitle: String + ): String { var chapterNameIndex = 0 var mangaTitleIndex = 0 while (chapterNameIndex < chapterName.length && mangaTitleIndex < mangaTitle.length) { @@ -282,29 +299,35 @@ class LocalSource(private val context: Context) : CatalogueSource { } } - private fun updateCover(chapter: SChapter, manga: SManga): File? { + private fun updateCover( + chapter: SChapter, + manga: SManga + ): File? { return when (val format = getFormat(chapter)) { is Format.Directory -> { - val entry = format.file.listFiles() - ?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } - ?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } } + val entry = + format.file.listFiles() + ?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } + ?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } } entry?.let { updateCover(context, manga, it.inputStream()) } } is Format.Zip -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { ZipFile(format.file.openReadOnlyChannel(context)).use { zip -> - val entry = zip.entries.toList() - .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } - .find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } + val entry = + zip.entries.toList() + .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } + .find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } entry?.let { updateCover(context, manga, zip.getInputStream(it)) } } } else { ZipFileCompat(format.file).use { zip -> - val entry = zip.entries().toList() - .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } - .find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } + val entry = + zip.entries().toList() + .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } + .find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } entry?.let { updateCover(context, manga, zip.getInputStream(it)) } } @@ -312,9 +335,10 @@ class LocalSource(private val context: Context) : CatalogueSource { } is Format.Rar -> { Archive(format.file.openReadOnlyChannel(context).toInputStream()).use { archive -> - val entry = archive.fileHeaders - .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) } - .find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } } + val entry = + archive.fileHeaders + .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) } + .find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } } entry?.let { updateCover(context, manga, archive.getInputStream(it)) } } @@ -322,17 +346,19 @@ class LocalSource(private val context: Context) : CatalogueSource { is Format.Epub -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { EpubFile(format.file.openReadOnlyChannel(context)).use { epub -> - val entry = epub.getImagesFromPages() - .firstOrNull() - ?.let { epub.getEntry(it) } + val entry = + epub.getImagesFromPages() + .firstOrNull() + ?.let { epub.getEntry(it) } entry?.let { updateCover(context, manga, epub.getInputStream(it)) } } } else { EpubFileCompat(format.file).use { epub -> - val entry = epub.getImagesFromPages() - .firstOrNull() - ?.let { epub.getEntry(it) } + val entry = + epub.getImagesFromPages() + .firstOrNull() + ?.let { epub.getEntry(it) } entry?.let { updateCover(context, manga, epub.getInputStream(it)) } } @@ -347,8 +373,11 @@ class LocalSource(private val context: Context) : CatalogueSource { sealed class Format { data class Directory(val file: File) : Format() + data class Zip(val file: File) : Format() + data class Rar(val file: File) : Format() + data class Epub(val file: File) : Format() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt b/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt index 17bd75ff0f07..ab1546a286e5 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt @@ -21,7 +21,6 @@ import uy.kohesive.injekt.api.get * A basic interface for creating a source. It could be an online source, a local source, etc... */ interface Source : tachiyomi.source.Source { - /** * Id for the source. Must be unique. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt index 41329b466e97..0cf1aaca2002 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt @@ -20,16 +20,15 @@ import exh.EXH_SOURCE_ID import exh.source.BlacklistedSources import exh.source.DelegatedHttpSource import exh.source.EnhancedHttpSource -import kotlin.reflect.KClass import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.launchIn import rx.Observable import uy.kohesive.injekt.injectLazy +import kotlin.reflect.KClass open class SourceManager(private val context: Context) { - private val prefs: PreferencesHelper by injectLazy() private val sourcesMap = mutableMapOf() @@ -61,34 +60,48 @@ open class SourceManager(private val context: Context) { fun getOnlineSources() = sourcesMap.values.filterIsInstance() - fun getVisibleOnlineSources() = sourcesMap.values.filterIsInstance().filter { - it.id !in BlacklistedSources.HIDDEN_SOURCES - } + fun getVisibleOnlineSources() = + sourcesMap.values.filterIsInstance().filter { + it.id !in BlacklistedSources.HIDDEN_SOURCES + } fun getCatalogueSources() = sourcesMap.values.filterIsInstance() - fun getVisibleCatalogueSources() = sourcesMap.values.filterIsInstance().filter { - it.id !in BlacklistedSources.HIDDEN_SOURCES - } + fun getVisibleCatalogueSources() = + sourcesMap.values.filterIsInstance().filter { + it.id !in BlacklistedSources.HIDDEN_SOURCES + } - fun getDelegatedCatalogueSources() = sourcesMap.values.filterIsInstance().mapNotNull { enhancedHttpSource -> - enhancedHttpSource.enchancedSource as? DelegatedHttpSource - } + fun getDelegatedCatalogueSources() = + sourcesMap.values.filterIsInstance().mapNotNull { enhancedHttpSource -> + enhancedHttpSource.enchancedSource as? DelegatedHttpSource + } - internal fun registerSource(source: Source, overwrite: Boolean = false) { + internal fun registerSource( + source: Source, + overwrite: Boolean = false + ) { // EXH --> val sourceQName = source::class.qualifiedName val delegate = DELEGATED_SOURCES[sourceQName] - val newSource = if (source is HttpSource && delegate != null) { - XLog.d("[EXH] Delegating source: %s -> %s!", sourceQName, delegate.newSourceClass.qualifiedName) - EnhancedHttpSource( - source, - delegate.newSourceClass.constructors.find { it.parameters.size == 1 }!!.call(source) - ) - } else source + val newSource = + if (source is HttpSource && delegate != null) { + XLog.d("[EXH] Delegating source: %s -> %s!", sourceQName, delegate.newSourceClass.qualifiedName) + EnhancedHttpSource( + source, + delegate.newSourceClass.constructors.find { it.parameters.size == 1 }!!.call(source) + ) + } else { + source + } if (source.id in BlacklistedSources.BLACKLISTED_EXT_SOURCES) { - XLog.d("[EXH] Removing blacklisted source: (id: %s, name: %s, lang: %s)!", source.id, source.name, (source as? CatalogueSource)?.lang) + XLog.d( + "[EXH] Removing blacklisted source: (id: %s, name: %s, lang: %s)!", + source.id, + source.name, + (source as? CatalogueSource)?.lang + ) return } // EXH <-- @@ -102,14 +115,16 @@ open class SourceManager(private val context: Context) { sourcesMap.remove(source.id) } - private fun createInternalSources(): List = listOf( - LocalSource(context) - ) + private fun createInternalSources(): List = + listOf( + LocalSource(context) + ) private fun createEHSources(): List { - val exSrcs = mutableListOf( - EHentai(EH_SOURCE_ID, false, context) - ) + val exSrcs = + mutableListOf( + EHentai(EH_SOURCE_ID, false, context) + ) if (prefs.enableExhentai().get()) { exSrcs += EHentai(EXH_SOURCE_ID, true, context) } @@ -118,7 +133,6 @@ open class SourceManager(private val context: Context) { } private inner class StubSource(override val id: Long) : Source { - override val name: String get() = id.toString() @@ -144,26 +158,27 @@ open class SourceManager(private val context: Context) { } companion object { - val DELEGATED_SOURCES = listOf( - DelegatedSource( - "Hentai Cafe", - 260868874183818481, - "eu.kanade.tachiyomi.extension.all.foolslide.HentaiCafe", - HentaiCafe::class - ), - DelegatedSource( - "Pururin", - 2221515250486218861, - "eu.kanade.tachiyomi.extension.en.pururin.Pururin", - Pururin::class - ), - DelegatedSource( - "Tsumino", - 6707338697138388238, - "eu.kanade.tachiyomi.extension.en.tsumino.Tsumino", - Tsumino::class - ) - ).associateBy { it.originalSourceQualifiedClassName } + val DELEGATED_SOURCES = + listOf( + DelegatedSource( + "Hentai Cafe", + 260868874183818481, + "eu.kanade.tachiyomi.extension.all.foolslide.HentaiCafe", + HentaiCafe::class + ), + DelegatedSource( + "Pururin", + 2221515250486218861, + "eu.kanade.tachiyomi.extension.en.pururin.Pururin", + Pururin::class + ), + DelegatedSource( + "Tsumino", + 6707338697138388238, + "eu.kanade.tachiyomi.extension.en.tsumino.Tsumino", + Tsumino::class + ) + ).associateBy { it.originalSourceQualifiedClassName } data class DelegatedSource( val sourceName: String, diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt index 59b0a32f9b6f..4d51361afe46 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.source.model sealed class Filter(val name: String, var state: T) { open class Header(name: String) : Filter(name, 0) + // --> EXH // name = button text open class HelpDialog(name: String, val dialogTitle: String = name, val markdown: String) : Filter(name, 0) @@ -10,11 +11,16 @@ sealed class Filter(val name: String, var state: T) { open class Separator(name: String = "") : Filter(name, 0) abstract class Select(name: String, val values: Array, state: Int = 0) : Filter(name, state) + abstract class Text(name: String, state: String = "") : Filter(name, state) + abstract class CheckBox(name: String, state: Boolean = false) : Filter(name, state) + abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter(name, state) { fun isIgnored() = state == STATE_IGNORE + fun isIncluded() = state == STATE_INCLUDE + fun isExcluded() = state == STATE_EXCLUDE companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt index 42b6bc74ba99..c05951fa003e 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.source.model data class FilterList(val list: List>) : List> by list { - constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt index 6c7d1493a97b..04406beebfc7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt @@ -11,7 +11,6 @@ open class Page( var imageUrl: String? = null, @Transient var uri: Uri? = null // Deprecated but can't be deleted due to extensions ) : ProgressListener { - val number: Int get() = index + 1 @@ -38,12 +37,17 @@ open class Page( @Transient private var statusCallback: ((Page) -> Unit)? = null - override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { - progress = if (contentLength > 0) { - (100 * bytesRead / contentLength).toInt() - } else { - -1 - } + override fun update( + bytesRead: Long, + contentLength: Long, + done: Boolean + ) { + progress = + if (contentLength > 0) { + (100 * bytesRead / contentLength).toInt() + } else { + -1 + } } fun setStatusSubject(subject: Subject?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt index 897f5c53eae7..fd96e2ca6da3 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt @@ -1,10 +1,9 @@ package eu.kanade.tachiyomi.source.model -import java.io.Serializable import tachiyomi.source.model.ChapterInfo +import java.io.Serializable interface SChapter : Serializable { - var url: String var name: String diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt index 4d5e43f1e4d8..55b3f3ff0bf4 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.source.model class SChapterImpl : SChapter { - override lateinit var url: String override lateinit var name: String diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt index 543776a1971b..4777e01822c9 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt @@ -1,10 +1,9 @@ package eu.kanade.tachiyomi.source.model -import java.io.Serializable import tachiyomi.source.model.MangaInfo +import java.io.Serializable interface SManga : Serializable { - var url: String var title: String diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaImpl.kt index 3010b06e5a41..4f73d29ec8c5 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaImpl.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.source.model class SMangaImpl : SManga { - override lateinit var url: String override var title: String = "" diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt index 5b6f5d3d9754..61955e6a3554 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -16,10 +16,6 @@ import eu.kanade.tachiyomi.source.model.SManga import exh.log.maybeInjectEHLogger import exh.patch.injectPatches import exh.source.DelegatedHttpSource -import java.net.URI -import java.net.URISyntaxException -import java.security.MessageDigest -import java.util.Locale import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request @@ -27,12 +23,15 @@ import okhttp3.Response import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.net.URI +import java.net.URISyntaxException +import java.security.MessageDigest +import java.util.Locale /** * A simple implementation for sources from a website. */ abstract class HttpSource : CatalogueSource { - /** * Network service. */ @@ -40,18 +39,20 @@ abstract class HttpSource : CatalogueSource { val original = Injekt.get() object : NetworkHelper(Injekt.get()) { override val client: OkHttpClient - get() = delegate?.networkHttpClient ?: original.client - .newBuilder() - .injectPatches { id } - .maybeInjectEHLogger() - .build() + get() = + delegate?.networkHttpClient ?: original.client + .newBuilder() + .injectPatches { id } + .maybeInjectEHLogger() + .build() override val cloudflareClient: OkHttpClient - get() = delegate?.networkCloudflareClient ?: original.cloudflareClient - .newBuilder() - .injectPatches { id } - .maybeInjectEHLogger() - .build() + get() = + delegate?.networkCloudflareClient ?: original.cloudflareClient + .newBuilder() + .injectPatches { id } + .maybeInjectEHLogger() + .build() override val cookieManager: AndroidCookieJar get() = original.cookieManager @@ -101,9 +102,10 @@ abstract class HttpSource : CatalogueSource { /** * Headers builder for requests. Implementations can override this method for custom headers. */ - protected open fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", network.defaultUserAgent) - } + protected open fun headersBuilder() = + Headers.Builder().apply { + add("User-Agent", network.defaultUserAgent) + } /** * Visible name of the source. @@ -146,7 +148,11 @@ abstract class HttpSource : CatalogueSource { * @param query the search query. * @param filters the list of filters to apply. */ - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList + ): Observable { return client.newCall(searchMangaRequest(page, query, filters)) .asObservableSuccess() .map { response -> @@ -161,7 +167,11 @@ abstract class HttpSource : CatalogueSource { * @param query the search query. * @param filters the list of filters to apply. */ - protected abstract fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request + protected abstract fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList + ): Request /** * Parses the response from the site and returns a [MangasPage] object. @@ -412,7 +422,10 @@ abstract class HttpSource : CatalogueSource { * @param chapter the chapter to be added. * @param manga the manga of the chapter. */ - open fun prepareNewChapter(chapter: SChapter, manga: SManga) { + open fun prepareNewChapter( + chapter: SChapter, + manga: SManga + ) { } /** @@ -422,11 +435,12 @@ abstract class HttpSource : CatalogueSource { // EXH --> private var delegate: DelegatedHttpSource? = null - get() = if (Injekt.get().eh_delegateSources().get()) { - field - } else { - null - } + get() = + if (Injekt.get().eh_delegateSources().get()) { + field + } else { + null + } fun bindDelegate(delegate: DelegatedHttpSource) { this.delegate = delegate diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/LewdSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/LewdSource.kt index 14dbf4f0c424..1f90d987be49 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/LewdSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/LewdSource.kt @@ -9,11 +9,11 @@ import eu.kanade.tachiyomi.source.model.SManga import exh.metadata.metadata.RaisedSearchMetadata import exh.metadata.metadata.base.getFlatMetadataForManga import exh.metadata.metadata.base.insertFlatMetadata -import kotlin.reflect.KClass import rx.Completable import rx.Single import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import kotlin.reflect.KClass /** * LEWD! @@ -29,33 +29,41 @@ interface LewdSource : CatalogueSource { /** * Parse the supplied input into the supplied metadata object */ - fun parseIntoMetadata(metadata: M, input: I) + fun parseIntoMetadata( + metadata: M, + input: I + ) /** * Use reflection to create a new instance of metadata */ - private fun newMetaInstance() = metaClass.constructors.find { - it.parameters.isEmpty() - }?.call() - ?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!") + private fun newMetaInstance() = + metaClass.constructors.find { + it.parameters.isEmpty() + }?.call() + ?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!") /** * Parses metadata from the input and then copies it into the manga * * Will also save the metadata to the DB if possible */ - fun parseToManga(manga: SManga, input: I): Completable { + fun parseToManga( + manga: SManga, + input: I + ): Completable { val mangaId = manga.id - val metaObservable = if (mangaId != null) { - // We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions - Single.fromCallable { - db.getFlatMetadataForManga(mangaId).executeAsBlocking() - }.map { - it?.raise(metaClass) ?: newMetaInstance() + val metaObservable = + if (mangaId != null) { + // We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions + Single.fromCallable { + db.getFlatMetadataForManga(mangaId).executeAsBlocking() + }.map { + it?.raise(metaClass) ?: newMetaInstance() + } + } else { + Single.just(newMetaInstance()) } - } else { - Single.just(newMetaInstance()) - } return metaObservable.map { parseIntoMetadata(it, input) @@ -65,7 +73,9 @@ interface LewdSource : CatalogueSource { if (mangaId != null) { it.mangaId = mangaId db.insertFlatMetadata(it.flatten()) - } else Completable.complete() + } else { + Completable.complete() + } } } @@ -76,15 +86,21 @@ interface LewdSource : CatalogueSource { * If the metadata needs to be parsed from the input producer, the resulting parsed metadata will * also be saved to the DB. */ - fun getOrLoadMetadata(mangaId: Long?, inputProducer: () -> Single): Single { - val metaObservable = if (mangaId != null) { - // We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions - Single.fromCallable { - db.getFlatMetadataForManga(mangaId).executeAsBlocking() - }.map { - it?.raise(metaClass) + fun getOrLoadMetadata( + mangaId: Long?, + inputProducer: () -> Single + ): Single { + val metaObservable = + if (mangaId != null) { + // We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions + Single.fromCallable { + db.getFlatMetadataForManga(mangaId).executeAsBlocking() + }.map { + it?.raise(metaClass) + } + } else { + Single.just(null) } - } else Single.just(null) return metaObservable.flatMap { existingMeta -> if (existingMeta == null) { @@ -95,9 +111,13 @@ interface LewdSource : CatalogueSource { if (mangaId != null) { newMeta.mangaId = mangaId db.insertFlatMetadata(newMeta.flatten()).andThen(newMetaSingle) - } else newMetaSingle + } else { + newMetaSingle + } } - } else Single.just(existingMeta) + } else { + Single.just(existingMeta) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt index 941a3167a88e..319a639a4046 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt @@ -13,7 +13,6 @@ import org.jsoup.nodes.Element * A simple implementation for sources from a website using Jsoup, an HTML parser. */ abstract class ParsedHttpSource : HttpSource() { - /** * Parses the response from the site and returns a [MangasPage] object. * @@ -22,13 +21,15 @@ abstract class ParsedHttpSource : HttpSource() { override fun popularMangaParse(response: Response): MangasPage { val document = response.asJsoup() - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } + val mangas = + document.select(popularMangaSelector()).map { element -> + popularMangaFromElement(element) + } - val hasNextPage = popularMangaNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null + val hasNextPage = + popularMangaNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null return MangasPage(mangas, hasNextPage) } @@ -60,13 +61,15 @@ abstract class ParsedHttpSource : HttpSource() { override fun searchMangaParse(response: Response): MangasPage { val document = response.asJsoup() - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } + val mangas = + document.select(searchMangaSelector()).map { element -> + searchMangaFromElement(element) + } - val hasNextPage = searchMangaNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null + val hasNextPage = + searchMangaNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null return MangasPage(mangas, hasNextPage) } @@ -98,13 +101,15 @@ abstract class ParsedHttpSource : HttpSource() { override fun latestUpdatesParse(response: Response): MangasPage { val document = response.asJsoup() - val mangas = document.select(latestUpdatesSelector()).map { element -> - latestUpdatesFromElement(element) - } + val mangas = + document.select(latestUpdatesSelector()).map { element -> + latestUpdatesFromElement(element) + } - val hasNextPage = latestUpdatesNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null + val hasNextPage = + latestUpdatesNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null return MangasPage(mangas, hasNextPage) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/UrlImportableSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/UrlImportableSource.kt index 958c3f09acc2..aee4c91d93cd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/UrlImportableSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/UrlImportableSource.kt @@ -4,6 +4,7 @@ import android.net.Uri import eu.kanade.tachiyomi.source.Source import java.net.URI import java.net.URISyntaxException + interface UrlImportableSource : Source { val matchingHosts: List diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt index 5102dcec769b..f5efde76379f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt @@ -45,7 +45,6 @@ import exh.util.UriFilter import exh.util.UriGroup import exh.util.ignore import exh.util.urlImportFetchSearchManga -import java.net.URLEncoder import kotlinx.coroutines.runBlocking import okhttp3.CacheControl import okhttp3.CookieJar @@ -61,6 +60,7 @@ import org.jsoup.nodes.TextNode import rx.Observable import rx.Single import uy.kohesive.injekt.injectLazy +import java.net.URLEncoder // TODO Consider gallery updating when doing tabbed browsing class EHentai( @@ -71,18 +71,20 @@ class EHentai( override val metaClass = EHentaiSearchMetadata::class val schema: String - get() = if (prefs.secureEXH().get()) { - "https" - } else { - "http" - } + get() = + if (prefs.secureEXH().get()) { + "https" + } else { + "http" + } val domain: String - get() = if (exh) { - "exhentai.org" - } else { - "e-hentai.org" - } + get() = + if (exh) { + "exhentai.org" + } else { + "e-hentai.org" + } override val baseUrl: String get() = "$schema://$domain" @@ -98,67 +100,76 @@ class EHentai( */ data class ParsedManga(val fav: Int, val manga: Manga) - fun extendedGenericMangaParse(doc: Document) = with(doc) { - // Parse mangas (supports compact + extended layout) - val parsedMangas = select(".itg > tbody > tr").filter { - // Do not parse header and ads - it.selectFirst("th") == null && it.selectFirst(".itd") == null - }.map { - val thumbnailElement = it.selectFirst(".gl1e img, .gl2c .glthumb img")!! - val column2 = it.selectFirst(".gl3e, .gl2c")!! - val linkElement = it.selectFirst(".gl3c > a, .gl2e > div > a")!! - - XLog.d(linkElement.attr("href")) - - val favElement = column2.children().find { it.attr("style").startsWith("border-color") } - - ParsedManga( - fav = FAVORITES_BORDER_HEX_COLORS.indexOf( - favElement?.attr("style")?.substring(14, 17) - ), - manga = Manga.create(id).apply { - // Get title - title = thumbnailElement.attr("title") - - url = EHentaiSearchMetadata.normalizeUrl(linkElement.attr("href")) - // Get image - thumbnail_url = thumbnailElement.attr("src") - - // TODO Parse genre + uploader + tags + fun extendedGenericMangaParse(doc: Document) = + with(doc) { + // Parse mangas (supports compact + extended layout) + val parsedMangas = + select(".itg > tbody > tr").filter { + // Do not parse header and ads + it.selectFirst("th") == null && it.selectFirst(".itd") == null + }.map { + val thumbnailElement = it.selectFirst(".gl1e img, .gl2c .glthumb img")!! + val column2 = it.selectFirst(".gl3e, .gl2c")!! + val linkElement = it.selectFirst(".gl3c > a, .gl2e > div > a")!! + + XLog.d(linkElement.attr("href")) + + val favElement = column2.children().find { it.attr("style").startsWith("border-color") } + + ParsedManga( + fav = + FAVORITES_BORDER_HEX_COLORS.indexOf( + favElement?.attr("style")?.substring(14, 17) + ), + manga = + Manga.create(id).apply { + // Get title + title = thumbnailElement.attr("title") + + url = EHentaiSearchMetadata.normalizeUrl(linkElement.attr("href")) + // Get image + thumbnail_url = thumbnailElement.attr("src") + + // TODO Parse genre + uploader + tags + } + ) } - ) - } - val parsedLocation = doc.location().toHttpUrlOrNull() + val parsedLocation = doc.location().toHttpUrlOrNull() - // Add to page if required - val hasNextPage = if (parsedLocation == null || - !parsedLocation.queryParameterNames.contains(REVERSE_PARAM) - ) { - select("a[onclick=return false]").last() - ?.let { - it.text() == ">" + // Add to page if required + val hasNextPage = + if (parsedLocation == null || + !parsedLocation.queryParameterNames.contains(REVERSE_PARAM) + ) { + select("a[onclick=return false]").last() + ?.let { + it.text() == ">" + } + ?: select(".searchnav >div > a") + .find { it.attr("href").contains("next") } + ?.let { true } + ?: false + } else { + parsedLocation.queryParameter(REVERSE_PARAM)!!.toBoolean() } - ?: select(".searchnav >div > a") - .find { it.attr("href").contains("next") } - ?.let { true } - ?: false - } else { - parsedLocation.queryParameter(REVERSE_PARAM)!!.toBoolean() + Pair(parsedMangas, hasNextPage) } - Pair(parsedMangas, hasNextPage) - } /** * Parse a list of galleries */ - fun genericMangaParse(response: Response) = extendedGenericMangaParse(response.asJsoup()).let { - MangasPage(it.first.map { it.manga }, it.second) - } + fun genericMangaParse(response: Response) = + extendedGenericMangaParse(response.asJsoup()).let { + MangasPage(it.first.map { it.manga }, it.second) + } override fun fetchChapterList(manga: SManga) = fetchChapterList(manga) {} - fun fetchChapterList(manga: SManga, throttleFunc: () -> Unit): Observable> { + fun fetchChapterList( + manga: SManga, + throttleFunc: () -> Unit + ): Observable> { return Single.fromCallable { // Pull all the way to the root gallery // We can't do this with RxJava or we run into stack overflows on shit like this: @@ -169,9 +180,10 @@ class EHentai( runBlocking { while (true) { val gid = EHentaiSearchMetadata.galleryId(url).toInt() - val cachedParent = updateHelper.parentLookupTable.get( - gid - ) + val cachedParent = + updateHelper.parentLookupTable.get( + gid + ) if (cachedParent == null) { throttleFunc() @@ -179,9 +191,10 @@ class EHentai( if (!resp.isSuccessful) error("HTTP error (${resp.code})!") doc = resp.asJsoup() - val parentLink = doc!!.select("#gdd .gdt1").find { el -> - el.text().lowercase() == "parent:" - }!!.nextElementSibling()!!.selectFirst("a")?.attr("href") + val parentLink = + doc!!.select("#gdd .gdt1").find { el -> + el.text().lowercase() == "parent:" + }!!.nextElementSibling()!!.selectFirst("a")?.attr("href") if (parentLink != null) { updateHelper.parentLookupTable.put( @@ -192,13 +205,16 @@ class EHentai( ) ) url = EHentaiSearchMetadata.normalizeUrl(parentLink) - } else break + } else { + break + } } else { XLog.d("Parent cache hit: %s!", gid) - url = EHentaiSearchMetadata.idAndTokenToUrl( - cachedParent.gId, - cachedParent.gToken - ) + url = + EHentaiSearchMetadata.idAndTokenToUrl( + cachedParent.gId, + cachedParent.gToken + ) } } } @@ -207,19 +223,22 @@ class EHentai( }.map { d -> val newDisplay = d.select("#gnd a") // Build chapter for root gallery - val self = SChapter.create().apply { - url = EHentaiSearchMetadata.normalizeUrl(d.location()) - name = "v1: " + d.selectFirst("#gn")!!.text() - chapter_number = 1f - date_upload = EX_DATE_FORMAT.parse( - d.select("#gdd .gdt1").find { el -> - el.text().lowercase() == "posted:" - }!!.nextElementSibling()!!.text() - )!!.time - } + val self = + SChapter.create().apply { + url = EHentaiSearchMetadata.normalizeUrl(d.location()) + name = "v1: " + d.selectFirst("#gn")!!.text() + chapter_number = 1f + date_upload = + EX_DATE_FORMAT.parse( + d.select("#gdd .gdt1").find { el -> + el.text().lowercase() == "posted:" + }!!.nextElementSibling()!!.text() + )!!.time + } // Build and append the rest of the galleries - if (DebugToggles.INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS.enabled) listOf(self) - else { + if (DebugToggles.INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS.enabled) { + listOf(self) + } else { newDisplay.mapIndexed { index, newGallery -> val link = newGallery.attr("href") val name = newGallery.text() @@ -235,11 +254,12 @@ class EHentai( }.toObservable() } - override fun fetchPageList(chapter: SChapter) = fetchChapterPage(chapter, baseUrl + chapter.url).map { - it.mapIndexed { i, s -> - Page(i, s) - } - }!! + override fun fetchPageList(chapter: SChapter) = + fetchChapterPage(chapter, baseUrl + chapter.url).map { + it.mapIndexed { i, s -> + Page(i, s) + } + }!! private fun fetchChapterPage( chapter: SChapter, @@ -259,40 +279,51 @@ class EHentai( } } - private fun parseChapterPage(response: Element) = with(response) { - select(".gdtm a").map { - Pair(it.child(0).attr("alt").toInt(), it.attr("href")) - }.sortedBy(Pair::first).map { it.second } - } + private fun parseChapterPage(response: Element) = + with(response) { + select(".gdtm a").map { + Pair(it.child(0).attr("alt").toInt(), it.attr("href")) + }.sortedBy(Pair::first).map { it.second } + } private fun chapterPageCall(np: String): Observable { return client.newCall(chapterPageRequest(np)).asObservableSuccess() } + private fun chapterPageRequest(np: String): Request { return exGet(np, null, headers) } - private fun nextPageUrl(element: Element): String? = element.select("a[onclick=return false]").last()?.let { - return if (it.text() == ">") it.attr("href") else null - } + private fun nextPageUrl(element: Element): String? = + element.select("a[onclick=return false]").last()?.let { + return if (it.text() == ">") it.attr("href") else null + } - override fun popularMangaRequest(page: Int) = if (exh) { - latestUpdatesRequest(page) - } else { - exGet("$baseUrl/toplist.php?tl=15&p=${page - 1}", null) // Custom page logic for toplists - } + override fun popularMangaRequest(page: Int) = + if (exh) { + latestUpdatesRequest(page) + } else { + exGet("$baseUrl/toplist.php?tl=15&p=${page - 1}", null) // Custom page logic for toplists + } // Support direct URL importing - override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = - urlImportFetchSearchManga(query) { - searchMangaRequestObservable(page, query, filters).flatMap { - client.newCall(it).asObservableSuccess() - }.map { response -> - searchMangaParse(response) - } + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList + ) = urlImportFetchSearchManga(query) { + searchMangaRequestObservable(page, query, filters).flatMap { + client.newCall(it).asObservableSuccess() + }.map { response -> + searchMangaParse(response) } + } - private fun searchMangaRequestObservable(page: Int, query: String, filters: FilterList): Observable { + private fun searchMangaRequestObservable( + page: Int, + query: String, + filters: FilterList + ): Observable { val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon() uri.appendQueryParameter("f_search", query) filters.forEach { @@ -323,15 +354,26 @@ class EHentai( } } - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException() + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList + ) = throw UnsupportedOperationException() override fun latestUpdatesRequest(page: Int) = exGet(baseUrl, page) override fun popularMangaParse(response: Response) = genericMangaParse(response) + override fun searchMangaParse(response: Response) = genericMangaParse(response) + override fun latestUpdatesParse(response: Response) = genericMangaParse(response) - fun exGet(url: String, page: Int? = null, additionalHeaders: Headers? = null, cache: Boolean = true): Request { + fun exGet( + url: String, + page: Int? = null, + additionalHeaders: Headers? = null, + cache: Boolean = true + ): Request { return GET( page?.let { if (page > 1) { @@ -372,11 +414,14 @@ class EHentai( // Pull to most recent val doc = response.asJsoup() val newerGallery = doc.select("#gnd a").lastOrNull() - val pre = if (newerGallery != null && DebugToggles.PULL_TO_ROOT_WHEN_LOADING_EXH_MANGA_DETAILS.enabled) { - manga.url = EHentaiSearchMetadata.normalizeUrl(newerGallery.attr("href")) - client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess().map { it.asJsoup() } - } else Observable.just(doc) + val pre = + if (newerGallery != null && DebugToggles.PULL_TO_ROOT_WHEN_LOADING_EXH_MANGA_DETAILS.enabled) { + manga.url = EHentaiSearchMetadata.normalizeUrl(newerGallery.attr("href")) + client.newCall(mangaDetailsRequest(manga)) + .asObservableSuccess().map { it.asJsoup() } + } else { + Observable.just(doc) + } pre.flatMap { parseToManga(manga, it).andThen( @@ -404,7 +449,10 @@ class EHentai( */ override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException() - override fun parseIntoMetadata(metadata: EHentaiSearchMetadata, input: Document) { + override fun parseIntoMetadata( + metadata: EHentaiSearchMetadata, + input: Document + ) { with(metadata) { with(input) { val url = input.location() @@ -416,15 +464,17 @@ class EHentai( altTitle = select("#gj").text().nullIfBlank()?.trim() - thumbnailUrl = select("#gd1 div").attr("style").nullIfBlank()?.let { - it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')')) - } - genre = select(".cs") - .attr("onclick") - .nullIfBlank() - ?.trim() - ?.substringAfterLast('/') - ?.removeSuffix("'") + thumbnailUrl = + select("#gd1 div").attr("style").nullIfBlank()?.let { + it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')')) + } + genre = + select(".cs") + .attr("onclick") + .nullIfBlank() + ?.trim() + ?.substringAfterLast('/') + ?.removeSuffix("'") uploader = select("#gdn").text().nullIfBlank()?.trim() @@ -443,9 +493,13 @@ class EHentai( // Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/ // Example JP gallery: https://exhentai.org/g/1375385/03519d541b/ // Parent is older variation of the gallery - "parent" -> parent = if (!right.equals("None", true)) { - rightElement.child(0).attr("href") - } else null + "parent" -> + parent = + if (!right.equals("None", true)) { + rightElement.child(0).attr("href") + } else { + null + } "visible" -> visible = right.nullIfBlank() "language" -> { language = right.removeSuffix(TR_SUFFIX).trim().nullIfBlank() @@ -469,17 +523,19 @@ class EHentai( // Parse ratings ignore { - averageRating = select("#rating_label") - .text() - .removePrefix("Average:") - .trim() - .nullIfBlank() - ?.toDouble() - ratingCount = select("#rating_count") - .text() - .trim() - .nullIfBlank() - ?.toInt() + averageRating = + select("#rating_label") + .text() + .removePrefix("Average:") + .trim() + .nullIfBlank() + ?.toDouble() + ratingCount = + select("#rating_count") + .text() + .trim() + .nullIfBlank() + ?.toInt() } // Parse tags @@ -519,7 +575,10 @@ class EHentai( .map { realImageUrlParse(it, page) } } - fun realImageUrlParse(response: Response, page: Page): String { + fun realImageUrlParse( + response: Response, + page: Page + ): String { with(response.asJsoup()) { val currentImage = getElementById("img")!!.attr("src") // Each press of the retry button will choose another server @@ -542,13 +601,14 @@ class EHentai( var favNames: List? = null do { - val response2 = client.newCall( - exGet( - favoriteUrl, - page = page, - cache = false - ) - ).execute() + val response2 = + client.newCall( + exGet( + favoriteUrl, + page = page, + cache = false + ) + ).execute() val doc = response2.asJsoup() // Parse favorites @@ -557,9 +617,10 @@ class EHentai( // Parse fav names if (favNames == null) { - favNames = doc.select(".fp:not(.fps)").mapNotNull { - it.child(2).text() - } + favNames = + doc.select(".fp:not(.fps)").mapNotNull { + it.child(2).text() + } } // Next page @@ -569,11 +630,12 @@ class EHentai( return Pair(result as List, favNames!!) } - fun spPref() = if (exh) { - prefs.eh_exhSettingsProfile() - } else { - prefs.eh_ehSettingsProfile() - } + fun spPref() = + if (exh) { + prefs.eh_exhSettingsProfile() + } else { + prefs.eh_ehSettingsProfile() + } fun rawCookies(sp: Int): Map { val cookies: MutableMap = mutableMapOf() @@ -613,35 +675,42 @@ class EHentai( // Headers override fun headersBuilder() = super.headersBuilder().add("Cookie", cookiesHeader()) - fun addParam(url: String, param: String, value: String) = Uri.parse(url) + fun addParam( + url: String, + param: String, + value: String + ) = Uri.parse(url) .buildUpon() .appendQueryParameter(param, value) .toString() - override val client = network.client.newBuilder() - .cookieJar(CookieJar.NO_COOKIES) - .addInterceptor { chain -> - val newReq = chain - .request() - .newBuilder() - .removeHeader("Cookie") - .addHeader("Cookie", cookiesHeader()) - .build() - - chain.proceed(newReq) - }.build() + override val client = + network.client.newBuilder() + .cookieJar(CookieJar.NO_COOKIES) + .addInterceptor { chain -> + val newReq = + chain + .request() + .newBuilder() + .removeHeader("Cookie") + .addHeader("Cookie", cookiesHeader()) + .build() + + chain.proceed(newReq) + }.build() // Filters - override fun getFilterList() = FilterList( - if (prefs.eh_watchedListDefaultState().get()) { - Watched(isEnabled = true) - } else { - Watched(isEnabled = false) - }, - GenreGroup(), - AdvancedGroup(), - ReverseFilter() - ) + override fun getFilterList() = + FilterList( + if (prefs.eh_watchedListDefaultState().get()) { + Watched(isEnabled = true) + } else { + Watched(isEnabled = false) + }, + GenreGroup(), + AdvancedGroup(), + ReverseFilter() + ) class Watched(val isEnabled: Boolean) : Filter.CheckBox("Watched List", isEnabled), UriFilter { override fun addToUri(builder: Uri.Builder) { @@ -652,6 +721,7 @@ class EHentai( } class GenreOption(name: String, val genreId: Int) : Filter.CheckBox(name, false) + class GenreGroup : Filter.Group( "Genres", @@ -670,9 +740,10 @@ class EHentai( ), UriFilter { override fun addToUri(builder: Uri.Builder) { - val bits = state.fold(0) { acc, genre -> - if (!genre.state) acc + genre.genreId else acc - } + val bits = + state.fold(0) { acc, genre -> + if (!genre.state) acc + genre.genreId else acc + } builder.appendQueryParameter("f_cats", bits.toString()) } } @@ -698,6 +769,7 @@ class EHentai( } class MinPagesOption : PageOption("Minimum Pages", "f_spf") + class MaxPagesOption : PageOption("Maximum Pages", "f_spt") class RatingOption : @@ -739,22 +811,28 @@ class EHentai( class ReverseFilter : Filter.CheckBox("Reverse search results") - override val name = if (exh) { - "ExHentai" - } else { - "E-Hentai" - } + override val name = + if (exh) { + "ExHentai" + } else { + "E-Hentai" + } class GalleryNotFoundException(cause: Throwable) : RuntimeException("Gallery not found!", cause) // === URL IMPORT STUFF - override val matchingHosts: List = if (exh) listOf( - "exhentai.org" - ) else listOf( - "g.e-hentai.org", - "e-hentai.org" - ) + override val matchingHosts: List = + if (exh) { + listOf( + "exhentai.org" + ) + } else { + listOf( + "g.e-hentai.org", + "e-hentai.org" + ) + } override fun mapUrlToMangaUrl(uri: Uri): String? { return when (uri.pathSegments.firstOrNull()) { @@ -782,24 +860,26 @@ class EHentai( val json = JsonObject() json["method"] = "gtoken" - json["pagelist"] = JsonArray().apply { - add( - JsonArray().apply { - add(gallery.toInt()) - add(pageToken) - add(pageNum.toInt()) - } - ) - } + json["pagelist"] = + JsonArray().apply { + add( + JsonArray().apply { + add(gallery.toInt()) + add(pageToken) + add(pageNum.toInt()) + } + ) + } - val outJson = JsonParser.parseString( - client.newCall( - Request.Builder() - .url(EH_API_BASE) - .post(json.toString().toRequestBody(JSON)) - .build() - ).execute().body.string() - ).obj + val outJson = + JsonParser.parseString( + client.newCall( + Request.Builder() + .url(EH_API_BASE) + .post(json.toString().toRequestBody(JSON)) + .build() + ).execute().body.string() + ).obj val obj = outJson["tokenlist"].array.first() return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/" @@ -813,21 +893,23 @@ class EHentai( private const val EH_API_BASE = "https://api.e-hentai.org/api.php" private val JSON = "application/json; charset=utf-8".toMediaTypeOrNull()!! - private val FAVORITES_BORDER_HEX_COLORS = listOf( - "000", - "f00", - "fa0", - "dd0", - "080", - "9f4", - "4bf", - "00f", - "508", - "e8e" - ) + private val FAVORITES_BORDER_HEX_COLORS = + listOf( + "000", + "f00", + "fa0", + "dd0", + "080", + "9f4", + "4bf", + "00f", + "508", + "e8e" + ) - fun buildCookies(cookies: Map) = cookies.entries.joinToString(separator = "; ") { - "${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}" - } + fun buildCookies(cookies: Map) = + cookies.entries.joinToString(separator = "; ") { + "${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}" + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MergedSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MergedSource.kt index fab469510220..ef2cf7fb6560 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MergedSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MergedSource.kt @@ -48,10 +48,19 @@ class MergedSource : HttpSource() { override val baseUrl = "" override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException() + override fun popularMangaParse(response: Response) = throw UnsupportedOperationException() - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException() + + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList + ) = throw UnsupportedOperationException() + override fun searchMangaParse(response: Response) = throw UnsupportedOperationException() + override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException() + override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException() override fun fetchMangaDetails(manga: SManga): Observable { @@ -71,7 +80,10 @@ class MergedSource : HttpSource() { val loadedMangas = readMangaConfig(manga).load(db, sourceManager).buffer() loadedMangas.map { loadedManga -> async(Dispatchers.IO) { - runAsObservable({ loadedManga.source.getChapterList(loadedManga.manga.toMangaInfo()).map { it.toSChapter() } }).map { chapterList -> + runAsObservable( + { loadedManga.source.getChapterList(loadedManga.manga.toMangaInfo()).map { it.toSChapter() } } + ).map { + chapterList -> chapterList.map { chapter -> chapter.apply { url = writeUrlConfig(UrlConfig(loadedManga.source.id, url, loadedManga.manga.url)) @@ -85,6 +97,7 @@ class MergedSource : HttpSource() { } override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException() + override fun chapterListParse(response: Response) = throw UnsupportedOperationException() override fun fetchPageList(chapter: SChapter): Observable> { @@ -104,34 +117,46 @@ class MergedSource : HttpSource() { override fun fetchImageUrl(page: Page): Observable { val config = readUrlConfig(page.url) - val source = sourceManager.getOrStub(config.source) as? HttpSource - ?: throw UnsupportedOperationException("This source does not support this operation!") + val source = + sourceManager.getOrStub(config.source) as? HttpSource + ?: throw UnsupportedOperationException("This source does not support this operation!") return source.fetchImageUrl(page.copyWithUrl(config.url)) } override fun pageListParse(response: Response) = throw UnsupportedOperationException() + override fun imageUrlParse(response: Response) = throw UnsupportedOperationException() override fun fetchImage(page: Page): Observable { val config = readUrlConfig(page.url) - val source = sourceManager.getOrStub(config.source) as? HttpSource - ?: throw UnsupportedOperationException("This source does not support this operation!") + val source = + sourceManager.getOrStub(config.source) as? HttpSource + ?: throw UnsupportedOperationException("This source does not support this operation!") return source.fetchImage(page.copyWithUrl(config.url)) } - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { + override fun prepareNewChapter( + chapter: SChapter, + manga: SManga + ) { val chapterConfig = readUrlConfig(chapter.url) - val source = sourceManager.getOrStub(chapterConfig.source) as? HttpSource - ?: throw UnsupportedOperationException("This source does not support this operation!") - val copiedManga = SManga.create().apply { - this.copyFrom(manga) - url = chapterConfig.mangaUrl - } + val source = + sourceManager.getOrStub(chapterConfig.source) as? HttpSource + ?: throw UnsupportedOperationException("This source does not support this operation!") + val copiedManga = + SManga.create().apply { + this.copyFrom(manga) + url = chapterConfig.mangaUrl + } chapter.url = chapterConfig.url source.prepareNewChapter(chapter, copiedManga) chapter.url = writeUrlConfig(UrlConfig(source.id, chapter.url, chapterConfig.mangaUrl)) - chapter.scanlator = if (chapter.scanlator.isNullOrBlank()) source.name - else "$source: ${chapter.scanlator}" + chapter.scanlator = + if (chapter.scanlator.isNullOrBlank()) { + source.name + } else { + "$source: ${chapter.scanlator}" + } } fun readMangaConfig(manga: SManga): MangaConfig { @@ -147,13 +172,17 @@ class MergedSource : HttpSource() { } data class LoadedMangaSource(val source: Source, val manga: Manga) + data class MangaSource( @SerializedName("s") val source: Long, @SerializedName("u") val url: String ) { - suspend fun load(db: DatabaseHelper, sourceManager: SourceManager): LoadedMangaSource? { + suspend fun load( + db: DatabaseHelper, + sourceManager: SourceManager + ): LoadedMangaSource? { val manga = db.getManga(url, source).executeAsBlocking() ?: return null val source = sourceManager.getOrStub(source) return LoadedMangaSource(source, manga) @@ -164,7 +193,10 @@ class MergedSource : HttpSource() { @SerializedName("c") val children: List ) { - fun load(db: DatabaseHelper, sourceManager: SourceManager): Flow { + fun load( + db: DatabaseHelper, + sourceManager: SourceManager + ): Flow { return children.asFlow().map { mangaSource -> mangaSource.load(db, sourceManager) ?: run { @@ -180,7 +212,10 @@ class MergedSource : HttpSource() { } companion object { - fun readFromUrl(gson: Gson, url: String): MangaConfig { + fun readFromUrl( + gson: Gson, + url: String + ): MangaConfig { return gson.fromJson(url) } } @@ -195,12 +230,13 @@ class MergedSource : HttpSource() { val mangaUrl: String ) - fun Page.copyWithUrl(newUrl: String) = Page( - index, - newUrl, - imageUrl, - uri - ) + fun Page.copyWithUrl(newUrl: String) = + Page( + index, + newUrl, + imageUrl, + uri + ) override val lang = "all" override val supportsLatest = false diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt index 32e9b264bc4c..0f8cc9b7489b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt @@ -56,11 +56,18 @@ class NHentai(context: Context) : HttpSource(), LewdSource { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList + ): Observable { val trimmedIdQuery = query.trim().removePrefix("id:") - val newQuery = if ((trimmedIdQuery.toIntOrNull() ?: -1) >= 0) { - "$baseUrl/g/$trimmedIdQuery/" - } else query + val newQuery = + if ((trimmedIdQuery.toIntOrNull() ?: -1) >= 0) { + "$baseUrl/g/$trimmedIdQuery/" + } else { + query + } return urlImportFetchSearchManga(newQuery) { searchMangaRequestObservable(page, query, filters).flatMap { @@ -71,23 +78,29 @@ class NHentai(context: Context) : HttpSource(), LewdSource { + private fun searchMangaRequestObservable( + page: Int, + query: String, + filters: FilterList + ): Observable { val langFilter = filters.filterIsInstance().firstOrNull() var langFilterString = "" if (langFilter != null) { langFilterString = SOURCE_LANG_LIST.first { it.first == langFilter.values[langFilter.state] }.second } - val uri = if (query.isNotBlank()) { - Uri.parse("$baseUrl/search/").buildUpon().apply { - appendQueryParameter("q", query + langFilterString) + val uri = + if (query.isNotBlank()) { + Uri.parse("$baseUrl/search/").buildUpon().apply { + appendQueryParameter("q", query + langFilterString) + } + } else { + Uri.parse(baseUrl).buildUpon() } - } else { - Uri.parse(baseUrl).buildUpon() - } - val sortFilter = filters.filterIsInstance().firstOrNull()?.state - ?: defaultSortFilterSelection() + val sortFilter = + filters.filterIsInstance().firstOrNull()?.state + ?: defaultSortFilterSelection() if (sortFilter.index == 1) { if (query.isBlank()) error("You must specify a search query if you wish to sort by popularity!") @@ -100,10 +113,11 @@ class NHentai(context: Context) : HttpSource(), LewdSource a").map { - SManga.create().apply { - url = it.attr("href") + val mangas = + doc.select(".gallery > a").map { + SManga.create().apply { + url = it.attr("href") - title = it.selectFirst(".caption")!!.text() + title = it.selectFirst(".caption")!!.text() - // last() is a hack to ignore the lazy-loader placeholder image on the front page - thumbnail_url = it.select("img").last()!!.attr("src") - // In some pages, the thumbnail url does not include the protocol - if (!thumbnail_url!!.startsWith("https:")) thumbnail_url = "https:$thumbnail_url" + // last() is a hack to ignore the lazy-loader placeholder image on the front page + thumbnail_url = it.select("img").last()!!.attr("src") + // In some pages, the thumbnail url does not include the protocol + if (!thumbnail_url!!.startsWith("https:")) thumbnail_url = "https:$thumbnail_url" + } } - } - val hasNextPage = if (!response.request.url.queryParameterNames.contains(REVERSE_PARAM)) { - doc.selectFirst(".next") != null - } else { - response.request.url.queryParameter(REVERSE_PARAM)!!.toBoolean() - } + val hasNextPage = + if (!response.request.url.queryParameterNames.contains(REVERSE_PARAM)) { + doc.selectFirst(".next") != null + } else { + response.request.url.queryParameter(REVERSE_PARAM)!!.toBoolean() + } return MangasPage(mangas, hasNextPage) } - override fun parseIntoMetadata(metadata: NHentaiSearchMetadata, input: Response) { - val json = GALLERY_JSON_REGEX.find(input.body.string())!!.groupValues[1].replace(UNICODE_ESCAPE_REGEX) { - it.groupValues[1].toInt( - radix = 16 - ).toChar().toString() - } + override fun parseIntoMetadata( + metadata: NHentaiSearchMetadata, + input: Response + ) { + val json = + GALLERY_JSON_REGEX.find(input.body.string())!!.groupValues[1].replace(UNICODE_ESCAPE_REGEX) { + it.groupValues[1].toInt( + radix = 16 + ).toChar().toString() + } val obj = JsonParser.parseString(json).asJsonObject with(metadata) { @@ -230,36 +254,45 @@ class NHentai(context: Context) : HttpSource(), LewdSource - if (metadata.mediaId == null) { - emptyList() - } else { - metadata.pageImageTypes.mapIndexed { index, s -> - val imageUrl = imageUrlFromType(metadata.mediaId!!, index + 1, s) - Page(index, imageUrl!!, imageUrl) + override fun fetchPageList(chapter: SChapter) = + getOrLoadMetadata(chapter.mangaId, NHentaiSearchMetadata.nhUrlToId(chapter.url)).map { metadata -> + if (metadata.mediaId == null) { + emptyList() + } else { + metadata.pageImageTypes.mapIndexed { index, s -> + val imageUrl = imageUrlFromType(metadata.mediaId!!, index + 1, s) + Page(index, imageUrl!!, imageUrl) + } } - } - }.toObservable() + }.toObservable() override fun fetchImageUrl(page: Page) = Observable.just(page.imageUrl!!)!! - fun imageUrlFromType(mediaId: String, page: Int, t: String) = NHentaiSearchMetadata.typeToExtension(t)?.let { + fun imageUrlFromType( + mediaId: String, + page: Int, + t: String + ) = NHentaiSearchMetadata.typeToExtension(t)?.let { "https://i.nhentai.net/galleries/$mediaId/$page.$it" } @@ -290,7 +323,10 @@ class NHentai(context: Context) : HttpSource(), LewdSource { return client.newCall(mangaDetailsRequest(manga)) @@ -55,51 +59,59 @@ class HentaiCafe(delegate: HttpSource) : /** * Parse the supplied input into the supplied metadata object */ - override fun parseIntoMetadata(metadata: HentaiCafeSearchMetadata, input: Document) { + override fun parseIntoMetadata( + metadata: HentaiCafeSearchMetadata, + input: Document + ) { with(metadata) { url = input.location() title = input.select("h3").text() val contentElement = input.select(".entry-content").first() thumbnailUrl = contentElement!!.child(0).child(0).attr("src") - fun filterableTagsOfType(type: String) = contentElement.select("a") - .filter { "$baseUrl/hc.fyi/$type/" in it.attr("href") } - .map { it.text() } + fun filterableTagsOfType(type: String) = + contentElement.select("a") + .filter { "$baseUrl/hc.fyi/$type/" in it.attr("href") } + .map { it.text() } tags.clear() - tags += filterableTagsOfType("tag").map { - RaisedTag(null, it, TAG_TYPE_DEFAULT) - } + tags += + filterableTagsOfType("tag").map { + RaisedTag(null, it, TAG_TYPE_DEFAULT) + } val artists = filterableTagsOfType("artist") artist = artists.joinToString() - tags += artists.map { - RaisedTag("artist", it, TAG_TYPE_VIRTUAL) - } + tags += + artists.map { + RaisedTag("artist", it, TAG_TYPE_VIRTUAL) + } readerId = input.select("[title=Read]").attr("href").toHttpUrlOrNull()!!.pathSegments[2] } } - override fun fetchChapterList(manga: SManga) = getOrLoadMetadata(manga.id) { - client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .map { it.asJsoup() } - .toSingle() - }.map { + override fun fetchChapterList(manga: SManga) = + getOrLoadMetadata(manga.id) { + client.newCall(mangaDetailsRequest(manga)) + .asObservableSuccess() + .map { it.asJsoup() } + .toSingle() + }.map { + listOf( + SChapter.create().apply { + setUrlWithoutDomain("/manga/read/${it.readerId}/en/0/1/") + name = "Chapter" + chapter_number = 0.0f + } + ) + }.toObservable() + + override val matchingHosts = listOf( - SChapter.create().apply { - setUrlWithoutDomain("/manga/read/${it.readerId}/en/0/1/") - name = "Chapter" - chapter_number = 0.0f - } + "hentai.cafe" ) - }.toObservable() - - override val matchingHosts = listOf( - "hentai.cafe" - ) override fun mapUrlToMangaUrl(uri: Uri): String? { val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.lowercase() ?: return null diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Pururin.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Pururin.kt index adff84f94123..f763c14fdee2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Pururin.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Pururin.kt @@ -26,17 +26,25 @@ class Pururin(delegate: HttpSource) : * An ISO 639-1 compliant language code (two letters in lower case). */ override val lang = "en" + /** * The class of the metadata used by this source */ override val metaClass = PururinSearchMetadata::class // Support direct URL importing - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList + ): Observable { val trimmedIdQuery = query.trim().removePrefix("id:") - val newQuery = if ((trimmedIdQuery.toIntOrNull() ?: -1) >= 0) { - "$baseUrl/gallery/$trimmedIdQuery/-" - } else query + val newQuery = + if ((trimmedIdQuery.toIntOrNull() ?: -1) >= 0) { + "$baseUrl/gallery/$trimmedIdQuery/-" + } else { + query + } return urlImportFetchSearchManga(newQuery) { super.fetchSearchManga(page, query, filters) @@ -52,7 +60,10 @@ class Pururin(delegate: HttpSource) : } } - override fun parseIntoMetadata(metadata: PururinSearchMetadata, input: Document) { + override fun parseIntoMetadata( + metadata: PururinSearchMetadata, + input: Document + ) { val selfLink = input.select("[itemprop=name]").last()!!.parent() val parsedSelfLink = Uri.parse(selfLink!!.attr("href")).pathSegments @@ -88,11 +99,12 @@ class Pururin(delegate: HttpSource) : else -> { value.select("a").forEach { link -> val searchUrl = Uri.parse(link.attr("href")) - tags += RaisedTag( - searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2], - searchUrl.lastPathSegment!!.substringBefore("."), - PururinSearchMetadata.TAG_TYPE_DEFAULT - ) + tags += + RaisedTag( + searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2], + searchUrl.lastPathSegment!!.substringBefore("."), + PururinSearchMetadata.TAG_TYPE_DEFAULT + ) } } } @@ -100,10 +112,11 @@ class Pururin(delegate: HttpSource) : } } - override val matchingHosts = listOf( - "pururin.io", - "www.pururin.io" - ) + override val matchingHosts = + listOf( + "pururin.io", + "www.pururin.io" + ) override fun mapUrlToMangaUrl(uri: Uri): String { return "${PururinSearchMetadata.BASE_URL}/gallery/${uri.pathSegments[1]}/${uri.lastPathSegment}" diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt index 7635ca28175b..47c197a84bb4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt @@ -14,10 +14,10 @@ import exh.metadata.metadata.TsuminoSearchMetadata.Companion.TAG_TYPE_DEFAULT import exh.metadata.metadata.base.RaisedTag import exh.source.DelegatedHttpSource import exh.util.urlImportFetchSearchManga -import java.text.SimpleDateFormat -import java.util.Locale import org.jsoup.nodes.Document import rx.Observable +import java.text.SimpleDateFormat +import java.util.Locale class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate), @@ -27,10 +27,13 @@ class Tsumino(delegate: HttpSource) : override val lang = "en" // Support direct URL importing - override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = - urlImportFetchSearchManga(query) { - super.fetchSearchManga(page, query, filters) - } + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList + ) = urlImportFetchSearchManga(query) { + super.fetchSearchManga(page, query, filters) + } override fun mapUrlToMangaUrl(uri: Uri): String? { val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.lowercase() ?: return null @@ -48,7 +51,10 @@ class Tsumino(delegate: HttpSource) : } } - override fun parseIntoMetadata(metadata: TsuminoSearchMetadata, input: Document) { + override fun parseIntoMetadata( + metadata: TsuminoSearchMetadata, + input: Document + ) { with(metadata) { tmId = TsuminoSearchMetadata.tmIdFromUrl(input.location())!!.toInt() tags.clear() @@ -118,10 +124,11 @@ class Tsumino(delegate: HttpSource) : } } - override val matchingHosts = listOf( - "www.tsumino.com", - "tsumino.com" - ) + override val matchingHosts = + listOf( + "www.tsumino.com", + "tsumino.com" + ) companion object { val TM_DATE_FORMAT = SimpleDateFormat("yyyy MMM dd", Locale.US) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt index 86f57b2132a5..eaddc32645d2 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt @@ -7,14 +7,13 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.viewbinding.ViewBinding import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.util.system.LocaleHelper import exh.ui.lock.LockActivityDelegate import uy.kohesive.injekt.injectLazy +import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values abstract class BaseActivity : AppCompatActivity() { - val preferences: PreferencesHelper by injectLazy() val scope = lifecycleScope diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt index 127517fd91d1..37caf85f6086 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt @@ -9,7 +9,6 @@ import exh.ui.lock.LockActivityDelegate import nucleus.view.NucleusAppCompatActivity abstract class BaseRxActivity> : NucleusAppCompatActivity

() { - val scope = lifecycleScope lateinit var binding: VB diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt index 08e0008f8159..48a88f1acd64 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt @@ -17,45 +17,68 @@ import timber.log.Timber abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateController(bundle), LayoutContainer { - lateinit var binding: VB init { - addLifecycleListener(object : LifecycleListener() { - override fun postCreateView(controller: Controller, view: View) { - onViewCreated(view) - } - - override fun preCreateView(controller: Controller) { - Timber.d("Create view for ${controller.instance()}") - } - - override fun preAttach(controller: Controller, view: View) { - Timber.d("Attach view for ${controller.instance()}") - } - - override fun preDetach(controller: Controller, view: View) { - Timber.d("Detach view for ${controller.instance()}") - } - - override fun preDestroyView(controller: Controller, view: View) { - Timber.d("Destroy view for ${controller.instance()}") + addLifecycleListener( + object : LifecycleListener() { + override fun postCreateView( + controller: Controller, + view: View + ) { + onViewCreated(view) + } + + override fun preCreateView(controller: Controller) { + Timber.d("Create view for ${controller.instance()}") + } + + override fun preAttach( + controller: Controller, + view: View + ) { + Timber.d("Attach view for ${controller.instance()}") + } + + override fun preDetach( + controller: Controller, + view: View + ) { + Timber.d("Detach view for ${controller.instance()}") + } + + override fun preDestroyView( + controller: Controller, + view: View + ) { + Timber.d("Destroy view for ${controller.instance()}") + } } - }) + ) } override val containerView: View? get() = view - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup, + savedViewState: Bundle? + ): View { return inflateView(inflater, container) } - abstract fun inflateView(inflater: LayoutInflater, container: ViewGroup): View + abstract fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View open fun onViewCreated(view: View) {} - override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { + override fun onChangeStarted( + handler: ControllerChangeHandler, + type: ControllerChangeType + ) { if (type.isEnter) { setTitle() } @@ -90,18 +113,23 @@ abstract class BaseController(bundle: Bundle? = null) : */ var expandActionViewFromInteraction = false - fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) { - setOnActionExpandListener(object : MenuItem.OnActionExpandListener { - override fun onMenuItemActionExpand(item: MenuItem): Boolean { - return onExpand?.invoke(item) ?: true - } - - override fun onMenuItemActionCollapse(item: MenuItem): Boolean { - activity?.invalidateOptionsMenu() - - return onCollapse?.invoke(item) ?: true + fun MenuItem.fixExpand( + onExpand: ((MenuItem) -> Boolean)? = null, + onCollapse: ((MenuItem) -> Boolean)? = null + ) { + setOnActionExpandListener( + object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem): Boolean { + return onExpand?.invoke(item) ?: true + } + + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + activity?.invalidateOptionsMenu() + + return onCollapse?.invoke(item) ?: true + } } - }) + ) if (expandActionViewFromInteraction) { expandActionViewFromInteraction = false diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt index 35fd15bd8af1..f612b5b5ce25 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt @@ -18,7 +18,10 @@ fun Router.popControllerWithTag(tag: String): Boolean { return false } -fun Controller.requestPermissionsSafe(permissions: Array, requestCode: Int) { +fun Controller.requestPermissionsSafe( + permissions: Array, + requestCode: Int +) { val activity = activity ?: return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { permissions.forEach { permission -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/DialogController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/DialogController.kt index 0e487cb03e5e..c3b4cc233051 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/DialogController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/DialogController.kt @@ -18,7 +18,6 @@ import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler * Implementations should override this class and implement [.onCreateDialog] to create a custom dialog, such as an [android.app.AlertDialog] */ abstract class DialogController : RestoreViewOnCreateController { - protected var dialog: Dialog? = null private set @@ -36,7 +35,11 @@ abstract class DialogController : RestoreViewOnCreateController { */ protected constructor(args: Bundle?) : super(args) - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup, + savedViewState: Bundle? + ): View { dialog = onCreateDialog(savedViewState) dialog!!.setOwnerActivity(activity!!) dialog!!.setOnDismissListener { dismissDialog() } @@ -49,7 +52,10 @@ abstract class DialogController : RestoreViewOnCreateController { return View(activity) // stub view } - override fun onSaveViewState(view: View, outState: Bundle) { + override fun onSaveViewState( + view: View, + outState: Bundle + ) { super.onSaveViewState(view, outState) val dialogState = dialog!!.onSaveInstanceState() outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState) @@ -85,7 +91,10 @@ abstract class DialogController : RestoreViewOnCreateController { * @param router The router on which the transaction will be applied * @param tag The tag for this controller */ - fun showDialog(router: Router, tag: String?) { + fun showDialog( + router: Router, + tag: String? + ) { dismissed = false router.pushController( RouterTransaction.with(this) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/NucleusController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/NucleusController.kt index 247b6d8bd250..8df51712f062 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/NucleusController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/NucleusController.kt @@ -14,7 +14,6 @@ import nucleus.presenter.Presenter abstract class NucleusController>(val bundle: Bundle? = null) : RxController(bundle), PresenterFactory

{ - private val delegate = NucleusConductorDelegate(this) val scope = CoroutineScope(Job() + Dispatchers.Main) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/RxController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/RxController.kt index e2869b0075cc..57d5e010ebcb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/RxController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/RxController.kt @@ -9,7 +9,6 @@ import rx.Subscription import rx.subscriptions.CompositeSubscription abstract class RxController(bundle: Bundle? = null) : BaseController(bundle) { - private var untilDestroySubscriptions = CompositeSubscription() private var untilDetachSubscriptions = CompositeSubscription() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/SecondaryDrawerController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/SecondaryDrawerController.kt index c2fc305e1e47..140f5c0844c5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/SecondaryDrawerController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/SecondaryDrawerController.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.base.controller import android.view.ViewGroup interface SecondaryDrawerController { - fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup? fun cleanupSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/TabbedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/TabbedController.kt index 48cbda58a5a3..280eea0de26d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/TabbedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/TabbedController.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.base.controller import com.google.android.material.tabs.TabLayout interface TabbedController { - fun configureTabs(tabs: TabLayout) {} fun cleanupTabs(tabs: TabLayout) {} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseFlexibleViewHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseFlexibleViewHolder.kt index e85cb77095f8..75a5f33a02ff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseFlexibleViewHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseFlexibleViewHolder.kt @@ -10,7 +10,6 @@ abstract class BaseFlexibleViewHolder( adapter: FlexibleAdapter<*>, stickyHeader: Boolean = false ) : FlexibleViewHolder(view, adapter, stickyHeader), LayoutContainer { - override val containerView: View? get() = itemView } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseViewHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseViewHolder.kt index 9b03064c47ae..1a33c5c21024 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseViewHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/BaseViewHolder.kt @@ -4,7 +4,6 @@ import android.view.View import kotlinx.android.extensions.LayoutContainer abstract class BaseViewHolder(view: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(view), LayoutContainer { - override val containerView: View? get() = itemView } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/SlicedHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/SlicedHolder.kt index 3acb98558ca7..578203434bb2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/SlicedHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/holder/SlicedHolder.kt @@ -9,7 +9,6 @@ import eu.kanade.tachiyomi.util.system.dpToPx import io.github.mthli.slice.Slice interface SlicedHolder { - val slice: Slice val adapter: FlexibleAdapter> @@ -58,7 +57,12 @@ interface SlicedHolder { setMargins(margin, if (topShadow) margin else 0, margin, if (bottomShadow) margin else 0) } - private fun setMargins(left: Int, top: Int, right: Int, bottom: Int) { + private fun setMargins( + left: Int, + top: Int, + right: Int, + bottom: Int + ) { if (viewToSlice.layoutParams is ViewGroup.MarginLayoutParams) { val p = viewToSlice.layoutParams as ViewGroup.MarginLayoutParams p.setMargins(left, top, right, bottom) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/BasePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/BasePresenter.kt index c8eb46e5b2d8..c3e96bc7a192 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/BasePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/BasePresenter.kt @@ -6,7 +6,6 @@ import nucleus.presenter.delivery.Delivery import rx.Observable open class BasePresenter : RxPresenter() { - override fun onCreate(savedState: Bundle?) { try { super.onCreate(savedState) @@ -23,7 +22,10 @@ open class BasePresenter : RxPresenter() { * @param onNext function to execute when the observable emits an item. * @param onError function to execute when the observable throws an error. */ - fun Observable.subscribeFirst(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) = compose(deliverFirst()).subscribe(split(onNext, onError)).apply { add(this) } + fun Observable.subscribeFirst( + onNext: (V, T) -> Unit, + onError: ((V, Throwable) -> Unit)? = null + ) = compose(deliverFirst()).subscribe(split(onNext, onError)).apply { add(this) } /** * Subscribes an observable with [deliverLatestCache] and adds it to the presenter's lifecycle @@ -32,7 +34,10 @@ open class BasePresenter : RxPresenter() { * @param onNext function to execute when the observable emits an item. * @param onError function to execute when the observable throws an error. */ - fun Observable.subscribeLatestCache(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) = compose(deliverLatestCache()).subscribe(split(onNext, onError)).apply { add(this) } + fun Observable.subscribeLatestCache( + onNext: (V, T) -> Unit, + onError: ((V, Throwable) -> Unit)? = null + ) = compose(deliverLatestCache()).subscribe(split(onNext, onError)).apply { add(this) } /** * Subscribes an observable with [deliverReplay] and adds it to the presenter's lifecycle @@ -41,7 +46,10 @@ open class BasePresenter : RxPresenter() { * @param onNext function to execute when the observable emits an item. * @param onError function to execute when the observable throws an error. */ - fun Observable.subscribeReplay(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) = compose(deliverReplay()).subscribe(split(onNext, onError)).apply { add(this) } + fun Observable.subscribeReplay( + onNext: (V, T) -> Unit, + onError: ((V, Throwable) -> Unit)? = null + ) = compose(deliverReplay()).subscribe(split(onNext, onError)).apply { add(this) } /** * Subscribes an observable with [DeliverWithView] and adds it to the presenter's lifecycle @@ -50,13 +58,15 @@ open class BasePresenter : RxPresenter() { * @param onNext function to execute when the observable emits an item. * @param onError function to execute when the observable throws an error. */ - fun Observable.subscribeWithView(onNext: (V, T) -> Unit, onError: ((V, Throwable) -> Unit)? = null) = compose(DeliverWithView(view())).subscribe(split(onNext, onError)).apply { add(this) } + fun Observable.subscribeWithView( + onNext: (V, T) -> Unit, + onError: ((V, Throwable) -> Unit)? = null + ) = compose(DeliverWithView(view())).subscribe(split(onNext, onError)).apply { add(this) } /** * A deliverable that only emits to the view if attached, otherwise the event is ignored. */ class DeliverWithView(private val view: Observable) : Observable.Transformer> { - override fun call(observable: Observable): Observable> { return observable .materialize() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorDelegate.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorDelegate.kt index cd07ed478ae4..bb65c7fc8438 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorDelegate.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorDelegate.kt @@ -5,7 +5,6 @@ import nucleus.factory.PresenterFactory import nucleus.presenter.Presenter class NucleusConductorDelegate

>(private val factory: PresenterFactory

) { - var presenter: P? = null get() { if (field == null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorLifecycleListener.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorLifecycleListener.kt index f59febccfaaa..5bd628c972fe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorLifecycleListener.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/NucleusConductorLifecycleListener.kt @@ -5,12 +5,17 @@ import android.view.View import com.bluelinelabs.conductor.Controller class NucleusConductorLifecycleListener(private val delegate: NucleusConductorDelegate<*>) : Controller.LifecycleListener() { - - override fun postCreateView(controller: Controller, view: View) { + override fun postCreateView( + controller: Controller, + view: View + ) { delegate.onTakeView(controller) } - override fun preDestroyView(controller: Controller, view: View) { + override fun preDestroyView( + controller: Controller, + view: View + ) { delegate.onDropView() } @@ -18,11 +23,17 @@ class NucleusConductorLifecycleListener(private val delegate: NucleusConductorDe delegate.onDestroy() } - override fun onSaveInstanceState(controller: Controller, outState: Bundle) { + override fun onSaveInstanceState( + controller: Controller, + outState: Bundle + ) { outState.putBundle(PRESENTER_STATE_KEY, delegate.onSaveInstanceState()) } - override fun onRestoreInstanceState(controller: Controller, savedInstanceState: Bundle) { + override fun onRestoreInstanceState( + controller: Controller, + savedInstanceState: Bundle + ) { delegate.onRestoreInstanceState(savedInstanceState.getBundle(PRESENTER_STATE_KEY)) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt index b9510868a115..569cdb63fab9 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryAdapter.kt @@ -9,7 +9,6 @@ import eu.davidea.flexibleadapter.FlexibleAdapter */ class CategoryAdapter(controller: CategoryController) : FlexibleAdapter(null, controller, true) { - /** * Listener called when an item of the list is released. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt index 55d5448caef9..1b2923d53234 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt @@ -34,7 +34,6 @@ class CategoryController : CategoryCreateDialog.Listener, CategoryRenameDialog.Listener, UndoHelper.OnActionListener { - /** * Object used to show ActionMode toolbar. */ @@ -68,7 +67,10 @@ class CategoryController : * @param inflater The layout inflater to create the view from XML. * @param container The parent view for this one. */ - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = CategoriesControllerBinding.inflate(inflater) return binding.root } @@ -137,7 +139,10 @@ class CategoryController : * @return true if the action mode should be created, false if entering this mode should be * aborted. */ - override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + override fun onCreateActionMode( + mode: ActionMode, + menu: Menu + ): Boolean { // Inflate menu. mode.menuInflater.inflate(R.menu.category_selection, menu) // Enable adapter multi selection. @@ -152,7 +157,10 @@ class CategoryController : * @param menu Menu used to populate action buttons. * @return true if the menu or action mode was updated, false otherwise. */ - override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + override fun onPrepareActionMode( + mode: ActionMode, + menu: Menu + ): Boolean { val adapter = adapter ?: return false val count = adapter.selectedItemCount mode.title = count.toString() @@ -171,15 +179,21 @@ class CategoryController : * @return true if this callback handled the event, false if the standard MenuItem invocation * should continue. */ - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + override fun onActionItemClicked( + mode: ActionMode, + item: MenuItem + ): Boolean { val adapter = adapter ?: return false when (item.itemId) { R.id.action_delete -> { undoHelper = UndoHelper(adapter, this) undoHelper?.start( - adapter.selectedPositions, view!!, - R.string.snack_categories_deleted, R.string.action_undo, 3000 + adapter.selectedPositions, + view!!, + R.string.snack_categories_deleted, + R.string.action_undo, + 3000 ) mode.finish() @@ -217,7 +231,10 @@ class CategoryController : * @param position The position of the clicked item. * @return true if this click should enable selection mode. */ - override fun onItemClick(view: View, position: Int): Boolean { + override fun onItemClick( + view: View, + position: Int + ): Boolean { // Check if action mode is initialized and selected item exist. return if (actionMode != null && position != RecyclerView.NO_POSITION) { toggleSelection(position) @@ -280,7 +297,10 @@ class CategoryController : * * @param action The action performed. */ - override fun onActionCanceled(action: Int, positions: MutableList?) { + override fun onActionCanceled( + action: Int, + positions: MutableList? + ) { adapter?.restoreDeletedItems() undoHelper = null } @@ -291,7 +311,10 @@ class CategoryController : * @param action The action performed. * @param event The event that triggered the action */ - override fun onActionConfirmed(action: Int, event: Int) { + override fun onActionConfirmed( + action: Int, + event: Int + ) { val adapter = adapter ?: return presenter.deleteCategories(adapter.deletedItems.map { it.category }) undoHelper = null @@ -312,7 +335,10 @@ class CategoryController : * @param category The category to rename. * @param name The new name of the category. */ - override fun renameCategory(category: Category, name: String) { + override fun renameCategory( + category: Category, + name: String + ) { presenter.renameCategory(category, name) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryCreateDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryCreateDialog.kt index bdaa51ad143a..bf829e43f952 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryCreateDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryCreateDialog.kt @@ -12,8 +12,7 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController * Dialog to create a new category for the library. */ class CategoryCreateDialog(bundle: Bundle? = null) : DialogController(bundle) - where T : Controller, T : CategoryCreateDialog.Listener { - + where T : Controller, T : CategoryCreateDialog.Listener { /** * Name of the new category. Value updated with each input from the user. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt index bfbf38a4a1bf..694b132d19cf 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryHolder.kt @@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder * @param adapter The adapter containing this holder. */ class CategoryHolder(view: View, val adapter: CategoryAdapter) : BaseFlexibleViewHolder(view, adapter) { - private val binding = CategoriesItemBinding.bind(view) + init { setDragHandleView(binding.reorder) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItem.kt index 4a1fb328e5e0..db411a5d03e8 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryItem.kt @@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.data.database.models.Category * Category item for a recycler view. */ class CategoryItem(val category: Category) : AbstractFlexibleItem() { - /** * Whether this item is currently selected. */ @@ -30,7 +29,10 @@ class CategoryItem(val category: Category) : AbstractFlexibleItem>): CategoryHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): CategoryHolder { return CategoryHolder(view, adapter as CategoryAdapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt index 1c6aab6b6a3b..1c29f449c37f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryPresenter.kt @@ -15,7 +15,6 @@ import uy.kohesive.injekt.api.get class CategoryPresenter( private val db: DatabaseHelper = Injekt.get() ) : BasePresenter() { - /** * List containing categories. */ @@ -86,7 +85,10 @@ class CategoryPresenter( * @param category The category to rename. * @param name The new name of the category. */ - fun renameCategory(category: Category, name: String) { + fun renameCategory( + category: Category, + name: String + ) { // Do not allow duplicate categories. if (categoryExists(name)) { Observable.just(Unit).subscribeFirst({ view, _ -> view.onCategoryExistsError() }) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryRenameDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryRenameDialog.kt index ac1073b654fe..ea2a91c07775 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryRenameDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryRenameDialog.kt @@ -13,8 +13,7 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController * Dialog to rename an existing category of the library. */ class CategoryRenameDialog(bundle: Bundle? = null) : DialogController(bundle) - where T : Controller, T : CategoryRenameDialog.Listener { - + where T : Controller, T : CategoryRenameDialog.Listener { private var category: Category? = null /** @@ -78,7 +77,10 @@ class CategoryRenameDialog(bundle: Bundle? = null) : DialogController(bundle) } interface Listener { - fun renameCategory(category: Category, name: String) + fun renameCategory( + category: Category, + name: String + ) } private companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt index 01c72f2df6c0..59fdb2c77c16 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadAdapter.kt @@ -13,7 +13,6 @@ class DownloadAdapter(controller: DownloadController) : FlexibleAdapter(), DownloadAdapter.DownloadItemListener { - /** * Adapter containing the active downloads. */ @@ -46,7 +45,10 @@ class DownloadController : setHasOptionsMenu(true) } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = DownloadControllerBinding.inflate(inflater) return binding.root } @@ -97,7 +99,10 @@ class DownloadController : super.onDestroyView(view) } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { inflater.inflate(R.menu.download_queue, menu) } @@ -122,8 +127,9 @@ class DownloadController : } R.id.newest, R.id.oldest -> { val adapter = adapter ?: return false - val items = adapter.currentItems.sortedBy { it.download.chapter.date_upload } - .toMutableList() + val items = + adapter.currentItems.sortedBy { it.download.chapter.date_upload } + .toMutableList() if (item.itemId == R.id.newest) { items.reverse() } @@ -162,23 +168,24 @@ class DownloadController : * @param download the download to observe its progress. */ private fun observeProgress(download: Download) { - val subscription = Observable.interval(50, TimeUnit.MILLISECONDS) - // Get the sum of percentages for all the pages. - .flatMap { - Observable.from(download.pages) - .map(Page::progress) - .reduce { x, y -> x + y } - } - // Keep only the latest emission to avoid backpressure. - .onBackpressureLatest() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { progress -> - // Update the view only if the progress has changed. - if (download.totalProgress != progress) { - download.totalProgress = progress - onUpdateProgress(download) + val subscription = + Observable.interval(50, TimeUnit.MILLISECONDS) + // Get the sum of percentages for all the pages. + .flatMap { + Observable.from(download.pages) + .map(Page::progress) + .reduce { x, y -> x + y } + } + // Keep only the latest emission to avoid backpressure. + .onBackpressureLatest() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { progress -> + // Update the view only if the progress has changed. + if (download.totalProgress != progress) { + download.totalProgress = progress + onUpdateProgress(download) + } } - } // Avoid leaking subscriptions progressSubscriptions.remove(download)?.unsubscribe() @@ -275,7 +282,10 @@ class DownloadController : * @param position The position of the item * @param menuItem The menu Item pressed */ - override fun onMenuItemClick(position: Int, menuItem: MenuItem) { + override fun onMenuItemClick( + position: Int, + menuItem: MenuItem + ) { when (menuItem.itemId) { R.id.move_to_top, R.id.move_to_bottom -> { val download = adapter?.getItem(position) ?: return diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt index c30c53311080..0137a2379246 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt @@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.util.view.popupMenu */ class DownloadHolder(private val view: View, val adapter: DownloadAdapter) : BaseFlexibleViewHolder(view, adapter) { - private val binding = DownloadItemBinding.bind(view) init { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadItem.kt index e523bbe02fe5..c3c57471e46b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadItem.kt @@ -9,7 +9,6 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.model.Download class DownloadItem(val download: Download) : AbstractFlexibleItem() { - override fun getLayoutRes(): Int { return R.layout.download_item } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt index 7f003959ee73..7e5579de70fe 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadPresenter.kt @@ -14,7 +14,6 @@ import uy.kohesive.injekt.injectLazy * Presenter of [DownloadController]. */ class DownloadPresenter : BasePresenter() { - val downloadManager: DownloadManager by injectLazy() /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionAdapter.kt index 8448f82b9557..20cc5b84780a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionAdapter.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.util.system.getResourceColor */ class ExtensionAdapter(val controller: ExtensionController) : FlexibleAdapter>(null, controller, true) { - val cardBackground = controller.activity!!.getResourceColor(R.attr.colorSurface) init { @@ -26,6 +25,7 @@ class ExtensionAdapter(val controller: ExtensionController) : interface OnButtonClickListener { fun onButtonClick(position: Int) + fun onCancelButtonClick(position: Int) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt index 01faf5ebba96..74d92b4be893 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionController.kt @@ -35,7 +35,6 @@ open class ExtensionController : FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, ExtensionTrustDialog.Listener { - /** * Adapter containing the list of manga from the catalogue. */ @@ -57,7 +56,10 @@ open class ExtensionController : return ExtensionPresenter() } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = ExtensionControllerBinding.inflate(inflater) return binding.root } @@ -99,7 +101,10 @@ open class ExtensionController : return super.onOptionsItemSelected(item) } - override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { + override fun onChangeStarted( + handler: ControllerChangeHandler, + type: ControllerChangeType + ) { super.onChangeStarted(handler, type) if (!type.isPush && handler is SettingsExtensionsFadeChangeHandler) { presenter.findAvailableExtensions() @@ -130,7 +135,10 @@ open class ExtensionController : presenter.cancelInstallUpdateExtension(extension) } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { inflater.inflate(R.menu.extension_main, menu) val searchItem = menu.findItem(R.id.action_search) @@ -155,7 +163,10 @@ open class ExtensionController : searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() }) } - override fun onItemClick(view: View, position: Int): Boolean { + override fun onItemClick( + view: View, + position: Int + ): Boolean { val extension = (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return false if (extension is Extension.Installed) { openDetails(extension) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt index 4cefe489b580..beaf53254325 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt @@ -21,7 +21,6 @@ import reactivecircus.flowbinding.android.view.clicks class ExtensionDetailsController(bundle: Bundle? = null) : NucleusController(bundle), FlexibleAdapter.OnItemClickListener { - constructor(pkgName: String) : this( Bundle().apply { putString(PKGNAME_KEY, pkgName) @@ -34,7 +33,11 @@ class ExtensionDetailsController(bundle: Bundle? = null) : private var adapter: ExtensionDetailsPrefsButtonAdapter? = null private var sources: List? = null - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = ExtensionDetailControllerBinding.inflate(inflater) return binding.root } @@ -56,7 +59,11 @@ class ExtensionDetailsController(bundle: Bundle? = null) : binding.extensionTitle.text = extension.name binding.extensionVersion.text = context.getString(R.string.ext_version_info, extension.versionName) - binding.extensionLang.text = context.getString(R.string.ext_language_info, LocaleHelper.getSourceDisplayName(extension.lang, context)) + binding.extensionLang.text = + context.getString( + R.string.ext_language_info, + LocaleHelper.getSourceDisplayName(extension.lang, context) + ) binding.extensionPkg.text = extension.pkgName extension.getApplicationIcon(context)?.let { binding.extensionIcon.setImageDrawable(it) } binding.extensionUninstallButton.clicks() @@ -96,7 +103,10 @@ class ExtensionDetailsController(bundle: Bundle? = null) : super.onDestroyView(view) } - override fun onItemClick(view: View?, position: Int): Boolean { + override fun onItemClick( + view: View?, + position: Int + ): Boolean { val id = sources?.get(position)?.id return if (id != null) { openPreferences(id) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPrefsButtonHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPrefsButtonHolder.kt index ed2336d9b651..f3b404d5a70c 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPrefsButtonHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPrefsButtonHolder.kt @@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.databinding.ExtensionDetailItemBinding * @param adapter The adapter containing this holder. */ class ExtensionDetailsPrefsButtonHolder(view: View, val adapter: ExtensionDetailsPrefsButtonAdapter) : FlexibleViewHolder(view, adapter) { - private val binding = ExtensionDetailItemBinding.bind(view) /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPrefsButtonItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPrefsButtonItem.kt index bc565ef6d83f..5fea517cc849 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPrefsButtonItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPrefsButtonItem.kt @@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.R * Repo item for a recycler view. */ class ExtensionDetailsPrefsButtonItem(val sourceName: String) : AbstractFlexibleItem() { - /** * Returns the layout resource for this item. */ @@ -25,7 +24,10 @@ class ExtensionDetailsPrefsButtonItem(val sourceName: String) : AbstractFlexible * @param view The view of this item. * @param adapter The adapter of this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ExtensionDetailsPrefsButtonHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): ExtensionDetailsPrefsButtonHolder { return ExtensionDetailsPrefsButtonHolder(view, adapter as ExtensionDetailsPrefsButtonAdapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPresenter.kt index 51e58c34b7cc..6d9b4418f3bb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPresenter.kt @@ -11,7 +11,6 @@ class ExtensionDetailsPresenter( val pkgName: String, private val extensionManager: ExtensionManager = Injekt.get() ) : BasePresenter() { - val extension = extensionManager.installedExtensions.find { it.pkgName == pkgName } override fun onCreate(savedState: Bundle?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDividerItemDecoration.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDividerItemDecoration.kt index 6bef0da268d9..9fa9557394ca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDividerItemDecoration.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDividerItemDecoration.kt @@ -8,7 +8,6 @@ import android.view.View import androidx.recyclerview.widget.RecyclerView class ExtensionDividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() { - private val divider: Drawable init { @@ -17,7 +16,11 @@ class ExtensionDividerItemDecoration(context: Context) : RecyclerView.ItemDecora a.recycle() } - override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { + override fun onDraw( + c: Canvas, + parent: RecyclerView, + state: RecyclerView.State + ) { val childCount = parent.childCount for (i in 0 until childCount - 1) { val child = parent.getChildAt(i) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionFilterController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionFilterController.kt index 6a4c8f904df0..2836ca8f2303 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionFilterController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionFilterController.kt @@ -12,40 +12,40 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class ExtensionFilterController : SettingsController() { + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + titleRes = R.string.action_filter + + val activeLangs = preferences.enabledLanguages().get() + + val availableLangs = + Injekt.get().availableExtensions.groupBy { + it.lang + }.keys.minus("all").partition { + it in activeLangs + }.let { + it.first + it.second + } - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.action_filter - - val activeLangs = preferences.enabledLanguages().get() - - val availableLangs = - Injekt.get().availableExtensions.groupBy { - it.lang - }.keys.minus("all").partition { - it in activeLangs - }.let { - it.first + it.second - } - - availableLangs.forEach { - switchPreference { - preferenceScreen.addPreference(this) - title = LocaleHelper.getSourceDisplayName(it, context) - isPersistent = false - isChecked = it in activeLangs - - onChange { newValue -> - val checked = newValue as Boolean - val currentActiveLangs = preferences.enabledLanguages().get() - - if (checked) { - preferences.enabledLanguages().set(currentActiveLangs + it) - } else { - preferences.enabledLanguages().set(currentActiveLangs - it) + availableLangs.forEach { + switchPreference { + preferenceScreen.addPreference(this) + title = LocaleHelper.getSourceDisplayName(it, context) + isPersistent = false + isChecked = it in activeLangs + + onChange { newValue -> + val checked = newValue as Boolean + val currentActiveLangs = preferences.enabledLanguages().get() + + if (checked) { + preferences.enabledLanguages().set(currentActiveLangs + it) + } else { + preferences.enabledLanguages().set(currentActiveLangs - it) + } + true } - true } } } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupHolder.kt index fe8c04b916b7..0dae5afe765d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupHolder.kt @@ -9,8 +9,8 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<*>) : BaseFlexibleViewHolder(view, adapter) { - private val binding = ExtensionCardHeaderBinding.bind(view) + @SuppressLint("SetTextI18n") fun bind(item: ExtensionGroupItem) { var text = item.name diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupItem.kt index dc5371acc46f..d725c5c40e0b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionGroupItem.kt @@ -17,7 +17,6 @@ data class ExtensionGroupItem( val size: Int, val showSize: Boolean = false ) : AbstractHeaderItem() { - var actionLabel: String? = null var actionOnClick: (View.OnClickListener)? = null @@ -31,7 +30,10 @@ data class ExtensionGroupItem( /** * Creates a new view holder for this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ExtensionGroupHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): ExtensionGroupHolder { return ExtensionGroupHolder(view, adapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt index b852cacab966..acdabb42cef0 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt @@ -16,12 +16,12 @@ import io.github.mthli.slice.Slice class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) : BaseFlexibleViewHolder(view, adapter), SlicedHolder { - private val binding = ExtensionCardItemBinding.bind(view) - override val slice = Slice(binding.card).apply { - setColor(adapter.cardBackground) - } + override val slice = + Slice(binding.card).apply { + setColor(adapter.cardBackground) + } override val viewToSlice: View get() = binding.card @@ -42,11 +42,12 @@ class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) : // Set source name binding.extTitle.text = extension.name binding.version.text = extension.versionName - binding.lang.text = if (extension !is Extension.Untrusted) { - LocaleHelper.getSourceDisplayName(extension.lang, itemView.context) - } else { - itemView.context.getString(R.string.ext_untrusted).uppercase() - } + binding.lang.text = + if (extension !is Extension.Untrusted) { + LocaleHelper.getSourceDisplayName(extension.lang, itemView.context) + } else { + itemView.context.getString(R.string.ext_untrusted).uppercase() + } GlideApp.with(itemView.context).clear(binding.image) if (extension is Extension.Available) { @@ -60,67 +61,72 @@ class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) : } @Suppress("ResourceType") - fun bindButtons(item: ExtensionItem) = with(binding.extButton) { - setTextColor(context.getResourceColor(R.attr.colorAccent)) + fun bindButtons(item: ExtensionItem) = + with(binding.extButton) { + setTextColor(context.getResourceColor(R.attr.colorAccent)) - val extension = item.extension + val extension = item.extension - val installStep = item.installStep - text = when (installStep) { - InstallStep.Pending -> context.getString(R.string.ext_pending) - InstallStep.Downloading -> context.getString(R.string.ext_downloading) - InstallStep.Installing -> context.getString(R.string.ext_installing) - InstallStep.Installed -> context.getString(R.string.ext_installed) - InstallStep.Error -> context.getString(R.string.action_retry) - InstallStep.Idle -> { - when (extension) { - is Extension.Installed -> { - when { - extension.hasUpdate -> { - context.getString(R.string.ext_update) - } - extension.isObsolete -> { - setTextColor(context.getResourceColor(R.attr.colorError)) - context.getString(R.string.ext_obsolete) - } - extension.isUnofficial -> { - setTextColor(context.getResourceColor(R.attr.colorError)) - context.getString(R.string.ext_unofficial) - } - extension.isRedundant -> { - setTextColor(context.getResourceColor(R.attr.colorError)) - context.getString(R.string.ext_redundant) - } - else -> { - context.getString(R.string.ext_details).plusRepo(extension) + val installStep = item.installStep + text = + when (installStep) { + InstallStep.Pending -> context.getString(R.string.ext_pending) + InstallStep.Downloading -> context.getString(R.string.ext_downloading) + InstallStep.Installing -> context.getString(R.string.ext_installing) + InstallStep.Installed -> context.getString(R.string.ext_installed) + InstallStep.Error -> context.getString(R.string.action_retry) + InstallStep.Idle -> { + when (extension) { + is Extension.Installed -> { + when { + extension.hasUpdate -> { + context.getString(R.string.ext_update) + } + extension.isObsolete -> { + setTextColor(context.getResourceColor(R.attr.colorError)) + context.getString(R.string.ext_obsolete) + } + extension.isUnofficial -> { + setTextColor(context.getResourceColor(R.attr.colorError)) + context.getString(R.string.ext_unofficial) + } + extension.isRedundant -> { + setTextColor(context.getResourceColor(R.attr.colorError)) + context.getString(R.string.ext_redundant) + } + else -> { + context.getString(R.string.ext_details).plusRepo(extension) + } + } } + is Extension.Untrusted -> context.getString(R.string.ext_trust) + is Extension.Available -> context.getString(R.string.ext_install) } } - is Extension.Untrusted -> context.getString(R.string.ext_trust) - is Extension.Available -> context.getString(R.string.ext_install) } - } - } - val isIdle = installStep == InstallStep.Idle || installStep == InstallStep.Error - binding.cancelButton.isVisible = !isIdle - isEnabled = isIdle - isClickable = isIdle - } + val isIdle = installStep == InstallStep.Idle || installStep == InstallStep.Error + binding.cancelButton.isVisible = !isIdle + isEnabled = isIdle + isClickable = isIdle + } // SY --> private fun String.plusRepo(extension: Extension): String { return if (extension is Extension.Available) { when (extension.repoUrl) { else -> { - this + if (this.isEmpty()) { - "" - } else { - " • " - } + itemView.context.getString(R.string.repo_source) + this + + if (this.isEmpty()) { + "" + } else { + " • " + } + itemView.context.getString(R.string.repo_source) } } - } else this + } else { + this + } } // SY <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt index 924a58e6c2fd..e6512907b7cf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt @@ -21,7 +21,6 @@ data class ExtensionItem( val installStep: InstallStep = InstallStep.Idle ) : AbstractSectionableItem(header) { - /** * Returns the layout resource of this item. */ @@ -32,7 +31,10 @@ data class ExtensionItem( /** * Creates a new view holder for this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ExtensionHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): ExtensionHolder { return ExtensionHolder(view, adapter as ExtensionAdapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPreferencesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPreferencesController.kt index 54a397fff251..f428d4756b00 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPreferencesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPreferencesController.kt @@ -37,7 +37,6 @@ class ExtensionPreferencesController(bundle: Bundle? = null) : NucleusController(bundle), PreferenceManager.OnDisplayPreferenceDialogListener, DialogPreference.TargetFragment { - private var lastOpenPreferencePosition: Int? = null private var preferenceScreen: PreferenceScreen? = null @@ -48,7 +47,10 @@ class ExtensionPreferencesController(bundle: Bundle? = null) : } ) - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { val themedInflater = inflater.cloneInContext(getPreferenceThemeContext()) binding = ExtensionPreferencesControllerBinding.inflate(themedInflater) return binding.root @@ -77,9 +79,10 @@ class ExtensionPreferencesController(bundle: Bundle? = null) : if (source is ConfigurableSource) { try { - val dataStore = SharedPreferencesDataStore( - sourcePreferences(source.preferenceKey()) - ) + val dataStore = + SharedPreferencesDataStore( + sourcePreferences(source.preferenceKey()) + ) manager.preferenceDataStore = dataStore screen.preferenceCategory { @@ -129,25 +132,27 @@ class ExtensionPreferencesController(bundle: Bundle? = null) : val screen = preference.parent!! - lastOpenPreferencePosition = (0 until screen.preferenceCount).indexOfFirst { - screen.getPreference(it) === preference - } + lastOpenPreferencePosition = + (0 until screen.preferenceCount).indexOfFirst { + screen.getPreference(it) === preference + } - val f = when (preference) { - is EditTextPreference -> - EditTextPreferenceDialogController - .newInstance(preference.getKey()) - is ListPreference -> - ListPreferenceDialogController - .newInstance(preference.getKey()) - is MultiSelectListPreference -> - MultiSelectListPreferenceDialogController - .newInstance(preference.getKey()) - else -> throw IllegalArgumentException( - "Tried to display dialog for unknown " + - "preference type. Did you forget to override onDisplayPreferenceDialog()?" - ) - } + val f = + when (preference) { + is EditTextPreference -> + EditTextPreferenceDialogController + .newInstance(preference.getKey()) + is ListPreference -> + ListPreferenceDialogController + .newInstance(preference.getKey()) + is MultiSelectListPreference -> + MultiSelectListPreferenceDialogController + .newInstance(preference.getKey()) + else -> throw IllegalArgumentException( + "Tried to display dialog for unknown " + + "preference type. Did you forget to override onDisplayPreferenceDialog()?" + ) + } f.targetController = this f.showDialog(router) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPreferencesPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPreferencesPresenter.kt index ddd391a0a118..bc097e5d69f8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPreferencesPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPreferencesPresenter.kt @@ -8,7 +8,6 @@ import uy.kohesive.injekt.api.get class ExtensionPreferencesPresenter( val sourceId: Long ) : BasePresenter() { - private val sourceManager: SourceManager = Injekt.get() val source = sourceManager.get(sourceId) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPresenter.kt index 7d9e96974ca7..65ba3aede5ea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionPresenter.kt @@ -11,12 +11,12 @@ import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.util.system.LocaleHelper -import java.util.concurrent.TimeUnit import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.concurrent.TimeUnit private typealias ExtensionTuple = Triple, List, List> @@ -28,7 +28,6 @@ open class ExtensionPresenter( private val extensionManager: ExtensionManager = Injekt.get(), private val preferences: PreferencesHelper = Injekt.get() ) : BasePresenter() { - private var extensions = emptyList() private var currentDownloads = hashMapOf() @@ -43,10 +42,15 @@ open class ExtensionPresenter( private fun bindToExtensionsObservable(): Subscription { val installedObservable = extensionManager.getInstalledExtensionsObservable() val untrustedObservable = extensionManager.getUntrustedExtensionsObservable() - val availableObservable = extensionManager.getAvailableExtensionsObservable() - .startWith(emptyList()) - - return Observable.combineLatest(installedObservable, untrustedObservable, availableObservable) { installed, untrusted, available -> Triple(installed, untrusted, available) } + val availableObservable = + extensionManager.getAvailableExtensionsObservable() + .startWith(emptyList()) + + return Observable.combineLatest( + installedObservable, + untrustedObservable, + availableObservable + ) { installed, untrusted, available -> Triple(installed, untrusted, available) } .debounce(100, TimeUnit.MILLISECONDS) .map(::toItems) .observeOn(AndroidSchedulers.mainThread()) @@ -64,52 +68,62 @@ open class ExtensionPresenter( val items = mutableListOf() val updatesSorted = installed.filter { it.hasUpdate && (showNsfwExtensions || !it.isNsfw) }.sortedBy { it.pkgName } - val installedSorted = installed.filter { !it.hasUpdate && (showNsfwExtensions || !it.isNsfw) }.sortedWith(compareBy({ !it.isObsolete && !it.isRedundant }, { it.pkgName })) + val installedSorted = + installed.filter { + !it.hasUpdate && (showNsfwExtensions || !it.isNsfw) + }.sortedWith(compareBy({ !it.isObsolete && !it.isRedundant }, { it.pkgName })) val untrustedSorted = untrusted.sortedBy { it.pkgName } - val availableSorted = available - // Filter out already installed extensions and disabled languages - .filter { avail -> - installed.none { it.pkgName == avail.pkgName } && - untrusted.none { it.pkgName == avail.pkgName } && - (avail.lang in activeLangs || avail.lang == "all") && - (showNsfwExtensions || !avail.isNsfw) - } - .sortedBy { it.pkgName } + val availableSorted = + available + // Filter out already installed extensions and disabled languages + .filter { avail -> + installed.none { it.pkgName == avail.pkgName } && + untrusted.none { it.pkgName == avail.pkgName } && + (avail.lang in activeLangs || avail.lang == "all") && + (showNsfwExtensions || !avail.isNsfw) + } + .sortedBy { it.pkgName } if (updatesSorted.isNotEmpty()) { val header = ExtensionGroupItem(context.getString(R.string.ext_updates_pending), updatesSorted.size, true) if (preferences.extensionInstaller().get() != PreferenceValues.ExtensionInstaller.LEGACY) { header.actionLabel = context.getString(R.string.ext_update_all) - header.actionOnClick = View.OnClickListener { _ -> - extensions - .filter { it.extension is Extension.Installed && it.extension.hasUpdate } - .forEach { updateExtension(it.extension as Extension.Installed) } - } - } - items += updatesSorted.map { extension -> - ExtensionItem(extension, header, currentDownloads[extension.pkgName] ?: InstallStep.Idle) + header.actionOnClick = + View.OnClickListener { _ -> + extensions + .filter { it.extension is Extension.Installed && it.extension.hasUpdate } + .forEach { updateExtension(it.extension as Extension.Installed) } + } } + items += + updatesSorted.map { extension -> + ExtensionItem(extension, header, currentDownloads[extension.pkgName] ?: InstallStep.Idle) + } } if (installedSorted.isNotEmpty() || untrustedSorted.isNotEmpty()) { val header = ExtensionGroupItem(context.getString(R.string.ext_installed), installedSorted.size + untrustedSorted.size) - items += installedSorted.map { extension -> - ExtensionItem(extension, header, currentDownloads[extension.pkgName] ?: InstallStep.Idle) - } - items += untrustedSorted.map { extension -> - ExtensionItem(extension, header) - } + items += + installedSorted.map { extension -> + ExtensionItem(extension, header, currentDownloads[extension.pkgName] ?: InstallStep.Idle) + } + items += + untrustedSorted.map { extension -> + ExtensionItem(extension, header) + } } if (availableSorted.isNotEmpty()) { - val availableGroupedByLang = availableSorted - .groupBy { LocaleHelper.getSourceDisplayName(it.lang, context) } - .toSortedMap() + val availableGroupedByLang = + availableSorted + .groupBy { LocaleHelper.getSourceDisplayName(it.lang, context) } + .toSortedMap() availableGroupedByLang .forEach { val header = ExtensionGroupItem(it.key, it.value.size) - items += it.value.map { extension -> - ExtensionItem(extension, header, currentDownloads[extension.pkgName] ?: InstallStep.Idle) - } + items += + it.value.map { extension -> + ExtensionItem(extension, header, currentDownloads[extension.pkgName] ?: InstallStep.Idle) + } } } @@ -118,7 +132,10 @@ open class ExtensionPresenter( } @Synchronized - private fun updateInstallStep(extension: Extension, state: InstallStep): ExtensionItem? { + private fun updateInstallStep( + extension: Extension, + state: InstallStep + ): ExtensionItem? { val extensions = extensions.toMutableList() val position = extensions.indexOfFirst { it.extension.pkgName == extension.pkgName } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt index 458a60f4b19c..3a22cf5fc75a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionTrustDialog.kt @@ -8,8 +8,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.controller.DialogController class ExtensionTrustDialog(bundle: Bundle? = null) : DialogController(bundle) - where T : Controller, T : ExtensionTrustDialog.Listener { - + where T : Controller, T : ExtensionTrustDialog.Listener { constructor(target: T, signatureHash: String, pkgName: String) : this( Bundle().apply { putString(SIGNATURE_KEY, signatureHash) @@ -38,6 +37,7 @@ class ExtensionTrustDialog(bundle: Bundle? = null) : DialogController(bundle) interface Listener { fun trustSignature(signatureHash: String) + fun uninstallExtension(pkgName: String) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoAdapter.kt index 1817af4a22ea..644f8651e669 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoAdapter.kt @@ -9,7 +9,6 @@ import eu.davidea.flexibleadapter.FlexibleAdapter */ class RepoAdapter(controller: RepoController) : FlexibleAdapter(null, controller, true) { - /** * Clears the active selections from the list and the model. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoController.kt index e2a3e7fe4c9b..0c3491cb029b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoController.kt @@ -31,10 +31,10 @@ class RepoController() : FlexibleAdapter.OnItemLongClickListener, RepoCreateDialog.Listener, UndoHelper.OnActionListener { - constructor(repoUrl: String) : this() { createRepo(repoUrl) } + /** * Object used to show ActionMode toolbar. */ @@ -68,7 +68,10 @@ class RepoController() : * @param inflater The layout inflater to create the view from XML. * @param container The parent view for this one. */ - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = CategoriesControllerBinding.inflate(inflater) return binding.root } @@ -136,7 +139,10 @@ class RepoController() : * @return true if the action mode should be created, false if entering this mode should be * aborted. */ - override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + override fun onCreateActionMode( + mode: ActionMode, + menu: Menu + ): Boolean { // Inflate menu. mode.menuInflater.inflate(R.menu.category_selection, menu) // Enable adapter multi selection. @@ -151,7 +157,10 @@ class RepoController() : * @param menu Menu used to populate action buttons. * @return true if the menu or action mode was updated, false otherwise. */ - override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + override fun onPrepareActionMode( + mode: ActionMode, + menu: Menu + ): Boolean { val adapter = adapter ?: return false val count = adapter.selectedItemCount mode.title = count.toString() @@ -170,15 +179,21 @@ class RepoController() : * @return true if this callback handled the event, false if the standard MenuItem invocation * should continue. */ - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + override fun onActionItemClicked( + mode: ActionMode, + item: MenuItem + ): Boolean { val adapter = adapter ?: return false when (item.itemId) { R.id.action_delete -> { undoHelper = UndoHelper(adapter, this) undoHelper?.start( - adapter.selectedPositions, view!!, - R.string.snack_repo_deleted, R.string.action_undo, 3000 + adapter.selectedPositions, + view!!, + R.string.snack_repo_deleted, + R.string.action_undo, + 3000 ) mode.finish() @@ -206,7 +221,10 @@ class RepoController() : * @param position The position of the clicked item. * @return true if this click should enable selection mode. */ - override fun onItemClick(view: View, position: Int): Boolean { + override fun onItemClick( + view: View, + position: Int + ): Boolean { // Check if action mode is initialized and selected item exist. return if (actionMode != null && position != RecyclerView.NO_POSITION) { toggleSelection(position) @@ -258,7 +276,10 @@ class RepoController() : * * @param action The action performed. */ - override fun onActionCanceled(action: Int, positions: MutableList?) { + override fun onActionCanceled( + action: Int, + positions: MutableList? + ) { adapter?.restoreDeletedItems() undoHelper = null } @@ -269,7 +290,10 @@ class RepoController() : * @param action The action performed. * @param event The event that triggered the action */ - override fun onActionConfirmed(action: Int, event: Int) { + override fun onActionConfirmed( + action: Int, + event: Int + ) { val adapter = adapter ?: return presenter.deleteRepos(adapter.deletedItems.map { it.repo }) undoHelper = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoCreateDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoCreateDialog.kt index 98750ac5a228..0a3c9d6d5e17 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoCreateDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoCreateDialog.kt @@ -12,8 +12,7 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController * Dialog to create a new repo for the library. */ class RepoCreateDialog(bundle: Bundle? = null) : DialogController(bundle) - where T : Controller, T : RepoCreateDialog.Listener { - + where T : Controller, T : RepoCreateDialog.Listener { /** * Name of the new repo. Value updated with each input from the user. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoHolder.kt index 234b0212362f..c6a4e7a3247d 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoHolder.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.databinding.CategoriesItemBinding * @param adapter The adapter containing this holder. */ class RepoHolder(view: View, val adapter: RepoAdapter) : FlexibleViewHolder(view, adapter) { - private val binding = CategoriesItemBinding.bind(view) /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoItem.kt index a52eaadbed10..bcb86eb2461d 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/repos/RepoItem.kt @@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.R * Repo item for a recycler view. */ class RepoItem(val repo: String) : AbstractFlexibleItem() { - /** * Whether this item is currently selected. */ @@ -30,7 +29,10 @@ class RepoItem(val repo: String) : AbstractFlexibleItem() { * @param view The view of this item. * @param adapter The adapter of this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): RepoHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): RepoHolder { return RepoHolder(view, adapter as RepoAdapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/ChangeMangaCategoriesDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/ChangeMangaCategoriesDialog.kt index be6e9c1b8d67..910a59394044 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/ChangeMangaCategoriesDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/ChangeMangaCategoriesDialog.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController class ChangeMangaCategoriesDialog(bundle: Bundle? = null) : DialogController(bundle) where T : Controller, T : ChangeMangaCategoriesDialog.Listener { - private var mangas = emptyList() private var categories = emptyList() @@ -48,6 +47,9 @@ class ChangeMangaCategoriesDialog(bundle: Bundle? = null) : } interface Listener { - fun updateCategoriesForMangas(mangas: List, categories: List) + fun updateCategoriesForMangas( + mangas: List, + categories: List + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/ChangeMangaCoverDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/ChangeMangaCoverDialog.kt index c12b0f13fc72..d9e4b8a7c468 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/ChangeMangaCoverDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/ChangeMangaCoverDialog.kt @@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController class ChangeMangaCoverDialog(bundle: Bundle? = null) : DialogController(bundle) where T : Controller, T : ChangeMangaCoverDialog.Listener { - private lateinit var manga: Manga constructor(target: T, manga: Manga) : this() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/DeleteLibraryMangasDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/DeleteLibraryMangasDialog.kt index 969f19f5784b..ae26ad454175 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/DeleteLibraryMangasDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/DeleteLibraryMangasDialog.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.widget.DialogCheckboxView class DeleteLibraryMangasDialog(bundle: Bundle? = null) : DialogController(bundle) where T : Controller, T : DeleteLibraryMangasDialog.Listener { - private var mangas = emptyList() constructor(target: T, mangas: List) : this() { @@ -21,10 +20,11 @@ class DeleteLibraryMangasDialog(bundle: Bundle? = null) : } override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val view = DialogCheckboxView(activity!!).apply { - setDescription(R.string.confirm_delete_manga) - setOptionDescription(R.string.also_delete_chapters) - } + val view = + DialogCheckboxView(activity!!).apply { + setDescription(R.string.confirm_delete_manga) + setOptionDescription(R.string.also_delete_chapters) + } return MaterialDialog(activity!!) .title(R.string.action_remove) @@ -40,6 +40,9 @@ class DeleteLibraryMangasDialog(bundle: Bundle? = null) : } interface Listener { - fun deleteMangasFromLibrary(mangas: List, deleteChapters: Boolean) + fun deleteMangasFromLibrary( + mangas: List, + deleteChapters: Boolean + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt index f465e9c93508..15601c26720a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt @@ -14,7 +14,6 @@ import eu.kanade.tachiyomi.widget.RecyclerViewPagerAdapter * @constructor creates an instance of the adapter. */ class LibraryAdapter(private val controller: LibraryController) : RecyclerViewPagerAdapter() { - /** * The categories to bind in the adapter. */ @@ -47,7 +46,10 @@ class LibraryAdapter(private val controller: LibraryController) : RecyclerViewPa * @param view the view to bind. * @param position the position in the adapter. */ - override fun bindView(view: View, position: Int) { + override fun bindView( + view: View, + position: Int + ) { (view as LibraryCategoryView).onBind(categories[position]) boundViews.add(view) } @@ -58,7 +60,10 @@ class LibraryAdapter(private val controller: LibraryController) : RecyclerViewPa * @param view the view to recycle. * @param position the position in the adapter. */ - override fun recycleView(view: View, position: Int) { + override fun recycleView( + view: View, + position: Int + ) { (view as LibraryCategoryView).onRecycle() boundViews.remove(view) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index e467a190210a..428e34d31868 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -59,7 +59,10 @@ class LibraryCategoryAdapter(view: LibraryCategoryView, val controller: LibraryC * * @param list the list to set. */ - suspend fun setItems(scope: CoroutineScope, list: List) { + suspend fun setItems( + scope: CoroutineScope, + list: List + ) { // A copy of manga always unfiltered. mangas = list.toList() @@ -84,76 +87,83 @@ class LibraryCategoryAdapter(view: LibraryCategoryView, val controller: LibraryC if (mangas.isNotEmpty() && searchText.isNotBlank()) { val savedSearchText = searchText - val job = scope.launch(Dispatchers.IO) { - val newManga = try { - // Prepare filter object - val parsedQuery = searchEngine.parseQuery(savedSearchText) - val sqlQuery = searchEngine.queryToSql(parsedQuery) - val queryResult = db.lowLevel().rawQuery( - RawQuery.builder() - .query(sqlQuery.first) - .args(*sqlQuery.second.toTypedArray()) - .build() - ) - - ensureActive() // Fail early when cancelled - - val mangaWithMetaIdsQuery = db.getIdsOfFavoriteMangaWithMetadata().await() - val mangaWithMetaIds = LongArray(mangaWithMetaIdsQuery.count) - if (mangaWithMetaIds.isNotEmpty()) { - val mangaIdCol = mangaWithMetaIdsQuery.getColumnIndex(MangaTable.COL_ID) - mangaWithMetaIdsQuery.moveToFirst() - while (!mangaWithMetaIdsQuery.isAfterLast) { + val job = + scope.launch(Dispatchers.IO) { + val newManga = + try { + // Prepare filter object + val parsedQuery = searchEngine.parseQuery(savedSearchText) + val sqlQuery = searchEngine.queryToSql(parsedQuery) + val queryResult = + db.lowLevel().rawQuery( + RawQuery.builder() + .query(sqlQuery.first) + .args(*sqlQuery.second.toTypedArray()) + .build() + ) + ensureActive() // Fail early when cancelled - mangaWithMetaIds[mangaWithMetaIdsQuery.position] = mangaWithMetaIdsQuery.getLong(mangaIdCol) - mangaWithMetaIdsQuery.moveToNext() - } - } + val mangaWithMetaIdsQuery = db.getIdsOfFavoriteMangaWithMetadata().await() + val mangaWithMetaIds = LongArray(mangaWithMetaIdsQuery.count) + if (mangaWithMetaIds.isNotEmpty()) { + val mangaIdCol = mangaWithMetaIdsQuery.getColumnIndex(MangaTable.COL_ID) + mangaWithMetaIdsQuery.moveToFirst() + while (!mangaWithMetaIdsQuery.isAfterLast) { + ensureActive() // Fail early when cancelled - ensureActive() // Fail early when cancelled + mangaWithMetaIds[mangaWithMetaIdsQuery.position] = mangaWithMetaIdsQuery.getLong(mangaIdCol) + mangaWithMetaIdsQuery.moveToNext() + } + } - val convertedResult = LongArray(queryResult.count) - if (convertedResult.isNotEmpty()) { - val mangaIdCol = queryResult.getColumnIndex(SearchMetadataTable.COL_MANGA_ID) - queryResult.moveToFirst() - while (!queryResult.isAfterLast) { ensureActive() // Fail early when cancelled - convertedResult[queryResult.position] = queryResult.getLong(mangaIdCol) - queryResult.moveToNext() - } - } + val convertedResult = LongArray(queryResult.count) + if (convertedResult.isNotEmpty()) { + val mangaIdCol = queryResult.getColumnIndex(SearchMetadataTable.COL_MANGA_ID) + queryResult.moveToFirst() + while (!queryResult.isAfterLast) { + ensureActive() // Fail early when cancelled - ensureActive() // Fail early when cancelled + convertedResult[queryResult.position] = queryResult.getLong(mangaIdCol) + queryResult.moveToNext() + } + } - // Flow the mangas to allow cancellation of this filter operation - mangas.asFlow().cancellable().filter { item -> - if (isLewdSource(item.manga.source)) { - val mangaId = item.manga.id ?: -1 - if (convertedResult.binarySearch(mangaId) < 0) { - // Check if this manga even has metadata - if (mangaWithMetaIds.binarySearch(mangaId) < 0) { - // No meta? Filter using title + ensureActive() // Fail early when cancelled + + // Flow the mangas to allow cancellation of this filter operation + mangas.asFlow().cancellable().filter { item -> + if (isLewdSource(item.manga.source)) { + val mangaId = item.manga.id ?: -1 + if (convertedResult.binarySearch(mangaId) < 0) { + // Check if this manga even has metadata + if (mangaWithMetaIds.binarySearch(mangaId) < 0) { + // No meta? Filter using title + item.filter(savedSearchText to true) + } else { + item.filter(savedSearchText to false) + } + } else { + true + } + } else { item.filter(savedSearchText to true) - } else item.filter(savedSearchText to false) - } else true - } else { - item.filter(savedSearchText to true) + } + }.toList() + } catch (e: Exception) { + // Do not catch cancellations + if (e is CancellationException) throw e + + Timber.w(e, "Could not filter mangas!") + mangas } - }.toList() - } catch (e: Exception) { - // Do not catch cancellations - if (e is CancellationException) throw e - Timber.w(e, "Could not filter mangas!") - mangas - } - - withContext(Dispatchers.Main) { - updateDataSet(newManga) + withContext(Dispatchers.Main) { + updateDataSet(newManga) + } } - } lastFilterJob = job job.join() } else { @@ -162,7 +172,10 @@ class LibraryCategoryAdapter(view: LibraryCategoryView, val controller: LibraryC } interface LibraryListener { - fun startReading(manga: Manga, adapter: LibraryCategoryAdapter) + fun startReading( + manga: Manga, + adapter: LibraryCategoryAdapter + ) } // EXH <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt index e909e7399fff..477842e68f32 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt @@ -23,7 +23,6 @@ import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.inflate import eu.kanade.tachiyomi.widget.AutofitRecyclerView import exh.ui.LoadingHandle -import java.util.concurrent.TimeUnit import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -38,17 +37,19 @@ import reactivecircus.flowbinding.swiperefreshlayout.refreshes import rx.android.schedulers.AndroidSchedulers import rx.subscriptions.CompositeSubscription import uy.kohesive.injekt.injectLazy +import java.util.concurrent.TimeUnit /** * Fragment containing the library manga for a certain category. */ -class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class LibraryCategoryView +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs), FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemMoveListener, CategoryAdapter.OnItemReleaseListener { - private val scope = CoroutineScope(Job() + Dispatchers.Main) private val preferences: PreferencesHelper by injectLazy() @@ -85,23 +86,28 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att private var initialLoadHandle: LoadingHandle? = null private lateinit var supervisorScope: CoroutineScope - private fun newScope() = object : CoroutineScope { - override val coroutineContext = SupervisorJob() + Dispatchers.Main - } + private fun newScope() = + object : CoroutineScope { + override val coroutineContext = SupervisorJob() + Dispatchers.Main + } // EXH <-- - fun onCreate(controller: LibraryController, binding: LibraryCategoryBinding) { + fun onCreate( + controller: LibraryController, + binding: LibraryCategoryBinding + ) { this.controller = controller - recycler = if (preferences.libraryDisplayMode().get() == DisplayMode.LIST) { - (binding.swipeRefresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply { - layoutManager = LinearLayoutManager(context) - } - } else { - (binding.swipeRefresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply { - spanCount = controller.mangaPerRow + recycler = + if (preferences.libraryDisplayMode().get() == DisplayMode.LIST) { + (binding.swipeRefresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply { + layoutManager = LinearLayoutManager(context) + } + } else { + (binding.swipeRefresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply { + spanCount = controller.mangaPerRow + } } - } adapter = LibraryCategoryAdapter(this, controller) @@ -112,8 +118,9 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att recycler.scrollStateChanges() .onEach { // Disable swipe refresh when view is not at the top - val firstPos = (recycler.layoutManager as LinearLayoutManager) - .findFirstCompletelyVisibleItemPosition() + val firstPos = + (recycler.layoutManager as LinearLayoutManager) + .findFirstCompletelyVisibleItemPosition() binding.swipeRefresh.isEnabled = firstPos <= 0 } .launchIn(scope) @@ -135,11 +142,12 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att fun onBind(category: Category) { this.category = category - adapter.mode = if (controller.selectedMangas.isNotEmpty()) { - SelectableAdapter.Mode.MULTI - } else { - SelectableAdapter.Mode.SINGLE - } + adapter.mode = + if (controller.selectedMangas.isNotEmpty()) { + SelectableAdapter.Mode.MULTI + } else { + SelectableAdapter.Mode.SINGLE + } val sortingMode = preferences.librarySortingMode().get() adapter.isLongPressDragEnabled = sortingMode == LibrarySort.DRAG_AND_DROP @@ -148,84 +156,92 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att initialLoadHandle = controller.loaderManager.openProgressBar() // EXH <-- - subscriptions += controller.searchRelay - .doOnNext { adapter.searchText = it } - .skip(1) - .debounce(500, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - // EXH --> - supervisorScope.launch { - val handle = controller.loaderManager.openProgressBar() - try { - // EXH <-- - adapter.performFilter(this) - // EXH --> - } finally { - controller.loaderManager.closeProgressBar(handle) + subscriptions += + controller.searchRelay + .doOnNext { adapter.searchText = it } + .skip(1) + .debounce(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + // EXH --> + supervisorScope.launch { + val handle = controller.loaderManager.openProgressBar() + try { + // EXH <-- + adapter.performFilter(this) + // EXH --> + } finally { + controller.loaderManager.closeProgressBar(handle) + } } + // EXH <-- } - // EXH <-- - } - subscriptions += controller.libraryMangaRelay - .subscribe { - // EXH --> - supervisorScope.launch { - try { - // EXH <-- - onNextLibraryManga(this, it) - // EXH --> - } finally { - controller.loaderManager.closeProgressBar(initialLoadHandle) + subscriptions += + controller.libraryMangaRelay + .subscribe { + // EXH --> + supervisorScope.launch { + try { + // EXH <-- + onNextLibraryManga(this, it) + // EXH --> + } finally { + controller.loaderManager.closeProgressBar(initialLoadHandle) + } } + // EXH <-- } - // EXH <-- - } - subscriptions += controller.selectionRelay - .subscribe { onSelectionChanged(it) } + subscriptions += + controller.selectionRelay + .subscribe { onSelectionChanged(it) } - subscriptions += controller.selectAllRelay - .subscribe { - if (it == category.id) { - adapter.currentItems.forEach { item -> - controller.setSelection(item.manga, true) + subscriptions += + controller.selectAllRelay + .subscribe { + if (it == category.id) { + adapter.currentItems.forEach { item -> + controller.setSelection(item.manga, true) + } + controller.invalidateActionMode() } - controller.invalidateActionMode() } - } - subscriptions += controller.reorganizeRelay - .subscribe { - if (it.first == category.id) { - var items = when (it.second) { - 1, 2 -> adapter.currentItems.sortedBy { - // if (preferences.removeArticles().getOrDefault()) - it.manga.title.removeArticles() + subscriptions += + controller.reorganizeRelay + .subscribe { + if (it.first == category.id) { + var items = + when (it.second) { + 1, 2 -> + adapter.currentItems.sortedBy { + // if (preferences.removeArticles().getOrDefault()) + it.manga.title.removeArticles() // else // it.manga.title + } + 3, 4 -> adapter.currentItems.sortedBy { it.manga.last_update } + else -> adapter.currentItems.sortedBy { it.manga.title } + } + if (it.second % 2 == 0) { + items = items.reversed() } - 3, 4 -> adapter.currentItems.sortedBy { it.manga.last_update } - else -> adapter.currentItems.sortedBy { it.manga.title } - } - if (it.second % 2 == 0) { - items = items.reversed() + runBlocking { adapter.setItems(this, items) } + adapter.notifyDataSetChanged() + onItemReleased(0) } - runBlocking { adapter.setItems(this, items) } - adapter.notifyDataSetChanged() - onItemReleased(0) } - } - subscriptions += controller.selectInverseRelay - .filter { it == category.id } - .subscribe { - adapter.currentItems.forEach { item -> - controller.toggleSelection(item.manga) + subscriptions += + controller.selectInverseRelay + .filter { it == category.id } + .subscribe { + adapter.currentItems.forEach { item -> + controller.toggleSelection(item.manga) + } + controller.invalidateActionMode() } - controller.invalidateActionMode() - } } fun onRecycle() { @@ -248,7 +264,10 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att * * @param event the event received. */ - suspend fun onNextLibraryManga(cScope: CoroutineScope, event: LibraryMangaEvent) { + suspend fun onNextLibraryManga( + cScope: CoroutineScope, + event: LibraryMangaEvent + ) { // Get the manga list for this category. val sortingMode = preferences.librarySortingMode().get() @@ -256,15 +275,17 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att var mangaForCategory = event.getMangaForCategory(category).orEmpty() if (sortingMode == LibrarySort.DRAG_AND_DROP) { if (category.name == "Default") { - category.mangaOrder = preferences.defaultMangaOrder().get().split("/") - .mapNotNull { it.toLongOrNull() } - } - mangaForCategory = mangaForCategory.sortedBy { - category.mangaOrder.indexOf( - it.manga - .id - ) + category.mangaOrder = + preferences.defaultMangaOrder().get().split("/") + .mapNotNull { it.toLongOrNull() } } + mangaForCategory = + mangaForCategory.sortedBy { + category.mangaOrder.indexOf( + it.manga + .id + ) + } } // Update the category with its manga. // EXH --> @@ -335,7 +356,10 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att * @param position the position of the element clicked. * @return true if the item should be selected, false otherwise. */ - override fun onItemClick(view: View, position: Int): Boolean { + override fun onItemClick( + view: View, + position: Int + ): Boolean { // If the action mode is created and the position is valid, toggle the selection. val item = adapter.getItem(position) ?: return false return if (adapter.mode == SelectableAdapter.Mode.MULTI) { @@ -369,7 +393,10 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att lastClickPosition = position } - override fun onItemMove(fromPosition: Int, toPosition: Int) { + override fun onItemMove( + fromPosition: Int, + toPosition: Int + ) { } override fun onItemReleased(position: Int) { @@ -385,7 +412,10 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att } } - override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean { + override fun shouldMoveItem( + fromPosition: Int, + toPosition: Int + ): Boolean { if (adapter.selectedItemCount > 1) { return false } @@ -395,7 +425,10 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att return true } - override fun onActionStateChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { + override fun onActionStateChanged( + viewHolder: RecyclerView.ViewHolder?, + actionState: Int + ) { val position = viewHolder?.adapterPosition ?: return if (actionState == 2) { onItemLongClick(position) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComfortableGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComfortableGridHolder.kt index 63180e5f0338..9c98685eac57 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComfortableGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComfortableGridHolder.kt @@ -29,7 +29,6 @@ class LibraryComfortableGridHolder( private val view: View, adapter: FlexibleAdapter> ) : LibraryHolder(view, adapter) { - override val binding = SourceComfortableGridItemBinding.bind(view) var manga: Manga? = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index da2521daadd6..22585589a334 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -53,7 +53,6 @@ import eu.kanade.tachiyomi.util.view.visible import exh.favorites.FavoritesIntroDialog import exh.favorites.FavoritesSyncStatus import exh.ui.LoaderManager -import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn @@ -65,6 +64,7 @@ import rx.android.schedulers.AndroidSchedulers import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.concurrent.TimeUnit class LibraryController( bundle: Bundle? = null, @@ -79,7 +79,6 @@ class LibraryController( ChangeMangaCategoriesDialog.Listener, DeleteLibraryMangasDialog.Listener, LibraryCategoryAdapter.LibraryListener { - /** * Position of the active category. */ @@ -165,8 +164,10 @@ class LibraryController( // --> EH // Sync dialog private var favSyncDialog: MaterialDialog? = null + // Old sync status private var oldSyncStatus: FavoritesSyncStatus? = null + // Favorites private var favoritesSyncSubscription: Subscription? = null val loaderManager = LoaderManager() @@ -185,7 +186,10 @@ class LibraryController( return LibraryPresenter() } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = LibraryControllerBinding.inflate(inflater) return binding.root } @@ -219,7 +223,10 @@ class LibraryController( // EXH <-- } - override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { + override fun onChangeStarted( + handler: ControllerChangeHandler, + type: ControllerChangeType + ) { super.onChangeStarted(handler, type) if (type.isEnter) { val activity = activity as MainActivity? @@ -268,14 +275,15 @@ class LibraryController( tabMode = TabLayout.MODE_SCROLLABLE } tabsVisibilitySubscription?.unsubscribe() - tabsVisibilitySubscription = tabsVisibilityRelay.subscribe { visible -> - val tabAnimator = (activity as? MainActivity)?.tabAnimator - if (visible) { - tabAnimator?.expand() - } else { - tabAnimator?.collapse() + tabsVisibilitySubscription = + tabsVisibilityRelay.subscribe { visible -> + val tabAnimator = (activity as? MainActivity)?.tabAnimator + if (visible) { + tabAnimator?.expand() + } else { + tabAnimator?.collapse() + } } - } } override fun cleanupTabs(tabs: TabLayout) { @@ -283,7 +291,10 @@ class LibraryController( tabsVisibilitySubscription = null } - fun onNextLibraryUpdate(categories: List, mangaMap: Map>) { + fun onNextLibraryUpdate( + categories: List, + mangaMap: Map> + ) { val view = view ?: return val adapter = adapter ?: return @@ -295,11 +306,12 @@ class LibraryController( } // Get the current active category. - val activeCat = if (adapter.categories.isNotEmpty()) { - binding.libraryPager.currentItem - } else { - activeCategory - } + val activeCat = + if (adapter.categories.isNotEmpty()) { + binding.libraryPager.currentItem + } else { + activeCategory + } // Set the categories adapter.categories = categories @@ -384,7 +396,10 @@ class LibraryController( actionMode?.finish() } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { inflater.inflate(R.menu.library, menu) val reorganizeItem = menu.findItem(R.id.action_reorganize) @@ -485,12 +500,18 @@ class LibraryController( actionMode?.invalidate() } - override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + override fun onCreateActionMode( + mode: ActionMode, + menu: Menu + ): Boolean { mode.menuInflater.inflate(R.menu.library_selection, menu) return true } - override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + override fun onPrepareActionMode( + mode: ActionMode, + menu: Menu + ): Boolean { val count = selectedMangas.size if (count == 0) { // Destroy action mode if there are no items selected. @@ -504,7 +525,10 @@ class LibraryController( return false } - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + override fun onActionItemClicked( + mode: ActionMode, + item: MenuItem + ): Boolean { return onActionItemClicked(item) } @@ -548,7 +572,10 @@ class LibraryController( * @param manga the manga whose selection has changed. * @param selected whether it's now selected or not. */ - fun setSelection(manga: Manga, selected: Boolean) { + fun setSelection( + manga: Manga, + selected: Boolean + ) { if (selected) { if (selectedMangas.add(manga)) { selectionRelay.call(LibrarySelectionEvent.Selected(manga)) @@ -601,9 +628,10 @@ class LibraryController( val categories = presenter.categories.filter { it.id != 0 } // Get indexes of the common categories to preselect. - val commonCategoriesIndexes = presenter.getCommonCategories(mangas) - .map { categories.indexOf(it) } - .toTypedArray() + val commonCategoriesIndexes = + presenter.getCommonCategories(mangas) + .map { categories.indexOf(it) } + .toTypedArray() ChangeMangaCategoriesDialog(this, mangas, categories, commonCategoriesIndexes) .showDialog(router) @@ -650,12 +678,18 @@ class LibraryController( destroyActionModeIfNeeded() } - override fun updateCategoriesForMangas(mangas: List, categories: List) { + override fun updateCategoriesForMangas( + mangas: List, + categories: List + ) { presenter.moveMangasToCategories(categories, mangas) destroyActionModeIfNeeded() } - override fun deleteMangasFromLibrary(mangas: List, deleteChapters: Boolean) { + override fun deleteMangasFromLibrary( + mangas: List, + deleteChapters: Boolean + ) { presenter.removeMangaFromLibrary(mangas, deleteChapters) destroyActionModeIfNeeded() } @@ -694,15 +728,17 @@ class LibraryController( releaseSyncLocks() } - private fun buildDialog() = activity?.let { - MaterialDialog(it) - } + private fun buildDialog() = + activity?.let { + MaterialDialog(it) + } private fun showSyncProgressDialog() { favSyncDialog?.dismiss() - favSyncDialog = buildDialog() - ?.title(text = "Favorites syncing") - ?.cancelable(false) + favSyncDialog = + buildDialog() + ?.title(text = "Favorites syncing") + ?.cancelable(false) // ?.progress(true, 0) favSyncDialog?.show() } @@ -727,47 +763,51 @@ class LibraryController( releaseSyncLocks() favSyncDialog?.dismiss() - favSyncDialog = buildDialog() - ?.title(text = "Favorites sync error") - ?.message(text = status.message + " Sync will not start until the gallery is in only one category.") - ?.cancelable(false) - ?.positiveButton(text = "Show gallery") { - openManga(status.manga) - presenter.favoritesSync.status.onNext(FavoritesSyncStatus.Idle()) - } - ?.negativeButton(android.R.string.ok) { - presenter.favoritesSync.status.onNext(FavoritesSyncStatus.Idle()) - } + favSyncDialog = + buildDialog() + ?.title(text = "Favorites sync error") + ?.message(text = status.message + " Sync will not start until the gallery is in only one category.") + ?.cancelable(false) + ?.positiveButton(text = "Show gallery") { + openManga(status.manga) + presenter.favoritesSync.status.onNext(FavoritesSyncStatus.Idle()) + } + ?.negativeButton(android.R.string.ok) { + presenter.favoritesSync.status.onNext(FavoritesSyncStatus.Idle()) + } favSyncDialog?.show() } is FavoritesSyncStatus.Error -> { releaseSyncLocks() favSyncDialog?.dismiss() - favSyncDialog = buildDialog() - ?.title(text = "Favorites sync error") - ?.message(text = "An error occurred during the sync process: ${status.message}") - ?.cancelable(false) - ?.positiveButton(android.R.string.ok) { - presenter.favoritesSync.status.onNext(FavoritesSyncStatus.Idle()) - } + favSyncDialog = + buildDialog() + ?.title(text = "Favorites sync error") + ?.message(text = "An error occurred during the sync process: ${status.message}") + ?.cancelable(false) + ?.positiveButton(android.R.string.ok) { + presenter.favoritesSync.status.onNext(FavoritesSyncStatus.Idle()) + } favSyncDialog?.show() } is FavoritesSyncStatus.CompleteWithErrors -> { releaseSyncLocks() favSyncDialog?.dismiss() - favSyncDialog = buildDialog() - ?.title(text = "Favorites sync complete with errors") - ?.message(text = "Errors occurred during the sync process that were ignored:\n${status.message}") - ?.cancelable(false) - ?.positiveButton(android.R.string.ok) { - presenter.favoritesSync.status.onNext(FavoritesSyncStatus.Idle()) - } + favSyncDialog = + buildDialog() + ?.title(text = "Favorites sync complete with errors") + ?.message(text = "Errors occurred during the sync process that were ignored:\n${status.message}") + ?.cancelable(false) + ?.positiveButton(android.R.string.ok) { + presenter.favoritesSync.status.onNext(FavoritesSyncStatus.Idle()) + } favSyncDialog?.show() } is FavoritesSyncStatus.Processing, - is FavoritesSyncStatus.Initializing -> { + is FavoritesSyncStatus.Initializing + -> { takeSyncLocks() if (favSyncDialog == null || ( @@ -785,7 +825,10 @@ class LibraryController( oldSyncStatus = status } - override fun startReading(manga: Manga, adapter: LibraryCategoryAdapter) { + override fun startReading( + manga: Manga, + adapter: LibraryCategoryAdapter + ) { if (adapter.mode == SelectableAdapter.Mode.MULTI) { toggleSelection(manga) return @@ -810,7 +853,11 @@ class LibraryController( } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + data: Intent? + ) { if (requestCode == REQUEST_IMAGE_OPEN) { val dataUri = data?.data if (dataUri == null || resultCode != Activity.RESULT_OK) return @@ -840,7 +887,10 @@ class LibraryController( } object HeightTopWindowInsetsListener : View.OnApplyWindowInsetsListener { - override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { + override fun onApplyWindowInsets( + v: View, + insets: WindowInsets + ): WindowInsets { val topInset = insets.systemWindowInsetTop v.setPadding(0, topInset, 0, 0) if (v.layoutParams.height != topInset) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt index 5b4a3197e39c..793d80ecabb9 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt @@ -33,7 +33,6 @@ open class LibraryGridHolder( private val view: View, adapter: FlexibleAdapter> ) : LibraryHolder(view, adapter) { - override val binding = SourceCompactGridItemBinding.bind(view) private val preferences: PreferencesHelper = Injekt.get() @@ -78,11 +77,12 @@ open class LibraryGridHolder( // set local visibility if its local manga binding.localText.visibleIf { item.manga.isLocal() } - binding.card.radius = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - preferences.eh_library_corner_radius().get().toFloat(), - view.context.resources.displayMetrics - ) + binding.card.radius = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + preferences.eh_library_corner_radius().get().toFloat(), + view.context.resources.displayMetrics + ) // SY --> binding.playLayout.isVisible = (item.manga.unread > 0 && item.startReadingButton) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt index 129ac583021d..c535faef5bfc 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt @@ -18,7 +18,6 @@ abstract class LibraryHolder( view: View, val adapter: FlexibleAdapter> ) : BaseFlexibleViewHolder(view, adapter) { - abstract val binding: VB /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index 7005ba254852..7aecdd561bfb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -31,7 +31,6 @@ import uy.kohesive.injekt.api.get class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Preference) : AbstractFlexibleItem>(), IFilterable> { - private val sourceManager: SourceManager = Injekt.get() private val trackManager: TrackManager = Injekt.get() private val db: DatabaseHelper = Injekt.get() @@ -55,7 +54,10 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe } } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LibraryHolder<*> { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): LibraryHolder<*> { return when (libraryDisplayMode.get()) { DisplayMode.COMPACT_GRID -> { val parent = adapter.recyclerView as AutofitRecyclerView @@ -63,9 +65,12 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe val binding = SourceCompactGridItemBinding.bind(view) view.apply { binding.card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight) - binding.gradient.layoutParams = FrameLayout.LayoutParams( - MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM - ) + binding.gradient.layoutParams = + FrameLayout.LayoutParams( + MATCH_PARENT, + coverHeight / 2, + Gravity.BOTTOM + ) } LibraryGridHolder(view, adapter) } @@ -74,9 +79,11 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe val coverHeight = parent.itemWidth / 3 * 4 val binding = SourceComfortableGridItemBinding.bind(view) view.apply { - binding.card.layoutParams = ConstraintLayout.LayoutParams( - MATCH_PARENT, coverHeight - ) + binding.card.layoutParams = + ConstraintLayout.LayoutParams( + MATCH_PARENT, + coverHeight + ) } LibraryComfortableGridHolder(view, adapter) } @@ -117,7 +124,10 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe constraint.second && ehContainsGenre(constraint.first) } - private fun filterTracks(constraint: String, tracks: List): Boolean { + private fun filterTracks( + constraint: String, + tracks: List + ): Boolean { return tracks.any { val trackService = trackManager.getService(it.sync_id) if (trackService != null) { @@ -131,20 +141,24 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe private fun ehContainsGenre(constraint: String): Boolean { val genres = manga.getGenres() - val raisedTags = if (source?.isNamespaceSource() == true) { - manga.getRaisedTags(genres) - } else null + val raisedTags = + if (source?.isNamespaceSource() == true) { + manga.getRaisedTags(genres) + } else { + null + } return if (constraint.contains(" ") || constraint.contains("\"")) { var cleanConstraint = "" var ignoreSpace = false for (i in constraint.trim().lowercase()) { when (i) { ' ' -> { - cleanConstraint = if (!ignoreSpace) { - "$cleanConstraint," - } else { - "$cleanConstraint " - } + cleanConstraint = + if (!ignoreSpace) { + "$cleanConstraint," + } else { + "$cleanConstraint " + } } '"' -> { ignoreSpace = !ignoreSpace @@ -155,9 +169,14 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe } } cleanConstraint.split(",").all { - if (raisedTags == null) containsGenre(it.trim(), genres) else containsRaisedGenre( - parseTag(it.trim()), raisedTags - ) + if (raisedTags == null) { + containsGenre(it.trim(), genres) + } else { + containsRaisedGenre( + parseTag(it.trim()), + raisedTags + ) + } } } else if (raisedTags == null) { containsGenre(constraint, genres) @@ -166,10 +185,14 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe } } - private fun containsRaisedGenre(tag: RaisedTag, genres: List): Boolean { - val genre = genres.find { - (it.namespace?.lowercase() == tag.namespace?.lowercase() && it.name.lowercase() == tag.name.lowercase()) - } + private fun containsRaisedGenre( + tag: RaisedTag, + genres: List + ): Boolean { + val genre = + genres.find { + (it.namespace?.lowercase() == tag.namespace?.lowercase() && it.name.lowercase() == tag.name.lowercase()) + } return if (tag.type == TAG_TYPE_EXCLUDE) { genre == null } else { @@ -178,7 +201,10 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe } // SY <-- - private fun containsGenre(tag: String, genres: List?): Boolean { + private fun containsGenre( + tag: String, + genres: List? + ): Boolean { return if (tag.startsWith("-")) { genres?.find { it.trim().lowercase() == tag.substringAfter("-").lowercase() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt index b43000052bd6..2c672018c36c 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt @@ -25,7 +25,6 @@ class LibraryListHolder( private val view: View, adapter: FlexibleAdapter> ) : LibraryHolder(view, adapter) { - override val binding = SourceListItemBinding.bind(view) /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt index e5dffe308646..74e07b3a29ed 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaEvent.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.library import eu.kanade.tachiyomi.data.database.models.Category class LibraryMangaEvent(val mangas: Map>) { - fun getMangaForCategory(category: Category): List? { return mangas[category.id] } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt index 170cfba1ffd5..d3a196ef1656 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt @@ -20,9 +20,10 @@ import uy.kohesive.injekt.injectLazy /** * The navigation view shown in a drawer with the different options to show the library. */ -class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class LibraryNavigationView +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : ExtendedNavigationView(context, attrs) { - /** * Preferences helper. */ @@ -61,7 +62,6 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A * Adapter of the recycler view. */ inner class Adapter(items: List) : ExtendedNavigationView.Adapter(items) { - override fun onItemClicked(item: Item) { if (item is GroupedItem) { item.group.onItemClicked(item) @@ -74,7 +74,6 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A * Filters group (unread, downloaded, ...). */ inner class FilterGroup : Group { - private val downloaded = Item.TriStateGroup(R.string.action_filter_downloaded, this) private val unread = Item.TriStateGroup(R.string.action_filter_unread, this) @@ -115,11 +114,12 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A override fun onItemClicked(item: Item) { // j2k changes item as Item.TriStateGroup - val newState = when (item.state) { - STATE_IGNORE -> STATE_INCLUDE - STATE_INCLUDE -> STATE_EXCLUDE - else -> STATE_IGNORE - } + val newState = + when (item.state) { + STATE_IGNORE -> STATE_INCLUDE + STATE_INCLUDE -> STATE_EXCLUDE + else -> STATE_IGNORE + } item.state = newState when (item) { downloaded -> preferences.filterDownloaded().set(item.state) @@ -138,7 +138,6 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A * Sorting group (alphabetically, by last read, ...) and ascending or descending. */ inner class SortGroup : Group { - private val alphabetically = Item.MultiSort(R.string.action_sort_alpha, this) private val total = Item.MultiSort(R.string.action_sort_total, this) @@ -165,11 +164,12 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A override fun initModels() { val sorting = preferences.librarySortingMode().get() - val order = if (preferences.librarySortingAscending().get()) { - SORT_ASC - } else { - SORT_DESC - } + val order = + if (preferences.librarySortingAscending().get()) { + SORT_ASC + } else { + SORT_DESC + } alphabetically.state = if (sorting == LibrarySort.ALPHA) order else SORT_NONE lastRead.state = if (sorting == LibrarySort.LAST_READ) order else SORT_NONE @@ -190,12 +190,13 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A if (item == dragAndDrop) { item.state = SORT_ASC } else { - item.state = when (prevState) { - SORT_NONE -> SORT_ASC - SORT_ASC -> SORT_DESC - SORT_DESC -> SORT_ASC - else -> throw Exception("Unknown state") - } + item.state = + when (prevState) { + SORT_NONE -> SORT_ASC + SORT_ASC -> SORT_DESC + SORT_DESC -> SORT_ASC + else -> throw Exception("Unknown state") + } } preferences.librarySortingMode().set( @@ -224,6 +225,7 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A override val header = null override val footer = null override val items = listOf(downloadBadge, unreadBadge) + override fun initModels() { downloadBadge.checked = preferences.downloadBadge().get() unreadBadge.checked = preferences.unreadBadge().get() @@ -269,7 +271,6 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A * Display group, to show the library as a list or a grid. */ inner class DisplayGroup : Group { - private val compactGrid = Item.Radio(R.string.action_display_grid, this) private val comfortableGrid = Item.Radio(R.string.action_display_comfortable_grid, this) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index b588aaf46350..acec08cec515 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -38,15 +38,15 @@ import exh.EH_SOURCE_ID import exh.EXH_SOURCE_ID import exh.favorites.FavoritesSyncHelper import exh.util.isLewd -import java.util.ArrayList -import java.util.Collections -import java.util.Comparator import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.ArrayList +import java.util.Collections +import java.util.Comparator /** * Class containing library information. @@ -68,7 +68,6 @@ class LibraryPresenter( private val sourceManager: SourceManager = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get() ) : BasePresenter() { - private val context = preferences.context /** @@ -111,20 +110,21 @@ class LibraryPresenter( */ fun subscribeLibrary() { if (librarySubscription.isNullOrUnsubscribed()) { - librarySubscription = getLibraryObservable() - .combineLatest(badgeTriggerRelay.observeOn(Schedulers.io())) { lib, _ -> - lib.apply { setBadges(mangaMap) } - } - .combineLatest(filterTriggerRelay.observeOn(Schedulers.io())) { lib, _ -> - lib.copy(mangaMap = applyFilters(lib.mangaMap)) - } - .combineLatest(sortTriggerRelay.observeOn(Schedulers.io())) { lib, _ -> - lib.copy(mangaMap = applySort(lib.mangaMap)) - } - .observeOn(AndroidSchedulers.mainThread()) - .subscribeLatestCache({ view, (categories, mangaMap) -> - view.onNextLibraryUpdate(categories, mangaMap) - }) + librarySubscription = + getLibraryObservable() + .combineLatest(badgeTriggerRelay.observeOn(Schedulers.io())) { lib, _ -> + lib.apply { setBadges(mangaMap) } + } + .combineLatest(filterTriggerRelay.observeOn(Schedulers.io())) { lib, _ -> + lib.copy(mangaMap = applyFilters(lib.mangaMap)) + } + .combineLatest(sortTriggerRelay.observeOn(Schedulers.io())) { lib, _ -> + lib.copy(mangaMap = applySort(lib.mangaMap)) + } + .observeOn(AndroidSchedulers.mainThread()) + .subscribeLatestCache({ view, (categories, mangaMap) -> + view.onNextLibraryUpdate(categories, mangaMap) + }) } } @@ -157,21 +157,28 @@ class LibraryPresenter( } if (filterTracked != STATE_IGNORE) { val tracks = db.getTracks(item.manga).executeAsBlocking() - if (filterTracked == STATE_INCLUDE && tracks.isEmpty()) return@f false - else if (filterTracked == STATE_EXCLUDE && tracks.isNotEmpty()) return@f false + if (filterTracked == STATE_INCLUDE && tracks.isEmpty()) { + return@f false + } else if (filterTracked == STATE_EXCLUDE && tracks.isNotEmpty()) { + return@f false + } } if (filterLewd != STATE_IGNORE) { val isLewd = item.manga.isLewd() - if (filterLewd == STATE_INCLUDE && !isLewd) return@f false - else if (filterLewd == STATE_EXCLUDE && isLewd) return@f false + if (filterLewd == STATE_INCLUDE && !isLewd) { + return@f false + } else if (filterLewd == STATE_EXCLUDE && isLewd) { + return@f false + } } // Filter when there are no downloads. if (filterDownloaded != STATE_IGNORE || filterDownloadedOnly) { - val isDownloaded = when { - item.manga.isLocal() -> true - item.downloadCount != -1 -> item.downloadCount > 0 - else -> downloadManager.getDownloadCount(item.manga) > 0 - } + val isDownloaded = + when { + item.manga.isLocal() -> true + item.downloadCount != -1 -> item.downloadCount > 0 + else -> downloadManager.getDownloadCount(item.manga) > 0 + } return@f if (filterDownloaded == STATE_INCLUDE) isDownloaded else !isDownloaded } true @@ -192,19 +199,21 @@ class LibraryPresenter( for ((_, itemList) in map) { for (item in itemList) { - item.downloadCount = if (showDownloadBadges) { - downloadManager.getDownloadCount(item.manga) - } else { - // Unset download count if not enabled - -1 - } + item.downloadCount = + if (showDownloadBadges) { + downloadManager.getDownloadCount(item.manga) + } else { + // Unset download count if not enabled + -1 + } - item.unreadCount = if (showUnreadBadges) { - item.manga.unread - } else { - // Unset unread count if not enabled - -1 - } + item.unreadCount = + if (showUnreadBadges) { + item.manga.unread + } else { + // Unset unread count if not enabled + -1 + } // SY --> item.startReadingButton = startReadingButton @@ -251,10 +260,12 @@ class LibraryPresenter( manga1TotalChapter.compareTo(mange2TotalChapter) } LibrarySort.LATEST_CHAPTER -> { - val manga1latestChapter = latestChapterManga[i1.manga.id!!] - ?: latestChapterManga.size - val manga2latestChapter = latestChapterManga[i2.manga.id!!] - ?: latestChapterManga.size + val manga1latestChapter = + latestChapterManga[i1.manga.id!!] + ?: latestChapterManga.size + val manga2latestChapter = + latestChapterManga[i2.manga.id!!] + ?: latestChapterManga.size manga1latestChapter.compareTo(manga2latestChapter) } LibrarySort.DATE_ADDED -> i2.manga.date_added.compareTo(i1.manga.date_added) @@ -270,16 +281,20 @@ class LibraryPresenter( } } - val comparator = if (preferences.librarySortingAscending().get()) { - Comparator(sortFn) - } else { - Collections.reverseOrder(sortFn) - } + val comparator = + if (preferences.librarySortingAscending().get()) { + Comparator(sortFn) + } else { + Collections.reverseOrder(sortFn) + } return map.mapValues { entry -> entry.value.sortedWith(comparator) } } - private fun sortAlphabetical(i1: LibraryItem, i2: LibraryItem): Int { + private fun sortAlphabetical( + i1: LibraryItem, + i2: LibraryItem + ): Int { // return if (preferences.removeArticles().getOrDefault()) return i1.manga.title.removeArticles().compareTo(i2.manga.title.removeArticles(), true) // else i1.manga.title.compareTo(i2.manga.title, true) @@ -292,11 +307,12 @@ class LibraryPresenter( */ private fun getLibraryObservable(): Observable { return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable()) { dbCategories, libraryManga -> - val categories = if (libraryManga.containsKey(0)) { - arrayListOf(Category.createDefault()) + dbCategories - } else { - dbCategories - } + val categories = + if (libraryManga.containsKey(0)) { + arrayListOf(Category.createDefault()) + dbCategories + } else { + dbCategories + } this.categories = categories Library(categories, libraryManga) @@ -375,8 +391,9 @@ class LibraryPresenter( fun downloadUnreadChapters(mangas: List) { mangas.forEach { manga -> launchIO { - val chapters = db.getChapters(manga).executeAsBlocking() - .filter { !it.read } + val chapters = + db.getChapters(manga).executeAsBlocking() + .filter { !it.read } downloadManager.downloadChapters(manga, chapters) } @@ -388,7 +405,10 @@ class LibraryPresenter( * * @param mangas the list of manga. */ - fun markReadStatus(mangas: List, read: Boolean) { + fun markReadStatus( + mangas: List, + read: Boolean + ) { mangas.forEach { manga -> launchIO { val chapters = db.getChapters(manga).executeAsBlocking() @@ -407,7 +427,10 @@ class LibraryPresenter( } } - private fun deleteChapters(manga: Manga, chapters: List) { + private fun deleteChapters( + manga: Manga, + chapters: List + ) { sourceManager.get(manga.source)?.let { source -> downloadManager.deleteChapters(chapters, manga, source) } @@ -419,7 +442,10 @@ class LibraryPresenter( * @param mangas the list of manga to delete. * @param deleteChapters whether to also delete downloaded chapters. */ - fun removeMangaFromLibrary(mangas: List, deleteChapters: Boolean) { + fun removeMangaFromLibrary( + mangas: List, + deleteChapters: Boolean + ) { launchIO { val mangaToDelete = mangas.distinctBy { it.id } @@ -446,7 +472,10 @@ class LibraryPresenter( * @param categories the selected categories. * @param mangas the list of manga to move. */ - fun moveMangasToCategories(categories: List, mangas: List) { + fun moveMangasToCategories( + categories: List, + mangas: List + ) { val mc = ArrayList() for (manga in mangas) { @@ -458,7 +487,11 @@ class LibraryPresenter( db.setMangaCategories(mc, mangas) } - fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) { + fun migrateManga( + prevManga: Manga, + manga: Manga, + replace: Boolean + ) { val source = sourceManager.get(manga.source) ?: return // state = state.copy(isReplacingManga = true) @@ -542,7 +575,11 @@ class LibraryPresenter( * @param context Context. * @param data uri of the cover resource. */ - fun editCover(manga: Manga, context: Context, data: Uri) { + fun editCover( + manga: Manga, + context: Context, + data: Uri + ) { Observable .fromCallable { context.contentResolver.openInputStream(data)?.use { @@ -577,6 +614,7 @@ class LibraryPresenter( ) } // SY --> + /** Returns first unread chapter of a manga */ fun getFirstUnread(manga: Manga): Chapter? { val chapters = db.getChapters(manga).executeAsBlocking() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt index 58f89a4aa3c0..1793a903647b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySelectionEvent.kt @@ -3,8 +3,9 @@ package eu.kanade.tachiyomi.ui.library import eu.kanade.tachiyomi.data.database.models.Manga sealed class LibrarySelectionEvent { - class Selected(val manga: Manga) : LibrarySelectionEvent() + class Unselected(val manga: Manga) : LibrarySelectionEvent() + class Cleared : LibrarySelectionEvent() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt index fa8b8d263269..57122afd4984 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.ui.library object LibrarySort { - const val ALPHA = 0 const val LAST_READ = 1 const val LAST_CHECKED = 2 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/ChangelogDialogController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/ChangelogDialogController.kt index 900ca4f54141..a06928c962a0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/ChangelogDialogController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/ChangelogDialogController.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController import it.gmariotti.changelibs.library.view.ChangeLogRecyclerView class ChangelogDialogController : DialogController() { - override fun onCreateDialog(savedViewState: Bundle?): Dialog { val activity = activity!! val view = WhatsNewRecyclerView(activity) @@ -23,7 +22,10 @@ class ChangelogDialogController : DialogController() { } class WhatsNewRecyclerView(context: Context) : ChangeLogRecyclerView(context) { - override fun initAttrs(attrs: AttributeSet?, defStyle: Int) { + override fun initAttrs( + attrs: AttributeSet?, + defStyle: Int + ) { mRowLayoutId = R.layout.changelog_row_layout mRowHeaderLayoutId = R.layout.changelog_header_layout mChangeLogFileResourceId = if (BuildConfig.DEBUG) R.raw.changelog_debug else R.raw.changelog_release diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/DeepLinkActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/DeepLinkActivity.kt index b1195c99de3b..5e55eb1df7ff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/DeepLinkActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/DeepLinkActivity.kt @@ -5,7 +5,6 @@ import android.content.Intent import android.os.Bundle class DeepLinkActivity : Activity() { - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/ForceCloseActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/ForceCloseActivity.kt index efcae017a912..e0e4a1392b43 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/ForceCloseActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/ForceCloseActivity.kt @@ -18,10 +18,11 @@ class ForceCloseActivity : AppCompatActivity() { companion object { fun closeApp(context: Context) { - val intent = Intent(context, ForceCloseActivity::class.java).apply { - addCategory(Intent.CATEGORY_HOME) - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - } + val intent = + Intent(context, ForceCloseActivity::class.java).apply { + addCategory(Intent.CATEGORY_HOME) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + } context.startActivity(intent) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 54676902b5da..400edceb96ac 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -58,16 +58,15 @@ import exh.ui.batchadd.BatchAddController import exh.ui.lock.LockActivityDelegate import exh.ui.lock.LockController import exh.ui.lock.lockEnabled -import java.util.Date -import java.util.LinkedList -import java.util.concurrent.TimeUnit import kotlinx.coroutines.delay import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import timber.log.Timber +import java.util.Date +import java.util.LinkedList +import java.util.concurrent.TimeUnit class MainActivity : BaseActivity() { - private lateinit var router: Router private var drawerArrow: DrawerArrowDrawable? = null @@ -166,7 +165,7 @@ class MainActivity : BaseActivity() { R.id.nav_drawer_extensions -> setRoot(ExtensionController(), id) // --> EXH R.id.nav_drawer_batch_add -> setRoot(BatchAddController(), id) - // <-- EHX + // <-- EHX R.id.nav_drawer_downloads -> { router.pushController(DownloadController().withFadeTransaction()) } @@ -197,26 +196,28 @@ class MainActivity : BaseActivity() { } } - router.addChangeListener(object : ControllerChangeHandler.ControllerChangeListener { - override fun onChangeStarted( - to: Controller?, - from: Controller?, - isPush: Boolean, - container: ViewGroup, - handler: ControllerChangeHandler - ) { - syncActivityViewWithController(to, from) - } + router.addChangeListener( + object : ControllerChangeHandler.ControllerChangeListener { + override fun onChangeStarted( + to: Controller?, + from: Controller?, + isPush: Boolean, + container: ViewGroup, + handler: ControllerChangeHandler + ) { + syncActivityViewWithController(to, from) + } - override fun onChangeCompleted( - to: Controller?, - from: Controller?, - isPush: Boolean, - container: ViewGroup, - handler: ControllerChangeHandler - ) { + override fun onChangeCompleted( + to: Controller?, + from: Controller?, + isPush: Boolean, + container: ViewGroup, + handler: ControllerChangeHandler + ) { + } } - }) + ) // --> EH initWhenIdle { @@ -226,7 +227,9 @@ class MainActivity : BaseActivity() { LockActivityDelegate.doLock(router, true) vibrate(50) true - } else false + } else { + false + } } } @@ -292,9 +295,10 @@ class MainActivity : BaseActivity() { } private fun setExtensionsBadge() { - val extUpdateText: TextView = binding.navView.menu.findItem( - R.id.nav_drawer_extensions - )?.actionView as? TextView ?: return + val extUpdateText: TextView = + binding.navView.menu.findItem( + R.id.nav_drawer_extensions + )?.actionView as? TextView ?: return val updates = preferences.extensionUpdatesCount().get() if (updates > 0) { @@ -427,7 +431,10 @@ class MainActivity : BaseActivity() { } } - private fun setRoot(controller: Controller, id: Int) { + private fun setRoot( + controller: Controller, + id: Int + ) { router.setRoot(controller.withFadeTransaction().tag(id.toString())) } @@ -457,7 +464,10 @@ class MainActivity : BaseActivity() { } } - private fun syncActivityViewWithController(to: Controller?, from: Controller? = null) { + private fun syncActivityViewWithController( + to: Controller?, + from: Controller? = null + ) { if (from is DialogController || to is DialogController) { return } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/TabsAnimator.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/TabsAnimator.kt index 78f3f37086fa..052d498b6486 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/TabsAnimator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/TabsAnimator.kt @@ -6,7 +6,6 @@ import android.view.animation.DecelerateInterpolator import com.google.android.material.tabs.TabLayout class TabsAnimator(val tabs: TabLayout) { - /** * The default height of the tab layout. It's unknown until the view is layout. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index 8780b03ca2c5..c56aac12256d 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -32,13 +32,12 @@ import eu.kanade.tachiyomi.ui.manga.info.MangaInfoController import eu.kanade.tachiyomi.ui.manga.track.TrackController import eu.kanade.tachiyomi.ui.source.SourceController import eu.kanade.tachiyomi.util.system.toast -import java.util.Date import rx.Subscription import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.Date class MangaController : RxController, TabbedController { - constructor( manga: Manga?, fromSource: Boolean = false, @@ -109,7 +108,10 @@ class MangaController : RxController, TabbedController { return manga?.title } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = MangaControllerBinding.inflate(inflater) return binding.root } @@ -135,7 +137,10 @@ class MangaController : RxController, TabbedController { adapter = null } - override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { + override fun onChangeStarted( + handler: ControllerChangeHandler, + type: ControllerChangeType + ) { super.onChangeStarted(handler, type) if (type.isEnter) { val activity = activity as MainActivity? @@ -144,7 +149,10 @@ class MangaController : RxController, TabbedController { } } - override fun onChangeEnded(handler: ControllerChangeHandler, type: ControllerChangeType) { + override fun onChangeEnded( + handler: ControllerChangeHandler, + type: ControllerChangeType + ) { super.onChangeEnded(handler, type) if (manga == null || source == null) { activity?.toast(R.string.manga_not_in_db) @@ -172,38 +180,47 @@ class MangaController : RxController, TabbedController { val activity = activity as MainActivity? val tab = activity?.binding?.tabs?.getTabAt(TRACK_CONTROLLER) ?: return - val drawable = if (visible) { - VectorDrawableCompat.create(resources!!, R.drawable.ic_done_white_18dp, null) - } else { - null - } + val drawable = + if (visible) { + VectorDrawableCompat.create(resources!!, R.drawable.ic_done_white_18dp, null) + } else { + null + } tab.icon = drawable } private inner class MangaDetailAdapter : RouterPagerAdapter(this@MangaController) { - private val tabCount = if (Injekt.get().hasLoggedServices()) 3 else 2 - private val tabTitles = listOf( - R.string.manga_detail_tab, - if (source is AnimeSource) { R.string.episodes_tab_title } else { R.string.manga_chapters_tab }, - R.string.manga_tracking_tab - ) - .map { resources!!.getString(it) } + private val tabTitles = + listOf( + R.string.manga_detail_tab, + if (source is AnimeSource) { + R.string.episodes_tab_title + } else { + R.string.manga_chapters_tab + }, + R.string.manga_tracking_tab + ) + .map { resources!!.getString(it) } override fun getCount(): Int { return tabCount } - override fun configureRouter(router: Router, position: Int) { + override fun configureRouter( + router: Router, + position: Int + ) { if (!router.hasRootController()) { - val controller = when (position) { - INFO_CONTROLLER -> MangaInfoController(fromSource) - CHAPTERS_CONTROLLER -> ChaptersController() - TRACK_CONTROLLER -> TrackController() - else -> error("Wrong position $position") - } + val controller = + when (position) { + INFO_CONTROLLER -> MangaInfoController(fromSource) + CHAPTERS_CONTROLLER -> ChaptersController() + TRACK_CONTROLLER -> TrackController() + else -> error("Wrong position $position") + } router.setRoot(RouterTransaction.with(controller)) } } @@ -214,7 +231,6 @@ class MangaController : RxController, TabbedController { } companion object { - const val UPDATE_EXTRA = "update" const val SMART_SEARCH_CONFIG_EXTRA = "smartSearchConfig" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt index a2fb679911dd..e0aabafa987b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt @@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.databinding.ChaptersItemBinding import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder -import java.util.Date import uy.kohesive.injekt.injectLazy +import java.util.Date class ChapterHolder( private val view: View, @@ -22,6 +22,7 @@ class ChapterHolder( private val prefs: PreferencesHelper by injectLazy() val binding = ChaptersItemBinding.bind(view) + init { // We need to post a Runnable to show the popup to make sure that the PopupMenu is // correctly positioned. The reason being that the view may change position before the @@ -29,16 +30,20 @@ class ChapterHolder( binding.chapterMenu.setOnClickListener { it.post { showPopupMenu(it) } } } - fun bind(item: ChapterItem, manga: Manga) { + fun bind( + item: ChapterItem, + manga: Manga + ) { val chapter = item.chapter - binding.chapterTitle.text = when (manga.displayMode) { - Manga.DISPLAY_NUMBER -> { - val number = adapter.decimalFormat.format(chapter.chapter_number.toDouble()) - itemView.context.getString(R.string.display_mode_chapter, number) + binding.chapterTitle.text = + when (manga.displayMode) { + Manga.DISPLAY_NUMBER -> { + val number = adapter.decimalFormat.format(chapter.chapter_number.toDouble()) + itemView.context.getString(R.string.display_mode_chapter, number) + } + else -> chapter.name } - else -> chapter.name - } // Set correct text color val chapterColor = if (chapter.read) adapter.readColor else adapter.unreadColor @@ -55,9 +60,10 @@ class ChapterHolder( } if ((!chapter.read /* --> EH */ || prefs.eh_preserveReadingPosition().getOrDefault()) /* <-- EH */ && chapter.last_page_read > 0) { - val lastPageRead = SpannableString(itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1)).apply { - setSpan(ForegroundColorSpan(adapter.readColor), 0, length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE) - } + val lastPageRead = + SpannableString(itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1)).apply { + setSpan(ForegroundColorSpan(adapter.readColor), 0, length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE) + } descriptions.add(lastPageRead) } if (!chapter.scanlator.isNullOrBlank()) { @@ -73,15 +79,16 @@ class ChapterHolder( notifyStatus(item.status) } - fun notifyStatus(status: Int) = with(binding.downloadText) { - when (status) { - Download.QUEUE -> setText(R.string.chapter_queued) - Download.DOWNLOADING -> setText(R.string.chapter_downloading) - Download.DOWNLOADED -> setText(R.string.chapter_downloaded) - Download.ERROR -> setText(R.string.chapter_error) - else -> text = "" + fun notifyStatus(status: Int) = + with(binding.downloadText) { + when (status) { + Download.QUEUE -> setText(R.string.chapter_queued) + Download.DOWNLOADING -> setText(R.string.chapter_downloading) + Download.DOWNLOADED -> setText(R.string.chapter_downloaded) + Download.ERROR -> setText(R.string.chapter_error) + else -> text = "" + } } - } private fun showPopupMenu(view: View) { val item = adapter.getItem(adapterPosition) ?: return @@ -106,9 +113,10 @@ class ChapterHolder( // Hide mark as unread when the chapter is unread if (!chapter.read && ( - chapter.last_page_read == 0 /* --> EH */ || prefs.eh_preserveReadingPosition() - .getOrDefault() - ) /* <-- EH */ + chapter.last_page_read == 0 /* --> EH */ || + prefs.eh_preserveReadingPosition() + .getOrDefault() + ) // <-- EH ) { popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt index 11caa35eb91c..28e7ac958610 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.data.download.model.Download class ChapterItem(val chapter: Chapter, val manga: Manga) : AbstractFlexibleItem(), Chapter by chapter { - private var _status: Int = 0 var status: Int @@ -31,7 +30,10 @@ class ChapterItem(val chapter: Chapter, val manga: Manga) : return R.layout.chapters_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ChapterHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): ChapterHolder { return ChapterHolder(view, adapter as ChaptersAdapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt index 708fea0e633d..43d52a9e047f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt @@ -6,16 +6,15 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.util.system.getResourceColor +import uy.kohesive.injekt.injectLazy import java.text.DateFormat import java.text.DecimalFormat import java.text.DecimalFormatSymbols -import uy.kohesive.injekt.injectLazy class ChaptersAdapter( controller: ChaptersController, context: Context ) : FlexibleAdapter(null, controller, true) { - val preferences: PreferencesHelper by injectLazy() var items: List = emptyList() @@ -26,11 +25,12 @@ class ChaptersAdapter( val bookmarkedColor = context.getResourceColor(R.attr.colorAccent) - val decimalFormat = DecimalFormat( - "#.###", - DecimalFormatSymbols() - .apply { decimalSeparator = '.' } - ) + val decimalFormat = + DecimalFormat( + "#.###", + DecimalFormatSymbols() + .apply { decimalSeparator = '.' } + ) val dateFormat: DateFormat = preferences.dateFormat() @@ -44,6 +44,9 @@ class ChaptersAdapter( } interface OnMenuItemClickListener { - fun onMenuItemClick(position: Int, item: MenuItem) + fun onMenuItemClick( + position: Int, + item: MenuItem + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt index b97a4b32264d..7bb6d95f0fab 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt @@ -54,7 +54,6 @@ class ChaptersController : ChaptersAdapter.OnMenuItemClickListener, DownloadCustomChaptersDialog.Listener, DeleteChaptersDialog.Listener { - private val sourceManager: SourceManager by injectLazy() /** @@ -82,12 +81,18 @@ class ChaptersController : override fun createPresenter(): ChaptersPresenter { val ctrl = parentController as MangaController return ChaptersPresenter( - ctrl.manga!!, ctrl.source!!, - ctrl.chapterCountRelay, ctrl.lastUpdateRelay, ctrl.mangaFavoriteRelay + ctrl.manga!!, + ctrl.source!!, + ctrl.chapterCountRelay, + ctrl.lastUpdateRelay, + ctrl.mangaFavoriteRelay ) } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = ChaptersControllerBinding.inflate(inflater) return binding.root } @@ -116,11 +121,12 @@ class ChaptersController : val item = presenter.getNextUnreadChapter() if (item != null) { // Create animation listener - val revealAnimationListener: Animator.AnimatorListener = object : AnimatorListenerAdapter() { - override fun onAnimationStart(animation: Animator) { - openChapter(item.chapter, true) + val revealAnimationListener: Animator.AnimatorListener = + object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator) { + openChapter(item.chapter, true) + } } - } // Get coordinates and start animation val coordinates = binding.fab.getCoordinates() @@ -136,7 +142,12 @@ class ChaptersController : presenter.redirectUserRelay .observeOn(AndroidSchedulers.mainThread()) .subscribeUntilDestroy { redirect -> - XLog.d("Redirecting to updated manga (manga.id: %s, manga.title: %s, update: %s)!", redirect.manga.id, redirect.manga.title, redirect.update) + XLog.d( + "Redirecting to updated manga (manga.id: %s, manga.title: %s, update: %s)!", + redirect.manga.id, + redirect.manga.title, + redirect.update + ) // Replace self parentController?.router?.replaceTopController(RouterTransaction.with(MangaController(redirect))) } @@ -160,7 +171,10 @@ class ChaptersController : super.onActivityResumed(activity) } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { inflater.inflate(R.menu.chapters, menu) } @@ -206,12 +220,13 @@ class ChaptersController : } // Sorting mode submenu - val sortingItem = when (presenter.manga.sorting) { - Manga.SORTING_SOURCE -> R.id.sort_by_source - Manga.SORTING_NUMBER -> R.id.sort_by_number - Manga.SORTING_UPLOAD_DATE -> R.id.sort_by_upload_date - else -> throw NotImplementedError("Unimplemented sorting method") - } + val sortingItem = + when (presenter.manga.sorting) { + Manga.SORTING_SOURCE -> R.id.sort_by_source + Manga.SORTING_NUMBER -> R.id.sort_by_number + Manga.SORTING_UPLOAD_DATE -> R.id.sort_by_upload_date + else -> throw NotImplementedError("Unimplemented sorting method") + } menu.findItem(sortingItem).isChecked = true } @@ -338,21 +353,28 @@ class ChaptersController : return binding.recycler.findViewHolderForItemId(chapter.id!!) as? ChapterHolder } - fun openChapter(chapter: Chapter, hasAnimation: Boolean = false) { + fun openChapter( + chapter: Chapter, + hasAnimation: Boolean = false + ) { val activity = activity ?: return - val intent = if (sourceManager.getOrStub(presenter.manga.source) is AnimeSource) { - VideoActivity.newIntent(activity, presenter.manga, chapter) - } else { - ReaderActivity.newIntent(activity, presenter.manga, chapter) - } + val intent = + if (sourceManager.getOrStub(presenter.manga.source) is AnimeSource) { + VideoActivity.newIntent(activity, presenter.manga, chapter) + } else { + ReaderActivity.newIntent(activity, presenter.manga, chapter) + } if (hasAnimation) { intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) } startActivity(intent) } - override fun onItemClick(view: View, position: Int): Boolean { + override fun onItemClick( + view: View, + position: Int + ): Boolean { val adapter = adapter ?: return false val item = adapter.getItem(position) ?: return false return if (actionMode != null && adapter.mode == SelectableAdapter.Mode.MULTI) { @@ -422,14 +444,20 @@ class ChaptersController : actionMode?.finish() } - override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + override fun onCreateActionMode( + mode: ActionMode, + menu: Menu + ): Boolean { mode.menuInflater.inflate(R.menu.chapter_selection, menu) adapter?.mode = SelectableAdapter.Mode.MULTI return true } @SuppressLint("StringFormatInvalid") - override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + override fun onPrepareActionMode( + mode: ActionMode, + menu: Menu + ): Boolean { val count = adapter?.selectedItemCount ?: 0 if (count == 0) { // Destroy action mode if there are no items selected. @@ -451,7 +479,10 @@ class ChaptersController : return false } - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + override fun onActionItemClicked( + mode: ActionMode, + item: MenuItem + ): Boolean { return onActionItemClicked(item) } @@ -479,7 +510,10 @@ class ChaptersController : actionMode = null } - override fun onMenuItemClick(position: Int, item: MenuItem) { + override fun onMenuItemClick( + position: Int, + item: MenuItem + ) { val chapter = adapter?.getItem(position) ?: return val chapters = listOf(chapter) @@ -554,7 +588,10 @@ class ChaptersController : destroyActionModeIfNeeded() } - private fun bookmarkChapters(chapters: List, bookmarked: Boolean) { + private fun bookmarkChapters( + chapters: List, + bookmarked: Boolean + ) { destroyActionModeIfNeeded() presenter.bookmarkChapters(chapters, bookmarked) destroyActionModeIfNeeded() @@ -587,24 +624,26 @@ class ChaptersController : adapter?.notifyDataSetChanged() } - private fun getUnreadChaptersSorted() = presenter.chapters - .filter { !it.read && it.status == Download.NOT_DOWNLOADED } - .distinctBy { it.name } - .sortedByDescending { it.source_order } + private fun getUnreadChaptersSorted() = + presenter.chapters + .filter { !it.read && it.status == Download.NOT_DOWNLOADED } + .distinctBy { it.name } + .sortedByDescending { it.source_order } private fun downloadChapters(choice: Int) { - val chaptersToDownload = when (choice) { - R.id.download_next -> getUnreadChaptersSorted().take(1) - R.id.download_next_5 -> getUnreadChaptersSorted().take(5) - R.id.download_next_10 -> getUnreadChaptersSorted().take(10) - R.id.download_custom -> { - showCustomDownloadDialog() - return + val chaptersToDownload = + when (choice) { + R.id.download_next -> getUnreadChaptersSorted().take(1) + R.id.download_next_5 -> getUnreadChaptersSorted().take(5) + R.id.download_next_10 -> getUnreadChaptersSorted().take(10) + R.id.download_custom -> { + showCustomDownloadDialog() + return + } + R.id.download_unread -> presenter.chapters.filter { !it.read } + R.id.download_all -> presenter.chapters + else -> emptyList() } - R.id.download_unread -> presenter.chapters.filter { !it.read } - R.id.download_all -> presenter.chapters - else -> emptyList() - } if (chaptersToDownload.isNotEmpty()) { downloadChapters(chaptersToDownload) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt index ba839671891a..905a4fe82502 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt @@ -24,7 +24,6 @@ import exh.EH_SOURCE_ID import exh.EXH_SOURCE_ID import exh.debug.DebugToggles import exh.eh.EHentaiUpdateHelper -import java.util.Date import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers @@ -33,6 +32,7 @@ import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.util.Date /** * Presenter of [ChaptersController]. @@ -47,7 +47,6 @@ class ChaptersPresenter( private val db: DatabaseHelper = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get() ) : BasePresenter() { - /** * List of chapters of the manga. It's always unfiltered and unsorted. */ @@ -58,7 +57,7 @@ class ChaptersPresenter( * Subject of list of chapters to allow updating the view without going to DB. */ val chaptersRelay: PublishRelay> - by lazy { PublishRelay.create>() } + by lazy { PublishRelay.create>() } /** * Whether the chapter list has been requested to the source. @@ -154,13 +153,14 @@ class ChaptersPresenter( private fun observeDownloads() { observeDownloadsSubscription?.let { remove(it) } - observeDownloadsSubscription = downloadManager.queue.getStatusObservable() - .observeOn(AndroidSchedulers.mainThread()) - .filter { download -> download.manga.id == manga.id } - .doOnNext { onDownloadStatusChange(it) } - .subscribeLatestCache(ChaptersController::onChapterStatusChange) { _, error -> - Timber.e(error) - } + observeDownloadsSubscription = + downloadManager.queue.getStatusObservable() + .observeOn(AndroidSchedulers.mainThread()) + .filter { download -> download.manga.id == manga.id } + .doOnNext { onDownloadStatusChange(it) } + .subscribeLatestCache(ChaptersController::onChapterStatusChange) { _, error -> + Timber.e(error) + } } /** @@ -200,26 +200,27 @@ class ChaptersPresenter( hasRequested = true if (!fetchChaptersSubscription.isNullOrUnsubscribed()) return - fetchChaptersSubscription = Observable.defer { - runAsObservable({ - source.getChapterList(manga.toMangaInfo()) - .map { it.toSChapter() } - }) - } - .subscribeOn(Schedulers.io()) - .map { syncChaptersWithSource(db, it, manga, source) } - .doOnNext { - if (manualFetch) { - downloadNewChapters(it.first) - } + fetchChaptersSubscription = + Observable.defer { + runAsObservable({ + source.getChapterList(manga.toMangaInfo()) + .map { it.toSChapter() } + }) } - .observeOn(AndroidSchedulers.mainThread()) - .subscribeFirst( - { view, _ -> - view.onFetchChaptersDone() - }, - ChaptersController::onFetchChaptersError - ) + .subscribeOn(Schedulers.io()) + .map { syncChaptersWithSource(db, it, manga, source) } + .doOnNext { + if (manualFetch) { + downloadNewChapters(it.first) + } + } + .observeOn(AndroidSchedulers.mainThread()) + .subscribeFirst( + { view, _ -> + view.onFetchChaptersDone() + }, + ChaptersController::onFetchChaptersError + ) } /** @@ -247,21 +248,25 @@ class ChaptersPresenter( if (onlyBookmarked()) { observable = observable.filter { it.bookmark } } - val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) { - Manga.SORTING_SOURCE -> when (sortDescending()) { - true -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) } - false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) } - } - Manga.SORTING_NUMBER -> when (sortDescending()) { - true -> { c1, c2 -> c2.chapter_number.compareTo(c1.chapter_number) } - false -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) } - } - Manga.SORTING_UPLOAD_DATE -> when (sortDescending()) { - true -> { c1, c2 -> c2.date_upload.compareTo(c1.date_upload) } - false -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) } + val sortFunction: (Chapter, Chapter) -> Int = + when (manga.sorting) { + Manga.SORTING_SOURCE -> + when (sortDescending()) { + true -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) } + false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) } + } + Manga.SORTING_NUMBER -> + when (sortDescending()) { + true -> { c1, c2 -> c2.chapter_number.compareTo(c1.chapter_number) } + false -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) } + } + Manga.SORTING_UPLOAD_DATE -> + when (sortDescending()) { + true -> { c1, c2 -> c2.date_upload.compareTo(c1.date_upload) } + false -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) } + } + else -> throw NotImplementedError("Unimplemented sorting method") } - else -> throw NotImplementedError("Unimplemented sorting method") - } return observable.toSortedList(sortFunction) } @@ -297,17 +302,22 @@ class ChaptersPresenter( * @param selectedChapters the list of selected chapters. * @param read whether to mark chapters as read or unread. */ - fun markChaptersRead(selectedChapters: List, read: Boolean) { - val chapters = selectedChapters.map { chapter -> - chapter.read = read - if (!read /* --> EH */ && !preferences - .eh_preserveReadingPosition() - .getOrDefault() /* <-- EH */ - ) { - chapter.last_page_read = 0 + fun markChaptersRead( + selectedChapters: List, + read: Boolean + ) { + val chapters = + selectedChapters.map { chapter -> + chapter.read = read + if (!read /* --> EH */ && + !preferences + .eh_preserveReadingPosition() + .getOrDefault() // <-- EH + ) { + chapter.last_page_read = 0 + } + chapter } - chapter - } launchIO { db.updateChaptersProgress(chapters).executeAsBlocking() @@ -330,7 +340,10 @@ class ChaptersPresenter( * Bookmarks the given list of chapters. * @param selectedChapters the list of chapters to bookmark. */ - fun bookmarkChapters(selectedChapters: List, bookmarked: Boolean) { + fun bookmarkChapters( + selectedChapters: List, + bookmarked: Boolean + ) { Observable.from(selectedChapters) .doOnNext { chapter -> chapter.bookmark = bookmarked diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DeleteChaptersDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DeleteChaptersDialog.kt index 5c0f95271f5a..ee48a5ec8ed0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DeleteChaptersDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DeleteChaptersDialog.kt @@ -8,8 +8,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.controller.DialogController class DeleteChaptersDialog(bundle: Bundle? = null) : DialogController(bundle) - where T : Controller, T : DeleteChaptersDialog.Listener { - + where T : Controller, T : DeleteChaptersDialog.Listener { constructor(target: T) : this() { targetController = target } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DownloadCustomChaptersDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DownloadCustomChaptersDialog.kt index 37b00d3a3126..c2ef13ae8e7d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DownloadCustomChaptersDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DownloadCustomChaptersDialog.kt @@ -13,8 +13,7 @@ import eu.kanade.tachiyomi.widget.DialogCustomDownloadView * Dialog used to let user select amount of chapters to download. */ class DownloadCustomChaptersDialog : DialogController - where T : Controller, T : DownloadCustomChaptersDialog.Listener { - + where T : Controller, T : DownloadCustomChaptersDialog.Listener { /** * Maximum number of chapters to download in download chooser. */ @@ -52,9 +51,10 @@ class DownloadCustomChaptersDialog : DialogController val activity = activity!! // Initialize view that lets user select number of chapters to download. - val view = DialogCustomDownloadView(activity).apply { - setMinMax(0, maxChapters) - } + val view = + DialogCustomDownloadView(activity).apply { + setMinMax(0, maxChapters) + } // Build dialog. // when positive dialog is pressed call custom listener. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt index ae4156ac8d69..2cbd59b7b08c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt @@ -49,10 +49,6 @@ import exh.EH_SOURCE_ID import exh.EXH_SOURCE_ID import exh.MERGED_SOURCE_ID import exh.util.setChipsExtended -import java.text.DateFormat -import java.text.DecimalFormat -import java.util.Date -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -68,6 +64,10 @@ import reactivecircus.flowbinding.swiperefreshlayout.refreshes import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.text.DateFormat +import java.text.DecimalFormat +import java.util.Date +import kotlin.coroutines.CoroutineContext /** * Fragment that shows manga information. @@ -78,7 +78,6 @@ class MangaInfoController(private val fromSource: Boolean = false) : NucleusController(), ChangeMangaCategoriesDialog.Listener, CoroutineScope { - private val preferences: PreferencesHelper by injectLazy() private val dateFormat: DateFormat by lazy { @@ -105,12 +104,19 @@ class MangaInfoController(private val fromSource: Boolean = false) : override fun createPresenter(): MangaInfoPresenter { val ctrl = parentController as MangaController return MangaInfoPresenter( - ctrl.manga!!, ctrl.source!!, ctrl.smartSearchConfig, - ctrl.chapterCountRelay, ctrl.lastUpdateRelay, ctrl.mangaFavoriteRelay + ctrl.manga!!, + ctrl.source!!, + ctrl.smartSearchConfig, + ctrl.chapterCountRelay, + ctrl.lastUpdateRelay, + ctrl.mangaFavoriteRelay ) } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = MangaInfoControllerBinding.inflate(inflater) return binding.root } @@ -199,7 +205,9 @@ class MangaInfoController(private val fromSource: Boolean = false) : .launchIn(scope) } smartSearchConfig?.let { smartSearchConfig -> - if (smartSearchConfig.origMangaId != null) { binding.mergeBtn.visible() } + if (smartSearchConfig.origMangaId != null) { + binding.mergeBtn.visible() + } binding.mergeBtn.clicks() .onEach { // Init presenter here to avoid threading issues @@ -207,9 +215,10 @@ class MangaInfoController(private val fromSource: Boolean = false) : launch { try { - val mergedManga = withContext(Dispatchers.IO + NonCancellable) { - presenter.smartSearchMerge(presenter.manga, smartSearchConfig.origMangaId!!) - } + val mergedManga = + withContext(Dispatchers.IO + NonCancellable) { + presenter.smartSearchMerge(presenter.manga, smartSearchConfig.origMangaId!!) + } parentController?.router?.pushController( MangaController( @@ -220,8 +229,9 @@ class MangaInfoController(private val fromSource: Boolean = false) : ) applicationContext?.toast("Manga merged!") } catch (e: Exception) { - if (e is CancellationException) throw e - else { + if (e is CancellationException) { + throw e + } else { applicationContext?.toast("Failed to merge manga: ${e.message}") } } @@ -232,7 +242,10 @@ class MangaInfoController(private val fromSource: Boolean = false) : // EXH <-- } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { inflater.inflate(R.menu.manga_info, menu) if (presenter.source !is HttpSource) { @@ -293,7 +306,10 @@ class MangaInfoController(private val fromSource: Boolean = false) : * @param manga manga object containing information about manga. * @param source the source of the manga. */ - fun onNextManga(manga: Manga, source: Source) { + fun onNextManga( + manga: Manga, + source: Source + ) { if (manga.initialized) { // Update view. setMangaInfo(manga, source) @@ -311,40 +327,47 @@ class MangaInfoController(private val fromSource: Boolean = false) : * @param manga manga object containing information about manga. * @param source the source of the manga. */ - private fun setMangaInfo(manga: Manga, source: Source?) { + private fun setMangaInfo( + manga: Manga, + source: Source? + ) { val view = view ?: return // TODO Duplicated in MigrationProcedureAdapter // update full title TextView. - binding.mangaFullTitle.text = if (manga.title.isBlank()) { - view.context.getString(R.string.unknown) - } else { - manga.title - } + binding.mangaFullTitle.text = + if (manga.title.isBlank()) { + view.context.getString(R.string.unknown) + } else { + manga.title + } // Update artist TextView. - binding.mangaArtist.text = if (manga.artist.isNullOrBlank()) { - view.context.getString(R.string.unknown) - } else { - manga.artist - } + binding.mangaArtist.text = + if (manga.artist.isNullOrBlank()) { + view.context.getString(R.string.unknown) + } else { + manga.artist + } // Update author TextView. - binding.mangaAuthor.text = if (manga.author.isNullOrBlank()) { - view.context.getString(R.string.unknown) - } else { - manga.author - } + binding.mangaAuthor.text = + if (manga.author.isNullOrBlank()) { + view.context.getString(R.string.unknown) + } else { + manga.author + } // If manga source is known update source TextView. if (source == null) { binding.mangaSource.text = view.context.getString(R.string.unknown) // EXH --> } else if (source.id == MERGED_SOURCE_ID) { - binding.mangaSource.text = MergedSource.MangaConfig.readFromUrl(gson, manga.url).children.map { - sourceManager.getOrStub(it.source).toString() - }.distinct().joinToString() + binding.mangaSource.text = + MergedSource.MangaConfig.readFromUrl(gson, manga.url).children.map { + sourceManager.getOrStub(it.source).toString() + }.distinct().joinToString() // EXH <-- } else { val mangaSource = source.toString() @@ -370,11 +393,12 @@ class MangaInfoController(private val fromSource: Boolean = false) : binding.mangaGenresTags.setChipsExtended(manga.getGenres(), this::performSearch, this::performGlobalSearch, manga.source) } - binding.mangaSummary.text = if (manga.description.isNullOrBlank()) { - view.context.getString(R.string.unknown) - } else { - manga.description - } + binding.mangaSummary.text = + if (manga.description.isNullOrBlank()) { + view.context.getString(R.string.unknown) + } else { + manga.description + } // Update status TextView. binding.mangaStatus.setText( @@ -392,11 +416,12 @@ class MangaInfoController(private val fromSource: Boolean = false) : // Set the favorite drawable to the correct one. setFavoriteDrawable(manga.favorite) - binding.mangaCoverCard.radius = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - preferences.eh_library_corner_radius().get().toFloat(), - view.context.resources.displayMetrics - ) + binding.mangaCoverCard.radius = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + preferences.eh_library_corner_radius().get().toFloat(), + view.context.resources.displayMetrics + ) // Set cover if it wasn't already. val mangaThumbnail = manga.toMangaThumbnail() @@ -456,11 +481,12 @@ class MangaInfoController(private val fromSource: Boolean = false) : private fun openInWebView() { val source = presenter.source as? HttpSource ?: return - val url = try { - source.getMangaUrl(presenter.manga) - } catch (e: Exception) { - return - } + val url = + try { + source.getMangaUrl(presenter.manga) + } catch (e: Exception) { + return + } val activity = activity ?: return val intent = WebViewActivity.newIntent(activity, url, source.id, presenter.manga.title) @@ -476,10 +502,11 @@ class MangaInfoController(private val fromSource: Boolean = false) : val source = presenter.source as? HttpSource ?: return try { val url = source.getMangaUrl(presenter.manga) - val intent = Intent(Intent.ACTION_SEND).apply { - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, url) - } + val intent = + Intent(Intent.ACTION_SEND).apply { + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, url) + } startActivity(Intent.createChooser(intent, context.getString(R.string.action_share))) } catch (e: Exception) { context.toast(e.message) @@ -575,9 +602,10 @@ class MangaInfoController(private val fromSource: Boolean = false) : // Choose a category else -> { val ids = presenter.getMangaCategoryIds(manga) - val preselected = ids.mapNotNull { id -> - categories.indexOfFirst { it.id == id }.takeIf { it != -1 } - }.toTypedArray() + val preselected = + ids.mapNotNull { id -> + categories.indexOfFirst { it.id == id }.takeIf { it != -1 } + }.toTypedArray() ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected) .showDialog(router) @@ -593,9 +621,10 @@ class MangaInfoController(private val fromSource: Boolean = false) : val categories = presenter.getCategories() val ids = presenter.getMangaCategoryIds(manga) - val preselected = ids.mapNotNull { id -> - categories.indexOfFirst { it.id == id }.takeIf { it != -1 } - }.toTypedArray() + val preselected = + ids.mapNotNull { id -> + categories.indexOfFirst { it.id == id }.takeIf { it != -1 } + }.toTypedArray() ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected) .showDialog(router) @@ -604,7 +633,10 @@ class MangaInfoController(private val fromSource: Boolean = false) : } } - override fun updateCategoriesForMangas(mangas: List, categories: List) { + override fun updateCategoriesForMangas( + mangas: List, + categories: List + ) { val manga = mangas.firstOrNull() ?: return if (!manga.favorite) { @@ -621,7 +653,10 @@ class MangaInfoController(private val fromSource: Boolean = false) : * @param label Label to show to the user describing the content * @param content the actual text to copy to the board */ - private fun copyToClipboard(label: String, content: String) { + private fun copyToClipboard( + label: String, + content: String + ) { if (content.isBlank()) return val activity = activity ?: return @@ -664,7 +699,8 @@ class MangaInfoController(private val fromSource: Boolean = false) : previousController.search(query) } is UpdatesController, - is HistoryController -> { + is HistoryController + -> { // Manually navigate to LibraryController router.handleBack() (router.activity as MainActivity).setSelectedDrawerItem(R.id.nav_drawer_library) @@ -679,7 +715,10 @@ class MangaInfoController(private val fromSource: Boolean = false) : } // --> EH - private fun wrapTag(namespace: String, tag: String) = if (tag.contains(' ')) { + private fun wrapTag( + namespace: String, + tag: String + ) = if (tag.contains(' ')) { "$namespace:\"$tag$\"" } else { "$namespace:$tag$" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt index ca71da59d20c..663114fee166 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt @@ -22,7 +22,6 @@ import eu.kanade.tachiyomi.util.prepUpdateCover import eu.kanade.tachiyomi.util.removeCovers import exh.MERGED_SOURCE_ID import exh.util.await -import java.util.Date import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.withContext import rx.Observable @@ -31,6 +30,7 @@ import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.Date /** * Presenter of MangaInfoFragment. @@ -49,7 +49,6 @@ class MangaInfoPresenter( private val coverCache: CoverCache = Injekt.get(), private val gson: Gson = Injekt.get() ) : BasePresenter() { - /** * Subscription to update the manga from the source. */ @@ -87,25 +86,26 @@ class MangaInfoPresenter( */ fun fetchMangaFromSource(manualFetch: Boolean = false) { if (!fetchMangaSubscription.isNullOrUnsubscribed()) return - fetchMangaSubscription = Observable.defer { - runAsObservable({ - val networkManga = source.getMangaDetails(manga.toMangaInfo()) - val sManga = networkManga.toSManga() - manga.prepUpdateCover(coverCache, sManga, manualFetch) - manga.copyFrom(sManga) - manga.initialized = true - db.insertManga(manga).executeAsBlocking() - manga - }) - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeFirst( - { view, _ -> - view.onFetchMangaDone() - }, - MangaInfoController::onFetchMangaError - ) + fetchMangaSubscription = + Observable.defer { + runAsObservable({ + val networkManga = source.getMangaDetails(manga.toMangaInfo()) + val sManga = networkManga.toSManga() + manga.prepUpdateCover(coverCache, sManga, manualFetch) + manga.copyFrom(sManga) + manga.initialized = true + db.insertManga(manga).executeAsBlocking() + manga + }) + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeFirst( + { view, _ -> + view.onFetchMangaDone() + }, + MangaInfoController::onFetchMangaError + ) } /** @@ -115,10 +115,11 @@ class MangaInfoPresenter( */ fun toggleFavorite(): Boolean { manga.favorite = !manga.favorite - manga.date_added = when (manga.favorite) { - true -> Date().time - false -> 0 - } + manga.date_added = + when (manga.favorite) { + true -> Date().time + false -> 0 + } if (!manga.favorite) { manga.removeCovers(coverCache) } @@ -173,7 +174,10 @@ class MangaInfoPresenter( * @param manga the manga to move. * @param categories the selected categories. */ - fun moveMangaToCategories(manga: Manga, categories: List) { + fun moveMangaToCategories( + manga: Manga, + categories: List + ) { val mc = categories.filter { it.id != 0 }.map { MangaCategory.create(manga, it) } db.setMangaCategories(mc, listOf(manga)) } @@ -184,54 +188,66 @@ class MangaInfoPresenter( * @param manga the manga to move. * @param category the selected category, or null for default category. */ - fun moveMangaToCategory(manga: Manga, category: Category?) { + fun moveMangaToCategory( + manga: Manga, + category: Category? + ) { moveMangaToCategories(manga, listOfNotNull(category)) } + /* suspend fun recommendationView(manga: Manga): Manga { val title = manga.title val source = manga.source }*/ - suspend fun smartSearchMerge(manga: Manga, originalMangaId: Long): Manga { - val originalManga = db.getManga(originalMangaId).await() - ?: throw IllegalArgumentException("Unknown manga ID: $originalMangaId") - val toInsert = if (originalManga.source == MERGED_SOURCE_ID) { - originalManga.apply { - val originalChildren = MergedSource.MangaConfig.readFromUrl(gson, url).children - if (originalChildren.any { it.source == manga.source && it.url == manga.url }) { - throw IllegalArgumentException("This manga is already merged with the current manga!") - } + suspend fun smartSearchMerge( + manga: Manga, + originalMangaId: Long + ): Manga { + val originalManga = + db.getManga(originalMangaId).await() + ?: throw IllegalArgumentException("Unknown manga ID: $originalMangaId") + val toInsert = + if (originalManga.source == MERGED_SOURCE_ID) { + originalManga.apply { + val originalChildren = MergedSource.MangaConfig.readFromUrl(gson, url).children + if (originalChildren.any { it.source == manga.source && it.url == manga.url }) { + throw IllegalArgumentException("This manga is already merged with the current manga!") + } - url = MergedSource.MangaConfig( - originalChildren + MergedSource.MangaSource( - manga.source, - manga.url - ) - ).writeAsUrl(gson) - } - } else { - val newMangaConfig = MergedSource.MangaConfig( - listOf( - MergedSource.MangaSource( - originalManga.source, - originalManga.url - ), - MergedSource.MangaSource( - manga.source, - manga.url + url = + MergedSource.MangaConfig( + originalChildren + + MergedSource.MangaSource( + manga.source, + manga.url + ) + ).writeAsUrl(gson) + } + } else { + val newMangaConfig = + MergedSource.MangaConfig( + listOf( + MergedSource.MangaSource( + originalManga.source, + originalManga.url + ), + MergedSource.MangaSource( + manga.source, + manga.url + ) + ) ) - ) - ) - Manga.create(newMangaConfig.writeAsUrl(gson), originalManga.title, MERGED_SOURCE_ID).apply { - copyFrom(originalManga) - favorite = true - last_update = originalManga.last_update - viewer = originalManga.viewer - chapter_flags = originalManga.chapter_flags - sorting = Manga.SORTING_NUMBER + Manga.create(newMangaConfig.writeAsUrl(gson), originalManga.title, MERGED_SOURCE_ID).apply { + copyFrom(originalManga) + favorite = true + last_update = originalManga.last_update + viewer = originalManga.viewer + chapter_flags = originalManga.chapter_flags + sorting = Manga.SORTING_NUMBER + } } - } // Note that if the manga are merged in a different order, this won't trigger, but I don't care lol val existingManga = db.getManga(toInsert.url, toInsert.source).await() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackChaptersDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackChaptersDialog.kt index 311bab8625ff..061b6edb5a76 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackChaptersDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackChaptersDialog.kt @@ -15,8 +15,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class SetTrackChaptersDialog : DialogController - where T : Controller, T : SetTrackChaptersDialog.Listener { - + where T : Controller, T : SetTrackChaptersDialog.Listener { private val item: TrackItem constructor(target: T, item: TrackItem) : super( @@ -38,18 +37,19 @@ class SetTrackChaptersDialog : DialogController override fun onCreateDialog(savedViewState: Bundle?): Dialog { val item = item - val dialog = MaterialDialog(activity!!) - .title(R.string.chapters) - .customView(R.layout.track_chapters_dialog, dialogWrapContent = false) - .positiveButton(android.R.string.ok) { dialog -> - val view = dialog.getCustomView() - // Remove focus to update selected number - val np: NumberPicker = view.findViewById(R.id.chapters_picker) - np.clearFocus() + val dialog = + MaterialDialog(activity!!) + .title(R.string.chapters) + .customView(R.layout.track_chapters_dialog, dialogWrapContent = false) + .positiveButton(android.R.string.ok) { dialog -> + val view = dialog.getCustomView() + // Remove focus to update selected number + val np: NumberPicker = view.findViewById(R.id.chapters_picker) + np.clearFocus() - (targetController as? Listener)?.setChaptersRead(item, np.value) - } - .negativeButton(android.R.string.cancel) + (targetController as? Listener)?.setChaptersRead(item, np.value) + } + .negativeButton(android.R.string.cancel) val view = dialog.getCustomView() val np: NumberPicker = view.findViewById(R.id.chapters_picker) @@ -68,7 +68,10 @@ class SetTrackChaptersDialog : DialogController } interface Listener { - fun setChaptersRead(item: TrackItem, chaptersRead: Int) + fun setChaptersRead( + item: TrackItem, + chaptersRead: Int + ) } private companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackReadingDatesDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackReadingDatesDialog.kt index 00aed713b2e1..fe5ea5ff78ab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackReadingDatesDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackReadingDatesDialog.kt @@ -9,13 +9,12 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.ui.base.controller.DialogController -import java.util.Calendar import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.Calendar class SetTrackReadingDatesDialog : DialogController - where T : Controller, T : SetTrackReadingDatesDialog.Listener { - + where T : Controller, T : SetTrackReadingDatesDialog.Listener { private val item: TrackItem private val dateToUpdate: ReadingDate @@ -60,10 +59,11 @@ class SetTrackReadingDatesDialog : DialogController // Today if no date is set, otherwise the already set date return Calendar.getInstance().apply { item.track?.let { - val date = when (dateToUpdate) { - ReadingDate.Start -> it.started_reading_date - ReadingDate.Finish -> it.finished_reading_date - } + val date = + when (dateToUpdate) { + ReadingDate.Start -> it.started_reading_date + ReadingDate.Finish -> it.finished_reading_date + } if (date != 0L) { timeInMillis = date } @@ -72,7 +72,11 @@ class SetTrackReadingDatesDialog : DialogController } interface Listener { - fun setReadingDate(item: TrackItem, type: ReadingDate, date: Long) + fun setReadingDate( + item: TrackItem, + type: ReadingDate, + date: Long + ) } enum class ReadingDate { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackScoreDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackScoreDialog.kt index 266b49f9fe8b..cccb117571f2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackScoreDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackScoreDialog.kt @@ -15,8 +15,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class SetTrackScoreDialog : DialogController - where T : Controller, T : SetTrackScoreDialog.Listener { - + where T : Controller, T : SetTrackScoreDialog.Listener { private val item: TrackItem constructor(target: T, item: TrackItem) : super( @@ -38,18 +37,19 @@ class SetTrackScoreDialog : DialogController override fun onCreateDialog(savedViewState: Bundle?): Dialog { val item = item - val dialog = MaterialDialog(activity!!) - .title(R.string.score) - .customView(R.layout.track_score_dialog, dialogWrapContent = false) - .positiveButton(android.R.string.ok) { dialog -> - val view = dialog.getCustomView() - // Remove focus to update selected number - val np: NumberPicker = view.findViewById(R.id.score_picker) - np.clearFocus() + val dialog = + MaterialDialog(activity!!) + .title(R.string.score) + .customView(R.layout.track_score_dialog, dialogWrapContent = false) + .positiveButton(android.R.string.ok) { dialog -> + val view = dialog.getCustomView() + // Remove focus to update selected number + val np: NumberPicker = view.findViewById(R.id.score_picker) + np.clearFocus() - (targetController as? Listener)?.setScore(item, np.value) - } - .negativeButton(android.R.string.cancel) + (targetController as? Listener)?.setScore(item, np.value) + } + .negativeButton(android.R.string.cancel) val view = dialog.getCustomView() val np: NumberPicker = view.findViewById(R.id.score_picker) @@ -68,7 +68,10 @@ class SetTrackScoreDialog : DialogController } interface Listener { - fun setScore(item: TrackItem, score: Int) + fun setScore( + item: TrackItem, + score: Int + ) } private companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackStatusDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackStatusDialog.kt index a7116362e72e..6abc72a8cbfa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackStatusDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackStatusDialog.kt @@ -13,8 +13,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class SetTrackStatusDialog : DialogController - where T : Controller, T : SetTrackStatusDialog.Listener { - + where T : Controller, T : SetTrackStatusDialog.Listener { private val item: TrackItem constructor(target: T, item: TrackItem) : super( @@ -53,7 +52,10 @@ class SetTrackStatusDialog : DialogController } interface Listener { - fun setStatus(item: TrackItem, selection: Int) + fun setStatus( + item: TrackItem, + selection: Int + ) } private companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt index d4d52dacc349..b72529027b5e 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.view.inflate class TrackAdapter(controller: TrackController) : androidx.recyclerview.widget.RecyclerView.Adapter() { - var items = emptyList() set(value) { if (field !== value) { @@ -24,23 +23,36 @@ class TrackAdapter(controller: TrackController) : androidx.recyclerview.widget.R return items.size } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackHolder { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): TrackHolder { val view = parent.inflate(R.layout.track_item) return TrackHolder(view, this) } - override fun onBindViewHolder(holder: TrackHolder, position: Int) { + override fun onBindViewHolder( + holder: TrackHolder, + position: Int + ) { holder.bind(items[position]) } interface OnClickListener { fun onLogoClick(position: Int) + fun onSetClick(position: Int) + fun onTitleLongClick(position: Int) + fun onStatusClick(position: Int) + fun onChaptersClick(position: Int) + fun onScoreClick(position: Int) + fun onStartDateClick(position: Int) + fun onFinishDateClick(position: Int) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt index 90f2736cf282..f62724d1bbef 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt @@ -25,7 +25,6 @@ class TrackController : SetTrackChaptersDialog.Listener, SetTrackScoreDialog.Listener, SetTrackReadingDatesDialog.Listener { - private var adapter: TrackAdapter? = null init { @@ -38,7 +37,10 @@ class TrackController : return TrackPresenter((parentController as MangaController).manga!!) } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = TrackControllerBinding.inflate(inflater) return binding.root } @@ -146,22 +148,35 @@ class TrackController : SetTrackReadingDatesDialog(this, SetTrackReadingDatesDialog.ReadingDate.Finish, item).showDialog(router) } - override fun setStatus(item: TrackItem, selection: Int) { + override fun setStatus( + item: TrackItem, + selection: Int + ) { presenter.setStatus(item, selection) binding.swipeRefresh.isRefreshing = true } - override fun setScore(item: TrackItem, score: Int) { + override fun setScore( + item: TrackItem, + score: Int + ) { presenter.setScore(item, score) binding.swipeRefresh.isRefreshing = true } - override fun setChaptersRead(item: TrackItem, chaptersRead: Int) { + override fun setChaptersRead( + item: TrackItem, + chaptersRead: Int + ) { presenter.setLastChapterRead(item, chaptersRead) binding.swipeRefresh.isRefreshing = true } - override fun setReadingDate(item: TrackItem, type: SetTrackReadingDatesDialog.ReadingDate, date: Long) { + override fun setReadingDate( + item: TrackItem, + type: SetTrackReadingDatesDialog.ReadingDate, + date: Long + ) { when (type) { SetTrackReadingDatesDialog.ReadingDate.Start -> presenter.setStartDate(item, date) SetTrackReadingDatesDialog.ReadingDate.Finish -> presenter.setFinishDate(item, date) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackHolder.kt index bf6f2ab14dba..bfc1e4a79c31 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackHolder.kt @@ -7,11 +7,10 @@ import eu.kanade.tachiyomi.databinding.TrackItemBinding import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.visibleIf -import java.text.DateFormat import uy.kohesive.injekt.injectLazy +import java.text.DateFormat class TrackHolder(view: View, adapter: TrackAdapter) : BaseViewHolder(view) { - val binding = TrackItemBinding.bind(view) private val preferences: PreferencesHelper by injectLazy() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackPresenter.kt index 31794cb9e188..887375d5dadf 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackPresenter.kt @@ -22,7 +22,6 @@ class TrackPresenter( private val db: DatabaseHelper = Injekt.get(), private val trackManager: TrackManager = Injekt.get() ) : BasePresenter() { - private val context = preferences.context private var trackList: List = emptyList() @@ -42,49 +41,58 @@ class TrackPresenter( fun fetchTrackings() { trackSubscription?.let { remove(it) } - trackSubscription = db.getTracks(manga) - .asRxObservable() - .map { tracks -> - loggedServices.map { service -> - TrackItem(tracks.find { it.sync_id == service.id }, service) + trackSubscription = + db.getTracks(manga) + .asRxObservable() + .map { tracks -> + loggedServices.map { service -> + TrackItem(tracks.find { it.sync_id == service.id }, service) + } } - } - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext { trackList = it } - .subscribeLatestCache(TrackController::onNextTrackings) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { trackList = it } + .subscribeLatestCache(TrackController::onNextTrackings) } fun refresh() { refreshSubscription?.let { remove(it) } - refreshSubscription = Observable.from(trackList) - .filter { it.track != null } - .concatMap { item -> - item.service.refresh(item.track!!) - .flatMap { db.insertTrack(it).asRxObservable() } - .map { item } - .onErrorReturn { item } - } - .toList() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeFirst( - { view, _ -> view.onRefreshDone() }, - TrackController::onRefreshError - ) + refreshSubscription = + Observable.from(trackList) + .filter { it.track != null } + .concatMap { item -> + item.service.refresh(item.track!!) + .flatMap { db.insertTrack(it).asRxObservable() } + .map { item } + .onErrorReturn { item } + } + .toList() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeFirst( + { view, _ -> view.onRefreshDone() }, + TrackController::onRefreshError + ) } - fun search(query: String, service: TrackService) { + fun search( + query: String, + service: TrackService + ) { searchSubscription?.let { remove(it) } - searchSubscription = service.search(query) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeLatestCache( - TrackController::onSearchResults, - TrackController::onSearchResultsError - ) + searchSubscription = + service.search(query) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeLatestCache( + TrackController::onSearchResults, + TrackController::onSearchResultsError + ) } - fun registerTracking(item: Track?, service: TrackService) { + fun registerTracking( + item: Track?, + service: TrackService + ) { if (item != null) { item.manga_id = manga.id!! add( @@ -106,7 +114,10 @@ class TrackPresenter( db.deleteTrackForManga(manga, service).executeAsBlocking() } - private fun updateRemote(track: Track, service: TrackService) { + private fun updateRemote( + track: Track, + service: TrackService + ) { service.update(track) .flatMap { db.insertTrack(track).asRxObservable() } .subscribeOn(Schedulers.io()) @@ -122,7 +133,10 @@ class TrackPresenter( ) } - fun setStatus(item: TrackItem, index: Int) { + fun setStatus( + item: TrackItem, + index: Int + ) { val track = item.track!! track.status = item.service.getStatusList()[index] if (track.status == item.service.getCompletionStatus() && track.total_chapters != 0) { @@ -131,13 +145,19 @@ class TrackPresenter( updateRemote(track, item.service) } - fun setScore(item: TrackItem, index: Int) { + fun setScore( + item: TrackItem, + index: Int + ) { val track = item.track!! track.score = item.service.indexToScore(index) updateRemote(track, item.service) } - fun setLastChapterRead(item: TrackItem, chapterNumber: Int) { + fun setLastChapterRead( + item: TrackItem, + chapterNumber: Int + ) { val track = item.track!! track.last_chapter_read = chapterNumber if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) { @@ -146,13 +166,19 @@ class TrackPresenter( updateRemote(track, item.service) } - fun setStartDate(item: TrackItem, date: Long) { + fun setStartDate( + item: TrackItem, + date: Long + ) { val track = item.track!! track.started_reading_date = date updateRemote(track, item.service) } - fun setFinishDate(item: TrackItem, date: Long) { + fun setFinishDate( + item: TrackItem, + date: Long + ) { val track = item.track!! track.finished_reading_date = date updateRemote(track, item.service) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt index aaa55723ddaf..b1d159b1b2cc 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt @@ -14,8 +14,11 @@ import eu.kanade.tachiyomi.util.view.inflate class TrackSearchAdapter(context: Context) : ArrayAdapter(context, R.layout.track_search_item, ArrayList()) { - - override fun getView(position: Int, view: View?, parent: ViewGroup): View { + override fun getView( + position: Int, + view: View?, + parent: ViewGroup + ): View { var v = view // Get the data item for this position val track = getItem(position)!! @@ -41,6 +44,7 @@ class TrackSearchAdapter(context: Context) : class TrackSearchHolder(private val view: View) { private val binding = TrackSearchItemBinding.bind(view) + fun onSetValues(track: TrackSearch) { binding.trackSearchTitle.text = track.title binding.trackSearchSummary.text = track.summary @@ -57,18 +61,20 @@ class TrackSearchAdapter(context: Context) : binding.trackSearchStatus.gone() binding.trackSearchStatusResult.gone() } else { - binding.trackSearchStatusResult.text = track.publishing_status.replaceFirstChar { - if (it.isLowerCase()) it.titlecase() else it.toString() - } + binding.trackSearchStatusResult.text = + track.publishing_status.replaceFirstChar { + if (it.isLowerCase()) it.titlecase() else it.toString() + } } if (track.publishing_type.isBlank()) { binding.trackSearchType.gone() binding.trackSearchTypeResult.gone() } else { - binding.trackSearchTypeResult.text = track.publishing_type.replaceFirstChar { - if (it.isLowerCase()) it.titlecase() else it.toString() - } + binding.trackSearchTypeResult.text = + track.publishing_type.replaceFirstChar { + if (it.isLowerCase()) it.titlecase() else it.toString() + } } if (track.start_date.isBlank()) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt index d41e33f48c11..5246c1370c57 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt @@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.databinding.TrackSearchDialogBinding import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.util.view.invisible import eu.kanade.tachiyomi.util.view.visible -import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn @@ -25,9 +24,9 @@ import reactivecircus.flowbinding.android.widget.itemClicks import reactivecircus.flowbinding.android.widget.textChanges import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.concurrent.TimeUnit class TrackSearchDialog : DialogController { - private var binding: TrackSearchDialogBinding? = null private var adapter: TrackSearchAdapter? = null @@ -56,18 +55,22 @@ class TrackSearchDialog : DialogController { override fun onCreateDialog(savedViewState: Bundle?): Dialog { binding = TrackSearchDialogBinding.inflate(LayoutInflater.from(activity!!)) - val dialog = MaterialDialog(activity!!) - .customView(view = binding!!.root) - .positiveButton(android.R.string.ok) { onPositiveButtonClick() } - .negativeButton(android.R.string.cancel) - .neutralButton(R.string.action_remove) { onRemoveButtonClick() } + val dialog = + MaterialDialog(activity!!) + .customView(view = binding!!.root) + .positiveButton(android.R.string.ok) { onPositiveButtonClick() } + .negativeButton(android.R.string.cancel) + .neutralButton(R.string.action_remove) { onRemoveButtonClick() } onViewCreated(dialog.view, savedViewState) return dialog } - fun onViewCreated(view: View, savedState: Bundle?) { + fun onViewCreated( + view: View, + savedState: Bundle? + ) { // Create adapter val adapter = TrackSearchAdapter(view.context) this.adapter = adapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaAdapter.kt index 5cf41f0cd6b9..cf9a341d7a67 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaAdapter.kt @@ -5,7 +5,6 @@ import eu.davidea.flexibleadapter.items.IFlexible class MangaAdapter(controller: MigrationController) : FlexibleAdapter>(null, controller) { - private var items: List>? = null override fun updateDataSet(items: MutableList>?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt index e5788ed27271..75bd90e8f556 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt @@ -12,8 +12,8 @@ class MangaHolder( view: View, adapter: FlexibleAdapter<*> ) : BaseFlexibleViewHolder(view, adapter) { - val binding = SourceListItemBinding.bind(view) + fun bind(item: MangaItem) { // Update the title of the manga. binding.title.text = item.manga.title diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaItem.kt index 36ca17915271..b1aeb8277950 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaItem.kt @@ -8,12 +8,14 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga class MangaItem(val manga: Manga) : AbstractFlexibleItem() { - override fun getLayoutRes(): Int { return R.layout.source_list_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): MangaHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): MangaHolder { return MangaHolder(view, adapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt index 3f79049baed5..4c760fc744c2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationController.kt @@ -27,7 +27,6 @@ class MigrationController : FlexibleAdapter.OnItemClickListener, SourceAdapter.OnAllClickListener, MigrationInterface { - private var adapter: FlexibleAdapter>? = null private var title: String? = null @@ -40,7 +39,10 @@ class MigrationController : return MigrationPresenter() } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = MigrationControllerBinding.inflate(inflater) return binding.root } @@ -104,7 +106,10 @@ class MigrationController : } } - override fun onItemClick(view: View?, position: Int): Boolean { + override fun onItemClick( + view: View?, + position: Int + ): Boolean { val item = adapter?.getItem(position) ?: return false if (item is MangaItem) { @@ -123,9 +128,10 @@ class MigrationController : val item = adapter?.getItem(position) as? SourceItem ?: return launchUI { - val manga = Injekt.get().getFavoriteMangas().asRxSingle().await( - Schedulers.io() - ) + val manga = + Injekt.get().getFavoriteMangas().asRxSingle().await( + Schedulers.io() + ) val sourceMangas = manga.asSequence().filter { it.source == item.source.id }.map { it.id!! }.toList() withContext(Dispatchers.Main) { @@ -138,12 +144,20 @@ class MigrationController : } } - override fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? { + override fun migrateManga( + prevManga: Manga, + manga: Manga, + replace: Boolean + ): Manga? { presenter.migrateManga(prevManga, manga, replace) return null } } interface MigrationInterface { - fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? + fun migrateManga( + prevManga: Manga, + manga: Manga, + replace: Boolean + ): Manga? } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationFlags.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationFlags.kt index ba24dcbc65b1..dfc7696a1641 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationFlags.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationFlags.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.migration import eu.kanade.tachiyomi.R object MigrationFlags { - const val CHAPTERS = 0b001 const val CATEGORIES = 0b010 const val TRACK = 0b100 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt index f12724d79199..1c91feb3f374 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt @@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController class MigrationMangaDialog(bundle: Bundle? = null) : DialogController(bundle) where T : Controller { - var copy = false var mangaSet = 0 var mangaSkipped = 0 @@ -23,14 +22,19 @@ class MigrationMangaDialog(bundle: Bundle? = null) : DialogController(bundle) override fun onCreateDialog(savedViewState: Bundle?): Dialog { val confirmRes = if (copy) R.plurals.copy_manga else R.plurals.migrate_manga - val confirmString = applicationContext?.resources?.getQuantityString( - confirmRes, mangaSet, - mangaSet, - ( - if (mangaSkipped > 0) " " + applicationContext?.getString(R.string.skipping_, mangaSkipped) - else "" - ) - ) ?: "" + val confirmString = + applicationContext?.resources?.getQuantityString( + confirmRes, + mangaSet, + mangaSet, + ( + if (mangaSkipped > 0) { + " " + applicationContext?.getString(R.string.skipping_, mangaSkipped) + } else { + "" + } + ) + ) ?: "" return MaterialDialog(activity!!) .message(text = confirmString) .positiveButton(if (copy) R.string.copy else R.string.migrate) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt index b0191eee4ab5..996bd8b1c79a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt @@ -27,7 +27,6 @@ class MigrationPresenter( private val db: DatabaseHelper = Injekt.get(), private val preferences: PreferencesHelper = Injekt.get() ) : BasePresenter() { - var state = ViewState() private set(value) { field = value @@ -76,11 +75,18 @@ class MigrationPresenter( .map { SourceItem(it, header) } } - private fun libraryToMigrationItem(library: List, sourceId: Long): List { + private fun libraryToMigrationItem( + library: List, + sourceId: Long + ): List { return library.filter { it.source == sourceId }.map(::MangaItem) } - fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) { + fun migrateManga( + prevManga: Manga, + manga: Manga, + replace: Boolean + ) { val source = sourceManager.get(manga.source) ?: return state = state.copy(isReplacingManga = true) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt index a6e4b116d2dd..1a457d5c0fc4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt @@ -29,7 +29,6 @@ class SearchController( private var manga: Manga? = null, private var sources: List? = null ) : GlobalSearchController(manga?.title) { - private var newManga: Manga? = null private var progress = 1 var totalProgress = 0 @@ -110,7 +109,9 @@ class SearchController( searchController.progress = progress + 1 searchController.totalProgress = totalProgress router.replaceTopController(searchController.withFadeTransaction()) - } else router.popController(this) + } else { + router.popController(this) + } } override fun onMangaClick(manga: Manga) { @@ -134,7 +135,6 @@ class SearchController( } class MigrationDialog : DialogController() { - private val preferences: PreferencesHelper by injectLazy() override fun onCreateDialog(savedViewState: Bundle?): Dialog { @@ -145,7 +145,8 @@ class SearchController( return MaterialDialog(activity!!) .message(R.string.data_to_include_in_migration) .listItemsMultiChoice( - items = MigrationFlags.titles.map + items = + MigrationFlags.titles.map { resources?.getString(it) as CharSequence }, initialSelection = preselected.toIntArray() ) { _, positions, _ -> @@ -167,7 +168,10 @@ class SearchController( * @param menu menu containing options. * @param inflater used to load the menu xml. */ - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { // Inflate menu. inflater.inflate(R.menu.source_browse, menu) @@ -175,17 +179,19 @@ class SearchController( val searchItem = menu.findItem(R.id.action_search) val searchView = searchItem.actionView as SearchView - searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { - override fun onMenuItemActionExpand(item: MenuItem): Boolean { - searchView.onActionViewExpanded() // Required to show the query in the view - searchView.setQuery(presenter.query, false) - return true - } + searchItem.setOnActionExpandListener( + object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem): Boolean { + searchView.onActionViewExpanded() // Required to show the query in the view + searchView.setQuery(presenter.query, false) + return true + } - override fun onMenuItemActionCollapse(item: MenuItem): Boolean { - return true + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + return true + } } - }) + ) searchView.queryTextEvents() .filter { it is QueryTextEvent.QuerySubmitted } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchPresenter.kt index 66a26e07418c..8306c37eac39 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchPresenter.kt @@ -11,14 +11,16 @@ class SearchPresenter( private val manga: Manga, sources: List? = null ) : GlobalSearchPresenter(initialQuery, sourcesToUse = sources) { - override fun getEnabledSources(): List { // Put the source of the selected manga at the top return super.getEnabledSources() .sortedByDescending { it.id == manga.source } } - override fun createCatalogueSearchItem(source: CatalogueSource, results: List?): GlobalSearchItem { + override fun createCatalogueSearchItem( + source: CatalogueSource, + results: List? + ): GlobalSearchItem { // Set the catalogue search item as highlighted if the source matches that of the selected manga return GlobalSearchItem(source, results, source.id == manga.source) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SelectionHeader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SelectionHeader.kt index 772fd574fe29..d6d2212e7dff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SelectionHeader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SelectionHeader.kt @@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder * Item that contains the selection header. */ class SelectionHeader : AbstractHeaderItem() { - /** * Returns the layout resource of this item. */ @@ -24,7 +23,10 @@ class SelectionHeader : AbstractHeaderItem() { /** * Creates a new view holder for this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } @@ -42,6 +44,7 @@ class SelectionHeader : AbstractHeaderItem() { class Holder(view: View, adapter: FlexibleAdapter>) : BaseFlexibleViewHolder(view, adapter) { private val binding = SourceMainControllerCardBinding.bind(view) + init { binding.title.text = view.context.getString(R.string.select_a_source_to_migrate_from) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceAdapter.kt index a0386c6505be..0a09b7f2ef49 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceAdapter.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.util.system.getResourceColor */ class SourceAdapter(val controller: MigrationController) : FlexibleAdapter>(null, controller, true) { - val cardBackground = controller.activity!!.getResourceColor(R.attr.colorSurface) private var items: List>? = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceHolder.kt index b1d52e228851..054308780674 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceHolder.kt @@ -11,12 +11,12 @@ import io.github.mthli.slice.Slice class SourceHolder(view: View, override val adapter: SourceAdapter) : BaseFlexibleViewHolder(view, adapter), SlicedHolder { - private val binding = SourceMainControllerCardItemBinding.bind(view) - override val slice = Slice(binding.card).apply { - setColor(adapter.cardBackground) - } + override val slice = + Slice(binding.card).apply { + setColor(adapter.cardBackground) + } override val viewToSlice: View get() = binding.card diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceItem.kt index 163819c265f4..9a18e90ac2d5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SourceItem.kt @@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.source.Source */ data class SourceItem(val source: Source, val header: SelectionHeader? = null) : AbstractSectionableItem(header) { - /** * Returns the layout resource of this item. */ @@ -26,7 +25,10 @@ data class SourceItem(val source: Source, val header: SelectionHeader? = null) : /** * Creates a new view holder for this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): SourceHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): SourceHolder { return SourceHolder(view, adapter as SourceAdapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationBottomSheetDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationBottomSheetDialog.kt index b5fd1d372390..ea4d54c3dcf5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationBottomSheetDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationBottomSheetDialog.kt @@ -23,8 +23,7 @@ import uy.kohesive.injekt.injectLazy class MigrationBottomSheetDialog( activity: Activity, theme: Int, - private val listener: - StartMigrationListener + private val listener: StartMigrationListener ) : BottomSheetDialog( activity, @@ -36,6 +35,7 @@ class MigrationBottomSheetDialog( private val preferences by injectLazy() private val binding = MigrationBottomSheetBinding.inflate(activity.layoutInflater) + init { // Use activity theme for this layout // val scroll = NestedScrollView(context) @@ -61,7 +61,9 @@ class MigrationBottomSheetDialog( listener.startMigration( if (binding.useSmartSearch.isChecked && binding.extraSearchParamText.text.isNotBlank()) { binding.extraSearchParamText.text.toString() - } else null + } else { + null + } ) dismiss() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceHolder.kt index 3b61edd31a86..2554b82d718d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceHolder.kt @@ -12,17 +12,26 @@ import uy.kohesive.injekt.injectLazy class MigrationSourceHolder(view: View, val adapter: MigrationSourceAdapter) : BaseFlexibleViewHolder(view, adapter) { private val binding = MigrationSourceItemBinding.bind(view) + init { setDragHandleView(binding.reorder) } - fun bind(source: HttpSource, sourceEnabled: Boolean) { + fun bind( + source: HttpSource, + sourceEnabled: Boolean + ) { val preferences by injectLazy() val isMultiLanguage = preferences.enabledLanguages().get().size > 1 // Set capitalized title. - val sourceName = if (isMultiLanguage) source.toString() else source.name.replaceFirstChar { - if (it.isLowerCase()) it.titlecase() else it.toString() - } + val sourceName = + if (isMultiLanguage) { + source.toString() + } else { + source.name.replaceFirstChar { + if (it.isLowerCase()) it.titlecase() else it.toString() + } + } binding.title.text = sourceName // Update circle letter image. itemView.post { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceItem.kt index 5b74c9ed2f60..6e5b730802e4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceItem.kt @@ -14,7 +14,10 @@ import kotlinx.parcelize.Parcelize class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) : AbstractFlexibleItem() { override fun getLayoutRes() = R.layout.migration_source_item - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): MigrationSourceHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): MigrationSourceHolder { return MigrationSourceHolder(view, adapter as MigrationSourceAdapter) } @@ -62,7 +65,10 @@ class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) : } companion object { - fun fromParcelable(sourceManager: SourceManager, si: ParcelableSI): MigrationSourceItem? { + fun fromParcelable( + sourceManager: SourceManager, + si: ParcelableSI + ): MigrationSourceItem? { val source = sourceManager.get(si.sourceId) as? HttpSource ?: return null return MigrationSourceItem( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/PreMigrationController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/PreMigrationController.kt index e961a74783e9..6257beb6a9a1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/PreMigrationController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/PreMigrationController.kt @@ -46,7 +46,10 @@ class PreMigrationController(bundle: Bundle? = null) : override fun getTitle() = view?.context?.getString(R.string.select_sources) - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = PreMigrationControllerBinding.inflate(inflater) return binding.root } @@ -54,10 +57,11 @@ class PreMigrationController(bundle: Bundle? = null) : override fun onViewCreated(view: View) { super.onViewCreated(view) - val ourAdapter = adapter ?: MigrationSourceAdapter( - getEnabledSources().map { MigrationSourceItem(it, isEnabled(it.id.toString())) }, - this - ) + val ourAdapter = + adapter ?: MigrationSourceAdapter( + getEnabledSources().map { MigrationSourceItem(it, isEnabled(it.id.toString())) }, + this + ) adapter = ourAdapter binding.recycler.layoutManager = LinearLayoutManager(view.context) binding.recycler.setHasFixedSize(true) @@ -83,9 +87,10 @@ class PreMigrationController(bundle: Bundle? = null) : if (dialog?.isShowing != true) { dialog = MigrationBottomSheetDialog(activity!!, R.style.SheetDialog, this) dialog?.show() - val bottomSheet = dialog?.findViewById( - com.google.android.material.R.id.design_bottom_sheet - ) + val bottomSheet = + dialog?.findViewById( + com.google.android.material.R.id.design_bottom_sheet + ) if (bottomSheet != null) { val behavior: BottomSheetBehavior<*> = BottomSheetBehavior.from(bottomSheet) behavior.state = BottomSheetBehavior.STATE_EXPANDED @@ -96,9 +101,10 @@ class PreMigrationController(bundle: Bundle? = null) : } override fun startMigration(extraParam: String?) { - val listOfSources = adapter?.items?.filter { - it.sourceEnabled - }?.joinToString("/") { it.source.id.toString() } ?: "" + val listOfSources = + adapter?.items?.filter { + it.sourceEnabled + }?.joinToString("/") { it.source.id.toString() } ?: "" prefs.migrationSources().set(listOfSources) router.replaceTopController( @@ -122,7 +128,10 @@ class PreMigrationController(bundle: Bundle? = null) : adapter?.onRestoreInstanceState(savedInstanceState) } - override fun onItemClick(view: View, position: Int): Boolean { + override fun onItemClick( + view: View, + position: Int + ): Boolean { adapter?.getItem(position)?.let { it.sourceEnabled = !it.sourceEnabled } @@ -138,10 +147,11 @@ class PreMigrationController(bundle: Bundle? = null) : private fun getEnabledSources(): List { val languages = prefs.enabledLanguages().get() val sourcesSaved = prefs.migrationSources().get().split("/") - var sources = sourceManager.getVisibleCatalogueSources() - .filterIsInstance() - .filter { it.lang in languages } - .sortedBy { "(${it.lang}) ${it.name}" } + var sources = + sourceManager.getVisibleCatalogueSources() + .filterIsInstance() + .filter { it.lang in languages } + .sortedBy { "(${it.lang}) ${it.name}" } sources = sources.filter { isEnabled(it.id.toString()) }.sortedBy { sourcesSaved.indexOf( @@ -157,11 +167,17 @@ class PreMigrationController(bundle: Bundle? = null) : fun isEnabled(id: String): Boolean { val sourcesSaved = prefs.migrationSources().get() val hiddenCatalogues = prefs.hiddenCatalogues().get() - return if (sourcesSaved.isEmpty()) id !in hiddenCatalogues - else sourcesSaved.split("/").contains(id) + return if (sourcesSaved.isEmpty()) { + id !in hiddenCatalogues + } else { + sourcesSaved.split("/").contains(id) + } } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { inflater.inflate(R.menu.pre_migration, menu) } @@ -174,18 +190,20 @@ class PreMigrationController(bundle: Bundle? = null) : adapter?.notifyDataSetChanged() } R.id.action_match_enabled, R.id.action_match_pinned -> { - val enabledSources = if (item.itemId == R.id.action_match_enabled) { - prefs.hiddenCatalogues().get().mapNotNull { it.toLongOrNull() } - } else { - prefs.pinnedCatalogues().get().mapNotNull { it.toLongOrNull() } - } - val items = adapter?.currentItems?.toList() ?: return true - items.forEach { - it.sourceEnabled = if (item.itemId == R.id.action_match_enabled) { - it.source.id !in enabledSources + val enabledSources = + if (item.itemId == R.id.action_match_enabled) { + prefs.hiddenCatalogues().get().mapNotNull { it.toLongOrNull() } } else { - it.source.id in enabledSources + prefs.pinnedCatalogues().get().mapNotNull { it.toLongOrNull() } } + val items = adapter?.currentItems?.toList() ?: return true + items.forEach { + it.sourceEnabled = + if (item.itemId == R.id.action_match_enabled) { + it.source.id !in enabledSources + } else { + it.source.id in enabledSources + } } val sortedItems = items.sortedBy { it.source.name }.sortedBy { !it.sourceEnabled } adapter?.updateDataSet(sortedItems) @@ -198,7 +216,11 @@ class PreMigrationController(bundle: Bundle? = null) : companion object { private const val MANGA_IDS_EXTRA = "manga_ids" - fun navigateToMigration(skipPre: Boolean, router: Router, mangaIds: List) { + fun navigateToMigration( + skipPre: Boolean, + router: Router, + mangaIds: List + ) { router.pushController( if (skipPre) { MigrationListController.create( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigratingManga.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigratingManga.kt index be29dfbebac6..1791a844ddf6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigratingManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigratingManga.kt @@ -5,10 +5,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.util.DeferredField -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.ConflatedBroadcastChannel +import kotlin.coroutines.CoroutineContext class MigratingManga( private val db: DatabaseHelper, @@ -27,6 +27,7 @@ class MigratingManga( @Volatile private var manga: Manga? = null + suspend fun manga(): Manga? { if (manga == null) manga = db.getManga(mangaId).executeAsBlocking() return manga diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt index 55ff57991db0..da2d98093f3c 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt @@ -42,8 +42,6 @@ import exh.smartsearch.SmartSearchEngine import exh.util.RecyclerWindowInsetsListener import exh.util.await import exh.util.executeOnIO -import java.util.concurrent.atomic.AtomicInteger -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -57,12 +55,13 @@ import kotlinx.coroutines.withContext import rx.schedulers.Schedulers import timber.log.Timber import uy.kohesive.injekt.injectLazy +import java.util.concurrent.atomic.AtomicInteger +import kotlin.coroutines.CoroutineContext class MigrationListController(bundle: Bundle? = null) : BaseController(bundle), MigrationProcessAdapter.MigrationProcessInterface, CoroutineScope { - init { setHasOptionsMenu(true) } @@ -85,7 +84,10 @@ class MigrationListController(bundle: Bundle? = null) : private var selectedPosition: Int? = null private var manualMigrations = 0 - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = MigrationListControllerBinding.inflate(inflater) return binding.root } @@ -101,13 +103,15 @@ class MigrationListController(bundle: Bundle? = null) : setTitle() val config = this.config ?: return - val newMigratingManga = migratingManga ?: run { - val new = config.mangaIds.map { - MigratingManga(db, sourceManager, it, coroutineContext) + val newMigratingManga = + migratingManga ?: run { + val new = + config.mangaIds.map { + MigratingManga(db, sourceManager, it, coroutineContext) + } + migratingManga = new.toMutableList() + new } - migratingManga = new.toMutableList() - new - } adapter = MigrationProcessAdapter(this) @@ -119,9 +123,10 @@ class MigrationListController(bundle: Bundle? = null) : adapter?.updateDataSet(newMigratingManga.map { it.toModal() }) if (migrationsJob == null) { - migrationsJob = launch { - runMigrations(newMigratingManga) - } + migrationsJob = + launch { + runMigrations(newMigratingManga) + } } } @@ -129,10 +134,11 @@ class MigrationListController(bundle: Bundle? = null) : val useSourceWithMost = preferences.useSourceWithMost().get() val useSmartSearch = preferences.smartMigration().get() - val sources = preferences.migrationSources().get().split("/").mapNotNull { - val value = it.toLongOrNull() ?: return - sourceManager.get(value) as? CatalogueSource - } + val sources = + preferences.migrationSources().get().split("/").mapNotNull { + val value = it.toLongOrNull() ?: return + sourceManager.get(value) as? CatalogueSource + } if (config == null) return for (manga in mangas) { if (migrationsJob?.isCancelled == true) { @@ -152,61 +158,115 @@ class MigrationListController(bundle: Bundle? = null) : val mangaSource = manga.mangaSource() - val result = try { - CoroutineScope(manga.migrationJob).async { - val validSources = if (sources.size == 1) { - sources - } else { - sources.filter { it.id != mangaSource.id } - } - if (useSourceWithMost) { - val sourceSemaphore = Semaphore(3) - val processedSources = AtomicInteger() + val result = + try { + CoroutineScope(manga.migrationJob).async { + val validSources = + if (sources.size == 1) { + sources + } else { + sources.filter { it.id != mangaSource.id } + } + if (useSourceWithMost) { + val sourceSemaphore = Semaphore(3) + val processedSources = AtomicInteger() + + validSources.map { source -> + async { + sourceSemaphore.withPermit { + try { + val searchResult = + if (useSmartSearch) { + smartSearchEngine.smartSearch( + source, + mangaObj.title + ) + } else { + smartSearchEngine.normalSearch( + source, + mangaObj.title + ) + } - validSources.map { source -> - async { - sourceSemaphore.withPermit { - try { - val searchResult = if (useSmartSearch) { - smartSearchEngine.smartSearch( - source, - mangaObj.title - ) - } else { - smartSearchEngine.normalSearch( - source, - mangaObj.title - ) + if (searchResult != null && + !( + searchResult.url == mangaObj.url && + source.id == mangaObj.source + ) + ) { + val localManga = + smartSearchEngine.networkToLocalManga( + searchResult, + source.id + ) + val chapters = + runAsObservable( + { source.getChapterList(localManga.toMangaInfo()).map { it.toSChapter() } } + ).toSingle() + .await( + Schedulers.io() + ) + try { + syncChaptersWithSource( + db, + chapters, + localManga, + source + ) + } catch (e: Exception) { + return@async null + } + manga.progress.send(validSources.size to processedSources.incrementAndGet()) + localManga to chapters.size + } else { + null + } + } catch (e: CancellationException) { + // Ignore cancellations + throw e + } catch (e: Exception) { + null } - - if (searchResult != null && - !( - searchResult.url == mangaObj.url && - source.id == mangaObj.source + } + } + }.mapNotNull { it.await() }.maxByOrNull { it.second }?.first + } else { + validSources.forEachIndexed { index, source -> + val searchResult = + try { + val searchResult = + if (useSmartSearch) { + smartSearchEngine.smartSearch( + source, + mangaObj.title + ) + } else { + smartSearchEngine.normalSearch( + source, + mangaObj.title ) - ) { + } + + if (searchResult != null) { val localManga = smartSearchEngine.networkToLocalManga( searchResult, source.id ) val chapters = - runAsObservable({ source.getChapterList(localManga.toMangaInfo()).map { it.toSChapter() } }).toSingle() - .await( - Schedulers.io() - ) - try { - syncChaptersWithSource( - db, - chapters, - localManga, - source - ) - } catch (e: Exception) { - return@async null + try { + runAsObservable( + { source.getChapterList(localManga.toMangaInfo()).map { it.toSChapter() } } + ).toSingle() + .await(Schedulers.io()) + } catch (e: java.lang.Exception) { + Timber.e(e) + emptyList() + } ?: emptyList() + withContext(Dispatchers.IO) { + syncChaptersWithSource(db, chapters, localManga, source) } - manga.progress.send(validSources.size to processedSources.incrementAndGet()) - localManga to chapters.size + localManga } else { null } @@ -216,60 +276,19 @@ class MigrationListController(bundle: Bundle? = null) : } catch (e: Exception) { null } - } - } - }.mapNotNull { it.await() }.maxByOrNull { it.second }?.first - } else { - validSources.forEachIndexed { index, source -> - val searchResult = try { - val searchResult = if (useSmartSearch) { - smartSearchEngine.smartSearch( - source, - mangaObj.title - ) - } else { - smartSearchEngine.normalSearch( - source, - mangaObj.title - ) - } - if (searchResult != null) { - val localManga = smartSearchEngine.networkToLocalManga( - searchResult, - source.id - ) - val chapters = try { - runAsObservable({ source.getChapterList(localManga.toMangaInfo()).map { it.toSChapter() } }).toSingle() - .await(Schedulers.io()) - } catch (e: java.lang.Exception) { - Timber.e(e) - emptyList() - } ?: emptyList() - withContext(Dispatchers.IO) { - syncChaptersWithSource(db, chapters, localManga, source) - } - localManga - } else null - } catch (e: CancellationException) { - // Ignore cancellations - throw e - } catch (e: Exception) { - null - } + manga.progress.send(validSources.size to (index + 1)) - manga.progress.send(validSources.size to (index + 1)) + if (searchResult != null) return@async searchResult + } - if (searchResult != null) return@async searchResult + null } - - null - } - }.await() - } catch (e: CancellationException) { - // Ignore canceled migrations - continue - } + }.await() + } catch (e: CancellationException) { + // Ignore canceled migrations + continue + } if (result != null && result.thumbnail_url == null) { try { @@ -329,7 +348,9 @@ class MigrationListController(bundle: Bundle? = null) : if (res != null) { activity?.toast( res.getQuantityString( - R.plurals.manga_migrated, manualMigrations, manualMigrations + R.plurals.manga_migrated, + manualMigrations, + manualMigrations ) ) } @@ -337,21 +358,26 @@ class MigrationListController(bundle: Bundle? = null) : } } - override fun onMenuItemClick(position: Int, item: MenuItem) { + override fun onMenuItemClick( + position: Int, + item: MenuItem + ) { when (item.itemId) { R.id.action_search_manually -> { launchUI { val manga = adapter?.getItem(position)?.manga?.manga() ?: return@launchUI selectedPosition = position - val sources = preferences.migrationSources().get().split("/").mapNotNull { - val value = it.toLongOrNull() ?: return@mapNotNull null - sourceManager.get(value) as? CatalogueSource - } - val validSources = if (sources.size == 1) { - sources - } else { - sources.filter { it.id != manga.source } - } + val sources = + preferences.migrationSources().get().split("/").mapNotNull { + val value = it.toLongOrNull() ?: return@mapNotNull null + sourceManager.get(value) as? CatalogueSource + } + val validSources = + if (sources.size == 1) { + sources + } else { + sources.filter { it.id != manga.source } + } val searchController = SearchController(manga, validSources) searchController.targetController = this@MigrationListController router.pushController(searchController.withFadeTransaction()) @@ -369,29 +395,36 @@ class MigrationListController(bundle: Bundle? = null) : } } - fun useMangaForMigration(manga: Manga, source: Source) { + fun useMangaForMigration( + manga: Manga, + source: Source + ) { val firstIndex = selectedPosition ?: return val migratingManga = adapter?.getItem(firstIndex) ?: return migratingManga.manga.migrationStatus = MigrationStatus.RUNNUNG adapter?.notifyItemChanged(firstIndex) launchUI { - val result = CoroutineScope(migratingManga.manga.migrationJob).async { - val localManga = smartSearchEngine.networkToLocalManga(manga, source.id) - val chapters = runAsObservable({ source.getChapterList(localManga.toMangaInfo()).map { it.toSChapter() } }).toSingle().await( - Schedulers.io() - ) - try { - syncChaptersWithSource(db, chapters, localManga, source) - } catch (e: Exception) { - return@async null - } - localManga - }.await() + val result = + CoroutineScope(migratingManga.manga.migrationJob).async { + val localManga = smartSearchEngine.networkToLocalManga(manga, source.id) + val chapters = + runAsObservable({ source.getChapterList(localManga.toMangaInfo()).map { it.toSChapter() } }).toSingle().await( + Schedulers.io() + ) + try { + syncChaptersWithSource(db, chapters, localManga, source) + } catch (e: Exception) { + return@async null + } + localManga + }.await() if (result != null) { try { val newManga = - runAsObservable({ sourceManager.getOrStub(result.source).getMangaDetails(result.toMangaInfo()).toSManga() }).toSingle() + runAsObservable( + { sourceManager.getOrStub(result.source).getMangaDetails(result.toMangaInfo()).toSManga() } + ).toSingle() .await() result.copyFrom(newManga) @@ -432,22 +465,26 @@ class MigrationListController(bundle: Bundle? = null) : launchUI { val hasDetails = router.backstack.any { it.controller() is MangaController } if (hasDetails) { - val manga = migratingManga?.firstOrNull()?.searchResult?.get()?.let { - db.getManga(it).executeOnIO() - } + val manga = + migratingManga?.firstOrNull()?.searchResult?.get()?.let { + db.getManga(it).executeOnIO() + } if (manga != null) { - val newStack = router.backstack.filter { - it.controller() !is MangaController && - it.controller() !is MigrationListController && - it.controller() !is PreMigrationController - } + MangaController(manga).withFadeTransaction() + val newStack = + router.backstack.filter { + it.controller() !is MangaController && + it.controller() !is MigrationListController && + it.controller() !is PreMigrationController + } + MangaController(manga).withFadeTransaction() router.setBackstack(newStack, FadeChangeHandler()) return@launchUI } } router.popCurrentController() } - } else router.popCurrentController() + } else { + router.popCurrentController() + } } override fun handleBack(): Boolean { @@ -468,7 +505,10 @@ class MigrationListController(bundle: Bundle? = null) : super.onDestroyView(view) } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { inflater.inflate(R.menu.migration_list, menu) } @@ -481,9 +521,12 @@ class MigrationListController(bundle: Bundle? = null) : val menuMigrate = menu.findItem(R.id.action_migrate_manga) if (adapter?.itemCount == 1) { - menuMigrate.icon = VectorDrawableCompat.create( - resources!!, R.drawable.ic_done_24dp, null - ) + menuMigrate.icon = + VectorDrawableCompat.create( + resources!!, + R.drawable.ic_done_24dp, + null + ) } menuCopy.icon?.mutate() @@ -500,18 +543,20 @@ class MigrationListController(bundle: Bundle? = null) : val totalManga = adapter?.itemCount ?: 0 val mangaSkipped = adapter?.mangasSkipped() ?: 0 when (item.itemId) { - R.id.action_copy_manga -> MigrationMangaDialog( - this, - true, - totalManga, - mangaSkipped - ).showDialog(router) - R.id.action_migrate_manga -> MigrationMangaDialog( - this, - false, - totalManga, - mangaSkipped - ).showDialog(router) + R.id.action_copy_manga -> + MigrationMangaDialog( + this, + true, + totalManga, + mangaSkipped + ).showDialog(router) + R.id.action_migrate_manga -> + MigrationMangaDialog( + this, + false, + totalManga, + mangaSkipped + ).showDialog(router) else -> return super.onOptionsItemSelected(item) } return true diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt index 99cbd39a0c7d..f9564c727d1a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt @@ -16,7 +16,6 @@ import uy.kohesive.injekt.injectLazy class MigrationProcessAdapter( val controller: MigrationListController ) : FlexibleAdapter(null, controller, true) { - private val db: DatabaseHelper by injectLazy() var items: List = emptyList() val preferences: PreferencesHelper by injectLazy() @@ -29,10 +28,17 @@ class MigrationProcessAdapter( } interface MigrationProcessInterface { - fun onMenuItemClick(position: Int, item: MenuItem) + fun onMenuItemClick( + position: Int, + item: MenuItem + ) + fun enableButtons() + fun removeManga(item: MigrationProcessItem) + fun noMigration() + fun updateCount() } @@ -42,12 +48,14 @@ class MigrationProcessAdapter( if (allMangasDone()) menuItemListener.enableButtons() } - fun allMangasDone() = ( - items.all { - it.manga.migrationStatus != MigrationStatus - .RUNNUNG - } && items.any { it.manga.migrationStatus == MigrationStatus.MANGA_FOUND } - ) + fun allMangasDone() = + ( + items.all { + it.manga.migrationStatus != + MigrationStatus + .RUNNUNG + } && items.any { it.manga.migrationStatus == MigrationStatus.MANGA_FOUND } + ) fun mangasSkipped() = (items.count { it.manga.migrationStatus == MigrationStatus.MANGA_NOT_FOUND }) @@ -71,7 +79,10 @@ class MigrationProcessAdapter( } } - fun migrateManga(position: Int, copy: Boolean) { + fun migrateManga( + position: Int, + copy: Boolean + ) { launchUI { val manga = getItem(position)?.manga ?: return@launchUI db.inTransaction { @@ -79,7 +90,9 @@ class MigrationProcessAdapter( db.getManga(manga.searchResult.get() ?: return@launchUI).executeAsBlocking() ?: return@launchUI migrateMangaInternal( - manga.manga() ?: return@launchUI, toMangaObj, !copy + manga.manga() ?: return@launchUI, + toMangaObj, + !copy ) } removeManga(position) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt index 38784ab6d3f3..a5e2d02d85ae 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt @@ -24,16 +24,15 @@ import eu.kanade.tachiyomi.util.view.invisible import eu.kanade.tachiyomi.util.view.setVectorCompat import eu.kanade.tachiyomi.util.view.visible import exh.MERGED_SOURCE_ID -import java.text.DecimalFormat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import uy.kohesive.injekt.injectLazy +import java.text.DecimalFormat class MigrationProcessHolder( private val view: View, private val adapter: MigrationProcessAdapter ) : BaseFlexibleViewHolder(view, adapter) { - private val db: DatabaseHelper by injectLazy() private val sourceManager: SourceManager by injectLazy() private var item: MigrationProcessItem? = null @@ -94,12 +93,14 @@ class MigrationProcessHolder( } }*/ - val searchResult = item.manga.searchResult.get()?.let { - db.getManga(it).executeAsBlocking() - } - val resultSource = searchResult?.source?.let { - sourceManager.get(it) - } + val searchResult = + item.manga.searchResult.get()?.let { + db.getManga(it).executeAsBlocking() + } + val resultSource = + searchResult?.source?.let { + sourceManager.get(it) + } withContext(Dispatchers.Main) { if (item.manga.mangaId != this@MigrationProcessHolder.item?.manga?.mangaId || item.manga.migrationStatus == MigrationStatus.RUNNUNG @@ -111,14 +112,16 @@ class MigrationProcessHolder( binding.migrationMangaCardTo.root.setOnClickListener { adapter.controller.router.pushController( MangaController( - searchResult, true + searchResult, + true ).withFadeTransaction() ) } } else { binding.migrationMangaCardTo.loadingGroup.gone() - binding.migrationMangaCardTo.title.text = view.context.applicationContext - .getString(R.string.no_alternatives_found) + binding.migrationMangaCardTo.title.text = + view.context.applicationContext + .getString(R.string.no_alternatives_found) } binding.migrationMenu.visible() binding.skipManga.gone() @@ -139,7 +142,10 @@ class MigrationProcessHolder( root.setOnClickListener(null) } - private fun MigrationMangaCardBinding.attachManga(manga: Manga, source: Source) { + private fun MigrationMangaCardBinding.attachManga( + manga: Manga, + source: Source + ) { loadingGroup.gone() GlideApp.with(view.context.applicationContext) .load(manga.toMangaThumbnail()) @@ -148,18 +154,20 @@ class MigrationProcessHolder( .dontAnimate() .into(thumbnail) - title.text = manga.title.ifBlank { - view.context.getString(R.string.unknown) - } + title.text = + manga.title.ifBlank { + view.context.getString(R.string.unknown) + } gradient.visible() - mangaSourceLabel.text = if (source.id == MERGED_SOURCE_ID) { - MergedSource.MangaConfig.readFromUrl(gson, manga.url).children.map { - sourceManager.getOrStub(it.source).toString() - }.distinct().joinToString() - } else { - source.toString() - } + mangaSourceLabel.text = + if (source.id == MERGED_SOURCE_ID) { + MergedSource.MangaConfig.readFromUrl(gson, manga.url).children.map { + sourceManager.getOrStub(it.source).toString() + }.distinct().joinToString() + } else { + source.toString() + } val mangaChaptersDB = db.getChapters(manga).executeAsBlocking() mangaChapters.visible() @@ -167,15 +175,17 @@ class MigrationProcessHolder( val latestChapter = mangaChaptersDB.maxByOrNull { it.chapter_number }?.chapter_number ?: -1f if (latestChapter > 0f) { - mangaLastChapterLabel.text = root.context.getString( - R.string.latest_, - DecimalFormat("#.#").format(latestChapter) - ) + mangaLastChapterLabel.text = + root.context.getString( + R.string.latest_, + DecimalFormat("#.#").format(latestChapter) + ) } else { - mangaLastChapterLabel.text = root.context.getString( - R.string.latest_, - root.context.getString(R.string.unknown) - ) + mangaLastChapterLabel.text = + root.context.getString( + R.string.latest_, + root.context.getString(R.string.unknown) + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessItem.kt index df77a57cc5c1..cbfabf12eb61 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessItem.kt @@ -9,12 +9,14 @@ import eu.kanade.tachiyomi.R class MigrationProcessItem(val manga: MigratingManga) : AbstractFlexibleItem() { - override fun getLayoutRes(): Int { return R.layout.migration_process_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): MigrationProcessHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): MigrationProcessHolder { return MigrationProcessHolder(view, adapter as MigrationProcessAdapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoadStrategy.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoadStrategy.kt index f637e5b09f5e..0e4d6493d829 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoadStrategy.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoadStrategy.kt @@ -15,21 +15,25 @@ class ChapterLoadBySource { * Load strategy using unique chapter numbers with same scanlator preference. */ class ChapterLoadByNumber { - fun get(allChapters: List, selectedChapter: Chapter): List { + fun get( + allChapters: List, + selectedChapter: Chapter + ): List { val chapters = mutableListOf() val chaptersByNumber = allChapters.groupBy { it.chapter_number } for ((number, chaptersForNumber) in chaptersByNumber) { - val preferredChapter = when { - // Make sure the selected chapter is always present - number == selectedChapter.chapter_number -> selectedChapter - // If there is only one chapter for this number, use it - chaptersForNumber.size == 1 -> chaptersForNumber.first() - // Prefer a chapter of the same scanlator as the selected - else -> - chaptersForNumber.find { it.scanlator == selectedChapter.scanlator } - ?: chaptersForNumber.first() - } + val preferredChapter = + when { + // Make sure the selected chapter is always present + number == selectedChapter.chapter_number -> selectedChapter + // If there is only one chapter for this number, use it + chaptersForNumber.size == 1 -> chaptersForNumber.first() + // Prefer a chapter of the same scanlator as the selected + else -> + chaptersForNumber.find { it.scanlator == selectedChapter.scanlator } + ?: chaptersForNumber.first() + } chapters.add(preferredChapter) } return chapters.sortedBy { it.chapter_number } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/PageIndicatorTextView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/PageIndicatorTextView.kt index d3f4db747c82..676f8ae513af 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/PageIndicatorTextView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/PageIndicatorTextView.kt @@ -17,26 +17,29 @@ class PageIndicatorTextView( context: Context, attrs: AttributeSet? = null ) : AppCompatTextView(context, attrs) { - init { setTextColor(fillColor) } @SuppressLint("SetTextI18n") - override fun setText(text: CharSequence?, type: BufferType?) { + override fun setText( + text: CharSequence?, + type: BufferType? + ) { // Add spaces at the start & end of the text, otherwise the stroke is cut-off because it's // not taken into account when measuring the text (view's padding doesn't help). val currText = " $text " // Also add a bit of spacing between each character, as the stroke overlaps them - val finalText = SpannableString(currText.asIterable().joinToString("\u00A0")).apply { - // Apply text outline - setSpan(spanOutline, 1, length - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - - for (i in 1..lastIndex step 2) { - setSpan(ScaleXSpan(0.2f), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + val finalText = + SpannableString(currText.asIterable().joinToString("\u00A0")).apply { + // Apply text outline + setSpan(spanOutline, 1, length - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + + for (i in 1..lastIndex step 2) { + setSpan(ScaleXSpan(0.2f), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } } - } super.setText(finalText, BufferType.SPANNABLE) } @@ -46,9 +49,10 @@ class PageIndicatorTextView( private val strokeColor = Color.rgb(45, 45, 45) // A span object with text outlining properties - val spanOutline = OutlineSpan( - strokeColor = strokeColor, - strokeWidth = 4f - ) + val spanOutline = + OutlineSpan( + strokeColor = strokeColor, + strokeWidth = 4f + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index cc0354a5983f..f8c53c17ef9f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -61,10 +61,6 @@ import eu.kanade.tachiyomi.util.view.showBar import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.widget.SimpleAnimationListener import eu.kanade.tachiyomi.widget.SimpleSeekBarListener -import java.io.File -import java.util.concurrent.TimeUnit -import kotlin.math.abs -import kotlin.math.roundToLong import kotlinx.coroutines.delay import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.launchIn @@ -81,6 +77,10 @@ import rx.android.schedulers.AndroidSchedulers import rx.subscriptions.CompositeSubscription import timber.log.Timber import uy.kohesive.injekt.injectLazy +import java.io.File +import java.util.concurrent.TimeUnit +import kotlin.math.abs +import kotlin.math.roundToLong /** * Activity containing the reader of Tachiyomi. This activity is mostly a container of the @@ -88,7 +88,6 @@ import uy.kohesive.injekt.injectLazy */ @RequiresPresenter(ReaderPresenter::class) class ReaderActivity : BaseRxActivity() { - private val preferences by injectLazy() /** @@ -140,7 +139,11 @@ class ReaderActivity : BaseRxActivity() const val WEBTOON_HORIZ_LTR = 6 const val WEBTOON_HORIZ_RTL = 7 - fun newIntent(context: Context, manga: Manga, chapter: Chapter): Intent { + fun newIntent( + context: Context, + manga: Manga, + chapter: Chapter + ): Intent { return Intent(context, ReaderActivity::class.java).apply { putExtra("manga", manga.id) putExtra("chapter", chapter.id) @@ -214,15 +217,19 @@ class ReaderActivity : BaseRxActivity() if (interval == -1f) return val intervalMs = (interval * 1000).roundToLong() - val sub = Observable.interval(intervalMs, intervalMs, TimeUnit.MILLISECONDS) - .onBackpressureDrop() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - viewer.let { v -> - if (v is PagerViewer) v.moveToNext() - else if (v is WebtoonViewer) v.scrollDown() + val sub = + Observable.interval(intervalMs, intervalMs, TimeUnit.MILLISECONDS) + .onBackpressureDrop() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + viewer.let { v -> + if (v is PagerViewer) { + v.moveToNext() + } else if (v is WebtoonViewer) { + v.scrollDown() + } + } } - } autoscrollSubscription = sub exhSubscriptions += sub @@ -359,13 +366,19 @@ class ReaderActivity : BaseRxActivity() } // Init listeners on bottom menu - binding.pageSeekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { - if (viewer != null && fromUser) { - moveToPageIndex(value) + binding.pageSeekbar.setOnSeekBarChangeListener( + object : SimpleSeekBarListener() { + override fun onProgressChanged( + seekBar: SeekBar, + value: Int, + fromUser: Boolean + ) { + if (viewer != null && fromUser) { + moveToPageIndex(value) + } } } - }) + ) binding.leftChapter.setOnClickListener { if (viewer != null) { if (viewer is R2LPagerViewer) { @@ -502,10 +515,11 @@ class ReaderActivity : BaseRxActivity() binding.ehBoostPage.clicks() .onEach { viewer?.let { _ -> - val curPage = exh_currentPage() ?: run { - toast("This page cannot be boosted (invalid page)!") - return@let - } + val curPage = + exh_currentPage() ?: run { + toast("This page cannot be boosted (invalid page)!") + return@let + } if (curPage.status == Page.ERROR) { toast("Page failed to load, press the retry button instead!") @@ -547,12 +561,13 @@ class ReaderActivity : BaseRxActivity() // EXH --> private fun exh_currentPage(): ReaderPage? { - val currentPage = ( + val currentPage = ( - (viewer as? PagerViewer)?.currentPage - ?: (viewer as? WebtoonViewer)?.currentPage - ) as? ReaderPage - )?.index + ( + (viewer as? PagerViewer)?.currentPage + ?: (viewer as? WebtoonViewer)?.currentPage + ) as? ReaderPage + )?.index return currentPage?.let { presenter.viewerChaptersRelay.value.currChapter.pages?.getOrNull(it) } } // EXH <-- @@ -561,7 +576,10 @@ class ReaderActivity : BaseRxActivity() * Sets the visibility of the menu according to [visible] and with an optional parameter to * [animate] the views. */ - private fun setMenuVisibility(visible: Boolean, animate: Boolean = true) { + private fun setMenuVisibility( + visible: Boolean, + animate: Boolean = true + ) { menuVisible = visible if (visible) { if (preferences.fullscreen().get()) { @@ -573,12 +591,14 @@ class ReaderActivity : BaseRxActivity() if (animate) { val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top) - toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() { - override fun onAnimationStart(animation: Animation) { - // Fix status bar being translucent the first time it's opened. - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + toolbarAnimation.setAnimationListener( + object : SimpleAnimationListener() { + override fun onAnimationStart(animation: Animation) { + // Fix status bar being translucent the first time it's opened. + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + } } - }) + ) // EXH --> binding.header.startAnimation(toolbarAnimation) // EXH <-- @@ -599,11 +619,13 @@ class ReaderActivity : BaseRxActivity() if (animate) { val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_top) - toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() { - override fun onAnimationEnd(animation: Animation) { - binding.readerMenu.gone() + toolbarAnimation.setAnimationListener( + object : SimpleAnimationListener() { + override fun onAnimationEnd(animation: Animation) { + binding.readerMenu.gone() + } } - }) + ) // EXH --> binding.header.startAnimation(toolbarAnimation) // EXH <-- @@ -632,15 +654,16 @@ class ReaderActivity : BaseRxActivity() */ fun setManga(manga: Manga) { val prevViewer = viewer - val newViewer = when (presenter.getMangaViewer()) { - RIGHT_TO_LEFT -> R2LPagerViewer(this) - VERTICAL -> VerticalPagerViewer(this) - WEBTOON -> WebtoonViewer(this) - VERTICAL_PLUS -> WebtoonViewer(this, isContinuous = false) - WEBTOON_HORIZ_LTR -> WebtoonViewer(this, isContinuous = true, isHorizontal = true) - WEBTOON_HORIZ_RTL -> WebtoonViewer(this, isContinuous = true, isHorizontal = true, isReversed = true) - else -> L2RPagerViewer(this) - } + val newViewer = + when (presenter.getMangaViewer()) { + RIGHT_TO_LEFT -> R2LPagerViewer(this) + VERTICAL -> VerticalPagerViewer(this) + WEBTOON -> WebtoonViewer(this) + VERTICAL_PLUS -> WebtoonViewer(this, isContinuous = false) + WEBTOON_HORIZ_LTR -> WebtoonViewer(this, isContinuous = true, isHorizontal = true) + WEBTOON_HORIZ_RTL -> WebtoonViewer(this, isContinuous = true, isHorizontal = true, isReversed = true) + else -> L2RPagerViewer(this) + } // Destroy previous viewer if there was one if (prevViewer != null) { @@ -690,11 +713,12 @@ class ReaderActivity : BaseRxActivity() @Suppress("DEPRECATION") fun setProgressDialog(show: Boolean) { progressDialog?.dismiss() - progressDialog = if (show) { - ProgressDialog.show(this, null, getString(R.string.loading), true) - } else { - null - } + progressDialog = + if (show) { + ProgressDialog.show(this, null, getString(R.string.loading), true) + } else { + null + } } /** @@ -805,11 +829,12 @@ class ReaderActivity : BaseRxActivity() */ fun onShareImageResult(file: File) { val stream = file.getUriCompat(this) - val intent = Intent(Intent.ACTION_SEND).apply { - putExtra(Intent.EXTRA_STREAM, stream) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION - type = "image/*" - } + val intent = + Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_STREAM, stream) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION + type = "image/*" + } startActivity(Intent.createChooser(intent, getString(R.string.action_share))) } @@ -877,7 +902,6 @@ class ReaderActivity : BaseRxActivity() * Class that handles the user preferences of the reader. */ private inner class ReaderConfig { - /** * Initializes the reader subscriptions. */ @@ -932,23 +956,24 @@ class ReaderActivity : BaseRxActivity() * Forces the user preferred [orientation] on the activity. */ private fun setOrientation(orientation: Int) { - val newOrientation = when (orientation) { - // Lock in current orientation - 2 -> { - val currentOrientation = resources.configuration.orientation - if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) { - ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT - } else { - ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE + val newOrientation = + when (orientation) { + // Lock in current orientation + 2 -> { + val currentOrientation = resources.configuration.orientation + if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) { + ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT + } else { + ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE + } } + // Lock in portrait + 3 -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT + // Lock in landscape + 4 -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE + // Rotation free + else -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED } - // Lock in portrait - 3 -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT - // Lock in landscape - 4 -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE - // Rotation free - else -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - } if (newOrientation != requestedOrientation) { requestedOrientation = newOrientation @@ -975,10 +1000,11 @@ class ReaderActivity : BaseRxActivity() @TargetApi(Build.VERSION_CODES.P) private fun setCutoutShort(enabled: Boolean) { - window.attributes.layoutInDisplayCutoutMode = when (enabled) { - true -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES - false -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER - } + window.attributes.layoutInDisplayCutoutMode = + when (enabled) { + true -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + false -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + } } /** @@ -1028,15 +1054,16 @@ class ReaderActivity : BaseRxActivity() */ private fun setCustomBrightnessValue(value: Int) { // Calculate and set reader brightness. - val readerBrightness = when { - value > 0 -> { - value / 100f - } - value < 0 -> { - 0.01f + val readerBrightness = + when { + value > 0 -> { + value / 100f + } + value < 0 -> { + 0.01f + } + else -> WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE } - else -> WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE - } window.attributes = window.attributes.apply { screenBrightness = readerBrightness } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt index bfa0f73cb18f..9a7462df50ea 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt @@ -13,17 +13,16 @@ import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener import eu.kanade.tachiyomi.widget.SimpleSeekBarListener -import kotlin.math.abs import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample import uy.kohesive.injekt.injectLazy +import kotlin.math.abs /** * Color filter sheet to toggle custom filter and brightness overlay. */ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheetDialog(activity) { - private val preferences by injectLazy() private var sheetBehavior: BottomSheetBehavior<*>? = null @@ -77,50 +76,81 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet preferences.customBrightness().set(isChecked) } - binding.includedReaderColorFilter.colorFilterMode.onItemSelectedListener = IgnoreFirstSpinnerListener { position -> - preferences.colorFilterMode().set(position) - } + binding.includedReaderColorFilter.colorFilterMode.onItemSelectedListener = + IgnoreFirstSpinnerListener { position -> + preferences.colorFilterMode().set(position) + } binding.includedReaderColorFilter.colorFilterMode.setSelection(preferences.colorFilterMode().get(), false) - binding.includedReaderColorFilter.seekbarColorFilterAlpha.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { - if (fromUser) { - setColorValue(value, ALPHA_MASK, 24) + binding.includedReaderColorFilter.seekbarColorFilterAlpha.setOnSeekBarChangeListener( + object : SimpleSeekBarListener() { + override fun onProgressChanged( + seekBar: SeekBar, + value: Int, + fromUser: Boolean + ) { + if (fromUser) { + setColorValue(value, ALPHA_MASK, 24) + } } } - }) - - binding.includedReaderColorFilter.seekbarColorFilterRed.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { - if (fromUser) { - setColorValue(value, RED_MASK, 16) + ) + + binding.includedReaderColorFilter.seekbarColorFilterRed.setOnSeekBarChangeListener( + object : SimpleSeekBarListener() { + override fun onProgressChanged( + seekBar: SeekBar, + value: Int, + fromUser: Boolean + ) { + if (fromUser) { + setColorValue(value, RED_MASK, 16) + } } } - }) - - binding.includedReaderColorFilter.seekbarColorFilterGreen.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { - if (fromUser) { - setColorValue(value, GREEN_MASK, 8) + ) + + binding.includedReaderColorFilter.seekbarColorFilterGreen.setOnSeekBarChangeListener( + object : SimpleSeekBarListener() { + override fun onProgressChanged( + seekBar: SeekBar, + value: Int, + fromUser: Boolean + ) { + if (fromUser) { + setColorValue(value, GREEN_MASK, 8) + } } } - }) - - binding.includedReaderColorFilter.seekbarColorFilterBlue.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { - if (fromUser) { - setColorValue(value, BLUE_MASK, 0) + ) + + binding.includedReaderColorFilter.seekbarColorFilterBlue.setOnSeekBarChangeListener( + object : SimpleSeekBarListener() { + override fun onProgressChanged( + seekBar: SeekBar, + value: Int, + fromUser: Boolean + ) { + if (fromUser) { + setColorValue(value, BLUE_MASK, 0) + } } } - }) - - binding.includedReaderColorFilter.brightnessSeekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { - if (fromUser) { - preferences.customBrightnessValue().set(value) + ) + + binding.includedReaderColorFilter.brightnessSeekbar.setOnSeekBarChangeListener( + object : SimpleSeekBarListener() { + override fun onProgressChanged( + seekBar: SeekBar, + value: Int, + fromUser: Boolean + ) { + if (fromUser) { + preferences.customBrightnessValue().set(value) + } } } - }) + ) } override fun onStart() { @@ -134,7 +164,10 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet * @param enabled determines if seekBar gets enabled * @param view view of the dialog */ - private fun setColorFilterSeekBar(enabled: Boolean, view: View) = with(view) { + private fun setColorFilterSeekBar( + enabled: Boolean, + view: View + ) = with(view) { binding.includedReaderColorFilter!! binding.includedReaderColorFilter.seekbarColorFilterRed.isEnabled = enabled binding.includedReaderColorFilter.seekbarColorFilterGreen.isEnabled = enabled @@ -147,7 +180,10 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet * @param enabled value which determines if seekBar gets enabled * @param view view of the dialog */ - private fun setCustomBrightnessSeekBar(enabled: Boolean, view: View) = with(view) { + private fun setCustomBrightnessSeekBar( + enabled: Boolean, + view: View + ) = with(view) { binding.includedReaderColorFilter!!.brightnessSeekbar.isEnabled = enabled } @@ -156,7 +192,10 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet * @param color integer containing color information * @param view view of the dialog */ - fun setValues(color: Int, view: View): Array { + fun setValues( + color: Int, + view: View + ): Array { val alpha = getAlphaFromColor(color) val red = getRedFromColor(color) val green = getGreenFromColor(color) @@ -177,7 +216,10 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet * @param enabled determines if the subscription get (un)subscribed * @param view view of the dialog */ - private fun setCustomBrightness(enabled: Boolean, view: View) { + private fun setCustomBrightness( + enabled: Boolean, + view: View + ) { if (enabled) { preferences.customBrightnessValue().asFlow() .sample(100) @@ -195,7 +237,11 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet * From 1 to 100 it sets that value as brightness. * 0 sets system brightness and hides the overlay. */ - private fun setCustomBrightnessValue(value: Int, view: View, isDisabled: Boolean = false) = with(view) { + private fun setCustomBrightnessValue( + value: Int, + view: View, + isDisabled: Boolean = false + ) = with(view) { // Set black overlay visibility. if (value < 0) { binding.brightnessOverlay.visible() @@ -215,7 +261,10 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet * @param enabled determines if the subscription get (un)subscribed * @param view view of the dialog */ - private fun setColorFilter(enabled: Boolean, view: View) { + private fun setColorFilter( + enabled: Boolean, + view: View + ) { if (enabled) { preferences.colorFilterValue().asFlow() .sample(100) @@ -232,7 +281,10 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet * @param color hex of color. * @param view view of the dialog */ - private fun setColorFilterValue(@ColorInt color: Int, view: View) = with(view) { + private fun setColorFilterValue( + @ColorInt color: Int, + view: View + ) = with(view) { binding.colorOverlay.visible() binding.colorOverlay.setFilterColor(color, preferences.colorFilterMode().get()) setValues(color, view) @@ -244,7 +296,11 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet * @param mask contains hex mask of chosen color * @param bitShift amounts of bits that gets shifted to receive value */ - fun setColorValue(color: Int, mask: Long, bitShift: Int) { + fun setColorValue( + color: Int, + mask: Long, + bitShift: Int + ) { val currentColor = preferences.colorFilterValue().get() val updatedColor = (color shl bitShift) or (currentColor and mask.inv().toInt()) preferences.colorFilterValue().set(updatedColor) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterView.kt index 4468ed32d35d..4b3ed52e49af 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterView.kt @@ -12,21 +12,24 @@ class ReaderColorFilterView( context: Context, attrs: AttributeSet? = null ) : View(context, attrs) { - private val colorFilterPaint: Paint = Paint() - fun setFilterColor(color: Int, filterMode: Int) { + fun setFilterColor( + color: Int, + filterMode: Int + ) { colorFilterPaint.color = color - colorFilterPaint.xfermode = PorterDuffXfermode( - when (filterMode) { - 1 -> PorterDuff.Mode.MULTIPLY - 2 -> PorterDuff.Mode.SCREEN - 3 -> PorterDuff.Mode.OVERLAY - 4 -> PorterDuff.Mode.LIGHTEN - 5 -> PorterDuff.Mode.DARKEN - else -> PorterDuff.Mode.SRC_OVER - } - ) + colorFilterPaint.xfermode = + PorterDuffXfermode( + when (filterMode) { + 1 -> PorterDuff.Mode.MULTIPLY + 2 -> PorterDuff.Mode.SCREEN + 3 -> PorterDuff.Mode.OVERLAY + 4 -> PorterDuff.Mode.LIGHTEN + 5 -> PorterDuff.Mode.DARKEN + else -> PorterDuff.Mode.SRC_OVER + } + ) invalidate() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt index 40ac39a7a5d0..8b1ea3237357 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt @@ -16,7 +16,6 @@ class ReaderPageSheet( private val activity: ReaderActivity, private val page: ReaderPage ) : BottomSheetDialog(activity) { - /** * View used on this sheet. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index 3a9c036a4fc8..79c05e3dd314 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -32,9 +32,6 @@ import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.isOnline import eu.kanade.tachiyomi.util.updateCoverLastModified import exh.util.defaultReaderType -import java.io.File -import java.util.Date -import java.util.concurrent.TimeUnit import rx.Completable import rx.Observable import rx.Subscription @@ -43,6 +40,9 @@ import rx.schedulers.Schedulers import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.File +import java.util.Date +import java.util.concurrent.TimeUnit /** * Presenter used by the activity to perform background operations. @@ -55,7 +55,6 @@ class ReaderPresenter( private val preferences: PreferencesHelper = Injekt.get(), private val delayedTrackingStore: DelayedTrackingStore = Injekt.get() ) : BasePresenter() { - /** * The manga loaded in the reader. It can be null when instantiated for a short time. */ @@ -80,7 +79,8 @@ class ReaderPresenter( /** * Relay for currently active viewer chapters. */ - /* [EXH] private */ val viewerChaptersRelay = BehaviorRelay.create() + // [EXH] private + val viewerChaptersRelay = BehaviorRelay.create() /** * Relay used when loading prev/next chapter needed to lock the UI (with a dialog). @@ -95,32 +95,34 @@ class ReaderPresenter( val manga = manga!! val dbChapters = db.getChapters(manga).executeAsBlocking() - val selectedChapter = dbChapters.find { it.id == chapterId } - ?: error("Requested chapter of id $chapterId not found in chapter list") + val selectedChapter = + dbChapters.find { it.id == chapterId } + ?: error("Requested chapter of id $chapterId not found in chapter list") val chaptersForReader = if (preferences.skipRead() || preferences.skipFiltered()) { - val list = dbChapters - .filter { - if (preferences.skipRead() && it.read) { - return@filter false - } else if (preferences.skipFiltered()) { - if ( - (manga.readFilter == Manga.SHOW_READ && !it.read) || - (manga.readFilter == Manga.SHOW_UNREAD && it.read) || - ( - manga.downloadedFilter == Manga.SHOW_DOWNLOADED && - !downloadManager.isChapterDownloaded(it, manga) - ) || - (manga.bookmarkedFilter == Manga.SHOW_BOOKMARKED && !it.bookmark) - ) { + val list = + dbChapters + .filter { + if (preferences.skipRead() && it.read) { return@filter false + } else if (preferences.skipFiltered()) { + if ( + (manga.readFilter == Manga.SHOW_READ && !it.read) || + (manga.readFilter == Manga.SHOW_UNREAD && it.read) || + ( + manga.downloadedFilter == Manga.SHOW_DOWNLOADED && + !downloadManager.isChapterDownloaded(it, manga) + ) || + (manga.bookmarkedFilter == Manga.SHOW_BOOKMARKED && !it.bookmark) + ) { + return@filter false + } } - } - true - } - .toMutableList() + true + } + .toMutableList() val find = list.find { it.id == chapterId } if (find == null) { @@ -205,7 +207,10 @@ class ReaderPresenter( * Initializes this presenter with the given [mangaId] and [initialChapterId]. This method will * fetch the manga from the database and initialize the initial chapter. */ - fun init(mangaId: Long, initialChapterId: Long) { + fun init( + mangaId: Long, + initialChapterId: Long + ) { if (!needsInit()) return db.getManga(mangaId).asRxObservable() @@ -224,7 +229,10 @@ class ReaderPresenter( * Initializes this presenter with the given [manga] and [initialChapterId]. This method will * set the chapter loader, view subscriptions and trigger an initial load. */ - private fun init(manga: Manga, initialChapterId: Long) { + private fun init( + manga: Manga, + initialChapterId: Long + ) { if (!needsInit()) return this.manga = manga @@ -240,17 +248,18 @@ class ReaderPresenter( // Read chapterList from an io thread because it's retrieved lazily and would block main. activeChapterSubscription?.unsubscribe() - activeChapterSubscription = Observable - .fromCallable { chapterList.first { chapterId == it.chapter.id } } - .flatMap { getLoadObservable(loader!!, it) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeFirst( - { _, _ -> - // Ignore onNext event - }, - ReaderActivity::setInitialChapterError - ) + activeChapterSubscription = + Observable + .fromCallable { chapterList.first { chapterId == it.chapter.id } } + .flatMap { getLoadObservable(loader!!, it) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeFirst( + { _, _ -> + // Ignore onNext event + }, + ReaderActivity::setInitialChapterError + ) } /** @@ -298,11 +307,12 @@ class ReaderPresenter( Timber.d("Loading ${chapter.chapter.url}") activeChapterSubscription?.unsubscribe() - activeChapterSubscription = getLoadObservable(loader, chapter) - .toCompletable() - .onErrorComplete() - .subscribe() - .also(::add) + activeChapterSubscription = + getLoadObservable(loader, chapter) + .toCompletable() + .onErrorComplete() + .subscribe() + .also(::add) } /** @@ -316,17 +326,18 @@ class ReaderPresenter( Timber.d("Loading adjacent ${chapter.chapter.url}") activeChapterSubscription?.unsubscribe() - activeChapterSubscription = getLoadObservable(loader, chapter) - .doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) } - .doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) } - .subscribeFirst( - { view, _ -> - view.moveToPageIndex(0) - }, - { _, _ -> - // Ignore onError event, viewers handle that state - } - ) + activeChapterSubscription = + getLoadObservable(loader, chapter) + .doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) } + .doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) } + .subscribeFirst( + { view, _ -> + view.moveToPageIndex(0) + }, + { _, _ -> + // Ignore onError event, viewers handle that state + } + ) } /** @@ -490,7 +501,11 @@ class ReaderPresenter( /** * Saves the image of this [page] in the given [directory] and returns the file location. */ - private fun saveImage(page: ReaderPage, directory: File, manga: Manga): File { + private fun saveImage( + page: ReaderPage, + directory: File, + manga: Manga + ): File { val stream = page.stream!! val type = ImageUtil.findImageType(stream) ?: throw Exception("Not an image") @@ -500,9 +515,10 @@ class ReaderPresenter( // Build destination file. val filenameSuffix = " - ${page.number}.${type.extension}" - val filename = DiskUtil.buildValidFilename( - "${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize()) - ) + filenameSuffix + val filename = + DiskUtil.buildValidFilename( + "${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize()) + ) + filenameSuffix val destFile = File(directory, filename) stream().use { input -> @@ -526,11 +542,12 @@ class ReaderPresenter( notifier.onClear() // Pictures directory. - val destDir = File( - Environment.getExternalStorageDirectory().absolutePath + - File.separator + Environment.DIRECTORY_PICTURES + - File.separator + context.getString(R.string.app_name) - ) + val destDir = + File( + Environment.getExternalStorageDirectory().absolutePath + + File.separator + Environment.DIRECTORY_PICTURES + + File.separator + context.getString(R.string.app_name) + ) // Copy file in background. Observable.fromCallable { saveImage(page, destDir, manga) } @@ -609,7 +626,9 @@ class ReaderPresenter( * Results of the set as cover feature. */ enum class SetAsCoverResult { - Success, AddToLibraryFirst, Error + Success, + AddToLibraryFirst, + Error } /** @@ -617,6 +636,7 @@ class ReaderPresenter( */ sealed class SaveImageResult { class Success(val file: File) : SaveImageResult() + class Error(val error: Throwable) : SaveImageResult() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSeekBar.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSeekBar.kt index 0615c1f4f15a..e750ca27bcb3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSeekBar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSeekBar.kt @@ -9,11 +9,12 @@ import androidx.appcompat.widget.AppCompatSeekBar /** * Seekbar to show current chapter progress. */ -class ReaderSeekBar @JvmOverloads constructor( +class ReaderSeekBar +@JvmOverloads +constructor( context: Context, attrs: AttributeSet? = null ) : AppCompatSeekBar(context, attrs) { - /** * Whether the seekbar should draw from right to left. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt index adee2ca1fb36..3aaff853ee38 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt @@ -6,7 +6,6 @@ import android.widget.CompoundButton import android.widget.Spinner import androidx.annotation.ArrayRes import androidx.core.widget.NestedScrollView -import com.f2prateek.rx.preferences.Preference as RxPreference import com.google.android.material.bottomsheet.BottomSheetDialog import com.tfcporciuncula.flow.Preference import eu.kanade.tachiyomi.R @@ -19,15 +18,16 @@ import eu.kanade.tachiyomi.util.view.invisible import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener import uy.kohesive.injekt.injectLazy +import com.f2prateek.rx.preferences.Preference as RxPreference /** * Sheet to show reader and viewer preferences. */ class ReaderSettingsSheet(private val activity: ReaderActivity) : BottomSheetDialog(activity) { - private val preferences by injectLazy() private val binding = ReaderSettingsSheetBinding.inflate(layoutInflater) + init { // Use activity theme for this layout val view = binding.root @@ -54,16 +54,17 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : BottomSheetDia * Init general reader preferences. */ private fun initGeneralPreferences() { - binding.viewer.onItemSelectedListener = IgnoreFirstSpinnerListener { position -> - activity.presenter.setMangaViewer(position) - - val mangaViewer = activity.presenter.getMangaViewer() - if (mangaViewer == ReaderActivity.WEBTOON || mangaViewer == ReaderActivity.VERTICAL_PLUS || mangaViewer == ReaderActivity.WEBTOON_HORIZ_LTR || mangaViewer == ReaderActivity.WEBTOON_HORIZ_RTL) { - initWebtoonPreferences() - } else { - initPagerPreferences() + binding.viewer.onItemSelectedListener = + IgnoreFirstSpinnerListener { position -> + activity.presenter.setMangaViewer(position) + + val mangaViewer = activity.presenter.getMangaViewer() + if (mangaViewer == ReaderActivity.WEBTOON || mangaViewer == ReaderActivity.VERTICAL_PLUS || mangaViewer == ReaderActivity.WEBTOON_HORIZ_LTR || mangaViewer == ReaderActivity.WEBTOON_HORIZ_RTL) { + initWebtoonPreferences() + } else { + initPagerPreferences() + } } - } binding.viewer.setSelection(activity.presenter.manga?.viewer ?: 0, false) binding.rotationMode.bindToPreference(preferences.rotation(), 1) @@ -71,8 +72,9 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : BottomSheetDia binding.showPageNumber.bindToPreference(preferences.showPageNumber()) binding.fullscreen.bindToPreference(preferences.fullscreen()) - val hasDisplayCutout = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && - activity.window?.decorView?.rootWindowInsets?.displayCutout != null + val hasDisplayCutout = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && + activity.window?.decorView?.rootWindowInsets?.displayCutout != null if (hasDisplayCutout) { binding.cutoutShort.visible() binding.cutoutShort.bindToPreference(preferences.cutoutShort()) @@ -124,20 +126,28 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : BottomSheetDia /** * Binds a spinner to an int preference with an optional offset for the value. */ - private fun Spinner.bindToPreference(pref: RxPreference, offset: Int = 0) { - onItemSelectedListener = IgnoreFirstSpinnerListener { position -> - pref.set(position + offset) - } + private fun Spinner.bindToPreference( + pref: RxPreference, + offset: Int = 0 + ) { + onItemSelectedListener = + IgnoreFirstSpinnerListener { position -> + pref.set(position + offset) + } setSelection(pref.getOrDefault() - offset, false) } /** * Binds a spinner to an int preference with an optional offset for the value. */ - private fun Spinner.bindToPreference(pref: Preference, offset: Int = 0) { - onItemSelectedListener = IgnoreFirstSpinnerListener { position -> - pref.set(position + offset) - } + private fun Spinner.bindToPreference( + pref: Preference, + offset: Int = 0 + ) { + onItemSelectedListener = + IgnoreFirstSpinnerListener { position -> + pref.set(position + offset) + } setSelection(pref.get() - offset, false) } @@ -146,11 +156,15 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : BottomSheetDia * correlate with the [intValues] resource item (in arrays.xml), which is a * of int values that will be parsed here and applied to the preference. */ - private fun Spinner.bindToIntPreference(pref: Preference, @ArrayRes intValuesResource: Int) { + private fun Spinner.bindToIntPreference( + pref: Preference, + @ArrayRes intValuesResource: Int + ) { val intValues = resources.getStringArray(intValuesResource).map { it.toIntOrNull() } - onItemSelectedListener = IgnoreFirstSpinnerListener { position -> - pref.set(intValues[position]!!) - } + onItemSelectedListener = + IgnoreFirstSpinnerListener { position -> + pref.set(intValues[position]!!) + } setSelection(intValues.indexOf(pref.get()), false) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt index 2b9fe851d89b..a931ebcd0f5e 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt @@ -17,7 +17,6 @@ import java.io.File * Class used to show BigPictureStyle notifications */ class SaveImageNotifier(private val context: Context) { - /** * Notification builder. */ @@ -36,13 +35,14 @@ class SaveImageNotifier(private val context: Context) { * @param file image file containing downloaded page image. */ fun onComplete(file: File) { - val bitmap = GlideApp.with(context) - .asBitmap() - .load(file) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(true) - .submit(720, 1280) - .get() + val bitmap = + GlideApp.with(context) + .asBitmap() + .load(file) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + .submit(720, 1280) + .get() if (bitmap != null) { showCompleteNotification(file, bitmap) @@ -51,7 +51,10 @@ class SaveImageNotifier(private val context: Context) { } } - private fun showCompleteNotification(file: File, image: Bitmap) { + private fun showCompleteNotification( + file: File, + image: Bitmap + ) { with(notificationBuilder) { setContentTitle(context.getString(R.string.picture_saved)) setSmallIcon(R.drawable.ic_photo_24dp) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt index 9ebbb392db7a..7d7d73dfd70b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt @@ -28,7 +28,6 @@ class ChapterLoader( private val manga: Manga, private val source: Source ) { - /** * Returns a completable that assigns the page loader and loads the its pages. It just * completes if the chapter is already loaded. @@ -61,9 +60,10 @@ class ChapterLoader( // If the chapter is partially read, set the starting page to the last the user read // otherwise use the requested page. - if (!chapter.chapter.read /* --> EH */ || prefs - .eh_preserveReadingPosition() - .getOrDefault() /* <-- EH */ + if (!chapter.chapter.read /* --> EH */ || + prefs + .eh_preserveReadingPosition() + .getOrDefault() // <-- EH ) { chapter.requestedPage = chapter.chapter.last_page_read } @@ -101,29 +101,30 @@ class ChapterLoader( return when { isDownloaded -> DownloadPageLoader(chapter, manga, source, downloadManager) source is HttpSource -> HttpPageLoader(chapter, source) - source is LocalSource -> source.getFormat(chapter.chapter).let { format -> - when (format) { - is LocalSource.Format.Directory -> DirectoryPageLoader(format.file) - is LocalSource.Format.Rar -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - RarPageLoader(format.file.openReadOnlyChannel(context).toInputStream()) - } else { - RarPageLoader(format.file) - } - is LocalSource.Format.Zip -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - ZipPageLoader(format.file.openReadOnlyChannel(context)) - } else { - ZipPageLoaderCompat(format.file) - } - is LocalSource.Format.Epub -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - EpubPageLoader(format.file.openReadOnlyChannel(context)) - } else { - EpubPageLoaderCompat(format.file) - } + source is LocalSource -> + source.getFormat(chapter.chapter).let { format -> + when (format) { + is LocalSource.Format.Directory -> DirectoryPageLoader(format.file) + is LocalSource.Format.Rar -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + RarPageLoader(format.file.openReadOnlyChannel(context).toInputStream()) + } else { + RarPageLoader(format.file) + } + is LocalSource.Format.Zip -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + ZipPageLoader(format.file.openReadOnlyChannel(context)) + } else { + ZipPageLoaderCompat(format.file) + } + is LocalSource.Format.Epub -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + EpubPageLoader(format.file.openReadOnlyChannel(context)) + } else { + EpubPageLoaderCompat(format.file) + } + } } - } else -> error("Loader not implemented") } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt index 015ad9d57227..f94eb377f751 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt @@ -4,15 +4,14 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.system.ImageUtil +import rx.Observable import java.io.File import java.io.FileInputStream -import rx.Observable /** * Loader used to load a chapter from a directory given on [file]. */ class DirectoryPageLoader(val file: File) : PageLoader() { - /** * Returns an observable containing the pages found on this directory ordered with a natural * comparator. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt index 838242d22b84..d1b96a5c42af 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt @@ -20,7 +20,6 @@ class DownloadPageLoader( private val source: Source, private val downloadManager: DownloadManager ) : PageLoader() { - /** * The application context. Needed to open input streams. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt index 8d3f670a5d47..882c07c82854 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt @@ -3,15 +3,14 @@ package eu.kanade.tachiyomi.ui.reader.loader import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.util.storage.EpubFile +import rx.Observable import java.io.File import java.nio.channels.SeekableByteChannel -import rx.Observable /** * Loader used to load a chapter from a .epub file. */ class EpubPageLoader(private val epub: EpubFile) : PageLoader() { - constructor(channel: SeekableByteChannel) : this(EpubFile(channel)) constructor(file: File) : this(EpubFile(file)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoaderCompat.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoaderCompat.kt index 320a496bace1..6b4e3be31e54 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoaderCompat.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoaderCompat.kt @@ -3,14 +3,13 @@ package eu.kanade.tachiyomi.ui.reader.loader import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.util.storage.EpubFileCompat -import java.io.File import rx.Observable +import java.io.File /** * Loader used to load a chapter from a .epub file. */ class EpubPageLoaderCompat(private val epub: EpubFileCompat) : PageLoader() { - constructor(file: File) : this(EpubFileCompat(file)) /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt index 54786a0857a4..af717f2b8d86 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt @@ -13,9 +13,6 @@ import eu.kanade.tachiyomi.util.lang.plusAssign import eu.kanade.tachiyomi.util.system.ImageUtil import exh.EH_SOURCE_ID import exh.EXH_SOURCE_ID -import java.util.concurrent.PriorityBlockingQueue -import java.util.concurrent.atomic.AtomicInteger -import kotlin.math.min import rx.Completable import rx.Observable import rx.schedulers.Schedulers @@ -26,6 +23,9 @@ import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.util.concurrent.PriorityBlockingQueue +import java.util.concurrent.atomic.AtomicInteger +import kotlin.math.min /** * Loader used to load chapters from an online source. @@ -55,24 +55,25 @@ class HttpPageLoader( // EXH --> repeat(prefs.eh_readerThreads().get()) { // EXH <-- - subscriptions += Observable.defer { Observable.just(queue.take().page) } - .filter { it.status == Page.QUEUE } - .concatMap { - source.fetchImageFromCacheThenNet(it).doOnNext { - XLog.d("Downloaded page: ${it.number}!") - } - } - .repeat() - .subscribeOn(Schedulers.io()) - .subscribe( - { - }, - { error -> - if (error !is InterruptedException) { - Timber.e(error) + subscriptions += + Observable.defer { Observable.just(queue.take().page) } + .filter { it.status == Page.QUEUE } + .concatMap { + source.fetchImageFromCacheThenNet(it).doOnNext { + XLog.d("Downloaded page: ${it.number}!") } } - ) + .repeat() + .subscribeOn(Schedulers.io()) + .subscribe( + { + }, + { error -> + if (error !is InterruptedException) { + Timber.e(error) + } + } + ) // EXH --> } // EXH <-- @@ -110,15 +111,18 @@ class HttpPageLoader( .getPageListFromCache(chapter.chapter) .onErrorResumeNext { source.fetchPageList(chapter.chapter) } .map { pages -> - val rp = pages.mapIndexed { index, page -> - // Don't trust sources and use our own indexing - ReaderPage(index, page.url, page.imageUrl) - } + val rp = + pages.mapIndexed { index, page -> + // Don't trust sources and use our own indexing + ReaderPage(index, page.url, page.imageUrl) + } if (prefs.eh_aggressivePageLoading().get()) { rp.mapNotNull { if (it.status == Page.QUEUE) { PriorityPage(it, 0) - } else null + } else { + null + } }.forEach { queue.offer(it) } } rp @@ -169,7 +173,10 @@ class HttpPageLoader( * Preloads the given [amount] of pages after the [currentPage] with a lower priority. * @return a list of [PriorityPage] that were added to the [queue] */ - private fun preloadNextPages(currentPage: ReaderPage, amount: Int): List { + private fun preloadNextPages( + currentPage: ReaderPage, + amount: Int + ): List { val pageIndex = currentPage.index val pages = currentPage.chapter.pages ?: return emptyList() if (pageIndex == pages.lastIndex) return emptyList() @@ -179,7 +186,9 @@ class HttpPageLoader( .mapNotNull { if (it.status == Page.QUEUE) { PriorityPage(it, 0).apply { queue.offer(this) } - } else null + } else { + null + } } } @@ -197,9 +206,9 @@ class HttpPageLoader( } if (prefs.eh_readerInstantRetry().get()) // EXH <-- - { - boostPage(page) - } else { + { + boostPage(page) + } else { // EXH <-- queue.offer(PriorityPage(page, 2)) } @@ -212,7 +221,6 @@ class HttpPageLoader( val page: ReaderPage, val priority: Int ) : Comparable { - companion object { private val idGenerator = AtomicInteger() } @@ -285,9 +293,12 @@ class HttpPageLoader( if (readerTheme >= 3) { val stream = chapterCache.getImageFile(imageUrl).inputStream() val image = BitmapFactory.decodeStream(stream) - page.bg = ImageUtil.autoSetBackground( - image, readerTheme == 2, prefs.context - ) + page.bg = + ImageUtil.autoSetBackground( + image, + readerTheme == 2, + prefs.context + ) page.bgType = PagerPageHolder.getBGType(readerTheme, prefs.context) stream.close() } @@ -329,18 +340,19 @@ class HttpPageLoader( // EXH --> fun boostPage(page: ReaderPage) { if (page.status == Page.QUEUE) { - subscriptions += Observable.just(page) - .concatMap { source.fetchImageFromCacheThenNet(it) } - .subscribeOn(Schedulers.io()) - .subscribe( - { - }, - { error -> - if (error !is InterruptedException) { - Timber.e(error) + subscriptions += + Observable.just(page) + .concatMap { source.fetchImageFromCacheThenNet(it) } + .subscribeOn(Schedulers.io()) + .subscribe( + { + }, + { error -> + if (error !is InterruptedException) { + Timber.e(error) + } } - } - ) + ) } } // EXH <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt index de7e4e541041..2ef6eedee0f1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt @@ -9,7 +9,6 @@ import rx.Observable * method [recycle] is called. */ abstract class PageLoader { - /** * Whether this loader has been already recycled. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt index f4f7e4737492..200106fe84f3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt @@ -6,18 +6,17 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.system.ImageUtil +import rx.Observable import java.io.File import java.io.InputStream import java.io.PipedInputStream import java.io.PipedOutputStream import java.util.concurrent.Executors -import rx.Observable /** * Loader used to load a chapter from a .rar or .cbr file. */ class RarPageLoader(private val archive: Archive) : PageLoader() { - constructor(inputStream: InputStream) : this(Archive(inputStream)) constructor(file: File) : this(Archive(file)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt index f817d863a5a5..62bb3c7c61df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt @@ -4,16 +4,15 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.system.ImageUtil -import java.io.File -import java.nio.channels.SeekableByteChannel import org.apache.commons.compress.archivers.zip.ZipFile import rx.Observable +import java.io.File +import java.nio.channels.SeekableByteChannel /** * Loader used to load a chapter from a .zip or .cbz file. */ class ZipPageLoader(private val zip: ZipFile) : PageLoader() { - constructor(channel: SeekableByteChannel) : this(ZipFile(channel)) constructor(file: File) : this(ZipFile(file)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoaderCompat.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoaderCompat.kt index bd6c33b245ac..ecb297bfd3eb 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoaderCompat.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoaderCompat.kt @@ -4,15 +4,14 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.system.ImageUtil +import rx.Observable import java.io.File import java.util.zip.ZipFile -import rx.Observable /** * Loader used to load a chapter from a .zip or .cbz file. */ class ZipPageLoaderCompat(private val zip: ZipFile) : PageLoader() { - constructor(file: File) : this(ZipFile(file)) /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ChapterTransition.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ChapterTransition.kt index 96105e6346b5..6418cffce05d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ChapterTransition.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ChapterTransition.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.ui.reader.model sealed class ChapterTransition { - abstract val from: ReaderChapter abstract val to: ReaderChapter? diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderChapter.kt index fe3285e9b798..42a63fcb5c38 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderChapter.kt @@ -6,13 +6,12 @@ import eu.kanade.tachiyomi.ui.reader.loader.PageLoader import timber.log.Timber data class ReaderChapter(val chapter: Chapter) { - var state: State = State.Wait set(value) { - field = value - stateRelay.call(value) - } + field = value + stateRelay.call(value) + } private val stateRelay by lazy { BehaviorRelay.create(state) } @@ -46,8 +45,11 @@ data class ReaderChapter(val chapter: Chapter) { sealed class State { object Wait : State() + object Loading : State() + class Error(val error: Throwable) : State() + class Loaded(val pages: List) : State() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt index 768cf1b8a97e..4a2daa68488a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt @@ -13,8 +13,6 @@ class ReaderPage( var bgType: Int? = null, // SY <-- var stream: (() -> InputStream)? = null - ) : Page(index, url, imageUrl, null) { - lateinit var chapter: ReaderChapter } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ViewerChapters.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ViewerChapters.kt index 9d43048409c4..cb0aa0de6444 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ViewerChapters.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ViewerChapters.kt @@ -5,7 +5,6 @@ data class ViewerChapters( val prevChapter: ReaderChapter?, val nextChapter: ReaderChapter? ) { - fun ref() { currChapter.ref() prevChapter?.ref() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/BaseViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/BaseViewer.kt index 223cb087f5ac..5f3190f52b76 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/BaseViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/BaseViewer.kt @@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters * Interface for implementing a viewer. */ interface BaseViewer { - /** * Returns the view this viewer uses. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/GestureDetectorWithLongTap.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/GestureDetectorWithLongTap.kt index 52e1ab9bcefc..3e5d8396ef02 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/GestureDetectorWithLongTap.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/GestureDetectorWithLongTap.kt @@ -15,7 +15,6 @@ open class GestureDetectorWithLongTap( context: Context, listener: Listener ) : GestureDetector(context, listener) { - private val handler = Handler() private val slop = ViewConfiguration.get(context).scaledTouchSlop private val longTapTime = ViewConfiguration.getLongPressTimeout().toLong() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt index 64853058c20e..7ad59a5ef2f2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt @@ -6,12 +6,18 @@ import kotlin.math.floor private val pattern = Regex("""\d+""") -fun hasMissingChapters(higherReaderChapter: ReaderChapter?, lowerReaderChapter: ReaderChapter?): Boolean { +fun hasMissingChapters( + higherReaderChapter: ReaderChapter?, + lowerReaderChapter: ReaderChapter? +): Boolean { if (higherReaderChapter == null || lowerReaderChapter == null) return false return hasMissingChapters(higherReaderChapter.chapter, lowerReaderChapter.chapter) } -fun hasMissingChapters(higherChapter: Chapter?, lowerChapter: Chapter?): Boolean { +fun hasMissingChapters( + higherChapter: Chapter?, + lowerChapter: Chapter? +): Boolean { if (higherChapter == null || lowerChapter == null) return false // Check if name contains a number that is potential chapter number if (!pattern.containsMatchIn(higherChapter.name) || !pattern.containsMatchIn(lowerChapter.name)) return false @@ -20,17 +26,26 @@ fun hasMissingChapters(higherChapter: Chapter?, lowerChapter: Chapter?): Boolean return hasMissingChapters(higherChapter.chapter_number, lowerChapter.chapter_number) } -fun hasMissingChapters(higherChapterNumber: Float, lowerChapterNumber: Float): Boolean { +fun hasMissingChapters( + higherChapterNumber: Float, + lowerChapterNumber: Float +): Boolean { if (higherChapterNumber < 0f || lowerChapterNumber < 0f) return false return calculateChapterDifference(higherChapterNumber, lowerChapterNumber) > 0f } -fun calculateChapterDifference(higherReaderChapter: ReaderChapter?, lowerReaderChapter: ReaderChapter?): Float { +fun calculateChapterDifference( + higherReaderChapter: ReaderChapter?, + lowerReaderChapter: ReaderChapter? +): Float { if (higherReaderChapter == null || lowerReaderChapter == null) return 0f return calculateChapterDifference(higherReaderChapter.chapter, lowerReaderChapter.chapter) } -fun calculateChapterDifference(higherChapter: Chapter?, lowerChapter: Chapter?): Float { +fun calculateChapterDifference( + higherChapter: Chapter?, + lowerChapter: Chapter? +): Float { if (higherChapter == null || lowerChapter == null) return 0f // Check if name contains a number that is potential chapter number if (!pattern.containsMatchIn(higherChapter.name) || !pattern.containsMatchIn(lowerChapter.name)) return 0f @@ -39,7 +54,10 @@ fun calculateChapterDifference(higherChapter: Chapter?, lowerChapter: Chapter?): return calculateChapterDifference(higherChapter.chapter_number, lowerChapter.chapter_number) } -fun calculateChapterDifference(higherChapterNumber: Float, lowerChapterNumber: Float): Float { +fun calculateChapterDifference( + higherChapterNumber: Float, + lowerChapterNumber: Float +): Float { if (higherChapterNumber < 0f || lowerChapterNumber < 0f) return 0f return floor(higherChapterNumber) - floor(lowerChapterNumber) - 1f } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressBar.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressBar.kt index c3c9fda920b0..d3fd436b82bf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressBar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressBar.kt @@ -24,12 +24,13 @@ import kotlin.math.min * the feedback to the user that the application isn't 'stuck', and by making it determinate the * user also approximately knows how much the operation will take. */ -class ReaderProgressBar @JvmOverloads constructor( +class ReaderProgressBar +@JvmOverloads +constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { - /** * The current sweep angle. It always starts at 10% because otherwise the bar and the rotation * wouldn't be visible. @@ -44,12 +45,13 @@ class ReaderProgressBar @JvmOverloads constructor( /** * The paint to use to draw the progress bar. */ - private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = context.getResourceColor(R.attr.colorAccent) - isAntiAlias = true - strokeCap = Paint.Cap.ROUND - style = Paint.Style.STROKE - } + private val paint = + Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = context.getResourceColor(R.attr.colorAccent) + isAntiAlias = true + strokeCap = Paint.Cap.ROUND + style = Paint.Style.STROKE + } /** * The rectangle of the canvas where the progress bar should be drawn. This is calculated on @@ -62,9 +64,12 @@ class ReaderProgressBar @JvmOverloads constructor( */ private val rotationAnimation by lazy { RotateAnimation( - 0f, 360f, - Animation.RELATIVE_TO_SELF, 0.5f, - Animation.RELATIVE_TO_SELF, 0.5f + 0f, + 360f, + Animation.RELATIVE_TO_SELF, + 0.5f, + Animation.RELATIVE_TO_SELF, + 0.5f ).apply { interpolator = LinearInterpolator() repeatCount = Animation.INFINITE @@ -75,7 +80,13 @@ class ReaderProgressBar @JvmOverloads constructor( /** * Called when the view is layout. The position and thickness of the progress bar is calculated. */ - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + override fun onLayout( + changed: Boolean, + left: Int, + top: Int, + right: Int, + bottom: Int + ) { super.onLayout(changed, left, top, right, bottom) val diameter = min(width, height) @@ -162,16 +173,18 @@ class ReaderProgressBar @JvmOverloads constructor( ObjectAnimator.ofFloat(this, "alpha", 1f, 0f).apply { interpolator = DecelerateInterpolator() duration = 1000 - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - gone() - alpha = 1f - } - - override fun onAnimationCancel(animation: Animator) { - alpha = 1f + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + gone() + alpha = 1f + } + + override fun onAnimationCancel(animation: Animator) { + alpha = 1f + } } - }) + ) start() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt index 8e8d7b1b657d..81fa39da8058 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt @@ -11,10 +11,12 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.databinding.ReaderTransitionViewBinding import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition -class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class ReaderTransitionView +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) { - private val binding: ReaderTransitionViewBinding + init { binding = ReaderTransitionViewBinding.inflate(LayoutInflater.from(context), this, true) layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) @@ -39,14 +41,16 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At binding.lowerText.isVisible = hasPrevChapter if (hasPrevChapter) { binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START - binding.upperText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_current)) } - append("\n${transition.from.chapter.name}") - } - binding.lowerText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_previous)) } - append("\n${prevChapter!!.chapter.name}") - } + binding.upperText.text = + buildSpannedString { + bold { append(context.getString(R.string.transition_current)) } + append("\n${transition.from.chapter.name}") + } + binding.lowerText.text = + buildSpannedString { + bold { append(context.getString(R.string.transition_previous)) } + append("\n${prevChapter!!.chapter.name}") + } } else { binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER binding.upperText.text = context.getString(R.string.transition_no_previous) @@ -63,14 +67,16 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At binding.lowerText.isVisible = hasNextChapter if (hasNextChapter) { binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START - binding.upperText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_finished)) } - append("\n${transition.from.chapter.name}") - } - binding.lowerText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_next)) } - append("\n${nextChapter!!.chapter.name}") - } + binding.upperText.text = + buildSpannedString { + bold { append(context.getString(R.string.transition_finished)) } + append("\n${transition.from.chapter.name}") + } + binding.lowerText.text = + buildSpannedString { + bold { append(context.getString(R.string.transition_next)) } + append("\n${nextChapter!!.chapter.name}") + } } else { binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER binding.upperText.text = context.getString(R.string.transition_no_next) @@ -83,22 +89,29 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At return } - val hasMissingChapters = when (transition) { - is ChapterTransition.Prev -> hasMissingChapters(transition.from, transition.to) - is ChapterTransition.Next -> hasMissingChapters(transition.to, transition.from) - } + val hasMissingChapters = + when (transition) { + is ChapterTransition.Prev -> hasMissingChapters(transition.from, transition.to) + is ChapterTransition.Next -> hasMissingChapters(transition.to, transition.from) + } if (!hasMissingChapters) { binding.warning.isVisible = false return } - val chapterDifference = when (transition) { - is ChapterTransition.Prev -> calculateChapterDifference(transition.from, transition.to) - is ChapterTransition.Next -> calculateChapterDifference(transition.to, transition.from) - } + val chapterDifference = + when (transition) { + is ChapterTransition.Prev -> calculateChapterDifference(transition.from, transition.to) + is ChapterTransition.Next -> calculateChapterDifference(transition.to, transition.from) + } - binding.warningText.text = resources.getQuantityString(R.plurals.missing_chapters_warning, chapterDifference.toInt(), chapterDifference.toInt()) + binding.warningText.text = + resources.getQuantityString( + R.plurals.missing_chapters_warning, + chapterDifference.toInt(), + chapterDifference.toInt() + ) binding.warning.isVisible = true } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt index 7520458216e4..5a5db0d9b9ba 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.flow.onEach * Common configuration for all viewers. */ abstract class ViewerConfig(preferences: PreferencesHelper) { - private val scope = CoroutineScope(Job() + Dispatchers.Main) var imagePropertyChangedListener: (() -> Unit)? = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/Pager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/Pager.kt index 85fa20621c1d..7ad8932aa002 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/Pager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/Pager.kt @@ -16,7 +16,6 @@ open class Pager( context: Context, isHorizontal: Boolean = true ) : DirectionalViewPager(context, isHorizontal) { - /** * Tap listener function to execute when a tap is detected. */ @@ -30,19 +29,20 @@ open class Pager( /** * Gesture listener that implements tap and long tap events. */ - private val gestureListener = object : GestureDetectorWithLongTap.Listener() { - override fun onSingleTapConfirmed(ev: MotionEvent): Boolean { - tapListener?.invoke(ev) - return true - } + private val gestureListener = + object : GestureDetectorWithLongTap.Listener() { + override fun onSingleTapConfirmed(ev: MotionEvent): Boolean { + tapListener?.invoke(ev) + return true + } - override fun onLongTapConfirmed(ev: MotionEvent) { - val listener = longTapListener - if (listener != null && listener.invoke(ev)) { - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + override fun onLongTapConfirmed(ev: MotionEvent) { + val listener = longTapListener + if (listener != null && listener.invoke(ev)) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + } } } - } /** * Gesture detector which handles motion events. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerButton.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerButton.kt index 57cef1c8a01a..f56b56015e16 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerButton.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerButton.kt @@ -11,7 +11,6 @@ import androidx.appcompat.widget.AppCompatButton */ @SuppressLint("ViewConstructor") class PagerButton(context: Context, viewer: PagerViewer) : AppCompatButton(context) { - init { setOnTouchListener { _, event -> viewer.pager.setGestureDetectorEnabled(false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt index d114049f2e00..35140f80e7fa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt @@ -10,7 +10,6 @@ import uy.kohesive.injekt.api.get */ class PagerConfig(private val viewer: PagerViewer, preferences: PreferencesHelper = Injekt.get()) : ViewerConfig(preferences) { - var usePageTransitions = false private set @@ -48,23 +47,27 @@ class PagerConfig(private val viewer: PagerViewer, preferences: PreferencesHelpe } private fun zoomTypeFromPreference(value: Int) { - imageZoomType = when (value) { - // Auto - 1 -> when (viewer) { - is L2RPagerViewer -> ZoomType.Left - is R2LPagerViewer -> ZoomType.Right + imageZoomType = + when (value) { + // Auto + 1 -> + when (viewer) { + is L2RPagerViewer -> ZoomType.Left + is R2LPagerViewer -> ZoomType.Right + else -> ZoomType.Center + } + // Left + 2 -> ZoomType.Left + // Right + 3 -> ZoomType.Right + // Center else -> ZoomType.Center } - // Left - 2 -> ZoomType.Left - // Right - 3 -> ZoomType.Right - // Center - else -> ZoomType.Center - } } enum class ZoomType { - Left, Center, Right + Left, + Center, + Right } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index e068343b8a20..a975b6535034 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -41,8 +41,6 @@ import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.widget.ViewPagerAdapter import exh.util.isInNightMode -import java.io.InputStream -import java.util.concurrent.TimeUnit import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import rx.Observable @@ -50,6 +48,8 @@ import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import uy.kohesive.injekt.injectLazy +import java.io.InputStream +import java.util.concurrent.TimeUnit /** * View of the ViewPager that contains a page of a chapter. @@ -59,7 +59,6 @@ class PagerPageHolder( val viewer: PagerViewer, val page: ReaderPage ) : FrameLayout(viewer.activity), ViewPagerAdapter.PositionableView { - /** * Item that identifies this view. Needed by the adapter to not recreate views. */ @@ -133,9 +132,10 @@ class PagerPageHolder( statusSubscription?.unsubscribe() val loader = page.chapter.pageLoader ?: return - statusSubscription = loader.getPage(page) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { processStatus(it) } + statusSubscription = + loader.getPage(page) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { processStatus(it) } } /** @@ -144,12 +144,13 @@ class PagerPageHolder( private fun observeProgress() { progressSubscription?.unsubscribe() - progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS) - .map { page.progress } - .distinctUntilChanged() - .onBackpressureLatest() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { value -> progressBar.setProgress(value) } + progressSubscription = + Observable.interval(100, TimeUnit.MILLISECONDS) + .map { page.progress } + .distinctUntilChanged() + .onBackpressureLatest() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { value -> progressBar.setProgress(value) } } /** @@ -240,53 +241,55 @@ class PagerPageHolder( val streamFn = page.stream ?: return var openStream: InputStream? = null - readImageHeaderSubscription = Observable - .fromCallable { - val stream = streamFn().buffered(16) - openStream = stream + readImageHeaderSubscription = + Observable + .fromCallable { + val stream = streamFn().buffered(16) + openStream = stream - ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext { isAnimated -> - if (!isAnimated) { - // SY --> - if (viewer.config.readerTheme >= 3) { - val imageView = initSubsamplingImageView() - if (page.bg != null && page.bgType == getBGType( - viewer.config.readerTheme, - context - ) - ) { - imageView.setImage(ImageSource.inputStream(openStream!!)) - imageView.background = page.bg - } - // if the user switches to automatic when pages are already cached, the bg needs to be loaded - else { - val bytesArray = openStream!!.readBytes() - val bytesStream = bytesArray.inputStream() - imageView.setImage(ImageSource.inputStream(bytesStream)) - bytesStream.close() - - launchUI { - imageView.background = setBG(bytesArray) - page.bg = imageView.background - page.bgType = getBGType(viewer.config.readerTheme, context) + ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { isAnimated -> + if (!isAnimated) { + // SY --> + if (viewer.config.readerTheme >= 3) { + val imageView = initSubsamplingImageView() + if (page.bg != null && page.bgType == + getBGType( + viewer.config.readerTheme, + context + ) + ) { + imageView.setImage(ImageSource.inputStream(openStream!!)) + imageView.background = page.bg + } + // if the user switches to automatic when pages are already cached, the bg needs to be loaded + else { + val bytesArray = openStream!!.readBytes() + val bytesStream = bytesArray.inputStream() + imageView.setImage(ImageSource.inputStream(bytesStream)) + bytesStream.close() + + launchUI { + imageView.background = setBG(bytesArray) + page.bg = imageView.background + page.bgType = getBGType(viewer.config.readerTheme, context) + } } + } else { + initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!)) } + // SY <-- } else { - initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!)) + initImageView().setImage(openStream!!) } - // SY <-- - } else { - initImageView().setImage(openStream!!) } - } - // Keep the Rx stream alive to close the input stream only when unsubscribed - .flatMap { Observable.never() } - .doOnUnsubscribe { openStream?.close() } - .subscribe({}, {}) + // Keep the Rx stream alive to close the input stream only when unsubscribed + .flatMap { Observable.never() } + .doOnUnsubscribe { openStream?.close() } + .subscribe({}, {}) } // SY --> @@ -295,9 +298,12 @@ class PagerPageHolder( val preferences by injectLazy() ImageUtil.autoSetBackground( BitmapFactory.decodeByteArray( - bytesArray, 0, bytesArray.size + bytesArray, + 0, + bytesArray.size ), - preferences.readerTheme().get() == 3, context + preferences.readerTheme().get() == 3, + context ) } } @@ -333,9 +339,10 @@ class PagerPageHolder( private fun createProgressBar(): ReaderProgressBar { return ReaderProgressBar(context, null).apply { val size = 48.dpToPx - layoutParams = LayoutParams(size, size).apply { - gravity = Gravity.CENTER - } + layoutParams = + LayoutParams(size, size).apply { + gravity = Gravity.CENTER + } } } @@ -347,31 +354,34 @@ class PagerPageHolder( val config = viewer.config - subsamplingImageView = SubsamplingScaleImageView(context).apply { - layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) - setMaxTileSize(viewer.activity.maxBitmapSize) - setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER) - setDoubleTapZoomDuration(config.doubleTapAnimDuration) - setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) - setMinimumScaleType(config.imageScaleType) - setMinimumDpi(90) - setMinimumTileDpi(180) - setCropBorders(config.imageCropBorders) - setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() { - override fun onReady() { - when (config.imageZoomType) { - ZoomType.Left -> setScaleAndCenter(scale, PointF(0f, 0f)) - ZoomType.Right -> setScaleAndCenter(scale, PointF(sWidth.toFloat(), 0f)) - ZoomType.Center -> setScaleAndCenter(scale, center.also { it?.y = 0f }) - } - onImageDecoded() - } + subsamplingImageView = + SubsamplingScaleImageView(context).apply { + layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) + setMaxTileSize(viewer.activity.maxBitmapSize) + setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER) + setDoubleTapZoomDuration(config.doubleTapAnimDuration) + setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) + setMinimumScaleType(config.imageScaleType) + setMinimumDpi(90) + setMinimumTileDpi(180) + setCropBorders(config.imageCropBorders) + setOnImageEventListener( + object : SubsamplingScaleImageView.DefaultOnImageEventListener() { + override fun onReady() { + when (config.imageZoomType) { + ZoomType.Left -> setScaleAndCenter(scale, PointF(0f, 0f)) + ZoomType.Right -> setScaleAndCenter(scale, PointF(sWidth.toFloat(), 0f)) + ZoomType.Center -> setScaleAndCenter(scale, center.also { it?.y = 0f }) + } + onImageDecoded() + } - override fun onImageLoadError(e: Exception) { - onImageDecodeError() - } - }) - } + override fun onImageLoadError(e: Exception) { + onImageDecodeError() + } + } + ) + } addView(subsamplingImageView) return subsamplingImageView!! } @@ -382,23 +392,26 @@ class PagerPageHolder( private fun initImageView(): ImageView { if (imageView != null) return imageView!! - imageView = PhotoView(context, null).apply { - layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) - adjustViewBounds = true - setZoomTransitionDuration(viewer.config.doubleTapAnimDuration) - setScaleLevels(1f, 2f, 3f) - // Force 2 scale levels on double tap - setOnDoubleTapListener(object : GestureDetector.SimpleOnGestureListener() { - override fun onDoubleTap(e: MotionEvent): Boolean { - if (scale > 1f) { - setScale(1f, e.x, e.y, true) - } else { - setScale(2f, e.x, e.y, true) + imageView = + PhotoView(context, null).apply { + layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) + adjustViewBounds = true + setZoomTransitionDuration(viewer.config.doubleTapAnimDuration) + setScaleLevels(1f, 2f, 3f) + // Force 2 scale levels on double tap + setOnDoubleTapListener( + object : GestureDetector.SimpleOnGestureListener() { + override fun onDoubleTap(e: MotionEvent): Boolean { + if (scale > 1f) { + setScale(1f, e.x, e.y, true) + } else { + setScale(2f, e.x, e.y, true) + } + return true + } } - return true - } - }) - } + ) + } addView(imageView) return imageView!! } @@ -409,15 +422,17 @@ class PagerPageHolder( private fun initRetryButton(): PagerButton { if (retryButton != null) return retryButton!! - retryButton = PagerButton(context, viewer).apply { - layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - gravity = Gravity.CENTER - } - setText(R.string.action_retry) - setOnClickListener { - page.chapter.pageLoader?.retryPage(page) + retryButton = + PagerButton(context, viewer).apply { + layoutParams = + LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { + gravity = Gravity.CENTER + } + setText(R.string.action_retry) + setOnClickListener { + page.chapter.pageLoader?.retryPage(page) + } } - } addView(retryButton) return retryButton!! } @@ -430,16 +445,18 @@ class PagerPageHolder( val margins = 8.dpToPx - val decodeLayout = LinearLayout(context).apply { - gravity = Gravity.CENTER - orientation = LinearLayout.VERTICAL - } + val decodeLayout = + LinearLayout(context).apply { + gravity = Gravity.CENTER + orientation = LinearLayout.VERTICAL + } decodeErrorLayout = decodeLayout TextView(context).apply { - layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - setMargins(margins, margins, margins, margins) - } + layoutParams = + LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { + setMargins(margins, margins, margins, margins) + } gravity = Gravity.CENTER setText(R.string.decode_image_error) @@ -447,9 +464,10 @@ class PagerPageHolder( } PagerButton(context, viewer).apply { - layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - setMargins(margins, margins, margins, margins) - } + layoutParams = + LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { + setMargins(margins, margins, margins, margins) + } setText(R.string.action_retry) setOnClickListener { page.chapter.pageLoader?.retryPage(page) @@ -461,9 +479,10 @@ class PagerPageHolder( val imageUrl = page.imageUrl if (imageUrl.orEmpty().startsWith("http", true)) { PagerButton(context, viewer).apply { - layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - setMargins(margins, margins, margins, margins) - } + layoutParams = + LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { + setMargins(margins, margins, margins, margins) + } setText(R.string.action_open_in_web_view) setOnClickListener { val intent = WebViewActivity.newIntent(context, imageUrl!!) @@ -488,38 +507,45 @@ class PagerPageHolder( .skipMemoryCache(true) .diskCacheStrategy(DiskCacheStrategy.NONE) .transition(DrawableTransitionOptions.with(NoTransition.getFactory())) - .listener(object : RequestListener { - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target, - isFirstResource: Boolean - ): Boolean { - onImageDecodeError() - return false - } + .listener( + object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target, + isFirstResource: Boolean + ): Boolean { + onImageDecodeError() + return false + } - override fun onResourceReady( - resource: GifDrawable, - model: Any, - target: Target, - dataSource: DataSource, - isFirstResource: Boolean - ): Boolean { - resource.setLoopCount(GifDrawable.LOOP_INTRINSIC) - onImageDecoded() - return false + override fun onResourceReady( + resource: GifDrawable, + model: Any, + target: Target, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { + resource.setLoopCount(GifDrawable.LOOP_INTRINSIC) + onImageDecoded() + return false + } } - }) + ) .into(this) } // SY --> companion object { - fun getBGType(readerTheme: Int, context: Context): Int { + fun getBGType( + readerTheme: Int, + context: Context + ): Int { return if (readerTheme == 3) { if (context.isInNightMode()) 2 else 1 - } else 0 + } else { + 0 + } } } // SY <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt index 5c3d556f4e0e..c1b2e3728d73 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt @@ -26,7 +26,6 @@ class PagerTransitionHolder( val viewer: PagerViewer, val transition: ChapterTransition ) : LinearLayout(viewer.activity), ViewPagerAdapter.PositionableView { - /** * Item that identifies this view. Needed by the adapter to not recreate views. */ @@ -42,11 +41,12 @@ class PagerTransitionHolder( * View container of the current status of the transition page. Child views will be added * dynamically. */ - private var pagesContainer = LinearLayout(context).apply { - layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT) - orientation = VERTICAL - gravity = Gravity.CENTER - } + private var pagesContainer = + LinearLayout(context).apply { + layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT) + orientation = VERTICAL + gravity = Gravity.CENTER + } init { orientation = VERTICAL @@ -78,18 +78,19 @@ class PagerTransitionHolder( */ private fun observeStatus(chapter: ReaderChapter) { statusSubscription?.unsubscribe() - statusSubscription = chapter.stateObserver - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { state -> - pagesContainer.removeAllViews() - when (state) { - is ReaderChapter.State.Wait -> { + statusSubscription = + chapter.stateObserver + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { state -> + pagesContainer.removeAllViews() + when (state) { + is ReaderChapter.State.Wait -> { + } + is ReaderChapter.State.Loading -> setLoading() + is ReaderChapter.State.Error -> setError(state.error) + is ReaderChapter.State.Loaded -> setLoaded() } - is ReaderChapter.State.Loading -> setLoading() - is ReaderChapter.State.Error -> setError(state.error) - is ReaderChapter.State.Loaded -> setLoaded() } - } } /** @@ -98,10 +99,11 @@ class PagerTransitionHolder( private fun setLoading() { val progress = ProgressBar(context, null, android.R.attr.progressBarStyle) - val textView = AppCompatTextView(context).apply { - wrapContent() - setText(R.string.transition_pages_loading) - } + val textView = + AppCompatTextView(context).apply { + wrapContent() + setText(R.string.transition_pages_loading) + } pagesContainer.addView(progress) pagesContainer.addView(textView) @@ -118,21 +120,23 @@ class PagerTransitionHolder( * Sets the error state on the pages container. */ private fun setError(error: Throwable) { - val textView = AppCompatTextView(context).apply { - wrapContent() - text = context.getString(R.string.transition_pages_error, error.message) - } + val textView = + AppCompatTextView(context).apply { + wrapContent() + text = context.getString(R.string.transition_pages_error, error.message) + } - val retryBtn = PagerButton(context, viewer).apply { - wrapContent() - setText(R.string.action_retry) - setOnClickListener { - val toChapter = transition.to - if (toChapter != null) { - viewer.activity.requestPreloadChapter(toChapter) + val retryBtn = + PagerButton(context, viewer).apply { + wrapContent() + setText(R.string.action_retry) + setOnClickListener { + val toChapter = transition.to + if (toChapter != null) { + viewer.activity.requestPreloadChapter(toChapter) + } } } - } pagesContainer.addView(textView) pagesContainer.addView(retryBtn) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt index 5185ff29dd99..5876be9cd8c0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt @@ -21,7 +21,6 @@ import timber.log.Timber */ @Suppress("LeakingThis") abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { - /** * View pager used by this viewer. It's abstract to implement L2R, R2L and vertical pagers on * top of this class. @@ -41,7 +40,8 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { /** * Currently active item. It can be a chapter page or a chapter transition. */ - /* [EXH] private */ var currentPage: Any? = null + // [EXH] private + var currentPage: Any? = null /** * Viewer chapters to set when the pager enters idle mode. Otherwise, if the view was settling @@ -70,15 +70,17 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { pager.offscreenPageLimit = 1 pager.id = R.id.reader_pager pager.adapter = adapter - pager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { - override fun onPageSelected(position: Int) { - onPageChange(position) - } + pager.addOnPageChangeListener( + object : ViewPager.SimpleOnPageChangeListener() { + override fun onPageSelected(position: Int) { + onPageChange(position) + } - override fun onPageScrollStateChanged(state: Int) { - isIdle = state == ViewPager.SCROLL_STATE_IDLE + override fun onPageScrollStateChanged(state: Int) { + isIdle = state == ViewPager.SCROLL_STATE_IDLE + } } - }) + ) pager.tapListener = { event -> val positionX = event.x val positionY = event.y @@ -156,7 +158,10 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { * Called when a [ReaderPage] is marked as active. It notifies the * activity of the change and requests the preload of the next chapter if this is the last page. */ - private fun onReaderPageSelected(page: ReaderPage, allowPreload: Boolean) { + private fun onReaderPageSelected( + page: ReaderPage, + allowPreload: Boolean + ) { val pages = page.chapter.pages ?: return Timber.d("onReaderPageSelected: ${page.number}/${pages.size}") activity.onPageSelected(page) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt index d20bea3e600e..54933c9340fa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt @@ -14,7 +14,6 @@ import timber.log.Timber * Pager adapter used by this [viewer] to where [ViewerChapters] updates are posted. */ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { - /** * List of currently set items. */ @@ -25,12 +24,16 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { private set var currentChapter: ReaderChapter? = null + /** * Updates this adapter with the given [chapters]. It handles setting a few pages of the * next/previous chapter to allow seamless transitions and inverting the pages if the viewer * has R2L direction. */ - fun setChapters(chapters: ViewerChapters, forceTransition: Boolean) { + fun setChapters( + chapters: ViewerChapters, + forceTransition: Boolean + ) { val newItems = mutableListOf() // Forces chapter transition if there is missing chapters @@ -61,14 +64,15 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { currentChapter = chapters.currChapter // Add next chapter transition and pages. - nextTransition = ChapterTransition.Next(chapters.currChapter, chapters.nextChapter) - .also { - if (nextHasMissingChapters || forceTransition || - chapters.nextChapter?.state !is ReaderChapter.State.Loaded - ) { - newItems.add(it) + nextTransition = + ChapterTransition.Next(chapters.currChapter, chapters.nextChapter) + .also { + if (nextHasMissingChapters || forceTransition || + chapters.nextChapter?.state !is ReaderChapter.State.Loaded + ) { + newItems.add(it) + } } - } if (chapters.nextChapter != null) { // Add at most two pages, because this chapter will be selected before the user can @@ -97,7 +101,10 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { /** * Creates a new view for the item at the given [position]. */ - override fun createView(container: ViewGroup, position: Int): View { + override fun createView( + container: ViewGroup, + position: Int + ): View { return when (val item = items[position]) { is ReaderPage -> PagerPageHolder(viewer, item) is ChapterTransition -> PagerTransitionHolder(viewer, item) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt index 53ae68417fd8..91bb0fe49177 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt @@ -13,8 +13,9 @@ import eu.kanade.tachiyomi.ui.reader.viewer.hasMissingChapters /** * RecyclerView Adapter used by this [viewer] to where [ViewerChapters] updates are posted. */ -class WebtoonAdapter(val viewer: WebtoonViewer) : androidx.recyclerview.widget.RecyclerView.Adapter() { - +class WebtoonAdapter( + val viewer: WebtoonViewer +) : androidx.recyclerview.widget.RecyclerView.Adapter() { /** * List of currently set items. */ @@ -22,11 +23,15 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : androidx.recyclerview.widget.R private set var currentChapter: ReaderChapter? = null + /** * Updates this adapter with the given [chapters]. It handles setting a few pages of the * next/previous chapter to allow seamless transitions. */ - fun setChapters(chapters: ViewerChapters, forceTransition: Boolean) { + fun setChapters( + chapters: ViewerChapters, + forceTransition: Boolean + ) { val newItems = mutableListOf() // Forces chapter transition if there is missing chapters @@ -96,7 +101,10 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : androidx.recyclerview.widget.R /** * Creates a new view holder for an item with the given [viewType]. */ - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): androidx.recyclerview.widget.RecyclerView.ViewHolder { return when (viewType) { PAGE_VIEW -> { val view = FrameLayout(parent.context) @@ -113,7 +121,10 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : androidx.recyclerview.widget.R /** * Binds an existing view [holder] with the item at the given [position]. */ - override fun onBindViewHolder(holder: androidx.recyclerview.widget.RecyclerView.ViewHolder, position: Int) { + override fun onBindViewHolder( + holder: androidx.recyclerview.widget.RecyclerView.ViewHolder, + position: Int + ) { val item = items[position] when (holder) { is WebtoonPageHolder -> holder.bind(item as ReaderPage) @@ -138,11 +149,13 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : androidx.recyclerview.widget.R private val oldItems: List, private val newItems: List ) : DiffUtil.Callback() { - /** * Returns true if these two items are the same. */ - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + override fun areItemsTheSame( + oldItemPosition: Int, + newItemPosition: Int + ): Boolean { val oldItem = oldItems[oldItemPosition] val newItem = newItems[newItemPosition] @@ -152,7 +165,10 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : androidx.recyclerview.widget.R /** * Returns true if the contents of the items are the same. */ - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + override fun areContentsTheSame( + oldItemPosition: Int, + newItemPosition: Int + ): Boolean { return true } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonBaseHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonBaseHolder.kt index 10a01b1377aa..b0b7307e9865 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonBaseHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonBaseHolder.kt @@ -10,7 +10,6 @@ abstract class WebtoonBaseHolder( view: View, protected val viewer: WebtoonViewer ) : BaseViewHolder(view) { - /** * Context getter because it's used often. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt index 6f108b892c79..86c169478ce3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt @@ -9,7 +9,6 @@ import uy.kohesive.injekt.api.get * Configuration used by webtoon viewers. */ class WebtoonConfig(preferences: PreferencesHelper = Injekt.get()) : ViewerConfig(preferences) { - var imageCropBorders = false private set diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonFrame.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonFrame.kt index b80c16fdec19..f6f55076464a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonFrame.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonFrame.kt @@ -14,7 +14,6 @@ import android.widget.FrameLayout * TODO consider integrating this class into [WebtoonViewer]. */ class WebtoonFrame(context: Context) : FrameLayout(context) { - /** * Scale detector, either with pinch or quick scale. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonLayoutManager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonLayoutManager.kt index 1f64ebf1672c..b2132e91b081 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonLayoutManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonLayoutManager.kt @@ -13,8 +13,11 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity * This layout manager uses the same package name as the support library in order to use a package * protected method. */ -class WebtoonLayoutManager(activity: ReaderActivity, orientation: Int = RecyclerView.VERTICAL, reverse: Boolean = false) : LinearLayoutManager(activity, orientation, reverse) { - +class WebtoonLayoutManager( + activity: ReaderActivity, + orientation: Int = RecyclerView.VERTICAL, + reverse: Boolean = false +) : LinearLayoutManager(activity, orientation, reverse) { /** * Extra layout space is set to half the screen height. */ @@ -42,13 +45,14 @@ class WebtoonLayoutManager(activity: ReaderActivity, orientation: Int = Recycler val fromIndex = childCount - 1 val toIndex = -1 - val child = if (mOrientation == HORIZONTAL) { - mHorizontalBoundCheck - .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0) - } else { - mVerticalBoundCheck - .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0) - } + val child = + if (mOrientation == HORIZONTAL) { + mHorizontalBoundCheck + .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0) + } else { + mVerticalBoundCheck + .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0) + } return if (child == null) NO_POSITION else getPosition(child) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt index 878ce005001e..8b2a849fdba0 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt @@ -32,12 +32,12 @@ import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.visible -import java.io.InputStream -import java.util.concurrent.TimeUnit import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers +import java.io.InputStream +import java.util.concurrent.TimeUnit /** * Holder of the webtoon reader for a single page of a chapter. @@ -50,7 +50,6 @@ class WebtoonPageHolder( private val frame: FrameLayout, viewer: WebtoonViewer ) : WebtoonBaseHolder(frame, viewer) { - /** * Loading progress bar to indicate the current progress. */ @@ -123,15 +122,16 @@ class WebtoonPageHolder( } private fun refreshLayoutParams() { - frame.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { - if (!viewer.isContinuous) { - bottomMargin = 15.dpToPx - } + frame.layoutParams = + FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { + if (!viewer.isContinuous) { + bottomMargin = 15.dpToPx + } - val margin = Resources.getSystem().displayMetrics.widthPixels * (viewer.config.sidePadding / 100f) - marginEnd = margin.toInt() - marginStart = margin.toInt() - } + val margin = Resources.getSystem().displayMetrics.widthPixels * (viewer.config.sidePadding / 100f) + marginEnd = margin.toInt() + marginStart = margin.toInt() + } } /** @@ -160,9 +160,10 @@ class WebtoonPageHolder( val page = page ?: return val loader = page.chapter.pageLoader ?: return - statusSubscription = loader.getPage(page) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { processStatus(it) } + statusSubscription = + loader.getPage(page) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { processStatus(it) } addSubscription(statusSubscription) } @@ -175,12 +176,13 @@ class WebtoonPageHolder( val page = page ?: return - progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS) - .map { page.progress } - .distinctUntilChanged() - .onBackpressureLatest() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { value -> progressBar.setProgress(value) } + progressSubscription = + Observable.interval(100, TimeUnit.MILLISECONDS) + .map { page.progress } + .distinctUntilChanged() + .onBackpressureLatest() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { value -> progressBar.setProgress(value) } addSubscription(progressSubscription) } @@ -277,30 +279,31 @@ class WebtoonPageHolder( val streamFn = page?.stream ?: return var openStream: InputStream? = null - readImageHeaderSubscription = Observable - .fromCallable { - val stream = streamFn().buffered(16) - openStream = stream + readImageHeaderSubscription = + Observable + .fromCallable { + val stream = streamFn().buffered(16) + openStream = stream - ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext { isAnimated -> - if (!isAnimated) { - val subsamplingView = initSubsamplingImageView() - subsamplingView.visible() - subsamplingView.setImage(ImageSource.inputStream(openStream!!)) - } else { - val imageView = initImageView() - imageView.visible() - imageView.setImage(openStream!!) + ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF } - } - // Keep the Rx stream alive to close the input stream only when unsubscribed - .flatMap { Observable.never() } - .doOnUnsubscribe { openStream?.close() } - .subscribe({}, {}) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { isAnimated -> + if (!isAnimated) { + val subsamplingView = initSubsamplingImageView() + subsamplingView.visible() + subsamplingView.setImage(ImageSource.inputStream(openStream!!)) + } else { + val imageView = initImageView() + imageView.visible() + imageView.setImage(openStream!!) + } + } + // Keep the Rx stream alive to close the input stream only when unsubscribed + .flatMap { Observable.never() } + .doOnUnsubscribe { openStream?.close() } + .subscribe({}, {}) addSubscription(readImageHeaderSubscription) } @@ -336,13 +339,15 @@ class WebtoonPageHolder( progressContainer = FrameLayout(context) frame.addView(progressContainer, MATCH_PARENT, parentHeight) - val progress = ReaderProgressBar(context).apply { - val size = 48.dpToPx - layoutParams = FrameLayout.LayoutParams(size, size).apply { - gravity = Gravity.CENTER_HORIZONTAL - setMargins(0, parentHeight / 4, 0, 0) + val progress = + ReaderProgressBar(context).apply { + val size = 48.dpToPx + layoutParams = + FrameLayout.LayoutParams(size, size).apply { + gravity = Gravity.CENTER_HORIZONTAL + setMargins(0, parentHeight / 4, 0, 0) + } } - } progressContainer.addView(progress) return progress } @@ -355,23 +360,26 @@ class WebtoonPageHolder( val config = viewer.config - subsamplingImageView = WebtoonSubsamplingImageView(context).apply { - setMaxTileSize(viewer.activity.maxBitmapSize) - setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) - setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH) - setMinimumDpi(90) - setMinimumTileDpi(180) - setCropBorders(config.imageCropBorders) - setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() { - override fun onReady() { - onImageDecoded() - } - - override fun onImageLoadError(e: Exception) { - onImageDecodeError() - } - }) - } + subsamplingImageView = + WebtoonSubsamplingImageView(context).apply { + setMaxTileSize(viewer.activity.maxBitmapSize) + setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) + setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH) + setMinimumDpi(90) + setMinimumTileDpi(180) + setCropBorders(config.imageCropBorders) + setOnImageEventListener( + object : SubsamplingScaleImageView.DefaultOnImageEventListener() { + override fun onReady() { + onImageDecoded() + } + + override fun onImageLoadError(e: Exception) { + onImageDecodeError() + } + } + ) + } frame.addView(subsamplingImageView, MATCH_PARENT, MATCH_PARENT) return subsamplingImageView!! } @@ -382,9 +390,10 @@ class WebtoonPageHolder( private fun initImageView(): ImageView { if (imageView != null) return imageView!! - imageView = AppCompatImageView(context).apply { - adjustViewBounds = true - } + imageView = + AppCompatImageView(context).apply { + adjustViewBounds = true + } frame.addView(imageView, MATCH_PARENT, MATCH_PARENT) return imageView!! } @@ -399,10 +408,11 @@ class WebtoonPageHolder( frame.addView(retryContainer, MATCH_PARENT, parentHeight) AppCompatButton(context).apply { - layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - gravity = Gravity.CENTER_HORIZONTAL - setMargins(0, parentHeight / 4, 0, 0) - } + layoutParams = + FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { + gravity = Gravity.CENTER_HORIZONTAL + setMargins(0, parentHeight / 4, 0, 0) + } setText(R.string.action_retry) setOnClickListener { page?.let { it.chapter.pageLoader?.retryPage(it) } @@ -421,19 +431,22 @@ class WebtoonPageHolder( val margins = 8.dpToPx - val decodeLayout = LinearLayout(context).apply { - layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, parentHeight).apply { - setMargins(0, parentHeight / 6, 0, 0) + val decodeLayout = + LinearLayout(context).apply { + layoutParams = + LinearLayout.LayoutParams(MATCH_PARENT, parentHeight).apply { + setMargins(0, parentHeight / 6, 0, 0) + } + gravity = Gravity.CENTER_HORIZONTAL + orientation = LinearLayout.VERTICAL } - gravity = Gravity.CENTER_HORIZONTAL - orientation = LinearLayout.VERTICAL - } decodeErrorLayout = decodeLayout TextView(context).apply { - layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - setMargins(0, margins, 0, margins) - } + layoutParams = + LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { + setMargins(0, margins, 0, margins) + } gravity = Gravity.CENTER setText(R.string.decode_image_error) @@ -441,9 +454,10 @@ class WebtoonPageHolder( } AppCompatButton(context).apply { - layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - setMargins(0, margins, 0, margins) - } + layoutParams = + FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { + setMargins(0, margins, 0, margins) + } setText(R.string.action_retry) setOnClickListener { page?.let { it.chapter.pageLoader?.retryPage(it) } @@ -455,9 +469,10 @@ class WebtoonPageHolder( val imageUrl = page?.imageUrl if (imageUrl.orEmpty().startsWith("http", true)) { AppCompatButton(context).apply { - layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - setMargins(0, margins, 0, margins) - } + layoutParams = + FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { + setMargins(0, margins, 0, margins) + } setText(R.string.action_open_in_web_view) setOnClickListener { val intent = WebViewActivity.newIntent(context, imageUrl!!) @@ -493,29 +508,31 @@ class WebtoonPageHolder( .skipMemoryCache(true) .diskCacheStrategy(DiskCacheStrategy.NONE) .transition(DrawableTransitionOptions.with(NoTransition.getFactory())) - .listener(object : RequestListener { - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target, - isFirstResource: Boolean - ): Boolean { - onImageDecodeError() - return false - } - - override fun onResourceReady( - resource: GifDrawable, - model: Any, - target: Target?, - dataSource: DataSource, - isFirstResource: Boolean - ): Boolean { - resource.setLoopCount(GifDrawable.LOOP_INTRINSIC) - onImageDecoded() - return false + .listener( + object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target, + isFirstResource: Boolean + ): Boolean { + onImageDecodeError() + return false + } + + override fun onResourceReady( + resource: GifDrawable, + model: Any, + target: Target?, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { + resource.setLoopCount(GifDrawable.LOOP_INTRINSIC) + onImageDecoded() + return false + } } - }) + ) .into(this) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt index 91c839ec86f2..223d221f12d1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt @@ -17,12 +17,13 @@ import kotlin.math.abs /** * Implementation of a [RecyclerView] used by the webtoon reader. */ -open class WebtoonRecyclerView @JvmOverloads constructor( +open class WebtoonRecyclerView +@JvmOverloads +constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : RecyclerView(context, attrs, defStyle) { - private var isZooming = false private var atLastPosition = false private var atFirstPosition = false @@ -40,7 +41,10 @@ open class WebtoonRecyclerView @JvmOverloads constructor( var tapListener: ((MotionEvent) -> Unit)? = null var longTapListener: ((MotionEvent) -> Boolean)? = null - override fun onMeasure(widthSpec: Int, heightSpec: Int) { + override fun onMeasure( + widthSpec: Int, + heightSpec: Int + ) { halfWidth = MeasureSpec.getSize(widthSpec) / 2 halfHeight = MeasureSpec.getSize(heightSpec) / 2 if (!heightSet) { @@ -55,7 +59,10 @@ open class WebtoonRecyclerView @JvmOverloads constructor( return super.onTouchEvent(e) } - override fun onScrolled(dx: Int, dy: Int) { + override fun onScrolled( + dx: Int, + dy: Int + ) { super.onScrolled(dx, dy) val layoutManager = layoutManager lastVisibleItemPosition = @@ -112,24 +119,29 @@ open class WebtoonRecyclerView @JvmOverloads constructor( animatorSet.duration = ANIMATOR_DURATION_TIME.toLong() animatorSet.interpolator = DecelerateInterpolator() animatorSet.start() - animatorSet.addListener(object : Animator.AnimatorListener { - override fun onAnimationStart(animation: Animator) { - } + animatorSet.addListener( + object : Animator.AnimatorListener { + override fun onAnimationStart(animation: Animator) { + } - override fun onAnimationEnd(animation: Animator) { - isZooming = false - currentScale = toRate - } + override fun onAnimationEnd(animation: Animator) { + isZooming = false + currentScale = toRate + } - override fun onAnimationCancel(animation: Animator) { - } + override fun onAnimationCancel(animation: Animator) { + } - override fun onAnimationRepeat(animation: Animator) { + override fun onAnimationRepeat(animation: Animator) { + } } - }) + ) } - fun zoomFling(velocityX: Int, velocityY: Int): Boolean { + fun zoomFling( + velocityX: Int, + velocityY: Int + ): Boolean { if (currentScale <= 1f) return false val distanceTimeFactor = 0.4f @@ -157,7 +169,10 @@ open class WebtoonRecyclerView @JvmOverloads constructor( return true } - private fun zoomScrollBy(dx: Int, dy: Int) { + private fun zoomScrollBy( + dx: Int, + dy: Int + ) { if (dx != 0) { x = getPositionX(x + dx) } @@ -173,14 +188,20 @@ open class WebtoonRecyclerView @JvmOverloads constructor( fun onScale(scaleFactor: Float) { currentScale *= scaleFactor - currentScale = currentScale.coerceIn( - MIN_RATE, - MAX_SCALE_RATE - ) + currentScale = + currentScale.coerceIn( + MIN_RATE, + MAX_SCALE_RATE + ) setScaleRate(currentScale) - layoutParams.height = if (currentScale < 1) { (originalHeight / currentScale).toInt() } else { originalHeight } + layoutParams.height = + if (currentScale < 1) { + (originalHeight / currentScale).toInt() + } else { + originalHeight + } halfHeight = layoutParams.height / 2 if (currentScale != DEFAULT_RATE) { @@ -207,7 +228,6 @@ open class WebtoonRecyclerView @JvmOverloads constructor( } inner class GestureListener : GestureDetectorWithLongTap.Listener() { - override fun onSingleTapConfirmed(ev: MotionEvent): Boolean { tapListener?.invoke(ev) return false @@ -240,7 +260,6 @@ open class WebtoonRecyclerView @JvmOverloads constructor( } inner class Detector : GestureDetectorWithLongTap(context, listener) { - private var scrollPointerId = 0 private var downX = 0 private var downY = 0 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonSubsamplingImageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonSubsamplingImageView.kt index 88f916161dee..575b972ef17a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonSubsamplingImageView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonSubsamplingImageView.kt @@ -9,11 +9,12 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView * Implementation of subsampling scale image view that ignores all touch events, because the * webtoon viewer handles all the gestures. */ -class WebtoonSubsamplingImageView @JvmOverloads constructor( +class WebtoonSubsamplingImageView +@JvmOverloads +constructor( context: Context, attrs: AttributeSet? = null ) : SubsamplingScaleImageView(context, attrs) { - override fun onTouchEvent(event: MotionEvent): Boolean { return false } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt index 6c6aff73a0c7..1233008ec734 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt @@ -23,7 +23,6 @@ class WebtoonTransitionHolder( val layout: LinearLayout, viewer: WebtoonViewer ) : WebtoonBaseHolder(layout, viewer) { - /** * Subscription for status changes of the transition page. */ @@ -35,10 +34,11 @@ class WebtoonTransitionHolder( * View container of the current status of the transition page. Child views will be added * dynamically. */ - private var pagesContainer = LinearLayout(context).apply { - orientation = LinearLayout.VERTICAL - gravity = Gravity.CENTER - } + private var pagesContainer = + LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + gravity = Gravity.CENTER + } init { layout.layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) @@ -50,9 +50,10 @@ class WebtoonTransitionHolder( layout.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical) val childMargins = 16.dpToPx - val childParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { - setMargins(0, childMargins, 0, childMargins) - } + val childParams = + LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { + setMargins(0, childMargins, 0, childMargins) + } layout.addView(transitionView) layout.addView(pagesContainer, childParams) @@ -78,22 +79,26 @@ class WebtoonTransitionHolder( * Observes the status of the page list of the next/previous chapter. Whenever there's a new * state, the pages container is cleaned up before setting the new state. */ - private fun observeStatus(chapter: ReaderChapter, transition: ChapterTransition) { + private fun observeStatus( + chapter: ReaderChapter, + transition: ChapterTransition + ) { unsubscribeStatus() - statusSubscription = chapter.stateObserver - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { state -> - pagesContainer.removeAllViews() - when (state) { - is ReaderChapter.State.Wait -> { + statusSubscription = + chapter.stateObserver + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { state -> + pagesContainer.removeAllViews() + when (state) { + is ReaderChapter.State.Wait -> { + } + is ReaderChapter.State.Loading -> setLoading() + is ReaderChapter.State.Error -> setError(state.error, transition) + is ReaderChapter.State.Loaded -> setLoaded() } - is ReaderChapter.State.Loading -> setLoading() - is ReaderChapter.State.Error -> setError(state.error, transition) - is ReaderChapter.State.Loaded -> setLoaded() + pagesContainer.visibleIf { pagesContainer.childCount > 0 } } - pagesContainer.visibleIf { pagesContainer.childCount > 0 } - } addSubscription(statusSubscription) } @@ -112,10 +117,11 @@ class WebtoonTransitionHolder( private fun setLoading() { val progress = ProgressBar(context, null, android.R.attr.progressBarStyle) - val textView = AppCompatTextView(context).apply { - wrapContent() - setText(R.string.transition_pages_loading) - } + val textView = + AppCompatTextView(context).apply { + wrapContent() + setText(R.string.transition_pages_loading) + } pagesContainer.addView(progress) pagesContainer.addView(textView) @@ -131,22 +137,27 @@ class WebtoonTransitionHolder( /** * Sets the error state on the pages container. */ - private fun setError(error: Throwable, transition: ChapterTransition) { - val textView = AppCompatTextView(context).apply { - wrapContent() - text = context.getString(R.string.transition_pages_error, error.message) - } + private fun setError( + error: Throwable, + transition: ChapterTransition + ) { + val textView = + AppCompatTextView(context).apply { + wrapContent() + text = context.getString(R.string.transition_pages_error, error.message) + } - val retryBtn = AppCompatButton(context).apply { - wrapContent() - setText(R.string.action_retry) - setOnClickListener { - val toChapter = transition.to - if (toChapter != null) { - viewer.activity.requestPreloadChapter(toChapter) + val retryBtn = + AppCompatButton(context).apply { + wrapContent() + setText(R.string.action_retry) + setOnClickListener { + val toChapter = transition.to + if (toChapter != null) { + viewer.activity.requestPreloadChapter(toChapter) + } } } - } pagesContainer.addView(textView) pagesContainer.addView(retryBtn) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt index 1f0eee4df97c..8722a181997f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt @@ -14,16 +14,20 @@ import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.visible -import kotlin.math.max -import kotlin.math.min import rx.subscriptions.CompositeSubscription import timber.log.Timber +import kotlin.math.max +import kotlin.math.min /** * Implementation of a [BaseViewer] to display pages with a [RecyclerView]. */ -class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = true, val isHorizontal: Boolean = false, val isReversed: Boolean = false) : BaseViewer { - +class WebtoonViewer( + val activity: ReaderActivity, + val isContinuous: Boolean = true, + val isHorizontal: Boolean = false, + val isReversed: Boolean = false +) : BaseViewer { /** * Recycler view used by this viewer. */ @@ -37,7 +41,8 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr /** * Layout manager of the recycler view. */ - private val layoutManager = WebtoonLayoutManager(activity, if (isHorizontal) RecyclerView.HORIZONTAL else RecyclerView.VERTICAL, isReversed) + private val layoutManager = + WebtoonLayoutManager(activity, if (isHorizontal) RecyclerView.HORIZONTAL else RecyclerView.VERTICAL, isReversed) /** * Adapter of the recycler view. @@ -52,7 +57,8 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr /** * Currently active item. It can be a chapter page or a chapter transition. */ - /* [EXH] private */ var currentPage: Any? = null + // [EXH] private + var currentPage: Any? = null /** * Configuration used by this viewer, like allow taps, or crop image borders. @@ -70,28 +76,34 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr recycler.itemAnimator = null recycler.layoutManager = layoutManager recycler.adapter = adapter - recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - val position = layoutManager.findLastEndVisibleItemPosition() - val item = adapter.items.getOrNull(position) - val allowPreload = checkAllowPreload(item as? ReaderPage) - if (item != null && currentPage != item) { - currentPage = item - when (item) { - is ReaderPage -> onPageSelected(item, allowPreload) - is ChapterTransition -> onTransitionSelected(item) + recycler.addOnScrollListener( + object : RecyclerView.OnScrollListener() { + override fun onScrolled( + recyclerView: RecyclerView, + dx: Int, + dy: Int + ) { + val position = layoutManager.findLastEndVisibleItemPosition() + val item = adapter.items.getOrNull(position) + val allowPreload = checkAllowPreload(item as? ReaderPage) + if (item != null && currentPage != item) { + currentPage = item + when (item) { + is ReaderPage -> onPageSelected(item, allowPreload) + is ChapterTransition -> onTransitionSelected(item) + } } - } - if (dy < 0) { - val firstIndex = layoutManager.findFirstVisibleItemPosition() - val firstItem = adapter.items.getOrNull(firstIndex) - if (firstItem is ChapterTransition.Prev && firstItem.to != null) { - activity.requestPreloadChapter(firstItem.to) + if (dy < 0) { + val firstIndex = layoutManager.findFirstVisibleItemPosition() + val firstItem = adapter.items.getOrNull(firstIndex) + if (firstItem is ChapterTransition.Prev && firstItem.to != null) { + activity.requestPreloadChapter(firstItem.to) + } } } } - }) + ) recycler.tapListener = { event -> val positionX = event.rawX val positionY = event.rawY @@ -165,7 +177,10 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr * Called from the RecyclerView listener when a [page] is marked as active. It notifies the * activity of the change and requests the preload of the next chapter if this is the last page. */ - private fun onPageSelected(page: ReaderPage, allowPreload: Boolean) { + private fun onPageSelected( + page: ReaderPage, + allowPreload: Boolean + ) { val pages = page.chapter.pages ?: return Timber.d("onPageSelected: ${page.number}/${pages.size}") activity.onPageSelected(page) @@ -238,7 +253,8 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr /** * Scrolls down by [scrollDistance]. */ - /* [EXH] private */ fun scrollDown() { + // [EXH] private + fun scrollDown() { recycler.smoothScrollBy(0, scrollDistance) } @@ -268,11 +284,13 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.KEYCODE_PAGE_UP -> if (isUp) scrollUp() + KeyEvent.KEYCODE_PAGE_UP + -> if (isUp) scrollUp() KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_DOWN, - KeyEvent.KEYCODE_PAGE_DOWN -> if (isUp) scrollDown() + KeyEvent.KEYCODE_PAGE_DOWN + -> if (isUp) scrollDown() else -> return false } return true diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/DateSectionItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/DateSectionItem.kt index 7cacef7e0346..a26241b1079f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/DateSectionItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/DateSectionItem.kt @@ -11,16 +11,23 @@ import eu.kanade.tachiyomi.R import java.util.Date class DateSectionItem(val date: Date) : AbstractHeaderItem() { - override fun getLayoutRes(): Int { return R.layout.recent_section_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: List? + ) { holder.bind(this) } @@ -37,7 +44,6 @@ class DateSectionItem(val date: Date) : AbstractHeaderItem) : FlexibleViewHolder(view, adapter, true) { - private val now = Date().time val section_text: TextView = view.findViewById(R.id.section_text) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryAdapter.kt index e1d761025bee..225bfe1d5d1a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryAdapter.kt @@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.ui.recent.history import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.source.SourceManager +import uy.kohesive.injekt.injectLazy import java.text.DecimalFormat import java.text.DecimalFormatSymbols -import uy.kohesive.injekt.injectLazy /** * Adapter of HistoryHolder. @@ -17,7 +17,6 @@ import uy.kohesive.injekt.injectLazy */ class HistoryAdapter(controller: HistoryController) : FlexibleAdapter>(null, controller, true) { - val sourceManager by injectLazy() val resumeClickListener: OnResumeClickListener = controller @@ -27,11 +26,12 @@ class HistoryAdapter(controller: HistoryController) : /** * DecimalFormat used to display correct chapter number */ - val decimalFormat = DecimalFormat( - "#.###", - DecimalFormatSymbols() - .apply { decimalSeparator = '.' } - ) + val decimalFormat = + DecimalFormat( + "#.###", + DecimalFormatSymbols() + .apply { decimalSeparator = '.' } + ) init { setDisplayHeadersAtStartUp(true) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt index d79d196c20ee..d8f9c3096387 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt @@ -42,7 +42,6 @@ class HistoryController : HistoryAdapter.OnResumeClickListener, HistoryAdapter.OnItemClickListener, RemoveHistoryDialog.Listener { - init { setHasOptionsMenu(true) } @@ -67,7 +66,10 @@ class HistoryController : return HistoryPresenter() } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = HistoryControllerBinding.inflate(inflater) return binding.root } @@ -97,7 +99,10 @@ class HistoryController : * * @param mangaHistory list of manga history */ - fun onNextManga(mangaHistory: List, cleanBatch: Boolean = false) { + fun onNextManga( + mangaHistory: List, + cleanBatch: Boolean = false + ) { if (adapter?.itemCount ?: 0 == 0 || cleanBatch) { resetProgressItem() } @@ -130,7 +135,10 @@ class HistoryController : adapter?.setEndlessScrollListener(this, progressItem!!) } - override fun onLoadMore(lastPosition: Int, currentPage: Int) { + override fun onLoadMore( + lastPosition: Int, + currentPage: Int + ) { val view = view ?: return if (BackupRestoreService.isRunning(view.context.applicationContext)) { onAddPageError(Throwable()) @@ -165,7 +173,11 @@ class HistoryController : router.pushController(MangaController(manga).withFadeTransaction()) } - override fun removeHistory(manga: Manga, history: History, all: Boolean) { + override fun removeHistory( + manga: Manga, + history: History, + all: Boolean + ) { if (all) { // Reset last read of chapter to 0L presenter.removeAllFromHistory(manga.id!!) @@ -184,7 +196,10 @@ class HistoryController : } } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { inflater.inflate(R.menu.recently_read, menu) val searchItem = menu.findItem(R.id.action_search) val searchView = searchItem.actionView as SearchView @@ -203,15 +218,17 @@ class HistoryController : .launchIn(scope) // Fixes problem with the overflow icon showing up in lieu of search - searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { - override fun onMenuItemActionExpand(item: MenuItem): Boolean { - return true - } - - override fun onMenuItemActionCollapse(item: MenuItem): Boolean { - activity?.invalidateOptionsMenu() - return true + searchItem.setOnActionExpandListener( + object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem): Boolean { + return true + } + + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + activity?.invalidateOptionsMenu() + return true + } } - }) + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryHolder.kt index 1199502a6e07..e5a6df6f5392 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryHolder.kt @@ -24,8 +24,8 @@ class HistoryHolder( view: View, val adapter: HistoryAdapter ) : BaseFlexibleViewHolder(view, adapter) { - private val binding = HistoryItemBinding.bind(view) + init { binding.holder.setOnClickListener { adapter.itemClickListener.onItemClick(bindingAdapterPosition) @@ -54,8 +54,9 @@ class HistoryHolder( // Set source + chapter title val formattedNumber = adapter.decimalFormat.format(chapter.chapter_number.toDouble()) - binding.mangaSource.text = itemView.context.getString(R.string.recent_manga_source) - .format(adapter.sourceManager.getOrStub(manga.source).toString(), formattedNumber) + binding.mangaSource.text = + itemView.context.getString(R.string.recent_manga_source) + .format(adapter.sourceManager.getOrStub(manga.source).toString(), formattedNumber) // Set last read timestamp title binding.lastRead.text = Date(history.last_read).toTimestampString() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryItem.kt index 9a10529b2aba..706ff94f7083 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryItem.kt @@ -11,12 +11,14 @@ import eu.kanade.tachiyomi.ui.recent.DateSectionItem class HistoryItem(val mch: MangaChapterHistory, header: DateSectionItem) : AbstractSectionableItem(header) { - override fun getLayoutRes(): Int { return R.layout.history_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): HistoryHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): HistoryHolder { return HistoryHolder(view, adapter as HistoryAdapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt index 335234e2f3e8..c95f8008f8ec 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt @@ -9,13 +9,13 @@ import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.recent.DateSectionItem import eu.kanade.tachiyomi.util.lang.toDateKey +import rx.Observable +import rx.android.schedulers.AndroidSchedulers +import uy.kohesive.injekt.injectLazy import java.util.Calendar import java.util.Comparator import java.util.Date import java.util.TreeMap -import rx.Observable -import rx.android.schedulers.AndroidSchedulers -import uy.kohesive.injekt.injectLazy /** * Presenter of HistoryFragment. @@ -23,7 +23,6 @@ import uy.kohesive.injekt.injectLazy * Observable updates should be called from here. */ class HistoryPresenter : BasePresenter() { - /** * Used to connect to database */ @@ -38,7 +37,10 @@ class HistoryPresenter : BasePresenter() { updateList() } - fun requestNext(offset: Int, search: String = "") { + fun requestNext( + offset: Int, + search: String = "" + ) { lastCount = offset lastSearch = search getRecentMangaObservable((offset), search) @@ -54,7 +56,10 @@ class HistoryPresenter : BasePresenter() { * Get recent manga observable * @return list of history */ - private fun getRecentMangaObservable(offset: Int = 0, search: String = ""): Observable> { + private fun getRecentMangaObservable( + offset: Int = 0, + search: String = "" + ): Observable> { // Set date for recent manga val cal = Calendar.getInstance() cal.time = Date() @@ -63,8 +68,9 @@ class HistoryPresenter : BasePresenter() { return db.getRecentManga(cal.time, offset, search).asRxObservable() .map { recents -> val map = TreeMap> { d1, d2 -> d2.compareTo(d1) } - val byDay = recents - .groupByTo(map, { it.history.last_read.toDateKey() }) + val byDay = + recents + .groupByTo(map, { it.history.last_read.toDateKey() }) byDay.flatMap { val dateItem = DateSectionItem(it.key) it.value.map { HistoryItem(it, dateItem) } @@ -77,7 +83,10 @@ class HistoryPresenter : BasePresenter() { * Get recent manga observable * @return list of history */ - private fun getRecentMangaLimitObservable(offset: Int = 0, search: String = ""): Observable> { + private fun getRecentMangaLimitObservable( + offset: Int = 0, + search: String = "" + ): Observable> { // Set limit for recent manga val cal = Calendar.getInstance() cal.time = Date() @@ -86,8 +95,9 @@ class HistoryPresenter : BasePresenter() { return db.getRecentMangaLimit(cal.time, lastCount, search).asRxObservable() .map { recents -> val map = TreeMap> { d1, d2 -> d2.compareTo(d1) } - val byDay = recents - .groupByTo(map, { it.history.last_read.toDateKey() }) + val byDay = + recents + .groupByTo(map, { it.history.last_read.toDateKey() }) byDay.flatMap { entry -> val dateItem = DateSectionItem(entry.key) entry.value.map { HistoryItem(it, dateItem) } @@ -134,20 +144,25 @@ class HistoryPresenter : BasePresenter() { * @param chapter the chapter of the history object. * @param manga the manga of the chapter. */ - fun getNextChapter(chapter: Chapter, manga: Manga): Chapter? { + fun getNextChapter( + chapter: Chapter, + manga: Manga + ): Chapter? { if (!chapter.read) { return chapter } - val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) { - Manga.SORTING_SOURCE -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) } - Manga.SORTING_NUMBER -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) } - Manga.SORTING_UPLOAD_DATE -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) } - else -> throw NotImplementedError("Unknown sorting method") - } + val sortFunction: (Chapter, Chapter) -> Int = + when (manga.sorting) { + Manga.SORTING_SOURCE -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) } + Manga.SORTING_NUMBER -> { c1, c2 -> c1.chapter_number.compareTo(c2.chapter_number) } + Manga.SORTING_UPLOAD_DATE -> { c1, c2 -> c1.date_upload.compareTo(c2.date_upload) } + else -> throw NotImplementedError("Unknown sorting method") + } - val chapters = db.getChapters(manga).executeAsBlocking() - .sortedWith(Comparator { c1, c2 -> sortFunction(c1, c2) }) + val chapters = + db.getChapters(manga).executeAsBlocking() + .sortedWith(Comparator { c1, c2 -> sortFunction(c1, c2) }) val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id } return when (manga.sorting) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/RemoveHistoryDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/RemoveHistoryDialog.kt index faeb1373ce8d..56ed6e2ecd7b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/RemoveHistoryDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/RemoveHistoryDialog.kt @@ -12,8 +12,7 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.widget.DialogCheckboxView class RemoveHistoryDialog(bundle: Bundle? = null) : DialogController(bundle) - where T : Controller, T : RemoveHistoryDialog.Listener { - + where T : Controller, T : RemoveHistoryDialog.Listener { private var manga: Manga? = null private var history: History? = null @@ -28,10 +27,11 @@ class RemoveHistoryDialog(bundle: Bundle? = null) : DialogController(bundle) val activity = activity!! // Create custom view - val dialogCheckboxView = DialogCheckboxView(activity).apply { - setDescription(R.string.dialog_with_checkbox_remove_description) - setOptionDescription(R.string.dialog_with_checkbox_reset) - } + val dialogCheckboxView = + DialogCheckboxView(activity).apply { + setDescription(R.string.dialog_with_checkbox_remove_description) + setOptionDescription(R.string.dialog_with_checkbox_reset) + } return MaterialDialog(activity) .title(R.string.action_remove) @@ -49,6 +49,10 @@ class RemoveHistoryDialog(bundle: Bundle? = null) : DialogController(bundle) } interface Listener { - fun removeHistory(manga: Manga, history: History, all: Boolean) + fun removeHistory( + manga: Manga, + history: History, + all: Boolean + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/ConfirmDeleteChaptersDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/ConfirmDeleteChaptersDialog.kt index 4d4c0480693b..71d9f82d8e93 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/ConfirmDeleteChaptersDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/ConfirmDeleteChaptersDialog.kt @@ -8,8 +8,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.controller.DialogController class ConfirmDeleteChaptersDialog(bundle: Bundle? = null) : DialogController(bundle) - where T : Controller, T : ConfirmDeleteChaptersDialog.Listener { - + where T : Controller, T : ConfirmDeleteChaptersDialog.Listener { private var chaptersToDelete = emptyList() constructor(target: T, chaptersToDelete: List) : this() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesAdapter.kt index 0d0c7d9e8780..2b1fc3452ab7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesAdapter.kt @@ -5,7 +5,6 @@ import eu.davidea.flexibleadapter.items.IFlexible class UpdatesAdapter(val controller: UpdatesController) : FlexibleAdapter>(null, controller, true) { - val coverClickListener: OnCoverClickListener = controller init { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt index 58a747f33789..e0f3c0e49628 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt @@ -47,7 +47,6 @@ class UpdatesController : FlexibleAdapter.OnUpdateListener, ConfirmDeleteChaptersDialog.Listener, UpdatesAdapter.OnCoverClickListener { - /** * Action mode for multiple selection. */ @@ -71,7 +70,10 @@ class UpdatesController : return UpdatesPresenter() } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = UpdatesControllerBinding.inflate(inflater) return binding.root } @@ -117,7 +119,10 @@ class UpdatesController : super.onDestroyView(view) } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { inflater.inflate(R.menu.updates, menu) } @@ -150,7 +155,10 @@ class UpdatesController : * Called when item in list is clicked * @param position position of clicked item */ - override fun onItemClick(view: View, position: Int): Boolean { + override fun onItemClick( + view: View, + position: Int + ): Boolean { val adapter = adapter ?: return false // Get item from position @@ -314,13 +322,19 @@ class UpdatesController : * @param mode the ActionMode object * @param menu menu object of ActionMode */ - override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + override fun onCreateActionMode( + mode: ActionMode, + menu: Menu + ): Boolean { mode.menuInflater.inflate(R.menu.updates_chapter_selection, menu) adapter?.mode = SelectableAdapter.Mode.MULTI return true } - override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { + override fun onPrepareActionMode( + mode: ActionMode, + menu: Menu + ): Boolean { val count = adapter?.selectedItemCount ?: 0 if (count == 0) { // Destroy action mode if there are no items selected. @@ -342,7 +356,10 @@ class UpdatesController : * @param mode the ActionMode object * @param item item from ActionMode. */ - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + override fun onActionItemClicked( + mode: ActionMode, + item: MenuItem + ): Boolean { return onActionItemClicked(item) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt index 1c5dfd8d4198..33e0eea24eff 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt @@ -23,7 +23,6 @@ import eu.kanade.tachiyomi.util.system.getResourceColor */ class UpdatesHolder(private val view: View, private val adapter: UpdatesAdapter) : BaseFlexibleViewHolder(view, adapter) { - private val binding = UpdatesItemBinding.bind(view) private var readColor = view.context.getResourceColor(R.attr.colorOnSurface, 0.38f) @@ -84,59 +83,61 @@ class UpdatesHolder(private val view: View, private val adapter: UpdatesAdapter) * * @param status download status */ - fun notifyStatus(status: Int) = with(binding.downloadText) { - when (status) { - Download.QUEUE -> setText(R.string.chapter_queued) - Download.DOWNLOADING -> setText(R.string.chapter_downloading) - Download.DOWNLOADED -> setText(R.string.chapter_downloaded) - Download.ERROR -> setText(R.string.chapter_error) - else -> text = "" + fun notifyStatus(status: Int) = + with(binding.downloadText) { + when (status) { + Download.QUEUE -> setText(R.string.chapter_queued) + Download.DOWNLOADING -> setText(R.string.chapter_downloading) + Download.DOWNLOADED -> setText(R.string.chapter_downloaded) + Download.ERROR -> setText(R.string.chapter_error) + else -> text = "" + } } - } /** * Show pop up menu * * @param view view containing popup menu. */ - private fun showPopupMenu(view: View) = item?.let { item -> - // Create a PopupMenu, giving it the clicked view for an anchor - val popup = PopupMenu(view.context, view) - - // Inflate our menu resource into the PopupMenu's Menu - popup.menuInflater.inflate(R.menu.chapter_recent, popup.menu) - - // Hide download and show delete if the chapter is downloaded and - if (item.isDownloaded) { - popup.menu.findItem(R.id.action_download).isVisible = false - popup.menu.findItem(R.id.action_delete).isVisible = true - } + private fun showPopupMenu(view: View) = + item?.let { item -> + // Create a PopupMenu, giving it the clicked view for an anchor + val popup = PopupMenu(view.context, view) + + // Inflate our menu resource into the PopupMenu's Menu + popup.menuInflater.inflate(R.menu.chapter_recent, popup.menu) + + // Hide download and show delete if the chapter is downloaded and + if (item.isDownloaded) { + popup.menu.findItem(R.id.action_download).isVisible = false + popup.menu.findItem(R.id.action_delete).isVisible = true + } - // Hide mark as unread when the chapter is unread - if (!item.chapter.read /*&& mangaChapter.chapter.last_page_read == 0*/) { - popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false - } + // Hide mark as unread when the chapter is unread + if (!item.chapter.read /*&& mangaChapter.chapter.last_page_read == 0*/) { + popup.menu.findItem(R.id.action_mark_as_unread).isVisible = false + } - // Hide mark as read when the chapter is read - if (item.chapter.read) { - popup.menu.findItem(R.id.action_mark_as_read).isVisible = false - } + // Hide mark as read when the chapter is read + if (item.chapter.read) { + popup.menu.findItem(R.id.action_mark_as_read).isVisible = false + } - // Set a listener so we are notified if a menu item is clicked - popup.setOnMenuItemClickListener { menuItem -> - with(adapter.controller) { - when (menuItem.itemId) { - R.id.action_download -> downloadChapter(item) - R.id.action_delete -> deleteChapter(item) - R.id.action_mark_as_read -> markAsRead(listOf(item)) - R.id.action_mark_as_unread -> markAsUnread(listOf(item)) + // Set a listener so we are notified if a menu item is clicked + popup.setOnMenuItemClickListener { menuItem -> + with(adapter.controller) { + when (menuItem.itemId) { + R.id.action_download -> downloadChapter(item) + R.id.action_delete -> deleteChapter(item) + R.id.action_mark_as_read -> markAsRead(listOf(item)) + R.id.action_mark_as_unread -> markAsUnread(listOf(item)) + } } + + true } - true + // Finally show the PopupMenu + popup.show() } - - // Finally show the PopupMenu - popup.show() - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesItem.kt index ecfc486ecd6f..2b6329bf6035 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesItem.kt @@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.ui.recent.DateSectionItem class UpdatesItem(val chapter: Chapter, val manga: Manga, header: DateSectionItem) : AbstractSectionableItem(header) { - private var _status: Int = 0 var status: Int @@ -32,7 +31,10 @@ class UpdatesItem(val chapter: Chapter, val manga: Manga, header: DateSectionIte return R.layout.updates_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): UpdatesHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): UpdatesHolder { return UpdatesHolder(view, adapter as UpdatesAdapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesPresenter.kt index 6a3852202b2d..941daf6dd8a7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesPresenter.kt @@ -10,15 +10,15 @@ import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.recent.DateSectionItem import eu.kanade.tachiyomi.util.lang.toDateKey -import java.util.Calendar -import java.util.Date -import java.util.TreeMap import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.Calendar +import java.util.Date +import java.util.TreeMap class UpdatesPresenter( val preferences: PreferencesHelper = Injekt.get(), @@ -26,7 +26,6 @@ class UpdatesPresenter( private val downloadManager: DownloadManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get() ) : BasePresenter() { - /** * List containing chapter and manga information */ @@ -52,17 +51,19 @@ class UpdatesPresenter( */ private fun getUpdatesObservable(): Observable> { // Set date limit for recent chapters - val cal = Calendar.getInstance().apply { - time = Date() - add(Calendar.MONTH, -1) - } + val cal = + Calendar.getInstance().apply { + time = Date() + add(Calendar.MONTH, -1) + } return db.getRecentChapters(cal.time).asRxObservable() // Convert to a list of recent chapters. .map { mangaChapters -> val map = TreeMap> { d1, d2 -> d2.compareTo(d1) } - val byDay = mangaChapters - .groupByTo(map, { it.chapter.date_fetch.toDateKey() }) + val byDay = + mangaChapters + .groupByTo(map, { it.chapter.date_fetch.toDateKey() }) byDay.flatMap { entry -> val dateItem = DateSectionItem(entry.key) entry.value @@ -134,7 +135,10 @@ class UpdatesPresenter( * @param items list of selected chapters * @param read read status */ - fun markChapterRead(items: List, read: Boolean) { + fun markChapterRead( + items: List, + read: Boolean + ) { val chapters = items.map { it.chapter } chapters.forEach { it.read = read diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt index 3bd6539df8ea..613e35919a13 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt @@ -20,15 +20,14 @@ import eu.kanade.tachiyomi.util.preference.onClick import eu.kanade.tachiyomi.util.preference.preference import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.system.toast +import timber.log.Timber import java.text.DateFormat import java.text.ParseException import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone -import timber.log.Timber class SettingsAboutController : SettingsController() { - /** * Checks for new releases */ @@ -38,73 +37,75 @@ class SettingsAboutController : SettingsController() { private val isUpdaterEnabled = BuildConfig.INCLUDE_UPDATER - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.pref_category_about - - preference { - title = "GitHub" - val url = "https://github.com/az4521/TachiyomiAZ" - summary = url - onClick { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) - } - } - preference { - title = "Dev Build" - val url = "https://crafty.moe/tachiyomiAZ.apk" - summary = url - onClick { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + titleRes = R.string.pref_category_about + + preference { + title = "GitHub" + val url = "https://github.com/az4521/TachiyomiAZ" + summary = url + onClick { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivity(intent) + } } - } - preference { - titleRes = R.string.version - summary = if (BuildConfig.DEBUG) { - "r" + BuildConfig.COMMIT_COUNT - } else { - BuildConfig.VERSION_NAME + preference { + title = "Dev Build" + val url = "https://crafty.moe/tachiyomiAZ.apk" + summary = url + onClick { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivity(intent) + } } + preference { + titleRes = R.string.version + summary = + if (BuildConfig.DEBUG) { + "r" + BuildConfig.COMMIT_COUNT + } else { + BuildConfig.VERSION_NAME + } - if (isUpdaterEnabled) { - onClick { checkVersion() } + if (isUpdaterEnabled) { + onClick { checkVersion() } + } } - } - preference { - titleRes = R.string.build_time - summary = getFormattedBuildTime() + preference { + titleRes = R.string.build_time + summary = getFormattedBuildTime() - onClick { - ChangelogDialogController().showDialog(router) + onClick { + ChangelogDialogController().showDialog(router) + } } - } - preference { - titleRes = R.string.website - val url = "https://crafty.moe/tachiAZ.htm" - summary = url - onClick { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) + preference { + titleRes = R.string.website + val url = "https://crafty.moe/tachiAZ.htm" + summary = url + onClick { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivity(intent) + } } - } - preference { - title = "Discord" - val url = "https://discord.gg/mihon" - summary = url - onClick { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) + preference { + title = "Discord" + val url = "https://discord.gg/mihon" + summary = url + onClick { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivity(intent) + } } - } - preference { - titleRes = R.string.licenses + preference { + titleRes = R.string.licenses - onClick { - startActivity(Intent(activity, OssLicensesMenuActivity::class.java)) + onClick { + startActivity(Intent(activity, OssLicensesMenuActivity::class.java)) + } } } - } /** * Checks version and shows a user prompt if an update is available. @@ -136,7 +137,6 @@ class SettingsAboutController : SettingsController() { } class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) { - constructor(body: String, url: String) : this( Bundle().apply { putString(BODY_KEY, body) @@ -171,9 +171,12 @@ class SettingsAboutController : SettingsController() { inputDf.timeZone = TimeZone.getTimeZone("UTC") val buildTime = inputDf.parse(BuildConfig.BUILD_TIME) - val outputDf = DateFormat.getDateTimeInstance( - DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault() - ) + val outputDf = + DateFormat.getDateTimeInstance( + DateFormat.MEDIUM, + DateFormat.SHORT, + Locale.getDefault() + ) outputDf.timeZone = TimeZone.getDefault() buildTime!!.toDateTimestampString(dateFormat) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt index d0f1c57d04f0..64c8b3c21383 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt @@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Target -import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.SourceManager.Companion.DELEGATED_SOURCES @@ -51,9 +50,9 @@ import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import uy.kohesive.injekt.injectLazy +import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys class SettingsAdvancedController : SettingsController() { - private val network: NetworkHelper by injectLazy() private val chapterCache: ChapterCache by injectLazy() @@ -61,249 +60,262 @@ class SettingsAdvancedController : SettingsController() { private val db: DatabaseHelper by injectLazy() @SuppressLint("BatteryLife") - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.pref_category_advanced + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + titleRes = R.string.pref_category_advanced - preference { - key = CLEAR_CACHE_KEY - titleRes = R.string.pref_clear_chapter_cache - summary = context.getString(R.string.used_cache, chapterCache.readableSize) + preference { + key = CLEAR_CACHE_KEY + titleRes = R.string.pref_clear_chapter_cache + summary = context.getString(R.string.used_cache, chapterCache.readableSize) - onClick { clearChapterCache() } - } - preference { - titleRes = R.string.pref_clear_cookies + onClick { clearChapterCache() } + } + preference { + titleRes = R.string.pref_clear_cookies - onClick { - network.cookieManager.removeAll() - activity?.toast(R.string.cookies_cleared) + onClick { + network.cookieManager.removeAll() + activity?.toast(R.string.cookies_cleared) + } } - } - switchPreference { - key = Keys.enableDoh - titleRes = R.string.pref_dns_over_https - summaryRes = R.string.pref_dns_over_https_summary - defaultValue = false - } - preference { - titleRes = R.string.pref_clear_database - summaryRes = R.string.pref_clear_database_summary - - onClick { - val ctrl = ClearDatabaseDialogController() - ctrl.targetController = this@SettingsAdvancedController - ctrl.showDialog(router) + switchPreference { + key = Keys.enableDoh + titleRes = R.string.pref_dns_over_https + summaryRes = R.string.pref_dns_over_https_summary + defaultValue = false } - } - preference { - titleRes = R.string.pref_refresh_library_metadata - - onClick { LibraryUpdateService.start(context, target = Target.DETAILS) } - } - preference { - titleRes = R.string.pref_refresh_library_covers - - onClick { LibraryUpdateService.start(context, target = Target.COVERS) } - } - preference { - titleRes = R.string.pref_refresh_library_tracking - summaryRes = R.string.pref_refresh_library_tracking_summary - - onClick { LibraryUpdateService.start(context, target = Target.TRACKING) } - } - preference { - key = "dump_crash_logs" - titleRes = R.string.pref_dump_crash_logs - summaryRes = R.string.pref_dump_crash_logs_summary + preference { + titleRes = R.string.pref_clear_database + summaryRes = R.string.pref_clear_database_summary - onClick { - CrashLogUtil(context).dumpLogs() + onClick { + val ctrl = ClearDatabaseDialogController() + ctrl.targetController = this@SettingsAdvancedController + ctrl.showDialog(router) + } } - } + preference { + titleRes = R.string.pref_refresh_library_metadata - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + onClick { LibraryUpdateService.start(context, target = Target.DETAILS) } + } preference { - titleRes = R.string.pref_disable_battery_optimization - summaryRes = R.string.pref_disable_battery_optimization_summary + titleRes = R.string.pref_refresh_library_covers - onClick { - val packageName: String = context.packageName - if (!context.powerManager.isIgnoringBatteryOptimizations(packageName)) { - try { - val intent = Intent().apply { - action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS - data = Uri.parse("package:$packageName") - } - startActivity(intent) - } catch (e: ActivityNotFoundException) { - context.toast(R.string.battery_optimization_setting_activity_not_found) - } - } else { - context.toast(R.string.battery_optimization_disabled) - } - } + onClick { LibraryUpdateService.start(context, target = Target.COVERS) } } - } + preference { + titleRes = R.string.pref_refresh_library_tracking + summaryRes = R.string.pref_refresh_library_tracking_summary - editTextPreference { - key = Keys.defaultUserAgent - titleRes = R.string.pref_user_agent_string - text = preferences.defaultUserAgent().get() - summary = network.defaultUserAgent - - onChange { - if (it.toString().isBlank()) { - activity?.toast(R.string.error_user_agent_string_blank) - false - } else { - activity?.toast(R.string.requires_app_restart) - true - } + onClick { LibraryUpdateService.start(context, target = Target.TRACKING) } } - } - if (preferences.defaultUserAgent().isSet()) { preference { - key = "pref_reset_user_agent" - titleRes = R.string.pref_reset_user_agent_string + key = "dump_crash_logs" + titleRes = R.string.pref_dump_crash_logs + summaryRes = R.string.pref_dump_crash_logs_summary onClick { - preferences.defaultUserAgent().delete() - activity?.toast(R.string.requires_app_restart) + CrashLogUtil(context).dumpLogs() } } - } - // <-- EXH - - preferenceCategory { - titleRes = R.string.label_extensions - - listPreference { - key = Keys.extensionInstaller - titleRes = R.string.ext_installer_pref - summary = "%s" - entriesRes = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - arrayOf( - R.string.ext_installer_legacy, - R.string.ext_installer_packageinstaller, - R.string.ext_installer_shizuku - ) - } else { - arrayOf( - R.string.ext_installer_legacy, - R.string.ext_installer_packageinstaller - ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + preference { + titleRes = R.string.pref_disable_battery_optimization + summaryRes = R.string.pref_disable_battery_optimization_summary + + onClick { + val packageName: String = context.packageName + if (!context.powerManager.isIgnoringBatteryOptimizations(packageName)) { + try { + val intent = + Intent().apply { + action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + data = Uri.parse("package:$packageName") + } + startActivity(intent) + } catch (e: ActivityNotFoundException) { + context.toast(R.string.battery_optimization_setting_activity_not_found) + } + } else { + context.toast(R.string.battery_optimization_disabled) + } + } } - entryValues = PreferenceValues.ExtensionInstaller.values().map { it.name }.toTypedArray() - defaultValue = if (MiuiUtil.isMiui() || (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)) { - PreferenceValues.ExtensionInstaller.LEGACY - } else { - PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER - }.name + } + + editTextPreference { + key = Keys.defaultUserAgent + titleRes = R.string.pref_user_agent_string + text = preferences.defaultUserAgent().get() + summary = network.defaultUserAgent onChange { - if (it == PreferenceValues.ExtensionInstaller.SHIZUKU.name && - !context.isPackageInstalled("moe.shizuku.privileged.api") - ) { - MaterialAlertDialogBuilder(context) - .setTitle(R.string.ext_installer_shizuku) - .setMessage(R.string.ext_installer_shizuku_unavailable_dialog) - .setPositiveButton(android.R.string.ok) { _, _ -> - openInBrowser("https://shizuku.rikka.app/download") - } - .setNegativeButton(android.R.string.cancel, null) - .show() + if (it.toString().isBlank()) { + activity?.toast(R.string.error_user_agent_string_blank) false } else { + activity?.toast(R.string.requires_app_restart) true } } } - } - - preferenceCategory { - title = "Developer tools" - isPersistent = false - - switchPreference { - title = "Enable integrated hentai features" - summary = "This is a experimental feature that will disable all hentai features if toggled off" - key = Keys.eh_is_hentai_enabled - defaultValue = true + if (preferences.defaultUserAgent().isSet()) { + preference { + key = "pref_reset_user_agent" + titleRes = R.string.pref_reset_user_agent_string + + onClick { + preferences.defaultUserAgent().delete() + activity?.toast(R.string.requires_app_restart) + } + } + } - onChange { - if (preferences.eh_isHentaiEnabled().get()) { - if (EH_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) { - BlacklistedSources.HIDDEN_SOURCES += EH_SOURCE_ID - } - if (EXH_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) { - BlacklistedSources.HIDDEN_SOURCES += EXH_SOURCE_ID - } - if (NHENTAI_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) { - BlacklistedSources.HIDDEN_SOURCES += NHENTAI_SOURCE_ID - } - } else { - if (EH_SOURCE_ID in BlacklistedSources.HIDDEN_SOURCES) { - BlacklistedSources.HIDDEN_SOURCES -= EH_SOURCE_ID - } - if (EXH_SOURCE_ID in BlacklistedSources.HIDDEN_SOURCES) { - BlacklistedSources.HIDDEN_SOURCES -= EXH_SOURCE_ID + // <-- EXH + + preferenceCategory { + titleRes = R.string.label_extensions + + listPreference { + key = Keys.extensionInstaller + titleRes = R.string.ext_installer_pref + summary = "%s" + entriesRes = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + arrayOf( + R.string.ext_installer_legacy, + R.string.ext_installer_packageinstaller, + R.string.ext_installer_shizuku + ) + } else { + arrayOf( + R.string.ext_installer_legacy, + R.string.ext_installer_packageinstaller + ) } - if (NHENTAI_SOURCE_ID in BlacklistedSources.HIDDEN_SOURCES) { - BlacklistedSources.HIDDEN_SOURCES -= NHENTAI_SOURCE_ID + entryValues = PreferenceValues.ExtensionInstaller.values().map { it.name }.toTypedArray() + defaultValue = + if (MiuiUtil.isMiui() || (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)) { + PreferenceValues.ExtensionInstaller.LEGACY + } else { + PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER + }.name + + onChange { + if (it == PreferenceValues.ExtensionInstaller.SHIZUKU.name && + !context.isPackageInstalled("moe.shizuku.privileged.api") + ) { + MaterialAlertDialogBuilder(context) + .setTitle(R.string.ext_installer_shizuku) + .setMessage(R.string.ext_installer_shizuku_unavailable_dialog) + .setPositiveButton(android.R.string.ok) { _, _ -> + openInBrowser("https://shizuku.rikka.app/download") + } + .setNegativeButton(android.R.string.cancel, null) + .show() + false + } else { + true } } - true } } - switchPreference { - title = "Enable delegated sources" - key = Keys.eh_delegateSources - defaultValue = true - summary = "Apply ${context.getString(R.string.app_name)} enhancements to the following sources if they are installed: ${DELEGATED_SOURCES.values.map { it.sourceName }.distinct().joinToString()}" - } + preferenceCategory { + title = "Developer tools" + isPersistent = false - preference { - titleRes = R.string.pref_clear_history - summaryRes = R.string.pref_clear_history_summary + switchPreference { + title = "Enable integrated hentai features" + summary = "This is a experimental feature that will disable all hentai features if toggled off" + key = Keys.eh_is_hentai_enabled + defaultValue = true - onClick { - val ctrl = ClearHistoryDialogController() - ctrl.targetController = this@SettingsAdvancedController - ctrl.showDialog(router) + onChange { + if (preferences.eh_isHentaiEnabled().get()) { + if (EH_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) { + BlacklistedSources.HIDDEN_SOURCES += EH_SOURCE_ID + } + if (EXH_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) { + BlacklistedSources.HIDDEN_SOURCES += EXH_SOURCE_ID + } + if (NHENTAI_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) { + BlacklistedSources.HIDDEN_SOURCES += NHENTAI_SOURCE_ID + } + } else { + if (EH_SOURCE_ID in BlacklistedSources.HIDDEN_SOURCES) { + BlacklistedSources.HIDDEN_SOURCES -= EH_SOURCE_ID + } + if (EXH_SOURCE_ID in BlacklistedSources.HIDDEN_SOURCES) { + BlacklistedSources.HIDDEN_SOURCES -= EXH_SOURCE_ID + } + if (NHENTAI_SOURCE_ID in BlacklistedSources.HIDDEN_SOURCES) { + BlacklistedSources.HIDDEN_SOURCES -= NHENTAI_SOURCE_ID + } + } + true + } } - } - intListPreference { - key = Keys.eh_logLevel - title = "Log level" + switchPreference { + title = "Enable delegated sources" + key = Keys.eh_delegateSources + defaultValue = true + summary = "Apply ${context.getString( + R.string.app_name + )} enhancements to the following sources if they are installed: ${DELEGATED_SOURCES.values.map { it.sourceName }.distinct().joinToString()}" + } - entries = EHLogLevel.values().map { ehLogLevel -> - "${ehLogLevel.name.lowercase() - .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }} (${ehLogLevel.description})" - }.toTypedArray() - entryValues = EHLogLevel.values().mapIndexed { index, _ -> "$index" }.toTypedArray() - defaultValue = "0" + preference { + titleRes = R.string.pref_clear_history + summaryRes = R.string.pref_clear_history_summary - summary = "Changing this can impact app performance. Force-restart app after changing. Current value: %s" - } + onClick { + val ctrl = ClearHistoryDialogController() + ctrl.targetController = this@SettingsAdvancedController + ctrl.showDialog(router) + } + } - switchPreference { - title = "Enable source blacklist" - key = Keys.eh_enableSourceBlacklist - defaultValue = true - summary = "Hide extensions/sources that are incompatible with ${context.getString(R.string.app_name)}. Force-restart app after changing." - } + intListPreference { + key = Keys.eh_logLevel + title = "Log level" - preference { - title = "Open debug menu" - summary = HtmlCompat.fromHtml("DO NOT TOUCH THIS MENU UNLESS YOU KNOW WHAT YOU ARE DOING! IT CAN CORRUPT YOUR LIBRARY!", HtmlCompat.FROM_HTML_MODE_LEGACY) - onClick { router.pushController(SettingsDebugController().withFadeTransaction()) } + entries = + EHLogLevel.values().map { ehLogLevel -> + "${ehLogLevel.name.lowercase() + .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }} (${ehLogLevel.description})" + }.toTypedArray() + entryValues = EHLogLevel.values().mapIndexed { index, _ -> "$index" }.toTypedArray() + defaultValue = "0" + + summary = "Changing this can impact app performance. Force-restart app after changing. Current value: %s" + } + + switchPreference { + title = "Enable source blacklist" + key = Keys.eh_enableSourceBlacklist + defaultValue = true + summary = "Hide extensions/sources that are incompatible with ${context.getString( + R.string.app_name + )}. Force-restart app after changing." + } + + preference { + title = "Open debug menu" + summary = + HtmlCompat.fromHtml( + "DO NOT TOUCH THIS MENU UNLESS YOU KNOW WHAT YOU ARE DOING! IT CAN CORRUPT YOUR LIBRARY!", + HtmlCompat.FROM_HTML_MODE_LEGACY + ) + onClick { router.pushController(SettingsDebugController().withFadeTransaction()) } + } } + // <-- EXH } - // <-- EXH - } private fun clearChapterCache() { if (activity == null) return diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt index ff919f42dcca..128c87f166a7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt @@ -25,7 +25,6 @@ import eu.kanade.tachiyomi.data.backup.full.FullBackupRestoreValidator import eu.kanade.tachiyomi.data.backup.full.models.BackupFull import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupRestoreValidator import eu.kanade.tachiyomi.data.backup.legacy.models.Backup -import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.preference.asImmediateFlow import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe @@ -43,128 +42,140 @@ import eu.kanade.tachiyomi.util.system.getFilePicker import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys class SettingsBackupController : SettingsController() { - /** * Flags containing information of what to backup. */ private var backupFlags = 0 - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { super.onViewCreated(view, savedInstanceState) requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 500) } - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.backup - - preferenceCategory { + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { titleRes = R.string.backup - preference { - key = "pref_create_backup" - titleRes = R.string.pref_create_backup - summaryRes = R.string.pref_create_backup_summ + preferenceCategory { + titleRes = R.string.backup - onClick { backup(context, BackupConst.BACKUP_TYPE_FULL) } - } - preference { - key = "pref_create_legacy_backup" - titleRes = R.string.pref_create_legacy_backup - summaryRes = R.string.pref_create_legacy_backup_summary + preference { + key = "pref_create_backup" + titleRes = R.string.pref_create_backup + summaryRes = R.string.pref_create_backup_summ - onClick { backup(context, BackupConst.BACKUP_TYPE_LEGACY) } - } - preference { - key = "pref_restore_backup" - titleRes = R.string.pref_restore_backup - summaryRes = R.string.pref_restore_backup_summ - - onClick { - if (!BackupRestoreService.isRunning(context)) { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.type = "application/*" - val title = resources?.getString(R.string.file_select_backup) - val chooser = Intent.createChooser(intent, title) - startActivityForResult(chooser, CODE_BACKUP_RESTORE) - } else { - context.toast(R.string.restore_in_progress) - } + onClick { backup(context, BackupConst.BACKUP_TYPE_FULL) } } - } - } - - preferenceCategory { - titleRes = R.string.pref_backup_service_category + preference { + key = "pref_create_legacy_backup" + titleRes = R.string.pref_create_legacy_backup + summaryRes = R.string.pref_create_legacy_backup_summary - intListPreference { - key = Keys.backupInterval - titleRes = R.string.pref_backup_interval - entriesRes = arrayOf( - R.string.update_never, R.string.update_6hour, - R.string.update_12hour, R.string.update_24hour, - R.string.update_48hour, R.string.update_weekly - ) - entryValues = arrayOf("0", "6", "12", "24", "48", "168") - defaultValue = "0" - summary = "%s" - - onChange { newValue -> - val interval = (newValue as String).toInt() - BackupCreatorJob.setupTask(context, interval) - true + onClick { backup(context, BackupConst.BACKUP_TYPE_LEGACY) } + } + preference { + key = "pref_restore_backup" + titleRes = R.string.pref_restore_backup + summaryRes = R.string.pref_restore_backup_summ + + onClick { + if (!BackupRestoreService.isRunning(context)) { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "application/*" + val title = resources?.getString(R.string.file_select_backup) + val chooser = Intent.createChooser(intent, title) + startActivityForResult(chooser, CODE_BACKUP_RESTORE) + } else { + context.toast(R.string.restore_in_progress) + } + } } } - preference { - key = Keys.backupDirectory - titleRes = R.string.pref_backup_directory - - onClick { - val currentDir = preferences.backupsDirectory().get() - try { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - startActivityForResult(intent, CODE_BACKUP_DIR) - } catch (e: ActivityNotFoundException) { - // Fall back to custom picker on error - startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_DIR) + + preferenceCategory { + titleRes = R.string.pref_backup_service_category + + intListPreference { + key = Keys.backupInterval + titleRes = R.string.pref_backup_interval + entriesRes = + arrayOf( + R.string.update_never, + R.string.update_6hour, + R.string.update_12hour, + R.string.update_24hour, + R.string.update_48hour, + R.string.update_weekly + ) + entryValues = arrayOf("0", "6", "12", "24", "48", "168") + defaultValue = "0" + summary = "%s" + + onChange { newValue -> + val interval = (newValue as String).toInt() + BackupCreatorJob.setupTask(context, interval) + true } } + preference { + key = Keys.backupDirectory + titleRes = R.string.pref_backup_directory + + onClick { + val currentDir = preferences.backupsDirectory().get() + try { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + startActivityForResult(intent, CODE_BACKUP_DIR) + } catch (e: ActivityNotFoundException) { + // Fall back to custom picker on error + startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_DIR) + } + } - preferences.backupInterval().asImmediateFlow { isVisible = it > 0 } - .launchIn(scope) + preferences.backupInterval().asImmediateFlow { isVisible = it > 0 } + .launchIn(scope) - preferences.backupsDirectory().asFlow() - .onEach { path -> - val dir = UniFile.fromUri(context, Uri.parse(path)) - summary = dir.filePath + "/automatic" - } - .launchIn(scope) - } - intListPreference { - key = Keys.numberOfBackups - titleRes = R.string.pref_backup_slots - entries = arrayOf("1", "2", "3", "4", "5") - entryValues = entries - defaultValue = "1" - summary = "%s" - - preferences.backupInterval().asImmediateFlow { isVisible = it > 0 } - .launchIn(scope) - } - switchPreference { - key = Keys.createLegacyBackup - titleRes = R.string.pref_backup_auto_create_legacy - defaultValue = false + preferences.backupsDirectory().asFlow() + .onEach { path -> + val dir = UniFile.fromUri(context, Uri.parse(path)) + summary = dir.filePath + "/automatic" + } + .launchIn(scope) + } + intListPreference { + key = Keys.numberOfBackups + titleRes = R.string.pref_backup_slots + entries = arrayOf("1", "2", "3", "4", "5") + entryValues = entries + defaultValue = "1" + summary = "%s" + + preferences.backupInterval().asImmediateFlow { isVisible = it > 0 } + .launchIn(scope) + } + switchPreference { + key = Keys.createLegacyBackup + titleRes = R.string.pref_backup_auto_create_legacy + defaultValue = false - preferences.backupInterval().asImmediateFlow { isVisible = it > 0 } - .launchIn(scope) + preferences.backupInterval().asImmediateFlow { isVisible = it > 0 } + .launchIn(scope) + } } } - } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + data: Intent? + ) { if (data != null && resultCode == Activity.RESULT_OK) { val activity = activity ?: return val uri = data.data @@ -172,8 +183,9 @@ class SettingsBackupController : SettingsController() { when (requestCode) { CODE_BACKUP_DIR -> { // Get UriPermission so it's possible to write files - val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION + val flags = + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION if (uri != null) { activity.contentResolver.takePersistableUriPermission(uri, flags) @@ -183,8 +195,9 @@ class SettingsBackupController : SettingsController() { preferences.backupsDirectory().set(uri.toString()) } CODE_FULL_BACKUP_CREATE, CODE_LEGACY_BACKUP_CREATE -> { - val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION + val flags = + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION if (uri != null) { activity.contentResolver.takePersistableUriPermission(uri, flags) @@ -206,11 +219,12 @@ class SettingsBackupController : SettingsController() { val fileName = DocumentFile.fromSingleUri(activity, uri)?.name ?: uri.toString() when { fileName.endsWith(".proto.gz") || fileName.endsWith(".tachibk") -> { - val options = arrayOf( - R.string.full_restore_offline, - R.string.full_restore_online - ) - .map { activity.getString(it) } + val options = + arrayOf( + R.string.full_restore_offline, + R.string.full_restore_online + ) + .map { activity.getString(it) } MaterialDialog(activity) .title(R.string.full_restore_mode) .listItemsSingleChoice( @@ -243,7 +257,10 @@ class SettingsBackupController : SettingsController() { } } - private fun backup(context: Context, type: Int) { + private fun backup( + context: Context, + type: Int + ) { if (!BackupCreateService.isRunning(context)) { val ctrl = CreateBackupDialog(type) ctrl.targetController = this@SettingsBackupController @@ -253,24 +270,30 @@ class SettingsBackupController : SettingsController() { } } - fun createBackup(flags: Int, type: Int) { + fun createBackup( + flags: Int, + type: Int + ) { backupFlags = flags val currentDir = preferences.backupsDirectory().get() - val code = when (type) { - BackupConst.BACKUP_TYPE_FULL -> CODE_FULL_BACKUP_CREATE - else -> CODE_LEGACY_BACKUP_CREATE - } - val fileName = when (type) { - BackupConst.BACKUP_TYPE_FULL -> BackupFull.getDefaultFilename() - else -> Backup.getDefaultFilename() - } + val code = + when (type) { + BackupConst.BACKUP_TYPE_FULL -> CODE_FULL_BACKUP_CREATE + else -> CODE_LEGACY_BACKUP_CREATE + } + val fileName = + when (type) { + BackupConst.BACKUP_TYPE_FULL -> BackupFull.getDefaultFilename() + else -> Backup.getDefaultFilename() + } try { // Use Android's built-in file creator - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType("application/*") - .putExtra(Intent.EXTRA_TITLE, fileName) + val intent = + Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType("application/*") + .putExtra(Intent.EXTRA_TITLE, fileName) startActivityForResult(intent, code) } catch (e: ActivityNotFoundException) { @@ -289,14 +312,15 @@ class SettingsBackupController : SettingsController() { override fun onCreateDialog(savedViewState: Bundle?): Dialog { val type = args.getInt(KEY_TYPE) val activity = activity!! - val options = arrayOf( - R.string.manga, - R.string.categories, - R.string.chapters, - R.string.track, - R.string.history - ) - .map { activity.getString(it) } + val options = + arrayOf( + R.string.manga, + R.string.categories, + R.string.chapters, + R.string.track, + R.string.history + ) + .map { activity.getString(it) } return MaterialDialog(activity) .title(R.string.pref_create_backup) @@ -349,10 +373,14 @@ class SettingsBackupController : SettingsController() { val results = validator.validate(activity, uri) if (results.missingSources.isNotEmpty()) { - message += "\n\n${activity.getString(R.string.backup_restore_missing_sources)}\n${results.missingSources.joinToString("\n") { "- $it" }}" + message += "\n\n${activity.getString( + R.string.backup_restore_missing_sources + )}\n${results.missingSources.joinToString("\n") { "- $it" }}" } if (results.missingTrackers.isNotEmpty()) { - message += "\n\n${activity.getString(R.string.backup_restore_missing_trackers)}\n${results.missingTrackers.joinToString("\n") { "- $it" }}" + message += "\n\n${activity.getString( + R.string.backup_restore_missing_trackers + )}\n${results.missingTrackers.joinToString("\n") { "- $it" }}" } MaterialDialog(activity) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBrowseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBrowseController.kt index 87015e01692c..33f850c96970 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBrowseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBrowseController.kt @@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.ui.setting import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys -import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values import eu.kanade.tachiyomi.extension.ExtensionUpdateJob import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.extension.repos.RepoController @@ -16,76 +14,80 @@ import eu.kanade.tachiyomi.util.preference.preference import eu.kanade.tachiyomi.util.preference.preferenceCategory import eu.kanade.tachiyomi.util.preference.switchPreference import eu.kanade.tachiyomi.util.preference.titleRes +import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys +import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values class SettingsBrowseController : SettingsController() { + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + titleRes = R.string.browse - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.browse - - preferenceCategory { - titleRes = R.string.label_extensions + preferenceCategory { + titleRes = R.string.label_extensions - switchPreference { - key = Keys.automaticExtUpdates - titleRes = R.string.pref_enable_automatic_extension_updates - defaultValue = true + switchPreference { + key = Keys.automaticExtUpdates + titleRes = R.string.pref_enable_automatic_extension_updates + defaultValue = true - onChange { newValue -> - val checked = newValue as Boolean - ExtensionUpdateJob.setupTask(activity!!, checked) - true + onChange { newValue -> + val checked = newValue as Boolean + ExtensionUpdateJob.setupTask(activity!!, checked) + true + } } - } - // SY --> - preference { - key = "pref_edit_extension_repos" - titleRes = R.string.action_edit_repos + // SY --> + preference { + key = "pref_edit_extension_repos" + titleRes = R.string.action_edit_repos - val catCount = preferences.extensionRepos().get().count() - summary = context.resources.getQuantityString(R.plurals.num_repos, catCount, catCount) + val catCount = preferences.extensionRepos().get().count() + summary = context.resources.getQuantityString(R.plurals.num_repos, catCount, catCount) - onClick { - router.pushController(RepoController().withFadeTransaction()) + onClick { + router.pushController(RepoController().withFadeTransaction()) + } } - } - // SY <-- + // SY <-- - listPreference { - key = Keys.allowNsfwSource - titleRes = R.string.pref_allow_nsfw_sources - entriesRes = arrayOf( - R.string.pref_allow_nsfw_sources_allowed, - R.string.pref_allow_nsfw_sources_allowed_multisource, - R.string.pref_allow_nsfw_sources_blocked - ) - entryValues = arrayOf( - Values.NsfwAllowance.ALLOWED.name, - Values.NsfwAllowance.PARTIAL.name, - Values.NsfwAllowance.BLOCKED.name - ) - defaultValue = Values.NsfwAllowance.ALLOWED.name - summary = "%s" + listPreference { + key = Keys.allowNsfwSource + titleRes = R.string.pref_allow_nsfw_sources + entriesRes = + arrayOf( + R.string.pref_allow_nsfw_sources_allowed, + R.string.pref_allow_nsfw_sources_allowed_multisource, + R.string.pref_allow_nsfw_sources_blocked + ) + entryValues = + arrayOf( + Values.NsfwAllowance.ALLOWED.name, + Values.NsfwAllowance.PARTIAL.name, + Values.NsfwAllowance.BLOCKED.name + ) + defaultValue = Values.NsfwAllowance.ALLOWED.name + summary = "%s" + } } - } - preferenceCategory { - titleRes = R.string.label_sources + preferenceCategory { + titleRes = R.string.label_sources - switchPreference { - key = Keys.hideLastUsedSource - titleRes = R.string.pref_hide_last_used_source - defaultValue = false + switchPreference { + key = Keys.hideLastUsedSource + titleRes = R.string.pref_hide_last_used_source + defaultValue = false + } } - } - preferenceCategory { - titleRes = R.string.action_global_search + preferenceCategory { + titleRes = R.string.action_global_search - switchPreference { - key = Keys.searchPinnedSourcesOnly - titleRes = R.string.pref_search_pinned_sources_only - defaultValue = false + switchPreference { + key = Keys.searchPinnedSourcesOnly + titleRes = R.string.pref_search_pinned_sources_only + defaultValue = false + } } } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt index ee9c15366d11..61e520140ccf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt @@ -25,14 +25,17 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get abstract class SettingsController : PreferenceController() { - val preferences: PreferencesHelper = Injekt.get() val scope = CoroutineScope(Job() + Dispatchers.Main) var untilDestroySubscriptions = CompositeSubscription() private set - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup, + savedInstanceState: Bundle? + ): View { if (untilDestroySubscriptions.isUnsubscribed) { untilDestroySubscriptions = CompositeSubscription() } @@ -44,7 +47,10 @@ abstract class SettingsController : PreferenceController() { untilDestroySubscriptions.unsubscribe() } - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + override fun onCreatePreferences( + savedInstanceState: Bundle?, + rootKey: String? + ) { val screen = preferenceManager.createPreferenceScreen(getThemedContext()) preferenceScreen = screen setupPreferenceScreen(screen) @@ -74,7 +80,10 @@ abstract class SettingsController : PreferenceController() { (activity as? AppCompatActivity)?.supportActionBar?.title = getTitle() } - override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { + override fun onChangeStarted( + handler: ControllerChangeHandler, + type: ControllerChangeType + ) { if (type.isEnter) { setTitle() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt index bb9b9ed06b24..2e00cf4f06b7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt @@ -15,7 +15,6 @@ import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category -import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.asImmediateFlow import eu.kanade.tachiyomi.ui.base.controller.DialogController @@ -29,116 +28,129 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory import eu.kanade.tachiyomi.util.preference.switchPreference import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.system.getFilePicker -import java.io.File import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.io.File +import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys class SettingsDownloadController : SettingsController() { - private val db: DatabaseHelper by injectLazy() - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.pref_category_downloads - - preference { - key = Keys.downloadsDirectory - titleRes = R.string.pref_download_directory - onClick { - val ctrl = DownloadDirectoriesDialog() - ctrl.targetController = this@SettingsDownloadController - ctrl.showDialog(router) - } - - preferences.downloadsDirectory().asFlow() - .onEach { path -> - val dir = UniFile.fromUri(context, Uri.parse(path)) - summary = dir.filePath ?: path + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + titleRes = R.string.pref_category_downloads + + preference { + key = Keys.downloadsDirectory + titleRes = R.string.pref_download_directory + onClick { + val ctrl = DownloadDirectoriesDialog() + ctrl.targetController = this@SettingsDownloadController + ctrl.showDialog(router) } - .launchIn(scope) - } - switchPreference { - key = Keys.downloadOnlyOverWifi - titleRes = R.string.pref_download_only_over_wifi - defaultValue = true - } - preferenceCategory { - titleRes = R.string.pref_remove_after_read + preferences.downloadsDirectory().asFlow() + .onEach { path -> + val dir = UniFile.fromUri(context, Uri.parse(path)) + summary = dir.filePath ?: path + } + .launchIn(scope) + } switchPreference { - key = Keys.removeAfterMarkedAsRead - titleRes = R.string.pref_remove_after_marked_as_read - defaultValue = false + key = Keys.downloadOnlyOverWifi + titleRes = R.string.pref_download_only_over_wifi + defaultValue = true } - intListPreference { - key = Keys.removeAfterReadSlots + preferenceCategory { titleRes = R.string.pref_remove_after_read - entriesRes = arrayOf( - R.string.disabled, R.string.last_read_chapter, - R.string.second_to_last, R.string.third_to_last, R.string.fourth_to_last, - R.string.fifth_to_last - ) - entryValues = arrayOf("-1", "0", "1", "2", "3", "4") - defaultValue = "-1" - summary = "%s" - } - } - - val dbCategories = db.getCategories().executeAsBlocking() - val categories = listOf(Category.createDefault()) + dbCategories - - preferenceCategory { - titleRes = R.string.pref_download_new - switchPreference { - key = Keys.downloadNew - titleRes = R.string.pref_download_new - defaultValue = false + switchPreference { + key = Keys.removeAfterMarkedAsRead + titleRes = R.string.pref_remove_after_marked_as_read + defaultValue = false + } + intListPreference { + key = Keys.removeAfterReadSlots + titleRes = R.string.pref_remove_after_read + entriesRes = + arrayOf( + R.string.disabled, + R.string.last_read_chapter, + R.string.second_to_last, + R.string.third_to_last, + R.string.fourth_to_last, + R.string.fifth_to_last + ) + entryValues = arrayOf("-1", "0", "1", "2", "3", "4") + defaultValue = "-1" + summary = "%s" + } } - multiSelectListPreference { - key = Keys.downloadNewCategories - titleRes = R.string.pref_download_new_categories - entries = categories.map { it.name }.toTypedArray() - entryValues = categories.map { it.id.toString() }.toTypedArray() - preferences.downloadNew().asImmediateFlow { isVisible = it } - .launchIn(scope) + val dbCategories = db.getCategories().executeAsBlocking() + val categories = listOf(Category.createDefault()) + dbCategories - preferences.downloadNewCategories().asFlow() - .onEach { mutableSet -> - val selectedCategories = mutableSet - .mapNotNull { id -> categories.find { it.id == id.toInt() } } - .sortedBy { it.order } + preferenceCategory { + titleRes = R.string.pref_download_new - summary = if (selectedCategories.isEmpty()) { - resources?.getString(R.string.all) - } else { - selectedCategories.joinToString { it.name } + switchPreference { + key = Keys.downloadNew + titleRes = R.string.pref_download_new + defaultValue = false + } + multiSelectListPreference { + key = Keys.downloadNewCategories + titleRes = R.string.pref_download_new_categories + entries = categories.map { it.name }.toTypedArray() + entryValues = categories.map { it.id.toString() }.toTypedArray() + + preferences.downloadNew().asImmediateFlow { isVisible = it } + .launchIn(scope) + + preferences.downloadNewCategories().asFlow() + .onEach { mutableSet -> + val selectedCategories = + mutableSet + .mapNotNull { id -> categories.find { it.id == id.toInt() } } + .sortedBy { it.order } + + summary = + if (selectedCategories.isEmpty()) { + resources?.getString(R.string.all) + } else { + selectedCategories.joinToString { it.name } + } } - } - .launchIn(scope) + .launchIn(scope) + } } } - } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + data: Intent? + ) { when (requestCode) { - DOWNLOAD_DIR -> if (data != null && resultCode == Activity.RESULT_OK) { - val context = applicationContext ?: return - val uri = data.data - val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION - - if (uri != null) { - @Suppress("NewApi") - context.contentResolver.takePersistableUriPermission(uri, flags) - } + DOWNLOAD_DIR -> + if (data != null && resultCode == Activity.RESULT_OK) { + val context = applicationContext ?: return + val uri = data.data + val flags = + Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + + if (uri != null) { + @Suppress("NewApi") + context.contentResolver.takePersistableUriPermission(uri, flags) + } - val file = UniFile.fromUri(context, uri) - preferences.downloadsDirectory().set(file.uri.toString()) - } + val file = UniFile.fromUri(context, uri) + preferences.downloadsDirectory().set(file.uri.toString()) + } } } @@ -157,7 +169,6 @@ class SettingsDownloadController : SettingsController() { } class DownloadDirectoriesDialog : DialogController() { - private val preferences: PreferencesHelper = Injekt.get() override fun onCreateDialog(savedViewState: Bundle?): Dialog { @@ -181,9 +192,10 @@ class SettingsDownloadController : SettingsController() { } private fun getExternalDirs(): List { - val defaultDir = Environment.getExternalStorageDirectory().absolutePath + - File.separator + resources?.getString(R.string.app_name) + - File.separator + "downloads" + val defaultDir = + Environment.getExternalStorageDirectory().absolutePath + + File.separator + resources?.getString(R.string.app_name) + + File.separator + "downloads" return mutableListOf(File(defaultDir)) + ContextCompat.getExternalFilesDirs(activity!!, "").filterNotNull() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsEhController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsEhController.kt index 2a45832840c3..57f5952cebb1 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsEhController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsEhController.kt @@ -51,7 +51,6 @@ import exh.ui.login.LoginController import exh.util.await import exh.util.trans import humanize.Humanize -import java.util.Date import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.launchIn @@ -60,6 +59,7 @@ import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import uy.kohesive.injekt.injectLazy +import java.util.Date /** * EH Settings fragment @@ -83,570 +83,593 @@ class SettingsEhController : SettingsController() { return true } - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - title = "E-Hentai" - - preferenceCategory { - title = "E-Hentai Website Account Settings" - - switchPreference { - title = "Enable ExHentai" - summaryOff = "Requires login" - key = PreferenceKeys.eh_enableExHentai - isPersistent = false - defaultValue = false - preferences.enableExhentai() - .asFlow() - .onEach { - isChecked = it - } - .launchIn(scope) + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + title = "E-Hentai" + + preferenceCategory { + title = "E-Hentai Website Account Settings" + + switchPreference { + title = "Enable ExHentai" + summaryOff = "Requires login" + key = PreferenceKeys.eh_enableExHentai + isPersistent = false + defaultValue = false + preferences.enableExhentai() + .asFlow() + .onEach { + isChecked = it + } + .launchIn(scope) - onChange { newVal -> - newVal as Boolean - if (!newVal) { - preferences.enableExhentai().set(false) - true - } else { - router.pushController( - RouterTransaction.with(LoginController()) - .pushChangeHandler(FadeChangeHandler()) - .popChangeHandler(FadeChangeHandler()) - ) - false + onChange { newVal -> + newVal as Boolean + if (!newVal) { + preferences.enableExhentai().set(false) + true + } else { + router.pushController( + RouterTransaction.with(LoginController()) + .pushChangeHandler(FadeChangeHandler()) + .popChangeHandler(FadeChangeHandler()) + ) + false + } } } - } - - intListPreference { - title = "Use Hentai@Home Network" - - key = PreferenceKeys.eh_enable_hah - if (preferences.eh_hathPerksCookies().get().isBlank()) { - summary = "Do you wish to load images through the Hentai@Home Network, if available? Disabling this option will reduce the amount of pages you are able to view\nOptions:\n - Any client (Recommended)\n - Default port clients only (Can be slower. Enable if behind firewall/proxy that blocks outgoing non-standard ports.)" - entries = arrayOf( - "Any client (Recommended)", - "Default port clients only" - ) - entryValues = arrayOf("0", "1") - } else { - summary = "Do you wish to load images through the Hentai@Home Network, if available? Disabling this option will reduce the amount of pages you are able to view\nOptions:\n - Any client (Recommended)\n - Default port clients only (Can be slower. Enable if behind firewall/proxy that blocks outgoing non-standard ports.)\n - No (Donator only. You will not be able to browse as many pages, enable only if having severe problems.)" - entries = arrayOf( - "Any client (Recommended)", - "Default port clients only", - "No(will select Default port clients only if you are not a donator)" - ) - entryValues = arrayOf("0", "1", "2") - } - onChange { preferences.useHentaiAtHome().reconfigure() } - }.dependency = PreferenceKeys.eh_enableExHentai - - switchPreference { - title = "Show Japanese titles in search results" - summaryOn = "Currently showing Japanese titles in search results. Clear the chapter cache after changing this (in the Advanced section)" - summaryOff = "Currently showing English/Romanized titles in search results. Clear the chapter cache after changing this (in the Advanced section)" - key = "use_jp_title" - defaultValue = false - - onChange { preferences.useJapaneseTitle().reconfigure() } - }.dependency = PreferenceKeys.eh_enableExHentai - - switchPreference { - title = "Use original images" - summaryOn = "Currently using original images" - summaryOff = "Currently using resampled images" - key = PreferenceKeys.eh_useOrigImages - defaultValue = false - - onChange { preferences.eh_useOriginalImages().reconfigure() } - }.dependency = PreferenceKeys.eh_enableExHentai - - preference { - title = "Watched Tags" - summary = "Opens a webview to your E/ExHentai watched tags page" - onClick { - val intent = if (preferences.enableExhentai().get()) { - WebViewActivity.newIntent(activity!!, url = "https://exhentai.org/mytags", title = "ExHentai Watched Tags") + intListPreference { + title = "Use Hentai@Home Network" + + key = PreferenceKeys.eh_enable_hah + if (preferences.eh_hathPerksCookies().get().isBlank()) { + summary = "Do you wish to load images through the Hentai@Home Network, if available? Disabling this option will reduce the amount of pages you are able to view\nOptions:\n - Any client (Recommended)\n - Default port clients only (Can be slower. Enable if behind firewall/proxy that blocks outgoing non-standard ports.)" + entries = + arrayOf( + "Any client (Recommended)", + "Default port clients only" + ) + entryValues = arrayOf("0", "1") } else { - WebViewActivity.newIntent(activity!!, url = "https://e-hentai.org/mytags", title = "E-Hentai Watched Tags") + summary = "Do you wish to load images through the Hentai@Home Network, if available? Disabling this option will reduce the amount of pages you are able to view\nOptions:\n - Any client (Recommended)\n - Default port clients only (Can be slower. Enable if behind firewall/proxy that blocks outgoing non-standard ports.)\n - No (Donator only. You will not be able to browse as many pages, enable only if having severe problems.)" + entries = + arrayOf( + "Any client (Recommended)", + "Default port clients only", + "No(will select Default port clients only if you are not a donator)" + ) + entryValues = arrayOf("0", "1", "2") } - startActivity(intent) - } - }.dependency = PreferenceKeys.eh_enableExHentai - - preference { - title = "Tag Filtering Threshold" - key = PreferenceKeys.eh_tag_filtering_value - defaultValue = 0 - - summary = "You can soft filter tags by adding them to the \"My Tags\" E/ExHentai page with a negative weight. If a gallery has tags that add up to weight below this value, it is filtered from view. This threshold can be set between -9999 and 0. Currently: ${preferences.ehTagFilterValue().get()}" - - onClick { - MaterialDialog(activity!!) - .title(text = "Tag Filtering Threshold") - .input( - inputType = InputType.TYPE_NUMBER_FLAG_SIGNED, - waitForPositiveButton = false, - allowEmpty = false - ) { dialog, number -> - val inputField = dialog.getInputField() - val value = number.toString().toIntOrNull() - - if (value != null && value in -9999..0) { - inputField.error = null + + onChange { preferences.useHentaiAtHome().reconfigure() } + }.dependency = PreferenceKeys.eh_enableExHentai + + switchPreference { + title = "Show Japanese titles in search results" + summaryOn = "Currently showing Japanese titles in search results. Clear the chapter cache after changing this (in the Advanced section)" + summaryOff = "Currently showing English/Romanized titles in search results. Clear the chapter cache after changing this (in the Advanced section)" + key = "use_jp_title" + defaultValue = false + + onChange { preferences.useJapaneseTitle().reconfigure() } + }.dependency = PreferenceKeys.eh_enableExHentai + + switchPreference { + title = "Use original images" + summaryOn = "Currently using original images" + summaryOff = "Currently using resampled images" + key = PreferenceKeys.eh_useOrigImages + defaultValue = false + + onChange { preferences.eh_useOriginalImages().reconfigure() } + }.dependency = PreferenceKeys.eh_enableExHentai + + preference { + title = "Watched Tags" + summary = "Opens a webview to your E/ExHentai watched tags page" + onClick { + val intent = + if (preferences.enableExhentai().get()) { + WebViewActivity.newIntent(activity!!, url = "https://exhentai.org/mytags", title = "ExHentai Watched Tags") } else { - inputField.error = "Must be between -9999 and 0!" + WebViewActivity.newIntent(activity!!, url = "https://e-hentai.org/mytags", title = "E-Hentai Watched Tags") } - dialog.setActionButtonEnabled(WhichButton.POSITIVE, value != null && value in -9999..0) - } - .positiveButton(android.R.string.ok) { - val value = it.getInputField().text.toString().toInt() - preferences.ehTagFilterValue().set(value) - summary = "You can soft filter tags by adding them to the \"My Tags\" E/ExHentai page with a negative weight. If a gallery has tags that add up to weight below this value, it is filtered from view. This threshold can be set between 0 and -9999. Currently: $value" - preferences.ehTagFilterValue().reconfigure() - } - .show() - } - }.dependency = PreferenceKeys.eh_enableExHentai - - preference { - title = "Tag Watching Threshold" - key = PreferenceKeys.eh_tag_watching_value - defaultValue = 0 - - summary = "Recently uploaded galleries will be included on the watched screen if it has at least one watched tag with positive weight, and the sum of weights on its watched tags add up to this value or higher. This threshold can be set between 0 and 9999. Currently: ${preferences.ehTagWatchingValue().get()}" - - onClick { - MaterialDialog(activity!!) - .title(text = "Tag Watching Threshold") - .input( - inputType = InputType.TYPE_NUMBER_FLAG_SIGNED, - maxLength = 4, - waitForPositiveButton = false, - allowEmpty = false - ) { dialog, number -> - val inputField = dialog.getInputField() - val value = number.toString().toIntOrNull() - - if (value != null && value in 0..9999) { - inputField.error = null - } else { - inputField.error = "Must be between 0 and 9999!" + startActivity(intent) + } + }.dependency = PreferenceKeys.eh_enableExHentai + + preference { + title = "Tag Filtering Threshold" + key = PreferenceKeys.eh_tag_filtering_value + defaultValue = 0 + + summary = "You can soft filter tags by adding them to the \"My Tags\" E/ExHentai page with a negative weight. If a gallery has tags that add up to weight below this value, it is filtered from view. This threshold can be set between -9999 and 0. Currently: ${preferences.ehTagFilterValue().get()}" + + onClick { + MaterialDialog(activity!!) + .title(text = "Tag Filtering Threshold") + .input( + inputType = InputType.TYPE_NUMBER_FLAG_SIGNED, + waitForPositiveButton = false, + allowEmpty = false + ) { dialog, number -> + val inputField = dialog.getInputField() + val value = number.toString().toIntOrNull() + + if (value != null && value in -9999..0) { + inputField.error = null + } else { + inputField.error = "Must be between -9999 and 0!" + } + dialog.setActionButtonEnabled(WhichButton.POSITIVE, value != null && value in -9999..0) } - dialog.setActionButtonEnabled(WhichButton.POSITIVE, value != null && value in 0..9999) - } - .positiveButton(android.R.string.ok) { - val value = it.getInputField().text.toString().toInt() - preferences.ehTagWatchingValue().set(value) - summary = "Recently uploaded galleries will be included on the watched screen if it has at least one watched tag with positive weight, and the sum of weights on its watched tags add up to this value or higher. This threshold can be set between 0 and 9999. Currently: $value" - preferences.ehTagWatchingValue().reconfigure() - } - .show() - } - }.dependency = PreferenceKeys.eh_enableExHentai - - preference { - title = "Language Filtering" - summary = "If you wish to hide galleries in certain languages from the gallery list and searches, select them in the dialog that will popup.\nNote that matching galleries will never appear regardless of your search query.\nTldr checkmarked = exclude" - - onClick { - MaterialDialog(activity!!) - .title(text = "Language Filtering") - .message(text = "If you wish to hide galleries in certain languages from the gallery list and searches, select them in the dialog that will popup.\nNote that matching galleries will never appear regardless of your search query.\nTldr checkmarked = exclude") - .customView(R.layout.eh_dialog_languages, scrollable = true) - .positiveButton(android.R.string.ok) { - val customView = it.view.contentLayout.customView!! - val binding = EhDialogLanguagesBinding.bind(customView) - - val languages = with(customView) { - listOfNotNull( - "${binding.japaneseOriginal.isChecked}*${binding.japaneseTranslated.isChecked}*${binding.japaneseRewrite.isChecked}", - "${binding.englishOriginal.isChecked}*${binding.englishTranslated.isChecked}*${binding.englishRewrite.isChecked}", - "${binding.chineseOriginal.isChecked}*${binding.chineseTranslated.isChecked}*${binding.chineseRewrite.isChecked}", - "${binding.dutchOriginal.isChecked}*${binding.dutchTranslated.isChecked}*${binding.dutchRewrite.isChecked}", - "${binding.frenchOriginal.isChecked}*${binding.frenchTranslated.isChecked}*${binding.frenchRewrite.isChecked}", - "${binding.germanOriginal.isChecked}*${binding.germanTranslated.isChecked}*${binding.germanRewrite.isChecked}", - "${binding.hungarianOriginal.isChecked}*${binding.hungarianTranslated.isChecked}*${binding.hungarianRewrite.isChecked}", - "${binding.italianOriginal.isChecked}*${binding.italianTranslated.isChecked}*${binding.italianRewrite.isChecked}", - "${binding.koreanOriginal.isChecked}*${binding.koreanTranslated.isChecked}*${binding.koreanRewrite.isChecked}", - "${binding.polishOriginal.isChecked}*${binding.polishTranslated.isChecked}*${binding.polishRewrite.isChecked}", - "${binding.portugueseOriginal.isChecked}*${binding.portugueseTranslated.isChecked}*${binding.portugueseRewrite.isChecked}", - "${binding.russianOriginal.isChecked}*${binding.russianTranslated.isChecked}*${binding.russianRewrite.isChecked}", - "${binding.spanishOriginal.isChecked}*${binding.spanishTranslated.isChecked}*${binding.spanishRewrite.isChecked}", - "${binding.thaiOriginal.isChecked}*${binding.thaiTranslated.isChecked}*${binding.thaiRewrite.isChecked}", - "${binding.vietnameseOriginal.isChecked}*${binding.vietnameseTranslated.isChecked}*${binding.vietnameseRewrite.isChecked}", - "${binding.notAvailableOriginal.isChecked}*${binding.notAvailableTranslated.isChecked}*${binding.notAvailableRewrite.isChecked}", - "${binding.otherOriginal.isChecked}*${binding.otherTranslated.isChecked}*${binding.otherRewrite.isChecked}" - ).joinToString("\n") + .positiveButton(android.R.string.ok) { + val value = it.getInputField().text.toString().toInt() + preferences.ehTagFilterValue().set(value) + summary = "You can soft filter tags by adding them to the \"My Tags\" E/ExHentai page with a negative weight. If a gallery has tags that add up to weight below this value, it is filtered from view. This threshold can be set between 0 and -9999. Currently: $value" + preferences.ehTagFilterValue().reconfigure() + } + .show() + } + }.dependency = PreferenceKeys.eh_enableExHentai + + preference { + title = "Tag Watching Threshold" + key = PreferenceKeys.eh_tag_watching_value + defaultValue = 0 + + summary = "Recently uploaded galleries will be included on the watched screen if it has at least one watched tag with positive weight, and the sum of weights on its watched tags add up to this value or higher. This threshold can be set between 0 and 9999. Currently: ${preferences.ehTagWatchingValue().get()}" + + onClick { + MaterialDialog(activity!!) + .title(text = "Tag Watching Threshold") + .input( + inputType = InputType.TYPE_NUMBER_FLAG_SIGNED, + maxLength = 4, + waitForPositiveButton = false, + allowEmpty = false + ) { dialog, number -> + val inputField = dialog.getInputField() + val value = number.toString().toIntOrNull() + + if (value != null && value in 0..9999) { + inputField.error = null + } else { + inputField.error = "Must be between 0 and 9999!" + } + dialog.setActionButtonEnabled(WhichButton.POSITIVE, value != null && value in 0..9999) + } + .positiveButton(android.R.string.ok) { + val value = it.getInputField().text.toString().toInt() + preferences.ehTagWatchingValue().set(value) + summary = "Recently uploaded galleries will be included on the watched screen if it has at least one watched tag with positive weight, and the sum of weights on its watched tags add up to this value or higher. This threshold can be set between 0 and 9999. Currently: $value" + preferences.ehTagWatchingValue().reconfigure() } + .show() + } + }.dependency = PreferenceKeys.eh_enableExHentai + + preference { + title = "Language Filtering" + summary = "If you wish to hide galleries in certain languages from the gallery list and searches, select them in the dialog that will popup.\nNote that matching galleries will never appear regardless of your search query.\nTldr checkmarked = exclude" + + onClick { + MaterialDialog(activity!!) + .title(text = "Language Filtering") + .message( + text = "If you wish to hide galleries in certain languages from the gallery list and searches, select them in the dialog that will popup.\nNote that matching galleries will never appear regardless of your search query.\nTldr checkmarked = exclude" + ) + .customView(R.layout.eh_dialog_languages, scrollable = true) + .positiveButton(android.R.string.ok) { + val customView = it.view.contentLayout.customView!! + val binding = EhDialogLanguagesBinding.bind(customView) + + val languages = + with(customView) { + listOfNotNull( + "${binding.japaneseOriginal.isChecked}*${binding.japaneseTranslated.isChecked}*${binding.japaneseRewrite.isChecked}", + "${binding.englishOriginal.isChecked}*${binding.englishTranslated.isChecked}*${binding.englishRewrite.isChecked}", + "${binding.chineseOriginal.isChecked}*${binding.chineseTranslated.isChecked}*${binding.chineseRewrite.isChecked}", + "${binding.dutchOriginal.isChecked}*${binding.dutchTranslated.isChecked}*${binding.dutchRewrite.isChecked}", + "${binding.frenchOriginal.isChecked}*${binding.frenchTranslated.isChecked}*${binding.frenchRewrite.isChecked}", + "${binding.germanOriginal.isChecked}*${binding.germanTranslated.isChecked}*${binding.germanRewrite.isChecked}", + "${binding.hungarianOriginal.isChecked}*${binding.hungarianTranslated.isChecked}*${binding.hungarianRewrite.isChecked}", + "${binding.italianOriginal.isChecked}*${binding.italianTranslated.isChecked}*${binding.italianRewrite.isChecked}", + "${binding.koreanOriginal.isChecked}*${binding.koreanTranslated.isChecked}*${binding.koreanRewrite.isChecked}", + "${binding.polishOriginal.isChecked}*${binding.polishTranslated.isChecked}*${binding.polishRewrite.isChecked}", + "${binding.portugueseOriginal.isChecked}*${binding.portugueseTranslated.isChecked}*${binding.portugueseRewrite.isChecked}", + "${binding.russianOriginal.isChecked}*${binding.russianTranslated.isChecked}*${binding.russianRewrite.isChecked}", + "${binding.spanishOriginal.isChecked}*${binding.spanishTranslated.isChecked}*${binding.spanishRewrite.isChecked}", + "${binding.thaiOriginal.isChecked}*${binding.thaiTranslated.isChecked}*${binding.thaiRewrite.isChecked}", + "${binding.vietnameseOriginal.isChecked}*${binding.vietnameseTranslated.isChecked}*${binding.vietnameseRewrite.isChecked}", + "${binding.notAvailableOriginal.isChecked}*${binding.notAvailableTranslated.isChecked}*${binding.notAvailableRewrite.isChecked}", + "${binding.otherOriginal.isChecked}*${binding.otherTranslated.isChecked}*${binding.otherRewrite.isChecked}" + ).joinToString("\n") + } - preferences.eh_settingsLanguages().set(languages) + preferences.eh_settingsLanguages().set(languages) - preferences.eh_settingsLanguages().reconfigure() - } - .show { - val customView = this.view.contentLayout.customView!! - val binding = EhDialogLanguagesBinding.bind(customView) - val settingsLanguages = preferences.eh_settingsLanguages().get().split("\n") - - val japanese = settingsLanguages[0].split("*").map { it.toBoolean() } - val english = settingsLanguages[1].split("*").map { it.toBoolean() } - val chinese = settingsLanguages[2].split("*").map { it.toBoolean() } - val dutch = settingsLanguages[3].split("*").map { it.toBoolean() } - val french = settingsLanguages[4].split("*").map { it.toBoolean() } - val german = settingsLanguages[5].split("*").map { it.toBoolean() } - val hungarian = settingsLanguages[6].split("*").map { it.toBoolean() } - val italian = settingsLanguages[7].split("*").map { it.toBoolean() } - val korean = settingsLanguages[8].split("*").map { it.toBoolean() } - val polish = settingsLanguages[9].split("*").map { it.toBoolean() } - val portuguese = settingsLanguages[10].split("*").map { it.toBoolean() } - val russian = settingsLanguages[11].split("*").map { it.toBoolean() } - val spanish = settingsLanguages[12].split("*").map { it.toBoolean() } - val thai = settingsLanguages[13].split("*").map { it.toBoolean() } - val vietnamese = settingsLanguages[14].split("*").map { it.toBoolean() } - val notAvailable = - settingsLanguages[15].split("*").map { it.toBoolean() } - val other = settingsLanguages[16].split("*").map { it.toBoolean() } - - with(customView) { - binding.japaneseOriginal.isChecked = japanese[0] - binding.japaneseTranslated.isChecked = japanese[1] - binding.japaneseRewrite.isChecked = japanese[2] - - binding.englishOriginal.isChecked = english[0] - binding.englishTranslated.isChecked = english[1] - binding.englishRewrite.isChecked = english[2] - - binding.chineseOriginal.isChecked = chinese[0] - binding.chineseTranslated.isChecked = chinese[1] - binding.chineseRewrite.isChecked = chinese[2] - - binding.dutchOriginal.isChecked = dutch[0] - binding.dutchTranslated.isChecked = dutch[1] - binding.dutchRewrite.isChecked = dutch[2] - - binding.frenchOriginal.isChecked = french[0] - binding.frenchTranslated.isChecked = french[1] - binding.frenchRewrite.isChecked = french[2] - - binding.germanOriginal.isChecked = german[0] - binding.germanTranslated.isChecked = german[1] - binding.germanRewrite.isChecked = german[2] - - binding.hungarianOriginal.isChecked = hungarian[0] - binding.hungarianTranslated.isChecked = hungarian[1] - binding.hungarianRewrite.isChecked = hungarian[2] - - binding.italianOriginal.isChecked = italian[0] - binding.italianTranslated.isChecked = italian[1] - binding.italianRewrite.isChecked = italian[2] - - binding.koreanOriginal.isChecked = korean[0] - binding.koreanTranslated.isChecked = korean[1] - binding.koreanRewrite.isChecked = korean[2] - - binding.polishOriginal.isChecked = polish[0] - binding.polishTranslated.isChecked = polish[1] - binding.polishRewrite.isChecked = polish[2] - - binding.portugueseOriginal.isChecked = portuguese[0] - binding.portugueseTranslated.isChecked = portuguese[1] - binding.portugueseRewrite.isChecked = portuguese[2] - - binding.russianOriginal.isChecked = russian[0] - binding.russianTranslated.isChecked = russian[1] - binding.russianRewrite.isChecked = russian[2] - - binding.spanishOriginal.isChecked = spanish[0] - binding.spanishTranslated.isChecked = spanish[1] - binding.spanishRewrite.isChecked = spanish[2] - - binding.thaiOriginal.isChecked = thai[0] - binding.thaiTranslated.isChecked = thai[1] - binding.thaiRewrite.isChecked = thai[2] - - binding.vietnameseOriginal.isChecked = vietnamese[0] - binding.vietnameseTranslated.isChecked = vietnamese[1] - binding.vietnameseRewrite.isChecked = vietnamese[2] - - binding.notAvailableOriginal.isChecked = notAvailable[0] - binding.notAvailableTranslated.isChecked = notAvailable[1] - binding.notAvailableRewrite.isChecked = notAvailable[2] - - binding.otherOriginal.isChecked = other[0] - binding.otherTranslated.isChecked = other[1] - binding.otherRewrite.isChecked = other[2] + preferences.eh_settingsLanguages().reconfigure() } - } - } - }.dependency = PreferenceKeys.eh_enableExHentai - - preference { - title = "Front Page Categories" - summary = "What categories would you like to show by default on the front page and in searches? They can still be enabled by enabling their filters" - - onClick { - MaterialDialog(activity!!) - .title(text = "Front Page Categories") - .message(text = "What categories would you like to show by default on the front page and in searches? They can still be enabled by enabling their filters") - .customView(R.layout.eh_dialog_categories, scrollable = true) - .positiveButton { - val customView = it.view.contentLayout.customView!! - val binding = EhDialogCategoriesBinding.bind(customView) - - with(customView) { - preferences.eh_EnabledCategories().set( - listOf( - (!binding.doujinshiCheckbox.isChecked).toString(), - (!binding.mangaCheckbox.isChecked).toString(), - (!binding.artistCgCheckbox.isChecked).toString(), - (!binding.gameCgCheckbox.isChecked).toString(), - (!binding.westernCheckbox.isChecked).toString(), - (!binding.nonHCheckbox.isChecked).toString(), - (!binding.imageSetCheckbox.isChecked).toString(), - (!binding.cosplayCheckbox.isChecked).toString(), - (!binding.asianPornCheckbox.isChecked).toString(), - (!binding.miscCheckbox.isChecked).toString() - ).joinToString(",") - ) + .show { + val customView = this.view.contentLayout.customView!! + val binding = EhDialogLanguagesBinding.bind(customView) + val settingsLanguages = preferences.eh_settingsLanguages().get().split("\n") + + val japanese = settingsLanguages[0].split("*").map { it.toBoolean() } + val english = settingsLanguages[1].split("*").map { it.toBoolean() } + val chinese = settingsLanguages[2].split("*").map { it.toBoolean() } + val dutch = settingsLanguages[3].split("*").map { it.toBoolean() } + val french = settingsLanguages[4].split("*").map { it.toBoolean() } + val german = settingsLanguages[5].split("*").map { it.toBoolean() } + val hungarian = settingsLanguages[6].split("*").map { it.toBoolean() } + val italian = settingsLanguages[7].split("*").map { it.toBoolean() } + val korean = settingsLanguages[8].split("*").map { it.toBoolean() } + val polish = settingsLanguages[9].split("*").map { it.toBoolean() } + val portuguese = settingsLanguages[10].split("*").map { it.toBoolean() } + val russian = settingsLanguages[11].split("*").map { it.toBoolean() } + val spanish = settingsLanguages[12].split("*").map { it.toBoolean() } + val thai = settingsLanguages[13].split("*").map { it.toBoolean() } + val vietnamese = settingsLanguages[14].split("*").map { it.toBoolean() } + val notAvailable = + settingsLanguages[15].split("*").map { it.toBoolean() } + val other = settingsLanguages[16].split("*").map { it.toBoolean() } + + with(customView) { + binding.japaneseOriginal.isChecked = japanese[0] + binding.japaneseTranslated.isChecked = japanese[1] + binding.japaneseRewrite.isChecked = japanese[2] + + binding.englishOriginal.isChecked = english[0] + binding.englishTranslated.isChecked = english[1] + binding.englishRewrite.isChecked = english[2] + + binding.chineseOriginal.isChecked = chinese[0] + binding.chineseTranslated.isChecked = chinese[1] + binding.chineseRewrite.isChecked = chinese[2] + + binding.dutchOriginal.isChecked = dutch[0] + binding.dutchTranslated.isChecked = dutch[1] + binding.dutchRewrite.isChecked = dutch[2] + + binding.frenchOriginal.isChecked = french[0] + binding.frenchTranslated.isChecked = french[1] + binding.frenchRewrite.isChecked = french[2] + + binding.germanOriginal.isChecked = german[0] + binding.germanTranslated.isChecked = german[1] + binding.germanRewrite.isChecked = german[2] + + binding.hungarianOriginal.isChecked = hungarian[0] + binding.hungarianTranslated.isChecked = hungarian[1] + binding.hungarianRewrite.isChecked = hungarian[2] + + binding.italianOriginal.isChecked = italian[0] + binding.italianTranslated.isChecked = italian[1] + binding.italianRewrite.isChecked = italian[2] + + binding.koreanOriginal.isChecked = korean[0] + binding.koreanTranslated.isChecked = korean[1] + binding.koreanRewrite.isChecked = korean[2] + + binding.polishOriginal.isChecked = polish[0] + binding.polishTranslated.isChecked = polish[1] + binding.polishRewrite.isChecked = polish[2] + + binding.portugueseOriginal.isChecked = portuguese[0] + binding.portugueseTranslated.isChecked = portuguese[1] + binding.portugueseRewrite.isChecked = portuguese[2] + + binding.russianOriginal.isChecked = russian[0] + binding.russianTranslated.isChecked = russian[1] + binding.russianRewrite.isChecked = russian[2] + + binding.spanishOriginal.isChecked = spanish[0] + binding.spanishTranslated.isChecked = spanish[1] + binding.spanishRewrite.isChecked = spanish[2] + + binding.thaiOriginal.isChecked = thai[0] + binding.thaiTranslated.isChecked = thai[1] + binding.thaiRewrite.isChecked = thai[2] + + binding.vietnameseOriginal.isChecked = vietnamese[0] + binding.vietnameseTranslated.isChecked = vietnamese[1] + binding.vietnameseRewrite.isChecked = vietnamese[2] + + binding.notAvailableOriginal.isChecked = notAvailable[0] + binding.notAvailableTranslated.isChecked = notAvailable[1] + binding.notAvailableRewrite.isChecked = notAvailable[2] + + binding.otherOriginal.isChecked = other[0] + binding.otherTranslated.isChecked = other[1] + binding.otherRewrite.isChecked = other[2] + } } + } + }.dependency = PreferenceKeys.eh_enableExHentai + + preference { + title = "Front Page Categories" + summary = "What categories would you like to show by default on the front page and in searches? They can still be enabled by enabling their filters" + + onClick { + MaterialDialog(activity!!) + .title(text = "Front Page Categories") + .message( + text = "What categories would you like to show by default on the front page and in searches? They can still be enabled by enabling their filters" + ) + .customView(R.layout.eh_dialog_categories, scrollable = true) + .positiveButton { + val customView = it.view.contentLayout.customView!! + val binding = EhDialogCategoriesBinding.bind(customView) + + with(customView) { + preferences.eh_EnabledCategories().set( + listOf( + (!binding.doujinshiCheckbox.isChecked).toString(), + (!binding.mangaCheckbox.isChecked).toString(), + (!binding.artistCgCheckbox.isChecked).toString(), + (!binding.gameCgCheckbox.isChecked).toString(), + (!binding.westernCheckbox.isChecked).toString(), + (!binding.nonHCheckbox.isChecked).toString(), + (!binding.imageSetCheckbox.isChecked).toString(), + (!binding.cosplayCheckbox.isChecked).toString(), + (!binding.asianPornCheckbox.isChecked).toString(), + (!binding.miscCheckbox.isChecked).toString() + ).joinToString(",") + ) + } - preferences.eh_EnabledCategories().reconfigure() - } - .show { - val customView = this.view.contentLayout.customView!! - val binding = EhDialogCategoriesBinding.bind(customView) - - with(customView) { - val list = preferences.eh_EnabledCategories().get().split(",").map { !it.toBoolean() } - binding.doujinshiCheckbox.isChecked = list[0] - binding.mangaCheckbox.isChecked = list[1] - binding.artistCgCheckbox.isChecked = list[2] - binding.gameCgCheckbox.isChecked = list[3] - binding.westernCheckbox.isChecked = list[4] - binding.nonHCheckbox.isChecked = list[5] - binding.imageSetCheckbox.isChecked = list[6] - binding.cosplayCheckbox.isChecked = list[7] - binding.asianPornCheckbox.isChecked = list[8] - binding.miscCheckbox.isChecked = list[9] + preferences.eh_EnabledCategories().reconfigure() } - } + .show { + val customView = this.view.contentLayout.customView!! + val binding = EhDialogCategoriesBinding.bind(customView) + + with(customView) { + val list = preferences.eh_EnabledCategories().get().split(",").map { !it.toBoolean() } + binding.doujinshiCheckbox.isChecked = list[0] + binding.mangaCheckbox.isChecked = list[1] + binding.artistCgCheckbox.isChecked = list[2] + binding.gameCgCheckbox.isChecked = list[3] + binding.westernCheckbox.isChecked = list[4] + binding.nonHCheckbox.isChecked = list[5] + binding.imageSetCheckbox.isChecked = list[6] + binding.cosplayCheckbox.isChecked = list[7] + binding.asianPornCheckbox.isChecked = list[8] + binding.miscCheckbox.isChecked = list[9] + } + } + } + }.dependency = PreferenceKeys.eh_enableExHentai + + switchPreference { + defaultValue = false + key = PreferenceKeys.eh_watched_list_default_state + title = "Watched List Filter Default State" + summary = "When browsing ExHentai/E-Hentai should the watched list filter be enabled by default" } - }.dependency = PreferenceKeys.eh_enableExHentai - switchPreference { - defaultValue = false - key = PreferenceKeys.eh_watched_list_default_state - title = "Watched List Filter Default State" - summary = "When browsing ExHentai/E-Hentai should the watched list filter be enabled by default" - } + switchPreference { + defaultValue = true + key = PreferenceKeys.eh_secure_exh + title = "Secure ExHentai/E-Hentai" + summary = "Use the HTTPS version of ExHentai/E-Hentai." + } - switchPreference { - defaultValue = true - key = PreferenceKeys.eh_secure_exh - title = "Secure ExHentai/E-Hentai" - summary = "Use the HTTPS version of ExHentai/E-Hentai." - } + listPreference { + defaultValue = "auto" + key = PreferenceKeys.eh_ehentai_quality + summary = "The quality of the downloaded images" + title = "Image quality" + entries = + arrayOf( + "Auto", + "2400x", + "1600x", + "1280x", + "980x", + "780x" + ) + entryValues = + arrayOf( + "auto", + "ovrs_2400", + "ovrs_1600", + "high", + "med", + "low" + ) - listPreference { - defaultValue = "auto" - key = PreferenceKeys.eh_ehentai_quality - summary = "The quality of the downloaded images" - title = "Image quality" - entries = arrayOf( - "Auto", - "2400x", - "1600x", - "1280x", - "980x", - "780x" - ) - entryValues = arrayOf( - "auto", - "ovrs_2400", - "ovrs_1600", - "high", - "med", - "low" - ) - - onChange { preferences.imageQuality().reconfigure() } - }.dependency = PreferenceKeys.eh_enableExHentai - } + onChange { preferences.imageQuality().reconfigure() } + }.dependency = PreferenceKeys.eh_enableExHentai + } - preferenceCategory { - title = "Favorites sync" + preferenceCategory { + title = "Favorites sync" - switchPreference { - title = "Disable favorites uploading" - summary = "Favorites are only downloaded from ExHentai. Any changes to favorites in the app will not be uploaded. Prevents accidental loss of favorites on ExHentai. Note that removals will still be downloaded (if you remove a favorites on ExHentai, it will be removed in the app as well)." - key = PreferenceKeys.eh_readOnlySync - defaultValue = false - } + switchPreference { + title = "Disable favorites uploading" + summary = "Favorites are only downloaded from ExHentai. Any changes to favorites in the app will not be uploaded. Prevents accidental loss of favorites on ExHentai. Note that removals will still be downloaded (if you remove a favorites on ExHentai, it will be removed in the app as well)." + key = PreferenceKeys.eh_readOnlySync + defaultValue = false + } - preference { - title = "Show favorites sync notes" - summary = "Show some information regarding the favorites sync feature" + preference { + title = "Show favorites sync notes" + summary = "Show some information regarding the favorites sync feature" - onClick { - activity?.let { - FavoritesIntroDialog().show(it) + onClick { + activity?.let { + FavoritesIntroDialog().show(it) + } } } - } - switchPreference { - title = "Ignore sync errors when possible" - summary = "Do not abort immediately when encountering errors during the sync process. Errors will still be displayed when the sync is complete. Can cause loss of favorites in some cases. Useful when syncing large libraries." - key = PreferenceKeys.eh_lenientSync - defaultValue = false - } + switchPreference { + title = "Ignore sync errors when possible" + summary = "Do not abort immediately when encountering errors during the sync process. Errors will still be displayed when the sync is complete. Can cause loss of favorites in some cases. Useful when syncing large libraries." + key = PreferenceKeys.eh_lenientSync + defaultValue = false + } - preference { - title = "Force sync state reset" - summary = "Performs a full resynchronization on the next sync. Removals will not be synced. All favorites in the app will be re-uploaded to ExHentai and all favorites on ExHentai will be re-downloaded into the app. Useful for repairing sync after sync has been interrupted." - - onClick { - activity?.let { activity -> - MaterialDialog(activity) - .title(R.string.eh_force_sync_reset_title) - .message(R.string.eh_force_sync_reset_message) - .positiveButton(android.R.string.yes) { - LocalFavoritesStorage().apply { - getRealm().use { - it.trans { - clearSnapshots(it) + preference { + title = "Force sync state reset" + summary = "Performs a full resynchronization on the next sync. Removals will not be synced. All favorites in the app will be re-uploaded to ExHentai and all favorites on ExHentai will be re-downloaded into the app. Useful for repairing sync after sync has been interrupted." + + onClick { + activity?.let { activity -> + MaterialDialog(activity) + .title(R.string.eh_force_sync_reset_title) + .message(R.string.eh_force_sync_reset_message) + .positiveButton(android.R.string.yes) { + LocalFavoritesStorage().apply { + getRealm().use { + it.trans { + clearSnapshots(it) + } } } + activity.toast("Sync state reset", Toast.LENGTH_LONG) } - activity.toast("Sync state reset", Toast.LENGTH_LONG) - } - .negativeButton(android.R.string.no) - .cancelable(false) - .show() + .negativeButton(android.R.string.no) + .cancelable(false) + .show() + } } } } - } - preferenceCategory { - title = "Gallery update checker" - - intListPreference { - key = PreferenceKeys.eh_autoUpdateFrequency - title = "Time between update batches" - entries = arrayOf( - "Never update galleries", - "1 hour", - "2 hours", - "3 hours", - "6 hours", - "12 hours", - "24 hours", - "48 hours" - ) - entryValues = arrayOf("0", "1", "2", "3", "6", "12", "24", "48") - defaultValue = "0" - - preferences.eh_autoUpdateFrequency().asFlow() - .onEach { newVal -> - summary = if (newVal == 0) { - "${context.getString(R.string.app_name)} will currently never check galleries in your library for updates." - } else { - "${context.getString(R.string.app_name)} checks/updates galleries in batches. " + - "This means it will wait $newVal hour(s), check ${EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION} galleries," + - " wait $newVal hour(s), check ${EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION} and so on..." + preferenceCategory { + title = "Gallery update checker" + + intListPreference { + key = PreferenceKeys.eh_autoUpdateFrequency + title = "Time between update batches" + entries = + arrayOf( + "Never update galleries", + "1 hour", + "2 hours", + "3 hours", + "6 hours", + "12 hours", + "24 hours", + "48 hours" + ) + entryValues = arrayOf("0", "1", "2", "3", "6", "12", "24", "48") + defaultValue = "0" + + preferences.eh_autoUpdateFrequency().asFlow() + .onEach { newVal -> + summary = + if (newVal == 0) { + "${context.getString( + R.string.app_name + )} will currently never check galleries in your library for updates." + } else { + "${context.getString(R.string.app_name)} checks/updates galleries in batches. " + + "This means it will wait $newVal hour(s), check ${EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION} galleries," + + " wait $newVal hour(s), check ${EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION} and so on..." + } } - } - .launchIn(scope) + .launchIn(scope) - onChange { newValue -> - val interval = (newValue as String).toInt() - EHentaiUpdateWorker.scheduleBackground(context, interval) - true + onChange { newValue -> + val interval = (newValue as String).toInt() + EHentaiUpdateWorker.scheduleBackground(context, interval) + true + } } - } - multiSelectListPreference { - key = PreferenceKeys.eh_autoUpdateRestrictions - title = "Auto update restrictions" - entriesRes = arrayOf(R.string.wifi, R.string.charging) - entryValues = arrayOf("wifi", "ac") - summaryRes = R.string.pref_library_update_restriction_summary - - preferences.eh_autoUpdateFrequency().asFlow() - .onEach { isVisible = it > 0 } - .launchIn(scope) - - onChange { - // Post to event looper to allow the preference to be updated. - Handler().post { EHentaiUpdateWorker.scheduleBackground(context) } - true + multiSelectListPreference { + key = PreferenceKeys.eh_autoUpdateRestrictions + title = "Auto update restrictions" + entriesRes = arrayOf(R.string.wifi, R.string.charging) + entryValues = arrayOf("wifi", "ac") + summaryRes = R.string.pref_library_update_restriction_summary + + preferences.eh_autoUpdateFrequency().asFlow() + .onEach { isVisible = it > 0 } + .launchIn(scope) + + onChange { + // Post to event looper to allow the preference to be updated. + Handler().post { EHentaiUpdateWorker.scheduleBackground(context) } + true + } } - } - preference { - title = "Show updater statistics" + preference { + title = "Show updater statistics" - onClick { - val progress = MaterialDialog(context) - .message(R.string.eh_show_update_statistics_dialog) - .cancelable(false) - progress.show() + onClick { + val progress = + MaterialDialog(context) + .message(R.string.eh_show_update_statistics_dialog) + .cancelable(false) + progress.show() + + GlobalScope.launch(Dispatchers.IO) { + val updateInfo = + try { + val stats = + preferences.eh_autoUpdateStats().get().nullIfBlank()?.let { + gson.fromJson(it) + } - GlobalScope.launch(Dispatchers.IO) { - val updateInfo = try { - val stats = - preferences.eh_autoUpdateStats().get().nullIfBlank()?.let { - gson.fromJson(it) - } + val statsText = + if (stats != null) { + "The updater last ran ${Humanize.naturalTime( + Date(stats.startTime) + )}, and checked ${stats.updateCount} out of the ${stats.possibleUpdates} galleries that were ready for checking." + } else { + "The updater has not ran yet." + } - val statsText = if (stats != null) { - "The updater last ran ${Humanize.naturalTime(Date(stats.startTime))}, and checked ${stats.updateCount} out of the ${stats.possibleUpdates} galleries that were ready for checking." - } else "The updater has not ran yet." - - val allMeta = db.getFavoriteMangaWithMetadata().await().filter { - it.source == EH_SOURCE_ID || it.source == EXH_SOURCE_ID - }.mapNotNull { - db.getFlatMetadataForManga(it.id!!).await() - ?.raise() - }.toList() - - fun metaInRelativeDuration(duration: Interval<*>): Int { - val durationMs = duration.inMilliseconds.longValue - return allMeta.asSequence().filter { - System.currentTimeMillis() - it.lastUpdateCheck < durationMs - }.count() - } + val allMeta = + db.getFavoriteMangaWithMetadata().await().filter { + it.source == EH_SOURCE_ID || it.source == EXH_SOURCE_ID + }.mapNotNull { + db.getFlatMetadataForManga(it.id!!).await() + ?.raise() + }.toList() + + fun metaInRelativeDuration(duration: Interval<*>): Int { + val durationMs = duration.inMilliseconds.longValue + return allMeta.asSequence().filter { + System.currentTimeMillis() - it.lastUpdateCheck < durationMs + }.count() + } - """ - $statsText - - Galleries that were checked in the last: - - hour: ${metaInRelativeDuration(1.hours)} - - 6 hours: ${metaInRelativeDuration(6.hours)} - - 12 hours: ${metaInRelativeDuration(12.hours)} - - day: ${metaInRelativeDuration(1.days)} - - 2 days: ${metaInRelativeDuration(2.days)} - - week: ${metaInRelativeDuration(7.days)} - - month: ${metaInRelativeDuration(30.days)} - - year: ${metaInRelativeDuration(365.days)} - """.trimIndent() - } finally { - progress.dismiss() - } + """ + $statsText + + Galleries that were checked in the last: + - hour: ${metaInRelativeDuration(1.hours)} + - 6 hours: ${metaInRelativeDuration(6.hours)} + - 12 hours: ${metaInRelativeDuration(12.hours)} + - day: ${metaInRelativeDuration(1.days)} + - 2 days: ${metaInRelativeDuration(2.days)} + - week: ${metaInRelativeDuration(7.days)} + - month: ${metaInRelativeDuration(30.days)} + - year: ${metaInRelativeDuration(365.days)} + """.trimIndent() + } finally { + progress.dismiss() + } - withContext(Dispatchers.Main) { - MaterialDialog(context) - .title(text = "Gallery updater statistics") - .message(text = updateInfo) - .positiveButton(android.R.string.ok) - .show() + withContext(Dispatchers.Main) { + MaterialDialog(context) + .title(text = "Gallery updater statistics") + .message(text = updateInfo) + .positiveButton(android.R.string.ok) + .show() + } } } } } } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt index 3b00216c3a09..b6d7a97c6fb6 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt @@ -5,8 +5,6 @@ import android.os.Build import android.provider.Settings import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys -import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values import eu.kanade.tachiyomi.data.preference.asImmediateFlow import eu.kanade.tachiyomi.util.preference.defaultValue import eu.kanade.tachiyomi.util.preference.entriesRes @@ -21,242 +19,256 @@ import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.system.LocaleHelper import exh.ui.lock.FingerLockPreference import exh.ui.lock.LockPreference -import java.util.Calendar import kotlinx.coroutines.flow.launchIn +import java.util.Calendar +import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys +import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values class SettingsGeneralController : SettingsController() { + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + titleRes = R.string.pref_category_general - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.pref_category_general - - intListPreference { - key = Keys.startScreen - titleRes = R.string.pref_start_screen - entriesRes = arrayOf( - R.string.label_library, - R.string.label_recent_updates, - R.string.label_recent_manga, - R.string.browse - ) - entryValues = arrayOf("1", "3", "2", "4") - defaultValue = "1" - summary = "%s" - } - switchPreference { - key = Keys.confirmExit - titleRes = R.string.pref_confirm_exit - defaultValue = false - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - preference { - titleRes = R.string.pref_manage_notifications - onClick { - val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { - putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) + intListPreference { + key = Keys.startScreen + titleRes = R.string.pref_start_screen + entriesRes = + arrayOf( + R.string.label_library, + R.string.label_recent_updates, + R.string.label_recent_manga, + R.string.browse + ) + entryValues = arrayOf("1", "3", "2", "4") + defaultValue = "1" + summary = "%s" + } + switchPreference { + key = Keys.confirmExit + titleRes = R.string.pref_confirm_exit + defaultValue = false + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + preference { + titleRes = R.string.pref_manage_notifications + onClick { + val intent = + Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { + putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) + } + startActivity(intent) } - startActivity(intent) } } - } - preferenceCategory { - titleRes = R.string.pref_category_display + preferenceCategory { + titleRes = R.string.pref_category_display + + listPreference { + key = Keys.lang + titleRes = R.string.pref_language - listPreference { - key = Keys.lang - titleRes = R.string.pref_language + val langs = mutableListOf>() + langs += Pair("", "${context.getString(R.string.system_default)} (${LocaleHelper.getDisplayName("")})") + langs += + arrayOf( + "ar", "bg", "bn", "ca", "cs", "de", "el", "en-US", "en-GB", "es", "fr", "he", + "hi", "hr", "hu", "in", "it", "ja", "ko", "lv", "ms", "nb-rNO", "nl", "pl", "pt", + "pt-BR", "ro", "ru", "sc", "sr", "sv", "th", "tl", "tr", "uk", "vi", "zh-rCN" + ) + .map { + Pair(it, LocaleHelper.getDisplayName(it)) + } + .sortedBy { it.second } - val langs = mutableListOf>() - langs += Pair("", "${context.getString(R.string.system_default)} (${LocaleHelper.getDisplayName("")})") - langs += arrayOf( - "ar", "bg", "bn", "ca", "cs", "de", "el", "en-US", "en-GB", "es", "fr", "he", - "hi", "hr", "hu", "in", "it", "ja", "ko", "lv", "ms", "nb-rNO", "nl", "pl", "pt", - "pt-BR", "ro", "ru", "sc", "sr", "sv", "th", "tl", "tr", "uk", "vi", "zh-rCN" - ) - .map { - Pair(it, LocaleHelper.getDisplayName(it)) + entryValues = langs.map { it.first }.toTypedArray() + entries = langs.map { it.second }.toTypedArray() + defaultValue = "" + summary = "%s" + + onChange { newValue -> + val activity = activity ?: return@onChange false + val app = activity.application + LocaleHelper.changeLocale(newValue.toString()) + LocaleHelper.updateConfiguration(app, app.resources.configuration) + activity.recreate() + true } - .sortedBy { it.second } + } + listPreference { + key = Keys.dateFormat + titleRes = R.string.pref_date_format + entryValues = arrayOf("", "MM/dd/yy", "dd/MM/yy", "yyyy-MM-dd") - entryValues = langs.map { it.first }.toTypedArray() - entries = langs.map { it.second }.toTypedArray() - defaultValue = "" - summary = "%s" + val currentDate = Calendar.getInstance().time + entries = + entryValues.map { value -> + val formattedDate = preferences.dateFormat(value.toString()).format(currentDate) + if (value == "") { + "${context.getString(R.string.system_default)} ($formattedDate)" + } else { + "$value ($formattedDate)" + } + }.toTypedArray() - onChange { newValue -> - val activity = activity ?: return@onChange false - val app = activity.application - LocaleHelper.changeLocale(newValue.toString()) - LocaleHelper.updateConfiguration(app, app.resources.configuration) - activity.recreate() - true + defaultValue = "" + summary = "%s" } - } - listPreference { - key = Keys.dateFormat - titleRes = R.string.pref_date_format - entryValues = arrayOf("", "MM/dd/yy", "dd/MM/yy", "yyyy-MM-dd") + listPreference { + key = Keys.themeMode + titleRes = R.string.pref_theme_mode - val currentDate = Calendar.getInstance().time - entries = entryValues.map { value -> - val formattedDate = preferences.dateFormat(value.toString()).format(currentDate) - if (value == "") { - "${context.getString(R.string.system_default)} ($formattedDate)" + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + entriesRes = + arrayOf( + R.string.theme_system, + R.string.theme_light, + R.string.theme_dark + ) + entryValues = + arrayOf( + Values.ThemeMode.system.name, + Values.ThemeMode.light.name, + Values.ThemeMode.dark.name + ) + defaultValue = Values.ThemeMode.system.name } else { - "$value ($formattedDate)" + entriesRes = + arrayOf( + R.string.theme_light, + R.string.theme_dark + ) + entryValues = + arrayOf( + Values.ThemeMode.light.name, + Values.ThemeMode.dark.name + ) + defaultValue = Values.ThemeMode.light.name } - }.toTypedArray() - defaultValue = "" - summary = "%s" - } - listPreference { - key = Keys.themeMode - titleRes = R.string.pref_theme_mode + summary = "%s" - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - entriesRes = arrayOf( - R.string.theme_system, - R.string.theme_light, - R.string.theme_dark - ) - entryValues = arrayOf( - Values.ThemeMode.system.name, - Values.ThemeMode.light.name, - Values.ThemeMode.dark.name - ) - defaultValue = Values.ThemeMode.system.name - } else { - entriesRes = arrayOf( - R.string.theme_light, - R.string.theme_dark - ) - entryValues = arrayOf( - Values.ThemeMode.light.name, - Values.ThemeMode.dark.name - ) - defaultValue = Values.ThemeMode.light.name + onChange { + activity?.recreate() + true + } } + listPreference { + key = Keys.themeLight + titleRes = R.string.pref_theme_light + entriesRes = + arrayOf( + R.string.theme_light_default, + R.string.theme_light_blue, + R.string.theme_smoothie, + R.string.theme_fumo + ) + entryValues = + arrayOf( + Values.LightThemeVariant.default.name, + Values.LightThemeVariant.blue.name, + Values.LightThemeVariant.smoothie.name, + Values.LightThemeVariant.fumo.name + ) + defaultValue = Values.LightThemeVariant.default.name + summary = "%s" - summary = "%s" + preferences.themeMode().asImmediateFlow { isVisible = it != Values.ThemeMode.dark } + .launchIn(scope) - onChange { - activity?.recreate() - true - } - } - listPreference { - key = Keys.themeLight - titleRes = R.string.pref_theme_light - entriesRes = arrayOf( - R.string.theme_light_default, - R.string.theme_light_blue, - R.string.theme_smoothie, - R.string.theme_fumo - ) - entryValues = arrayOf( - Values.LightThemeVariant.default.name, - Values.LightThemeVariant.blue.name, - Values.LightThemeVariant.smoothie.name, - Values.LightThemeVariant.fumo.name - ) - defaultValue = Values.LightThemeVariant.default.name - summary = "%s" - - preferences.themeMode().asImmediateFlow { isVisible = it != Values.ThemeMode.dark } - .launchIn(scope) - - onChange { - if (preferences.themeMode().get() != Values.ThemeMode.dark) { - activity?.recreate() + onChange { + if (preferences.themeMode().get() != Values.ThemeMode.dark) { + activity?.recreate() + } + true } - true } - } - listPreference { - key = Keys.themeDark - titleRes = R.string.pref_theme_dark - entriesRes = arrayOf( - R.string.theme_dark_default, - R.string.theme_dark_blue, - R.string.theme_dark_amoled, - R.string.theme_dark_red - ) - entryValues = arrayOf( - Values.DarkThemeVariant.default.name, - Values.DarkThemeVariant.blue.name, - Values.DarkThemeVariant.amoled.name, - Values.DarkThemeVariant.red.name - ) - defaultValue = Values.DarkThemeVariant.default.name - summary = "%s" + listPreference { + key = Keys.themeDark + titleRes = R.string.pref_theme_dark + entriesRes = + arrayOf( + R.string.theme_dark_default, + R.string.theme_dark_blue, + R.string.theme_dark_amoled, + R.string.theme_dark_red + ) + entryValues = + arrayOf( + Values.DarkThemeVariant.default.name, + Values.DarkThemeVariant.blue.name, + Values.DarkThemeVariant.amoled.name, + Values.DarkThemeVariant.red.name + ) + defaultValue = Values.DarkThemeVariant.default.name + summary = "%s" - preferences.themeMode().asImmediateFlow { isVisible = it != Values.ThemeMode.light } - .launchIn(scope) + preferences.themeMode().asImmediateFlow { isVisible = it != Values.ThemeMode.light } + .launchIn(scope) - onChange { - if (preferences.themeMode().get() != Values.ThemeMode.light) { - activity?.recreate() + onChange { + if (preferences.themeMode().get() != Values.ThemeMode.light) { + activity?.recreate() + } + true } - true } } - } - // --> EXH - switchPreference { - key = Keys.eh_expandFilters - title = "Expand all search filters by default" - defaultValue = false - } + // --> EXH + switchPreference { + key = Keys.eh_expandFilters + title = "Expand all search filters by default" + defaultValue = false + } - switchPreference { - key = Keys.eh_autoSolveCaptchas - title = "Automatically solve captcha" - summary = - "Use HIGHLY EXPERIMENTAL automatic ReCAPTCHA solver. Will be grayed out if unsupported by your device." - defaultValue = false - } + switchPreference { + key = Keys.eh_autoSolveCaptchas + title = "Automatically solve captcha" + summary = + "Use HIGHLY EXPERIMENTAL automatic ReCAPTCHA solver. Will be grayed out if unsupported by your device." + defaultValue = false + } - preferenceCategory { - title = "Application lock" + preferenceCategory { + title = "Application lock" - LockPreference(context).apply { - key = "pref_app_lock" // Not persistent so use random key - isPersistent = false + LockPreference(context).apply { + key = "pref_app_lock" // Not persistent so use random key + isPersistent = false - addPreference(this) - } + addPreference(this) + } - FingerLockPreference(context).apply { - key = "pref_lock_finger" // Not persistent so use random key - isPersistent = false + FingerLockPreference(context).apply { + key = "pref_lock_finger" // Not persistent so use random key + isPersistent = false - addPreference(this) + addPreference(this) - // Call after addPreference - dependency = "pref_app_lock" - } + // Call after addPreference + dependency = "pref_app_lock" + } - switchPreference { - key = Keys.eh_lock_manually + switchPreference { + key = Keys.eh_lock_manually - title = "Lock manually only" - summary = - "Disable automatic app locking. The app can still be locked manually by long-pressing the three-lines/back button in the top left corner." - defaultValue = false - } - switchPreference { - key = Keys.secureScreen - title = "Enable Secure Screen" - defaultValue = false - } - switchPreference { - key = Keys.hideNotificationContent - titleRes = R.string.hide_notification_content - defaultValue = false + title = "Lock manually only" + summary = + "Disable automatic app locking. The app can still be locked manually by long-pressing the three-lines/back button in the top left corner." + defaultValue = false + } + switchPreference { + key = Keys.secureScreen + title = "Enable Secure Screen" + defaultValue = false + } + switchPreference { + key = Keys.hideNotificationContent + titleRes = R.string.hide_notification_content + defaultValue = false + } } + // <-- EXH } - // <-- EXH - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt index 65fde4bfb2a6..05cf72e18e66 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt @@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.library.LibraryUpdateJob -import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.asImmediateFlow import eu.kanade.tachiyomi.ui.base.controller.DialogController @@ -34,219 +33,230 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys class SettingsLibraryController : SettingsController() { - private val db: DatabaseHelper = Injekt.get() - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.pref_category_library + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + titleRes = R.string.pref_category_library - preferenceCategory { - titleRes = R.string.pref_category_display + preferenceCategory { + titleRes = R.string.pref_category_display - preference { - titleRes = R.string.pref_library_columns - onClick { - LibraryColumnsDialog().showDialog(router) - } - - fun getColumnValue(value: Int): String { - return if (value == 0) { - context.getString(R.string.default_columns) - } else { - value.toString() + preference { + titleRes = R.string.pref_library_columns + onClick { + LibraryColumnsDialog().showDialog(router) } - } - preferences.portraitColumns().asFlow().combine(preferences.landscapeColumns().asFlow()) { portraitCols, landscapeCols -> Pair(portraitCols, landscapeCols) } - .onEach { (portraitCols, landscapeCols) -> - val portrait = getColumnValue(portraitCols) - val landscape = getColumnValue(landscapeCols) - summary = "${context.getString(R.string.portrait)}: $portrait, " + - "${context.getString(R.string.landscape)}: $landscape" + fun getColumnValue(value: Int): String { + return if (value == 0) { + context.getString(R.string.default_columns) + } else { + value.toString() + } } - .launchIn(scope) - } - intListPreference { - key = Keys.eh_library_rounded_corners - title = "Rounded Corner Radius" - entriesRes = arrayOf( - R.string.eh_rounded_corner_0, R.string.eh_rounded_corner_1, - R.string.eh_rounded_corner_2, R.string.eh_rounded_corner_3, R.string.eh_rounded_corner_4, - R.string.eh_rounded_corner_5, R.string.eh_rounded_corner_6, R.string.eh_rounded_corner_7, - R.string.eh_rounded_corner_8, R.string.eh_rounded_corner_9, R.string.eh_rounded_corner_10 - ) - entryValues = arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10") - defaultValue = "4" - summaryRes = R.string.eh_rounded_corners_desc - } - } - - val dbCategories = db.getCategories().executeAsBlocking() - val categories = listOf(Category.createDefault()) + dbCategories - - preferenceCategory { - titleRes = R.string.pref_category_library_update - - intListPreference { - key = Keys.libraryUpdateInterval - titleRes = R.string.pref_library_update_interval - entriesRes = arrayOf( - R.string.update_never, - R.string.update_12hour, R.string.update_24hour, R.string.update_48hour - ) - entryValues = arrayOf("0", "12", "24", "48") - defaultValue = "24" - summary = "%s" - onChange { newValue -> - val interval = (newValue as String).toInt() - LibraryUpdateJob.setupTask(context, interval) - true + preferences.portraitColumns().asFlow().combine( + preferences.landscapeColumns().asFlow() + ) { portraitCols, landscapeCols -> Pair(portraitCols, landscapeCols) } + .onEach { (portraitCols, landscapeCols) -> + val portrait = getColumnValue(portraitCols) + val landscape = getColumnValue(landscapeCols) + summary = "${context.getString(R.string.portrait)}: $portrait, " + + "${context.getString(R.string.landscape)}: $landscape" + } + .launchIn(scope) } - } - multiSelectListPreference { - key = Keys.libraryUpdateRestriction - titleRes = R.string.pref_library_update_restriction - entriesRes = arrayOf(R.string.wifi, R.string.charging) - entryValues = arrayOf("wifi", "ac") - summaryRes = R.string.pref_library_update_restriction_summary - defaultValue = setOf("wifi") - - preferences.libraryUpdateInterval().asImmediateFlow { isVisible = it > 0 } - .launchIn(scope) - - onChange { - // Post to event looper to allow the preference to be updated. - Handler().post { LibraryUpdateJob.setupTask(context) } - true + intListPreference { + key = Keys.eh_library_rounded_corners + title = "Rounded Corner Radius" + entriesRes = + arrayOf( + R.string.eh_rounded_corner_0, R.string.eh_rounded_corner_1, + R.string.eh_rounded_corner_2, R.string.eh_rounded_corner_3, R.string.eh_rounded_corner_4, + R.string.eh_rounded_corner_5, R.string.eh_rounded_corner_6, R.string.eh_rounded_corner_7, + R.string.eh_rounded_corner_8, R.string.eh_rounded_corner_9, R.string.eh_rounded_corner_10 + ) + entryValues = arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10") + defaultValue = "4" + summaryRes = R.string.eh_rounded_corners_desc } } - switchPreference { - key = Keys.updateOnlyNonCompleted - titleRes = R.string.pref_update_only_non_completed - defaultValue = false - } - multiSelectListPreference { - key = Keys.libraryUpdateCategories - titleRes = R.string.pref_library_update_categories - entries = categories.map { it.name }.toTypedArray() - entryValues = categories.map { it.id.toString() }.toTypedArray() - preferences.libraryUpdateCategories().asFlow() - .onEach { mutableSet -> - val selectedCategories = mutableSet - .mapNotNull { id -> categories.find { it.id == id.toInt() } } - .sortedBy { it.order } - summary = if (selectedCategories.isEmpty()) { - context.getString(R.string.all) - } else { - selectedCategories.joinToString { it.name } + val dbCategories = db.getCategories().executeAsBlocking() + val categories = listOf(Category.createDefault()) + dbCategories + + preferenceCategory { + titleRes = R.string.pref_category_library_update + + intListPreference { + key = Keys.libraryUpdateInterval + titleRes = R.string.pref_library_update_interval + entriesRes = + arrayOf( + R.string.update_never, + R.string.update_12hour, + R.string.update_24hour, + R.string.update_48hour + ) + entryValues = arrayOf("0", "12", "24", "48") + defaultValue = "24" + summary = "%s" + + onChange { newValue -> + val interval = (newValue as String).toInt() + LibraryUpdateJob.setupTask(context, interval) + true + } + } + multiSelectListPreference { + key = Keys.libraryUpdateRestriction + titleRes = R.string.pref_library_update_restriction + entriesRes = arrayOf(R.string.wifi, R.string.charging) + entryValues = arrayOf("wifi", "ac") + summaryRes = R.string.pref_library_update_restriction_summary + defaultValue = setOf("wifi") + + preferences.libraryUpdateInterval().asImmediateFlow { isVisible = it > 0 } + .launchIn(scope) + + onChange { + // Post to event looper to allow the preference to be updated. + Handler().post { LibraryUpdateJob.setupTask(context) } + true + } + } + switchPreference { + key = Keys.updateOnlyNonCompleted + titleRes = R.string.pref_update_only_non_completed + defaultValue = false + } + multiSelectListPreference { + key = Keys.libraryUpdateCategories + titleRes = R.string.pref_library_update_categories + entries = categories.map { it.name }.toTypedArray() + entryValues = categories.map { it.id.toString() }.toTypedArray() + preferences.libraryUpdateCategories().asFlow() + .onEach { mutableSet -> + val selectedCategories = + mutableSet + .mapNotNull { id -> categories.find { it.id == id.toInt() } } + .sortedBy { it.order } + + summary = + if (selectedCategories.isEmpty()) { + context.getString(R.string.all) + } else { + selectedCategories.joinToString { it.name } + } } + .launchIn(scope) + } + intListPreference { + key = Keys.libraryUpdatePrioritization + titleRes = R.string.pref_library_update_prioritization + + // The following array lines up with the list rankingScheme in: + // ../../data/library/LibraryUpdateRanker.kt + val priorities = + arrayOf( + Pair("0", R.string.action_sort_alpha), + Pair("1", R.string.action_sort_last_checked) + ) + val defaultPriority = priorities[0] + + entriesRes = priorities.map { it.second }.toTypedArray() + entryValues = priorities.map { it.first }.toTypedArray() + defaultValue = defaultPriority.first + + val selectedPriority = priorities.find { it.first.toInt() == preferences.libraryUpdatePrioritization().get() } + summaryRes = selectedPriority?.second ?: defaultPriority.second + onChange { newValue -> + summaryRes = priorities.find { + it.first == (newValue as String) + }?.second ?: defaultPriority.second + true } - .launchIn(scope) - } - intListPreference { - key = Keys.libraryUpdatePrioritization - titleRes = R.string.pref_library_update_prioritization - - // The following array lines up with the list rankingScheme in: - // ../../data/library/LibraryUpdateRanker.kt - val priorities = arrayOf( - Pair("0", R.string.action_sort_alpha), - Pair("1", R.string.action_sort_last_checked) - ) - val defaultPriority = priorities[0] - - entriesRes = priorities.map { it.second }.toTypedArray() - entryValues = priorities.map { it.first }.toTypedArray() - defaultValue = defaultPriority.first - - val selectedPriority = priorities.find { it.first.toInt() == preferences.libraryUpdatePrioritization().get() } - summaryRes = selectedPriority?.second ?: defaultPriority.second - onChange { newValue -> - summaryRes = priorities.find { - it.first == (newValue as String) - }?.second ?: defaultPriority.second - true + } + switchPreference { + key = Keys.autoUpdateMetadata + titleRes = R.string.pref_library_update_refresh_metadata + summaryRes = R.string.pref_library_update_refresh_metadata_summary + defaultValue = false + } + switchPreference { + key = Keys.showLibraryUpdateErrors + titleRes = R.string.pref_library_update_error_notification + defaultValue = false } } - switchPreference { - key = Keys.autoUpdateMetadata - titleRes = R.string.pref_library_update_refresh_metadata - summaryRes = R.string.pref_library_update_refresh_metadata_summary - defaultValue = false - } - switchPreference { - key = Keys.showLibraryUpdateErrors - titleRes = R.string.pref_library_update_error_notification - defaultValue = false - } - } - preferenceCategory { - titleRes = R.string.pref_category_library_categories + preferenceCategory { + titleRes = R.string.pref_category_library_categories - preference { - titleRes = R.string.action_edit_categories + preference { + titleRes = R.string.action_edit_categories - val catCount = db.getCategories().executeAsBlocking().size - summary = context.resources.getQuantityString(R.plurals.num_categories, catCount, catCount) + val catCount = db.getCategories().executeAsBlocking().size + summary = context.resources.getQuantityString(R.plurals.num_categories, catCount, catCount) - onClick { - router.pushController(CategoryController().withFadeTransaction()) + onClick { + router.pushController(CategoryController().withFadeTransaction()) + } } - } - - intListPreference { - key = Keys.defaultCategory - titleRes = R.string.default_category - - entries = arrayOf(context.getString(R.string.default_category_summary)) + - categories.map { it.name }.toTypedArray() - entryValues = arrayOf("-1") + categories.map { it.id.toString() }.toTypedArray() - defaultValue = "-1" - val selectedCategory = categories.find { it.id == preferences.defaultCategory() } - summary = selectedCategory?.name - ?: context.getString(R.string.default_category_summary) - onChange { newValue -> - summary = categories.find { - it.id == (newValue as String).toInt() - }?.name ?: context.getString(R.string.default_category_summary) - true + intListPreference { + key = Keys.defaultCategory + titleRes = R.string.default_category + + entries = arrayOf(context.getString(R.string.default_category_summary)) + + categories.map { it.name }.toTypedArray() + entryValues = arrayOf("-1") + categories.map { it.id.toString() }.toTypedArray() + defaultValue = "-1" + + val selectedCategory = categories.find { it.id == preferences.defaultCategory() } + summary = selectedCategory?.name + ?: context.getString(R.string.default_category_summary) + onChange { newValue -> + summary = categories.find { + it.id == (newValue as String).toInt() + }?.name ?: context.getString(R.string.default_category_summary) + true + } } - } - if (preferences.skipPreMigration().get() || preferences.migrationSources() - .get().isNotEmpty() - ) { - switchPreference { - key = Keys.skipPreMigration - titleRes = R.string.pref_skip_pre_migration - summaryRes = R.string.pref_skip_pre_migration_summary - defaultValue = false + if (preferences.skipPreMigration().get() || + preferences.migrationSources() + .get().isNotEmpty() + ) { + switchPreference { + key = Keys.skipPreMigration + titleRes = R.string.pref_skip_pre_migration + summaryRes = R.string.pref_skip_pre_migration_summary + defaultValue = false + } } } } - } class LibraryColumnsDialog : DialogController() { - private val preferences: PreferencesHelper = Injekt.get() private var portrait = preferences.portraitColumns().get() private var landscape = preferences.landscapeColumns().get() override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val dialog = MaterialDialog(activity!!) - .title(R.string.pref_library_columns) - .customView(R.layout.pref_library_columns) - .positiveButton(android.R.string.ok) { - preferences.portraitColumns().set(portrait) - preferences.landscapeColumns().set(landscape) - } - .negativeButton(android.R.string.cancel) + val dialog = + MaterialDialog(activity!!) + .title(R.string.pref_library_columns) + .customView(R.layout.pref_library_columns) + .positiveButton(android.R.string.ok) { + preferences.portraitColumns().set(portrait) + preferences.landscapeColumns().set(landscape) + } + .negativeButton(android.R.string.cancel) onViewCreated(dialog.view) return dialog diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt index 032738b2ec98..665807be5544 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt @@ -15,12 +15,14 @@ import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.openInBrowser class SettingsMainController : SettingsController() { - init { setHasOptionsMenu(true) } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { inflater.inflate(R.menu.settings, menu) } @@ -32,80 +34,81 @@ class SettingsMainController : SettingsController() { return super.onOptionsItemSelected(item) } - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.label_settings + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + titleRes = R.string.label_settings - val tintColor = context.getResourceColor(R.attr.colorAccent) + val tintColor = context.getResourceColor(R.attr.colorAccent) - preference { - iconRes = R.drawable.ic_tune_24dp - iconTint = tintColor - titleRes = R.string.pref_category_general - onClick { navigateTo(SettingsGeneralController()) } - } - preference { - iconRes = R.drawable.ic_collections_bookmark_24dp - iconTint = tintColor - titleRes = R.string.pref_category_library - onClick { navigateTo(SettingsLibraryController()) } - } - preference { - iconRes = R.drawable.ic_chrome_reader_mode_24dp - iconTint = tintColor - titleRes = R.string.pref_category_reader - onClick { navigateTo(SettingsReaderController()) } - } - preference { - iconRes = R.drawable.ic_file_download_black_24dp - iconTint = tintColor - titleRes = R.string.pref_category_downloads - onClick { navigateTo(SettingsDownloadController()) } - } - preference { - iconRes = R.drawable.ic_sync_24dp - iconTint = tintColor - titleRes = R.string.pref_category_tracking - onClick { navigateTo(SettingsTrackingController()) } - } - preference { - iconRes = R.drawable.ic_explore_24dp - iconTint = tintColor - titleRes = R.string.browse - onClick { navigateTo(SettingsBrowseController()) } - } - preference { - iconRes = R.drawable.ic_backup_24dp - iconTint = tintColor - titleRes = R.string.backup - onClick { navigateTo(SettingsBackupController()) } - } - if (preferences.eh_isHentaiEnabled().get()) { preference { - iconRes = R.drawable.eh_ic_ehlogo_red_24dp + iconRes = R.drawable.ic_tune_24dp iconTint = tintColor - titleRes = R.string.pref_category_eh - onClick { navigateTo(SettingsEhController()) } + titleRes = R.string.pref_category_general + onClick { navigateTo(SettingsGeneralController()) } } preference { - iconRes = R.drawable.eh_ic_nhlogo_color + iconRes = R.drawable.ic_collections_bookmark_24dp iconTint = tintColor - titleRes = R.string.pref_category_nh - onClick { navigateTo(SettingsNhController()) } + titleRes = R.string.pref_category_library + onClick { navigateTo(SettingsLibraryController()) } + } + preference { + iconRes = R.drawable.ic_chrome_reader_mode_24dp + iconTint = tintColor + titleRes = R.string.pref_category_reader + onClick { navigateTo(SettingsReaderController()) } + } + preference { + iconRes = R.drawable.ic_file_download_black_24dp + iconTint = tintColor + titleRes = R.string.pref_category_downloads + onClick { navigateTo(SettingsDownloadController()) } + } + preference { + iconRes = R.drawable.ic_sync_24dp + iconTint = tintColor + titleRes = R.string.pref_category_tracking + onClick { navigateTo(SettingsTrackingController()) } + } + preference { + iconRes = R.drawable.ic_explore_24dp + iconTint = tintColor + titleRes = R.string.browse + onClick { navigateTo(SettingsBrowseController()) } + } + preference { + iconRes = R.drawable.ic_backup_24dp + iconTint = tintColor + titleRes = R.string.backup + onClick { navigateTo(SettingsBackupController()) } + } + if (preferences.eh_isHentaiEnabled().get()) { + preference { + iconRes = R.drawable.eh_ic_ehlogo_red_24dp + iconTint = tintColor + titleRes = R.string.pref_category_eh + onClick { navigateTo(SettingsEhController()) } + } + preference { + iconRes = R.drawable.eh_ic_nhlogo_color + iconTint = tintColor + titleRes = R.string.pref_category_nh + onClick { navigateTo(SettingsNhController()) } + } + } + preference { + iconRes = R.drawable.ic_code_24dp + iconTint = tintColor + titleRes = R.string.pref_category_advanced + onClick { navigateTo(SettingsAdvancedController()) } + } + preference { + iconRes = R.drawable.ic_info_24dp + iconTint = tintColor + titleRes = R.string.pref_category_about + onClick { navigateTo(SettingsAboutController()) } } } - preference { - iconRes = R.drawable.ic_code_24dp - iconTint = tintColor - titleRes = R.string.pref_category_advanced - onClick { navigateTo(SettingsAdvancedController()) } - } - preference { - iconRes = R.drawable.ic_info_24dp - iconTint = tintColor - titleRes = R.string.pref_category_about - onClick { navigateTo(SettingsAboutController()) } - } - } private fun navigateTo(controller: SettingsController) { router.pushController(controller.withFadeTransaction()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsNhController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsNhController.kt index 2e7d27ba050d..dfdca09210e2 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsNhController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsNhController.kt @@ -10,14 +10,15 @@ import eu.kanade.tachiyomi.util.preference.switchPreference */ class SettingsNhController : SettingsController() { - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - title = "nhentai" + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + title = "nhentai" - switchPreference { - title = "Use high-quality thumbnails" - summary = "May slow down search results" - key = PreferenceKeys.eh_nh_useHighQualityThumbs - defaultValue = false + switchPreference { + title = "Use high-quality thumbnails" + summary = "May slow down search results" + key = PreferenceKeys.eh_nh_useHighQualityThumbs + defaultValue = false + } } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt index 64e5947743e4..798a835fbd5d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.setting import android.os.Build import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.util.preference.defaultValue import eu.kanade.tachiyomi.util.preference.entriesRes import eu.kanade.tachiyomi.util.preference.intListPreference @@ -12,309 +11,342 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory import eu.kanade.tachiyomi.util.preference.summaryRes import eu.kanade.tachiyomi.util.preference.switchPreference import eu.kanade.tachiyomi.util.preference.titleRes +import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys class SettingsReaderController : SettingsController() { + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + titleRes = R.string.pref_category_reader - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.pref_category_reader + preferenceCategory { + titleRes = R.string.pref_category_general - preferenceCategory { - titleRes = R.string.pref_category_general + intListPreference { + key = Keys.defaultViewer + titleRes = R.string.pref_viewer_type + entriesRes = + arrayOf( + R.string.left_to_right_viewer, + R.string.right_to_left_viewer, + R.string.vertical_viewer, + R.string.webtoon_viewer, + R.string.vertical_plus_viewer + ) + entryValues = arrayOf("1", "2", "3", "4", "5") + defaultValue = "1" + summary = "%s" + } + intListPreference { + key = Keys.rotation + titleRes = R.string.pref_rotation_type + entriesRes = + arrayOf( + R.string.rotation_free, + R.string.rotation_lock, + R.string.rotation_force_portrait, + R.string.rotation_force_landscape + ) + entryValues = arrayOf("1", "2", "3", "4") + defaultValue = "1" + summary = "%s" + } + intListPreference { + key = Keys.readerTheme + titleRes = R.string.pref_reader_theme + entriesRes = + arrayOf( + R.string.black_background, + R.string.gray_background, + R.string.white_background, + R.string.smart_based_on_page, + R.string.smart_based_on_page_and_theme + ) + entryValues = arrayOf("1", "2", "0", "3", "4") + defaultValue = "1" + summary = "%s" + } + intListPreference { + key = Keys.doubleTapAnimationSpeed + titleRes = R.string.pref_double_tap_anim_speed + entries = + arrayOf( + context.getString(R.string.double_tap_anim_speed_0), + context.getString(R.string.double_tap_anim_speed_fast), + context.getString(R.string.double_tap_anim_speed_normal) + ) + entryValues = arrayOf("1", "250", "500") // using a value of 0 breaks the image viewer, so min is 1 + defaultValue = "500" + summary = "%s" + } + switchPreference { + key = Keys.fullscreen + titleRes = R.string.pref_fullscreen + defaultValue = true + } - intListPreference { - key = Keys.defaultViewer - titleRes = R.string.pref_viewer_type - entriesRes = arrayOf( - R.string.left_to_right_viewer, R.string.right_to_left_viewer, - R.string.vertical_viewer, R.string.webtoon_viewer, R.string.vertical_plus_viewer - ) - entryValues = arrayOf("1", "2", "3", "4", "5") - defaultValue = "1" - summary = "%s" - } - intListPreference { - key = Keys.rotation - titleRes = R.string.pref_rotation_type - entriesRes = arrayOf( - R.string.rotation_free, R.string.rotation_lock, - R.string.rotation_force_portrait, R.string.rotation_force_landscape - ) - entryValues = arrayOf("1", "2", "3", "4") - defaultValue = "1" - summary = "%s" - } - intListPreference { - key = Keys.readerTheme - titleRes = R.string.pref_reader_theme - entriesRes = arrayOf(R.string.black_background, R.string.gray_background, R.string.white_background, R.string.smart_based_on_page, R.string.smart_based_on_page_and_theme) - entryValues = arrayOf("1", "2", "0", "3", "4") - defaultValue = "1" - summary = "%s" - } - intListPreference { - key = Keys.doubleTapAnimationSpeed - titleRes = R.string.pref_double_tap_anim_speed - entries = arrayOf(context.getString(R.string.double_tap_anim_speed_0), context.getString(R.string.double_tap_anim_speed_fast), context.getString(R.string.double_tap_anim_speed_normal)) - entryValues = arrayOf("1", "250", "500") // using a value of 0 breaks the image viewer, so min is 1 - defaultValue = "500" - summary = "%s" - } - switchPreference { - key = Keys.fullscreen - titleRes = R.string.pref_fullscreen - defaultValue = true - } + val hasDisplayCutout = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && + activity?.window?.decorView?.rootWindowInsets?.displayCutout != null + if (hasDisplayCutout) { + switchPreference { + key = Keys.cutoutShort + titleRes = R.string.pref_cutout_short + defaultValue = true + } + } - val hasDisplayCutout = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && - activity?.window?.decorView?.rootWindowInsets?.displayCutout != null - if (hasDisplayCutout) { switchPreference { - key = Keys.cutoutShort - titleRes = R.string.pref_cutout_short + key = Keys.keepScreenOn + titleRes = R.string.pref_keep_screen_on defaultValue = true } - } - - switchPreference { - key = Keys.keepScreenOn - titleRes = R.string.pref_keep_screen_on - defaultValue = true - } - switchPreference { - key = Keys.showPageNumber - titleRes = R.string.pref_show_page_number - defaultValue = true - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { switchPreference { - key = Keys.trueColor - titleRes = R.string.pref_true_color - summaryRes = R.string.pref_true_color_summary - defaultValue = false + key = Keys.showPageNumber + titleRes = R.string.pref_show_page_number + defaultValue = true + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + switchPreference { + key = Keys.trueColor + titleRes = R.string.pref_true_color + summaryRes = R.string.pref_true_color_summary + defaultValue = false + } } } - } - preferenceCategory { - titleRes = R.string.pref_category_reading + preferenceCategory { + titleRes = R.string.pref_category_reading - switchPreference { - key = Keys.skipRead - titleRes = R.string.pref_skip_read_chapters - defaultValue = false - } - switchPreference { - key = Keys.skipFiltered - titleRes = R.string.pref_skip_filtered_chapters - defaultValue = true - } - switchPreference { - key = Keys.alwaysShowChapterTransition - titleRes = R.string.pref_always_show_chapter_transition - defaultValue = true + switchPreference { + key = Keys.skipRead + titleRes = R.string.pref_skip_read_chapters + defaultValue = false + } + switchPreference { + key = Keys.skipFiltered + titleRes = R.string.pref_skip_filtered_chapters + defaultValue = true + } + switchPreference { + key = Keys.alwaysShowChapterTransition + titleRes = R.string.pref_always_show_chapter_transition + defaultValue = true + } } - } - // EXH --> - preferenceCategory { - titleRes = R.string.eh_settings_category + // EXH --> + preferenceCategory { + titleRes = R.string.eh_settings_category - intListPreference { - key = Keys.eh_readerThreads - title = "Download threads" - entries = arrayOf("1", "2", "3", "4", "5") - entryValues = entries - defaultValue = "2" - summary = - "Higher values can speed up image downloading significantly, but can also trigger bans. Recommended value is 2 or 3. Current value is: %s" - } - switchPreference { - key = Keys.eh_aggressivePageLoading - title = "Aggressively load pages" - summary = - "Slowly download the entire gallery while reading instead of just loading the pages you are viewing." - defaultValue = false - } - switchPreference { - key = Keys.eh_readerInstantRetry - title = "Skip queue on retry" - summary = - "Normally, pressing the retry button on a failed download will wait until the downloader has finished downloading the last page before beginning to re-download the failed page. Enabling this will force the downloader to begin re-downloading the failed page as soon as you press the retry button." - defaultValue = true - } - intListPreference { - key = Keys.eh_preload_size - title = "Reader Preload amount" - entryValues = arrayOf( - "1", - "2", - "3", - "4", - "6", - "8", - "10", - "12", - "14", - "16" - ) - entries = arrayOf( - "1 Page", - "2 Pages", - "3 Pages", - "4 Pages", - "6 Pages", - "8 Pages", - "10 Pages", - "12 Pages", - "14 Pages", - "16 Pages" - ) - defaultValue = "4" - summary = - "The amount of pages to preload when reading. Higher values will result in a smoother reading experience, at the cost of higher cache usage, it is recommended to increase the amount of cache you allocate when using larger values" - } - listPreference { - key = Keys.eh_cacheSize - title = "Reader cache size" - entryValues = arrayOf( - "50", - "75", - "100", - "150", - "250", - "500", - "750", - "1000", - "1500", - "2000", - "2500", - "3000", - "3500", - "4000", - "4500", - "5000" - ) - entries = arrayOf( - "50 MB", - "75 MB", - "100 MB", - "150 MB", - "250 MB", - "500 MB", - "750 MB", - "1 GB", - "1.5 GB", - "2 GB", - "2.5 GB", - "3 GB", - "3.5 GB", - "4 GB", - "4.5 GB", - "5 GB" - ) - defaultValue = "75" - summary = - "The amount of images to save on device while reading. Higher values will result in a smoother reading experience, at the cost of higher disk space usage" - } - switchPreference { - key = Keys.eh_preserveReadingPosition - title = "Preserve reading position on read manga" - defaultValue = false - } - switchPreference { - key = Keys.eh_use_auto_webtoon - title = "Auto Webtoon Mode" - summary = "Use auto webtoon mode for manga that are detected to likely use the long strip format" - defaultValue = true + intListPreference { + key = Keys.eh_readerThreads + title = "Download threads" + entries = arrayOf("1", "2", "3", "4", "5") + entryValues = entries + defaultValue = "2" + summary = + "Higher values can speed up image downloading significantly, but can also trigger bans. Recommended value is 2 or 3. Current value is: %s" + } + switchPreference { + key = Keys.eh_aggressivePageLoading + title = "Aggressively load pages" + summary = + "Slowly download the entire gallery while reading instead of just loading the pages you are viewing." + defaultValue = false + } + switchPreference { + key = Keys.eh_readerInstantRetry + title = "Skip queue on retry" + summary = + "Normally, pressing the retry button on a failed download will wait until the downloader has finished downloading the last page before beginning to re-download the failed page. Enabling this will force the downloader to begin re-downloading the failed page as soon as you press the retry button." + defaultValue = true + } + intListPreference { + key = Keys.eh_preload_size + title = "Reader Preload amount" + entryValues = + arrayOf( + "1", + "2", + "3", + "4", + "6", + "8", + "10", + "12", + "14", + "16" + ) + entries = + arrayOf( + "1 Page", + "2 Pages", + "3 Pages", + "4 Pages", + "6 Pages", + "8 Pages", + "10 Pages", + "12 Pages", + "14 Pages", + "16 Pages" + ) + defaultValue = "4" + summary = + "The amount of pages to preload when reading. Higher values will result in a smoother reading experience, at the cost of higher cache usage, it is recommended to increase the amount of cache you allocate when using larger values" + } + listPreference { + key = Keys.eh_cacheSize + title = "Reader cache size" + entryValues = + arrayOf( + "50", + "75", + "100", + "150", + "250", + "500", + "750", + "1000", + "1500", + "2000", + "2500", + "3000", + "3500", + "4000", + "4500", + "5000" + ) + entries = + arrayOf( + "50 MB", + "75 MB", + "100 MB", + "150 MB", + "250 MB", + "500 MB", + "750 MB", + "1 GB", + "1.5 GB", + "2 GB", + "2.5 GB", + "3 GB", + "3.5 GB", + "4 GB", + "4.5 GB", + "5 GB" + ) + defaultValue = "75" + summary = + "The amount of images to save on device while reading. Higher values will result in a smoother reading experience, at the cost of higher disk space usage" + } + switchPreference { + key = Keys.eh_preserveReadingPosition + title = "Preserve reading position on read manga" + defaultValue = false + } + switchPreference { + key = Keys.eh_use_auto_webtoon + title = "Auto Webtoon Mode" + summary = "Use auto webtoon mode for manga that are detected to likely use the long strip format" + defaultValue = true + } } - } - preferenceCategory { - titleRes = R.string.pager_viewer + preferenceCategory { + titleRes = R.string.pager_viewer - intListPreference { - key = Keys.imageScaleType - titleRes = R.string.pref_image_scale_type - entriesRes = arrayOf( - R.string.scale_type_fit_screen, R.string.scale_type_stretch, - R.string.scale_type_fit_width, R.string.scale_type_fit_height, - R.string.scale_type_original_size, R.string.scale_type_smart_fit - ) - entryValues = arrayOf("1", "2", "3", "4", "5", "6") - defaultValue = "1" - summary = "%s" - } - intListPreference { - key = Keys.zoomStart - titleRes = R.string.pref_zoom_start - entriesRes = arrayOf( - R.string.zoom_start_automatic, R.string.zoom_start_left, - R.string.zoom_start_right, R.string.zoom_start_center - ) - entryValues = arrayOf("1", "2", "3", "4") - defaultValue = "1" - summary = "%s" - } - switchPreference { - key = Keys.enableTransitions - titleRes = R.string.pref_page_transitions - defaultValue = true - } - switchPreference { - key = Keys.cropBorders - titleRes = R.string.pref_crop_borders - defaultValue = false + intListPreference { + key = Keys.imageScaleType + titleRes = R.string.pref_image_scale_type + entriesRes = + arrayOf( + R.string.scale_type_fit_screen, + R.string.scale_type_stretch, + R.string.scale_type_fit_width, + R.string.scale_type_fit_height, + R.string.scale_type_original_size, + R.string.scale_type_smart_fit + ) + entryValues = arrayOf("1", "2", "3", "4", "5", "6") + defaultValue = "1" + summary = "%s" + } + intListPreference { + key = Keys.zoomStart + titleRes = R.string.pref_zoom_start + entriesRes = + arrayOf( + R.string.zoom_start_automatic, + R.string.zoom_start_left, + R.string.zoom_start_right, + R.string.zoom_start_center + ) + entryValues = arrayOf("1", "2", "3", "4") + defaultValue = "1" + summary = "%s" + } + switchPreference { + key = Keys.enableTransitions + titleRes = R.string.pref_page_transitions + defaultValue = true + } + switchPreference { + key = Keys.cropBorders + titleRes = R.string.pref_crop_borders + defaultValue = false + } } - } - preferenceCategory { - titleRes = R.string.webtoon_viewer + preferenceCategory { + titleRes = R.string.webtoon_viewer - switchPreference { - key = Keys.cropBordersWebtoon - titleRes = R.string.pref_crop_borders - defaultValue = false - } + switchPreference { + key = Keys.cropBordersWebtoon + titleRes = R.string.pref_crop_borders + defaultValue = false + } - intListPreference { - key = Keys.webtoonSidePadding - titleRes = R.string.pref_webtoon_side_padding - entriesRes = arrayOf( - R.string.webtoon_side_padding_0, - R.string.webtoon_side_padding_10, - R.string.webtoon_side_padding_15, - R.string.webtoon_side_padding_20, - R.string.webtoon_side_padding_25 - ) - entryValues = arrayOf("0", "10", "15", "20", "25") - defaultValue = "0" - summary = "%s" + intListPreference { + key = Keys.webtoonSidePadding + titleRes = R.string.pref_webtoon_side_padding + entriesRes = + arrayOf( + R.string.webtoon_side_padding_0, + R.string.webtoon_side_padding_10, + R.string.webtoon_side_padding_15, + R.string.webtoon_side_padding_20, + R.string.webtoon_side_padding_25 + ) + entryValues = arrayOf("0", "10", "15", "20", "25") + defaultValue = "0" + summary = "%s" + } } - } - preferenceCategory { - titleRes = R.string.pref_reader_navigation + preferenceCategory { + titleRes = R.string.pref_reader_navigation - switchPreference { - key = Keys.readWithTapping - titleRes = R.string.pref_read_with_tapping - defaultValue = true - } - switchPreference { - key = Keys.readWithLongTap - titleRes = R.string.pref_read_with_long_tap - defaultValue = true - } - switchPreference { - key = Keys.readWithVolumeKeys - titleRes = R.string.pref_read_with_volume_keys - defaultValue = false + switchPreference { + key = Keys.readWithTapping + titleRes = R.string.pref_read_with_tapping + defaultValue = true + } + switchPreference { + key = Keys.readWithLongTap + titleRes = R.string.pref_read_with_long_tap + defaultValue = true + } + switchPreference { + key = Keys.readWithVolumeKeys + titleRes = R.string.pref_read_with_volume_keys + defaultValue = false + } + switchPreference { + key = Keys.readWithVolumeKeysInverted + titleRes = R.string.pref_read_with_volume_keys_inverted + defaultValue = false + }.apply { dependency = Keys.readWithVolumeKeys } } - switchPreference { - key = Keys.readWithVolumeKeysInverted - titleRes = R.string.pref_read_with_volume_keys_inverted - defaultValue = false - }.apply { dependency = Keys.readWithVolumeKeys } } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt index 39e2fe07961b..02442aad1d16 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt @@ -17,16 +17,15 @@ import eu.kanade.tachiyomi.util.preference.switchPreferenceCategory import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory -import java.util.TreeMap import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import reactivecircus.flowbinding.appcompat.queryTextChanges import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.TreeMap class SettingsSourcesController : SettingsController() { - init { setHasOptionsMenu(true) } @@ -42,55 +41,56 @@ class SettingsSourcesController : SettingsController() { private var sourcesByLang: TreeMap> = TreeMap() private var sorting = SourcesSort.Alpha - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.action_filter + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + titleRes = R.string.action_filter - sorting = SourcesSort.from(preferences.sourceSorting().get()) ?: SourcesSort.Alpha - activity?.invalidateOptionsMenu() + sorting = SourcesSort.from(preferences.sourceSorting().get()) ?: SourcesSort.Alpha + activity?.invalidateOptionsMenu() - // Get the list of active language codes. - val activeLangsCodes = preferences.enabledLanguages().get() + // Get the list of active language codes. + val activeLangsCodes = preferences.enabledLanguages().get() - // Get a map of sources grouped by language. - sourcesByLang = onlineSources.groupByTo(TreeMap(), { it.lang }) - - // Order first by active languages, then inactive ones - orderedLangs = sourcesByLang.keys.filter { it in activeLangsCodes } + - sourcesByLang.keys.filterNot { it in activeLangsCodes } - - orderedLangs.forEach { lang -> - val sources = sourcesByLang[lang].orEmpty().sortedBy { it.name } - - // Create a preference group and set initial state and change listener - langPrefs.add( - Pair( - lang, - switchPreferenceCategory { - preferenceScreen.addPreference(this) - title = LocaleHelper.getSourceDisplayName(lang, context) - isPersistent = false - if (lang in activeLangsCodes) { - setChecked(true) - addLanguageSources(this, sortedSources(sourcesByLang[lang])) - } + // Get a map of sources grouped by language. + sourcesByLang = onlineSources.groupByTo(TreeMap(), { it.lang }) - onChange { newValue -> - val checked = newValue as Boolean - val current = preferences.enabledLanguages().get() - if (!checked) { - preferences.enabledLanguages().set(current - lang) - removeAll() - } else { - preferences.enabledLanguages().set(current + lang) + // Order first by active languages, then inactive ones + orderedLangs = sourcesByLang.keys.filter { it in activeLangsCodes } + + sourcesByLang.keys.filterNot { it in activeLangsCodes } + + orderedLangs.forEach { lang -> + val sources = sourcesByLang[lang].orEmpty().sortedBy { it.name } + + // Create a preference group and set initial state and change listener + langPrefs.add( + Pair( + lang, + switchPreferenceCategory { + preferenceScreen.addPreference(this) + title = LocaleHelper.getSourceDisplayName(lang, context) + isPersistent = false + if (lang in activeLangsCodes) { + setChecked(true) addLanguageSources(this, sortedSources(sourcesByLang[lang])) } - true + + onChange { newValue -> + val checked = newValue as Boolean + val current = preferences.enabledLanguages().get() + if (!checked) { + preferences.enabledLanguages().set(current - lang) + removeAll() + } else { + preferences.enabledLanguages().set(current + lang) + addLanguageSources(this, sortedSources(sourcesByLang[lang])) + } + true + } } - } + ) ) - ) + } } - } override fun setDivider(divider: Drawable?) { super.setDivider(null) @@ -101,63 +101,68 @@ class SettingsSourcesController : SettingsController() { * * @param group the language category. */ - private fun addLanguageSources(group: PreferenceGroup, sources: List) { + private fun addLanguageSources( + group: PreferenceGroup, + sources: List + ) { val hiddenCatalogues = preferences.hiddenCatalogues().get() - val selectAllPreference = CheckBoxPreference(group.context).apply { - title = "\t\t${context.getString(R.string.pref_category_all_sources)}" - key = "all_${sources.first().lang}" - isPersistent = false - isChecked = sources.all { it.id.toString() !in hiddenCatalogues } - isVisible = query.isEmpty() - - onChange { newValue -> - val checked = newValue as Boolean - val current = preferences.hiddenCatalogues().get() as MutableSet? ?: mutableSetOf() - if (checked) { - current.removeAll(sources.map { it.id.toString() }) - } else { - current.addAll(sources.map { it.id.toString() }) - } - preferences.hiddenCatalogues().set(current) - group.removeAll() - addLanguageSources(group, sortedSources(sources)) - true - } - } - group.addPreference(selectAllPreference) - - sources.forEach { source -> - val sourcePreference = CheckBoxPreference(group.context).apply { - val id = source.id.toString() - title = source.name - key = getSourceKey(source.id) + val selectAllPreference = + CheckBoxPreference(group.context).apply { + title = "\t\t${context.getString(R.string.pref_category_all_sources)}" + key = "all_${sources.first().lang}" isPersistent = false - isChecked = id !in hiddenCatalogues - isVisible = query.isEmpty() || source.name.contains(query, ignoreCase = true) - - val sourceIcon = source.icon() - if (sourceIcon != null) { - icon = sourceIcon - } + isChecked = sources.all { it.id.toString() !in hiddenCatalogues } + isVisible = query.isEmpty() onChange { newValue -> val checked = newValue as Boolean - val current = preferences.hiddenCatalogues().get() - - preferences.hiddenCatalogues().set( - if (checked) { - current - id - } else { - current + id - } - ) - + val current = preferences.hiddenCatalogues().get() as MutableSet? ?: mutableSetOf() + if (checked) { + current.removeAll(sources.map { it.id.toString() }) + } else { + current.addAll(sources.map { it.id.toString() }) + } + preferences.hiddenCatalogues().set(current) group.removeAll() addLanguageSources(group, sortedSources(sources)) true } } + group.addPreference(selectAllPreference) + + sources.forEach { source -> + val sourcePreference = + CheckBoxPreference(group.context).apply { + val id = source.id.toString() + title = source.name + key = getSourceKey(source.id) + isPersistent = false + isChecked = id !in hiddenCatalogues + isVisible = query.isEmpty() || source.name.contains(query, ignoreCase = true) + + val sourceIcon = source.icon() + if (sourceIcon != null) { + icon = sourceIcon + } + + onChange { newValue -> + val checked = newValue as Boolean + val current = preferences.hiddenCatalogues().get() + + preferences.hiddenCatalogues().set( + if (checked) { + current - id + } else { + current + id + } + ) + + group.removeAll() + addLanguageSources(group, sortedSources(sources)) + true + } + } group.addPreference(sourcePreference) } @@ -173,10 +178,16 @@ class SettingsSourcesController : SettingsController() { * @param menu menu containing options. * @param inflater used to load the menu xml. */ - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { inflater.inflate(R.menu.settings_sources, menu) - if (sorting == SourcesSort.Alpha) menu.findItem(R.id.action_sort_alpha).isChecked = true - else menu.findItem(R.id.action_sort_enabled).isChecked = true + if (sorting == SourcesSort.Alpha) { + menu.findItem(R.id.action_sort_alpha).isChecked = true + } else { + menu.findItem(R.id.action_sort_enabled).isChecked = true + } val searchItem = menu.findItem(R.id.action_search) val searchView = searchItem.actionView as SearchView @@ -197,16 +208,18 @@ class SettingsSourcesController : SettingsController() { .launchIn(scope) // Fixes problem with the overflow icon showing up in lieu of search - searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { - override fun onMenuItemActionExpand(item: MenuItem): Boolean { - return true - } + searchItem.setOnActionExpandListener( + object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem): Boolean { + return true + } - override fun onMenuItemActionCollapse(item: MenuItem): Boolean { - activity?.invalidateOptionsMenu() - return true + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + activity?.invalidateOptionsMenu() + return true + } } - }) + ) } private fun drawSources() { @@ -237,11 +250,12 @@ class SettingsSourcesController : SettingsController() { * @return True if this event has been consumed, false if it has not. */ override fun onOptionsItemSelected(item: MenuItem): Boolean { - sorting = when (item.itemId) { - R.id.action_sort_alpha -> SourcesSort.Alpha - R.id.action_sort_enabled -> SourcesSort.Enabled - else -> return super.onOptionsItemSelected(item) - } + sorting = + when (item.itemId) { + R.id.action_sort_alpha -> SourcesSort.Alpha + R.id.action_sort_enabled -> SourcesSort.Enabled + else -> return super.onOptionsItemSelected(item) + } item.isChecked = true preferences.sourceSorting().set(sorting.value) drawSources() @@ -249,7 +263,9 @@ class SettingsSourcesController : SettingsController() { } enum class SourcesSort(val value: Int) { - Alpha(0), Enabled(1); + Alpha(0), + Enabled(1) + ; companion object { fun from(i: Int): SourcesSort? = values().find { it.value == i } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt index ef39691955bd..8d1b5eb4d0a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt @@ -5,7 +5,6 @@ import android.content.Intent import androidx.browser.customtabs.CustomTabsIntent import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.anilist.AnilistApi @@ -24,59 +23,63 @@ import eu.kanade.tachiyomi.widget.preference.LoginPreference import eu.kanade.tachiyomi.widget.preference.TrackLoginDialog import eu.kanade.tachiyomi.widget.preference.TrackLogoutDialog import uy.kohesive.injekt.injectLazy +import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys class SettingsTrackingController : SettingsController(), TrackLoginDialog.Listener, TrackLogoutDialog.Listener { - private val trackManager: TrackManager by injectLazy() - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - titleRes = R.string.pref_category_tracking - - switchPreference { - key = Keys.autoUpdateTrack - titleRes = R.string.pref_auto_update_manga_sync - defaultValue = true - } - preferenceCategory { - titleRes = R.string.services + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + titleRes = R.string.pref_category_tracking - trackPreference(trackManager.myAnimeList) { - startActivity(MyAnimeListLoginActivity.newIntent(activity!!)) - } - trackPreference(trackManager.aniList) { - val tabsIntent = CustomTabsIntent.Builder() - .setToolbarColor(context.getResourceColor(R.attr.colorPrimary)) - .build() - tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) - tabsIntent.launchUrl(activity!!, AnilistApi.authUrl()) + switchPreference { + key = Keys.autoUpdateTrack + titleRes = R.string.pref_auto_update_manga_sync + defaultValue = true } - trackPreference(trackManager.kitsu) { - val dialog = TrackLoginDialog(trackManager.kitsu, R.string.email) - dialog.targetController = this@SettingsTrackingController - dialog.showDialog(router) - } - trackPreference(trackManager.shikimori) { - val tabsIntent = CustomTabsIntent.Builder() - .setToolbarColor(context.getResourceColor(R.attr.colorPrimary)) - .build() - tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) - tabsIntent.launchUrl(activity!!, ShikimoriApi.authUrl()) + preferenceCategory { + titleRes = R.string.services + + trackPreference(trackManager.myAnimeList) { + startActivity(MyAnimeListLoginActivity.newIntent(activity!!)) + } + trackPreference(trackManager.aniList) { + val tabsIntent = + CustomTabsIntent.Builder() + .setToolbarColor(context.getResourceColor(R.attr.colorPrimary)) + .build() + tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) + tabsIntent.launchUrl(activity!!, AnilistApi.authUrl()) + } + trackPreference(trackManager.kitsu) { + val dialog = TrackLoginDialog(trackManager.kitsu, R.string.email) + dialog.targetController = this@SettingsTrackingController + dialog.showDialog(router) + } + trackPreference(trackManager.shikimori) { + val tabsIntent = + CustomTabsIntent.Builder() + .setToolbarColor(context.getResourceColor(R.attr.colorPrimary)) + .build() + tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) + tabsIntent.launchUrl(activity!!, ShikimoriApi.authUrl()) + } + trackPreference(trackManager.bangumi) { + val tabsIntent = + CustomTabsIntent.Builder() + .setToolbarColor(context.getResourceColor(R.attr.colorPrimary)) + .build() + tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) + tabsIntent.launchUrl(activity!!, BangumiApi.authUrl()) + } } - trackPreference(trackManager.bangumi) { - val tabsIntent = CustomTabsIntent.Builder() - .setToolbarColor(context.getResourceColor(R.attr.colorPrimary)) - .build() - tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) - tabsIntent.launchUrl(activity!!, BangumiApi.authUrl()) + preferenceCategory { + infoPreference(R.string.tracking_info) } } - preferenceCategory { - infoPreference(R.string.tracking_info) - } - } private inline fun PreferenceScreen.trackPreference( service: TrackService, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/AnilistLoginActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/AnilistLoginActivity.kt index ba2448705c01..0dd563a97b3a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/AnilistLoginActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/AnilistLoginActivity.kt @@ -14,7 +14,6 @@ import rx.schedulers.Schedulers import uy.kohesive.injekt.injectLazy class AnilistLoginActivity : AppCompatActivity() { - private val trackManager: TrackManager by injectLazy() override fun onCreate(savedState: Bundle?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/BangumiLoginActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/BangumiLoginActivity.kt index 76e1ca304925..8155986fa569 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/BangumiLoginActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/BangumiLoginActivity.kt @@ -14,7 +14,6 @@ import rx.schedulers.Schedulers import uy.kohesive.injekt.injectLazy class BangumiLoginActivity : AppCompatActivity() { - private val trackManager: TrackManager by injectLazy() override fun onCreate(savedState: Bundle?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/MyAnimeListLoginActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/MyAnimeListLoginActivity.kt index 4a217bd82bbb..1e6a26acf05e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/MyAnimeListLoginActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/MyAnimeListLoginActivity.kt @@ -15,42 +15,48 @@ import rx.schedulers.Schedulers import uy.kohesive.injekt.injectLazy class MyAnimeListLoginActivity : BaseWebViewActivity() { - private val trackManager: TrackManager by injectLazy() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (bundle == null) { - binding.webview.webViewClient = object : WebViewClientCompat() { - override fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean { - view.loadUrl(url) - return true - } + binding.webview.webViewClient = + object : WebViewClientCompat() { + override fun shouldOverrideUrlCompat( + view: WebView, + url: String + ): Boolean { + view.loadUrl(url) + return true + } - override fun onPageFinished(view: WebView?, url: String?) { - super.onPageFinished(view, url) + override fun onPageFinished( + view: WebView?, + url: String? + ) { + super.onPageFinished(view, url) - // Get CSRF token from HTML after post-login redirect - if (url == "https://myanimelist.net/") { - view?.evaluateJavascript( - "(function(){return document.querySelector('meta[name=csrf_token]').getAttribute('content')})();" - ) { - trackManager.myAnimeList.login(it.replace("\"", "")) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - returnToSettings() - }, - { - returnToSettings() - } - ) + // Get CSRF token from HTML after post-login redirect + if (url == "https://myanimelist.net/") { + view?.evaluateJavascript( + "(function(){return document.querySelector('meta[name=csrf_token]').getAttribute('content')})();" + ) { + trackManager.myAnimeList.login(it.replace("\"", "")) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + returnToSettings() + }, + { + returnToSettings() + } + ) + } } } } - } binding.webview.loadUrl(MyAnimeListApi.loginUrl()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/ShikimoriLoginActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/ShikimoriLoginActivity.kt index 4cb217d8507b..4008ed2774da 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/ShikimoriLoginActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/ShikimoriLoginActivity.kt @@ -14,7 +14,6 @@ import rx.schedulers.Schedulers import uy.kohesive.injekt.injectLazy class ShikimoriLoginActivity : AppCompatActivity() { - private val trackManager: TrackManager by injectLazy() override fun onCreate(savedState: Bundle?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangHolder.kt index a45ff2e3886c..ff1a1206200d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangHolder.kt @@ -8,8 +8,8 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper class LangHolder(view: View, adapter: FlexibleAdapter<*>) : BaseFlexibleViewHolder(view, adapter) { - private val binding = SourceMainControllerCardBinding.bind(view) + fun bind(item: LangItem) { binding.title.text = LocaleHelper.getSourceDisplayName(item.code, itemView.context) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangItem.kt index 2a1b6041b995..414cbaf8b6cd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangItem.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.R * @param code The lang code. */ data class LangItem(val code: String) : AbstractHeaderItem() { - /** * Returns the layout resource of this item. */ @@ -23,7 +22,10 @@ data class LangItem(val code: String) : AbstractHeaderItem() { /** * Creates a new view holder for this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LangHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): LangHolder { return LangHolder(view, adapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceAdapter.kt index 206d26a91589..84d22be0c7ed 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceAdapter.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.util.system.getResourceColor */ class SourceAdapter(val controller: SourceController) : FlexibleAdapter>(null, controller, true) { - val cardBackground = controller.activity!!.getResourceColor(R.attr.colorSurface) init { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceController.kt index 90d458a9a1eb..b2f9e2b02d85 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceController.kt @@ -55,7 +55,6 @@ class SourceController(bundle: Bundle? = null) : FlexibleAdapter.OnItemLongClickListener, SourceAdapter.OnBrowseClickListener, SourceAdapter.OnLatestClickListener { - private val preferences: PreferencesHelper = Injekt.get() /** @@ -95,7 +94,10 @@ class SourceController(bundle: Bundle? = null) : * @param container containing parent views. * @return inflated view. */ - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = SourceMainControllerBinding.inflate(inflater) return binding.root } @@ -119,7 +121,10 @@ class SourceController(bundle: Bundle? = null) : super.onDestroyView(view) } - override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { + override fun onChangeStarted( + handler: ControllerChangeHandler, + type: ControllerChangeType + ) { super.onChangeStarted(handler, type) if (!type.isPush && handler is SettingsSourcesFadeChangeHandler) { presenter.updateSources() @@ -129,7 +134,10 @@ class SourceController(bundle: Bundle? = null) : /** * Called when item is clicked */ - override fun onItemClick(view: View?, position: Int): Boolean { + override fun onItemClick( + view: View?, + position: Int + ): Boolean { val item = adapter?.getItem(position) as? SourceItem ?: return false val source = item.source when (mode) { @@ -137,14 +145,15 @@ class SourceController(bundle: Bundle? = null) : // Open the catalogue view. openCatalogue(source, BrowseSourceController(source)) } - Mode.SMART_SEARCH -> router.pushController( - SmartSearchController( - Bundle().apply { - putLong(SmartSearchController.ARG_SOURCE_ID, source.id) - putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig) - } - ).withFadeTransaction() - ) + Mode.SMART_SEARCH -> + router.pushController( + SmartSearchController( + Bundle().apply { + putLong(SmartSearchController.ARG_SOURCE_ID, source.id) + putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig) + } + ).withFadeTransaction() + ) } return false } @@ -158,7 +167,8 @@ class SourceController(bundle: Bundle? = null) : MaterialDialog(activity) .title(text = item.source.name) .listItems( - items = listOf( + items = + listOf( activity.getString(R.string.action_hide), activity.getString(if (isPinned) R.string.action_unpin else R.string.action_pin) ), @@ -180,7 +190,10 @@ class SourceController(bundle: Bundle? = null) : presenter.updateSources() } - private fun pinCatalogue(source: Source, isPinned: Boolean) { + private fun pinCatalogue( + source: Source, + isPinned: Boolean + ) { val current = preferences.pinnedCatalogues().get() if (isPinned) { preferences.pinnedCatalogues().set(current - source.id.toString()) @@ -209,7 +222,10 @@ class SourceController(bundle: Bundle? = null) : /** * Opens a catalogue with the given controller. */ - private fun openCatalogue(source: CatalogueSource, controller: BrowseSourceController) { + private fun openCatalogue( + source: CatalogueSource, + controller: BrowseSourceController + ) { preferences.lastUsedCatalogueSource().set(source.id) router.pushController(controller.withFadeTransaction()) } @@ -220,7 +236,10 @@ class SourceController(bundle: Bundle? = null) : * @param menu menu containing options. * @param inflater used to load the menu xml. */ - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { // Inflate menu inflater.inflate(R.menu.source_main, menu) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceDividerItemDecoration.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceDividerItemDecoration.kt index b5df257e926d..d0237f6a9497 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceDividerItemDecoration.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceDividerItemDecoration.kt @@ -8,7 +8,6 @@ import android.view.View import androidx.recyclerview.widget.RecyclerView class SourceDividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() { - private val divider: Drawable init { @@ -17,7 +16,11 @@ class SourceDividerItemDecoration(context: Context) : RecyclerView.ItemDecoratio a.recycle() } - override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { + override fun onDraw( + c: Canvas, + parent: RecyclerView, + state: RecyclerView.State + ) { val childCount = parent.childCount for (i in 0 until childCount - 1) { val child = parent.getChildAt(i) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceHolder.kt index d329b55033bc..c4dfb46e4922 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceHolder.kt @@ -10,15 +10,16 @@ import eu.kanade.tachiyomi.ui.base.holder.SlicedHolder import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.visible import io.github.mthli.slice.Slice + class SourceHolder(view: View, override val adapter: SourceAdapter, val showButtons: Boolean) : BaseFlexibleViewHolder(view, adapter), SlicedHolder { - private val binding = SourceMainControllerCardItemBinding.bind(view) - override val slice = Slice(binding.card).apply { - setColor(adapter.cardBackground) - } + override val slice = + Slice(binding.card).apply { + setColor(adapter.cardBackground) + } override val viewToSlice: View get() = binding.card diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt index e838afff9c5c..746390a83d8e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt @@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource */ data class SourceItem(val source: CatalogueSource, val header: LangItem? = null, val showButtons: Boolean) : AbstractSectionableItem(header) { - /** * Returns the layout resource of this item. */ @@ -27,7 +26,10 @@ data class SourceItem(val source: CatalogueSource, val header: LangItem? = null, /** * Creates a new view holder for this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): SourceHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): SourceHolder { return SourceHolder(view, adapter as SourceAdapter, showButtons) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourcePresenter.kt index b1af4aced22c..9cfd6603e318 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourcePresenter.kt @@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter -import java.util.TreeMap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -19,6 +18,7 @@ import rx.Observable import rx.Subscription import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.TreeMap /** * Presenter of [SourceController] @@ -32,7 +32,6 @@ class SourcePresenter( private val preferences: PreferencesHelper = Injekt.get(), private val controllerMode: SourceController.Mode ) : BasePresenter() { - private val scope = CoroutineScope(Job() + Dispatchers.Main) var sources = getEnabledSources() @@ -59,32 +58,35 @@ class SourcePresenter( val pinnedSources = mutableListOf() val pinnedCatalogues = preferences.pinnedCatalogues().get() - val map = TreeMap> { d1, d2 -> - // Catalogues without a lang defined will be placed at the end - when { - d1 == "" && d2 != "" -> 1 - d2 == "" && d1 != "" -> -1 - else -> d1.compareTo(d2) + val map = + TreeMap> { d1, d2 -> + // Catalogues without a lang defined will be placed at the end + when { + d1 == "" && d2 != "" -> 1 + d2 == "" && d1 != "" -> -1 + else -> d1.compareTo(d2) + } } - } val byLang = sources.groupByTo(map, { it.lang }) - var sourceItems = byLang.flatMap { - val langItem = LangItem(it.key) - it.value.map { source -> - if (source.id.toString() in pinnedCatalogues) { - pinnedSources.add(SourceItem(source, LangItem(PINNED_KEY), controllerMode == SourceController.Mode.CATALOGUE)) + var sourceItems = + byLang.flatMap { + val langItem = LangItem(it.key) + it.value.map { source -> + if (source.id.toString() in pinnedCatalogues) { + pinnedSources.add(SourceItem(source, LangItem(PINNED_KEY), controllerMode == SourceController.Mode.CATALOGUE)) + } + + SourceItem(source, langItem, controllerMode == SourceController.Mode.CATALOGUE) } - - SourceItem(source, langItem, controllerMode == SourceController.Mode.CATALOGUE) } - } if (pinnedSources.isNotEmpty()) { sourceItems = pinnedSources + sourceItems } - sourceSubscription = Observable.just(sourceItems) - .subscribeLatestCache(SourceController::setSources) + sourceSubscription = + Observable.just(sourceItems) + .subscribeLatestCache(SourceController::setSources) } private fun loadLastUsedSource() { @@ -103,7 +105,10 @@ class SourcePresenter( if (preferences.hideLastUsedSource().get()) { view().subscribe { view -> view?.setLastUsedSource(null) } } else { - val source = (sourceManager.get(sourceId) as? CatalogueSource)?.let { SourceItem(it, showButtons = controllerMode == SourceController.Mode.CATALOGUE) } + val source = + (sourceManager.get(sourceId) as? CatalogueSource)?.let { + SourceItem(it, showButtons = controllerMode == SourceController.Mode.CATALOGUE) + } source?.let { view().subscribe { view -> view?.setLastUsedSource(it) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt index 3c854053eddb..04c3f127cd04 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt @@ -75,7 +75,6 @@ open class BrowseSourceController(bundle: Bundle) : FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.EndlessScrollListener, ChangeMangaCategoriesDialog.Listener { - constructor( source: CatalogueSource, searchQuery: String? = null, @@ -151,7 +150,10 @@ open class BrowseSourceController(bundle: Bundle) : ) } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = SourceControllerBinding.inflate(inflater) return binding.root } @@ -177,7 +179,9 @@ open class BrowseSourceController(bundle: Bundle) : } override fun createSecondaryDrawer(drawer: DrawerLayout): ViewGroup? { - if (mode == Mode.RECOMMENDS) { return null } + if (mode == Mode.RECOMMENDS) { + return null + } // Inflate and prepare drawer val navView = drawer.inflate(R.layout.source_drawer) as SourceFilterSheet // TODO whatever this is this.navView = navView @@ -197,11 +201,13 @@ open class BrowseSourceController(bundle: Bundle) : if (searchName.isNotBlank() && oldSavedSearches.size < SourceFilterSheet.MAX_SAVED_SEARCHES ) { - val newSearches = oldSavedSearches + EXHSavedSearch( - searchName.toString().trim(), - presenter.query, - presenter.sourceFilters - ) + val newSearches = + oldSavedSearches + + EXHSavedSearch( + searchName.toString().trim(), + presenter.query, + presenter.sourceFilters + ) presenter.saveSearches(newSearches) navView.setSavedSearches(newSearches) } @@ -259,9 +265,10 @@ open class BrowseSourceController(bundle: Bundle) : .message(text = "Are you sure you wish to delete your saved search query: '${search.name}'?") .positiveButton(R.string.action_cancel) .negativeButton(text = "Confirm") { - val newSearches = savedSearches.filterIndexed { index, _ -> - index != indexToDelete - } + val newSearches = + savedSearches.filterIndexed { index, _ -> + index != indexToDelete + } presenter.saveSearches(newSearches) navView.setSavedSearches(newSearches) } @@ -304,31 +311,34 @@ open class BrowseSourceController(bundle: Bundle) : binding.catalogueView.removeView(oldRecycler) } - val recycler = if (preferences.catalogueDisplayMode().get() == DisplayMode.LIST) { - RecyclerView(view.context).apply { - id = R.id.recycler - layoutManager = LinearLayoutManager(context) - layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) - addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) - } - } else { - (binding.catalogueView.inflate(R.layout.source_recycler_autofit) as AutofitRecyclerView).apply { - numColumnsJob = getColumnsPreferenceForCurrentOrientation().asImmediateFlow { spanCount = it } - .drop(1) - // Set again the adapter to recalculate the covers height - .onEach { adapter = this@BrowseSourceController.adapter } - .launchIn(scope) - - (layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { - override fun getSpanSize(position: Int): Int { - return when (adapter?.getItemViewType(position)) { - R.layout.source_compact_grid_item, R.layout.source_comfortable_grid_item, null -> 1 - else -> spanCount + val recycler = + if (preferences.catalogueDisplayMode().get() == DisplayMode.LIST) { + RecyclerView(view.context).apply { + id = R.id.recycler + layoutManager = LinearLayoutManager(context) + layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) + } + } else { + (binding.catalogueView.inflate(R.layout.source_recycler_autofit) as AutofitRecyclerView).apply { + numColumnsJob = + getColumnsPreferenceForCurrentOrientation().asImmediateFlow { spanCount = it } + .drop(1) + // Set again the adapter to recalculate the covers height + .onEach { adapter = this@BrowseSourceController.adapter } + .launchIn(scope) + + (layoutManager as GridLayoutManager).spanSizeLookup = + object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return when (adapter?.getItemViewType(position)) { + R.layout.source_compact_grid_item, R.layout.source_comfortable_grid_item, null -> 1 + else -> spanCount + } + } } - } } } - } recycler.setHasFixedSize(true) recycler.adapter = adapter @@ -340,7 +350,10 @@ open class BrowseSourceController(bundle: Bundle) : this.recycler = recycler } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { inflater.inflate(R.menu.source_browse, menu) if (mode == Mode.RECOMMENDS) { @@ -387,11 +400,12 @@ open class BrowseSourceController(bundle: Bundle) : } // Show next display mode - val displayItem = when (preferences.catalogueDisplayMode().get()) { - DisplayMode.COMPACT_GRID -> R.id.action_compact_grid - DisplayMode.COMFORTABLE_GRID -> R.id.action_comfortable_grid - DisplayMode.LIST -> R.id.action_list - } + val displayItem = + when (preferences.catalogueDisplayMode().get()) { + DisplayMode.COMPACT_GRID -> R.id.action_compact_grid + DisplayMode.COMFORTABLE_GRID -> R.id.action_comfortable_grid + DisplayMode.LIST -> R.id.action_list + } menu.findItem(displayItem).isChecked = true } @@ -453,7 +467,10 @@ open class BrowseSourceController(bundle: Bundle) : * @param page the current page. * @param mangas the list of manga of the page. */ - fun onAddPage(page: Int, mangas: List) { + fun onAddPage( + page: Int, + mangas: List + ) { val adapter = adapter ?: return hideProgressBar() if (page == 1) { @@ -488,15 +505,16 @@ open class BrowseSourceController(bundle: Bundle) : snack?.dismiss() val message = getErrorMessage(error) - val retryAction = View.OnClickListener { - // If not the first page, show bottom progress bar. - if (adapter.mainItemCount > 0 && progressItem != null) { - adapter.addScrollableFooterWithDelay(progressItem!!, 0, true) - } else { - showProgressBar() + val retryAction = + View.OnClickListener { + // If not the first page, show bottom progress bar. + if (adapter.mainItemCount > 0 && progressItem != null) { + adapter.addScrollableFooterWithDelay(progressItem!!, 0, true) + } else { + showProgressBar() + } + presenter.requestNext() } - presenter.requestNext() - } if (adapter.isEmpty) { Log.d("Adapter", "empty") @@ -514,9 +532,10 @@ open class BrowseSourceController(bundle: Bundle) : binding.emptyView.show(message, actions) } else { - snack = binding.catalogueView.snack(message, Snackbar.LENGTH_INDEFINITE) { - setAction(R.string.action_retry, retryAction) - } + snack = + binding.catalogueView.snack(message, Snackbar.LENGTH_INDEFINITE) { + setAction(R.string.action_retry, retryAction) + } } } @@ -527,7 +546,9 @@ open class BrowseSourceController(bundle: Bundle) : return when { error.message == null -> "" - error.message!!.startsWith("HTTP error") -> "${error.message}: ${binding.catalogueView.context.getString(R.string.http_error_hint)}" + error.message!!.startsWith( + "HTTP error" + ) -> "${error.message}: ${binding.catalogueView.context.getString(R.string.http_error_hint)}" else -> error.message!! } } @@ -544,7 +565,10 @@ open class BrowseSourceController(bundle: Bundle) : /** * Called by the adapter when scrolled near the bottom. */ - override fun onLoadMore(lastPosition: Int, currentPage: Int) { + override fun onLoadMore( + lastPosition: Int, + currentPage: Int + ) { if (presenter.hasNextPage()) { presenter.requestNext() } else { @@ -580,9 +604,10 @@ open class BrowseSourceController(bundle: Bundle) : // Initialize mangas if not on a metered connection if (!view.context.connectivityManager.isActiveNetworkMetered) { - val mangas = (0 until adapter.itemCount).mapNotNull { - (adapter.getItem(it) as? SourceItem)?.manga - } + val mangas = + (0 until adapter.itemCount).mapNotNull { + (adapter.getItem(it) as? SourceItem)?.manga + } presenter.initializeMangas(mangas) } } @@ -643,17 +668,21 @@ open class BrowseSourceController(bundle: Bundle) : * @param position the position of the element clicked. * @return true if the item should be selected, false otherwise. */ - override fun onItemClick(view: View, position: Int): Boolean { + override fun onItemClick( + view: View, + position: Int + ): Boolean { val item = adapter?.getItem(position) as? SourceItem ?: return false when (mode) { - Mode.CATALOGUE -> router.pushController( - MangaController( - item.manga, - true, - args.getParcelable(SMART_SEARCH_CONFIG_KEY) - ).withFadeTransaction() - ) + Mode.CATALOGUE -> + router.pushController( + MangaController( + item.manga, + true, + args.getParcelable(SMART_SEARCH_CONFIG_KEY) + ).withFadeTransaction() + ) Mode.RECOMMENDS -> openSmartSearch(item.manga.title) } return false @@ -672,6 +701,7 @@ open class BrowseSourceController(bundle: Bundle) : } // AZ <-- + /** * Called when a manga is long clicked. * @@ -682,7 +712,9 @@ open class BrowseSourceController(bundle: Bundle) : * @param position the position of the element clicked. */ override fun onItemLongClick(position: Int) { - if (mode == Mode.RECOMMENDS) { return } + if (mode == Mode.RECOMMENDS) { + return + } val activity = activity ?: return val manga = (adapter?.getItem(position) as? SourceItem?)?.manga ?: return @@ -728,9 +760,10 @@ open class BrowseSourceController(bundle: Bundle) : // Choose a category else -> { val ids = presenter.getMangaCategoryIds(manga) - val preselected = ids.mapNotNull { id -> - categories.indexOfFirst { it.id == id }.takeIf { it != -1 } - }.toTypedArray() + val preselected = + ids.mapNotNull { id -> + categories.indexOfFirst { it.id == id }.takeIf { it != -1 } + }.toTypedArray() ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected) .showDialog(router) @@ -745,7 +778,10 @@ open class BrowseSourceController(bundle: Bundle) : * @param mangas The list of manga to move to categories. * @param categories The list of categories where manga will be placed. */ - override fun updateCategoriesForMangas(mangas: List, categories: List) { + override fun updateCategoriesForMangas( + mangas: List, + categories: List + ) { val manga = mangas.firstOrNull() ?: return presenter.changeMangaFavorite(manga) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt index aecdb021196f..65b4d9e828ff 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt @@ -37,8 +37,6 @@ import eu.kanade.tachiyomi.util.system.withUIContext import exh.isEhBasedSource import exh.savedsearches.EXHSavedSearch import exh.savedsearches.JsonSavedSearch -import java.lang.RuntimeException -import java.util.Date import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asFlow @@ -58,6 +56,8 @@ import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import xyz.nulldev.ts.api.http.serializer.FilterSerializer +import java.lang.RuntimeException +import java.util.Date /** * Presenter of [BrowseSourceController]. @@ -72,7 +72,6 @@ open class BrowseSourcePresenter( private val coverCache: CoverCache = Injekt.get(), private val recommends: Boolean = false ) : BasePresenter() { - /** * Selected source. */ @@ -146,14 +145,22 @@ open class BrowseSourcePresenter( * @param query the query. * @param filters the current state of the filters (for search mode). */ - fun restartPager(query: String = this.query, filters: FilterList = this.appliedFilters) { + fun restartPager( + query: String = this.query, + filters: FilterList = this.appliedFilters + ) { this.query = query this.appliedFilters = filters // Create a new pager. - pager = if (recommends && searchManga != null) RecommendsPager( - searchManga - ) else createPager(query, filters) + pager = + if (recommends && searchManga != null) { + RecommendsPager( + searchManga + ) + } else { + createPager(query, filters) + } val sourceId = source.id @@ -161,20 +168,21 @@ open class BrowseSourcePresenter( // Prepare the pager. pagerSubscription?.let { remove(it) } - pagerSubscription = pager.results() - .observeOn(Schedulers.io()) - .map { pair -> pair.first to pair.second.map { networkToLocalManga(it, sourceId) } } - .doOnNext { initializeMangas(it.second) } - .map { pair -> pair.first to pair.second.map { SourceItem(it, catalogueDisplayMode) } } - .observeOn(AndroidSchedulers.mainThread()) - .subscribeReplay( - { view, (page, mangas) -> - view.onAddPage(page, mangas) - }, - { _, error -> - Timber.e(error) - } - ) + pagerSubscription = + pager.results() + .observeOn(Schedulers.io()) + .map { pair -> pair.first to pair.second.map { networkToLocalManga(it, sourceId) } } + .doOnNext { initializeMangas(it.second) } + .map { pair -> pair.first to pair.second.map { SourceItem(it, catalogueDisplayMode) } } + .observeOn(AndroidSchedulers.mainThread()) + .subscribeReplay( + { view, (page, mangas) -> + view.onAddPage(page, mangas) + }, + { _, error -> + Timber.e(error) + } + ) // Request first page. requestNext() @@ -187,15 +195,16 @@ open class BrowseSourcePresenter( if (!hasNextPage()) return nextPageJob?.cancel() - nextPageJob = launchIO { - try { - pager.requestNextPage() - } catch (e: Throwable) { - withUIContext { - view().subscribe { view -> view?.onAddPageError(e) } + nextPageJob = + launchIO { + try { + pager.requestNextPage() + } catch (e: Throwable) { + withUIContext { + view().subscribe { view -> view?.onAddPageError(e) } + } } } - } } /** @@ -212,7 +221,10 @@ open class BrowseSourcePresenter( * @param sManga the manga from the source. * @return a manga from the database. */ - private fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { + private fun networkToLocalManga( + sManga: SManga, + sourceId: Long + ): Manga { var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking() if (localManga == null) { val newManga = Manga.create(sManga.url, sManga.title, sourceId) @@ -270,10 +282,11 @@ open class BrowseSourcePresenter( */ fun changeMangaFavorite(manga: Manga) { manga.favorite = !manga.favorite - manga.date_added = when (manga.favorite) { - true -> Date().time - false -> 0 - } + manga.date_added = + when (manga.favorite) { + true -> Date().time + false -> 0 + } if (!manga.favorite) { manga.removeCovers(coverCache) @@ -291,8 +304,15 @@ open class BrowseSourcePresenter( restartPager(filters = filters) } - open fun createPager(query: String, filters: FilterList): Pager { - return if (source.isEhBasedSource()) { ExhPager(source, query, filters) } else { SourcePager(source, query, filters) } + open fun createPager( + query: String, + filters: FilterList + ): Pager { + return if (source.isEhBasedSource()) { + ExhPager(source, query, filters) + } else { + SourcePager(source, query, filters) + } } private fun FilterList.toItems(): List> { @@ -309,24 +329,26 @@ open class BrowseSourcePresenter( is Filter.Select<*> -> SelectItem(filter) is Filter.Group<*> -> { val group = GroupItem(filter) - val subItems = filter.state.mapNotNull { - when (it) { - is Filter.CheckBox -> CheckboxSectionItem(it) - is Filter.TriState -> TriStateSectionItem(it) - is Filter.Text -> TextSectionItem(it) - is Filter.Select<*> -> SelectSectionItem(it) - else -> null + val subItems = + filter.state.mapNotNull { + when (it) { + is Filter.CheckBox -> CheckboxSectionItem(it) + is Filter.TriState -> TriStateSectionItem(it) + is Filter.Text -> TextSectionItem(it) + is Filter.Select<*> -> SelectSectionItem(it) + else -> null + } } - } subItems.forEach { it.header = group } group.subItems = subItems group } is Filter.Sort -> { val group = SortGroup(filter) - val subItems = filter.values.map { - SortItem(it, group) - } + val subItems = + filter.values.map { + SortItem(it, group) + } group.subItems = subItems group } @@ -360,7 +382,10 @@ open class BrowseSourcePresenter( * @param categories the selected categories. * @param manga the manga to move. */ - private fun moveMangaToCategories(manga: Manga, categories: List) { + private fun moveMangaToCategories( + manga: Manga, + categories: List + ) { val mc = categories.filter { it.id != 0 }.map { MangaCategory.create(manga, it) } db.setMangaCategories(mc, listOf(manga)) } @@ -371,7 +396,10 @@ open class BrowseSourcePresenter( * @param category the selected category. * @param manga the manga to move. */ - fun moveMangaToCategory(manga: Manga, category: Category?) { + fun moveMangaToCategory( + manga: Manga, + category: Category? + ) { moveMangaToCategories(manga, listOfNotNull(category)) } @@ -381,7 +409,10 @@ open class BrowseSourcePresenter( * @param manga needed to change * @param selectedCategories selected categories */ - fun updateMangaCategories(manga: Manga, selectedCategories: List) { + fun updateMangaCategories( + manga: Manga, + selectedCategories: List + ) { if (!manga.favorite) { changeMangaFavorite(manga) } @@ -391,17 +422,21 @@ open class BrowseSourcePresenter( // EXH --> private val filterSerializer = FilterSerializer() + fun saveSearches(searches: List) { - val otherSerialized = prefs.eh_savedSearches().get().filter { - !it.startsWith("${source.id}:") - } - val newSerialized = searches.map { - "${source.id}:" + buildJsonObject { - put("name", it.name) - put("query", it.query) - put("filters", filterSerializer.serialize(it.filterList)) - }.toString() - } + val otherSerialized = + prefs.eh_savedSearches().get().filter { + !it.startsWith("${source.id}:") + } + val newSerialized = + searches.map { + "${source.id}:" + + buildJsonObject { + put("name", it.name) + put("query", it.query) + put("filters", filterSerializer.serialize(it.filterList)) + }.toString() + } prefs.eh_savedSearches().set((otherSerialized + newSerialized).toSet()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/ExhPager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/ExhPager.kt index 90143560b8a4..8eb39c508714 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/ExhPager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/ExhPager.kt @@ -7,15 +7,15 @@ import eu.kanade.tachiyomi.util.lang.awaitSingle import exh.EH_SOURCE_ID open class ExhPager(val source: CatalogueSource, val query: String, val filters: FilterList) : Pager() { - override suspend fun requestNextPage() { val page = currentPage - val observable = if (query.isBlank() && filters.isEmpty()) { - source.fetchPopularManga(page) - } else { - source.fetchSearchManga(page, query, filters) - } + val observable = + if (query.isBlank() && filters.isEmpty()) { + source.fetchPopularManga(page) + } else { + source.fetchSearchManga(page, query, filters) + } val mangasPage = observable.awaitSingle() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/Pager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/Pager.kt index 05051919fe3e..8ad704f6e865 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/Pager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/Pager.kt @@ -9,7 +9,6 @@ import rx.Observable * A general pager for source requests (latest updates, popular, search) */ abstract class Pager(var currentPage: Int = 1) { - var hasNextPage = true protected set @@ -20,6 +19,7 @@ abstract class Pager(var currentPage: Int = 1) { } abstract suspend fun requestNextPage() + open fun onPageReceived(mangasPage: MangasPage) { val page = currentPage currentPage++ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/ProgressItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/ProgressItem.kt index 3d73b808a08c..1f18df4c1f58 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/ProgressItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/ProgressItem.kt @@ -13,18 +13,25 @@ import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.visible class ProgressItem : AbstractFlexibleItem() { - private var loadMore = true override fun getLayoutRes(): Int { return R.layout.source_progress_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: List) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: List + ) { holder.progressBar.gone() holder.progressMessage.gone() @@ -48,7 +55,6 @@ class ProgressItem : AbstractFlexibleItem() { } class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) { - val progressBar: ProgressBar = view.findViewById(R.id.progress_bar) val progressMessage: TextView = view.findViewById(R.id.progress_message) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/RecommendsPager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/RecommendsPager.kt index 4a407d615368..748b29668455 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/RecommendsPager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/RecommendsPager.kt @@ -57,14 +57,16 @@ class MyAnimeList() : API("https://api.jikan.moe/v3/") { urlBuilder.addPathSegment("recommendations") val url = urlBuilder.build().toUrl() - val request = Request.Builder() - .url(url) - .get() - .build() + val request = + Request.Builder() + .url(url) + .get() + .build() - val handler = CoroutineExceptionHandler { _, exception -> - callback.invoke(null, exception) - } + val handler = + CoroutineExceptionHandler { _, exception -> + callback.invoke(null, exception) + } scope.launch(handler) { val response = client.newCall(request).await() @@ -73,18 +75,20 @@ class MyAnimeList() : API("https://api.jikan.moe/v3/") { throw Exception("Null Response") } val data = JsonParser.parseString(body).obj - val recommendations = data["recommendations"].nullArray - ?: throw Exception("Unexpected response") - val recs = recommendations.map { rec -> - Timber.tag("RECOMMENDATIONS") - .d("MYANIMELIST > FOUND RECOMMENDATION > %s", rec["title"].string) - SMangaImpl().apply { - this.title = rec["title"].string - this.thumbnail_url = rec["image_url"].string - this.initialized = true - this.url = rec["url"].string + val recommendations = + data["recommendations"].nullArray + ?: throw Exception("Unexpected response") + val recs = + recommendations.map { rec -> + Timber.tag("RECOMMENDATIONS") + .d("MYANIMELIST > FOUND RECOMMENDATION > %s", rec["title"].string) + SMangaImpl().apply { + this.title = rec["title"].string + this.thumbnail_url = rec["image_url"].string + this.initialized = true + this.url = rec["url"].string + } } - } callback.invoke(recs, null) } } @@ -105,14 +109,16 @@ class MyAnimeList() : API("https://api.jikan.moe/v3/") { urlBuilder.addQueryParameter("q", search) val url = urlBuilder.build().toUrl() - val request = Request.Builder() - .url(url) - .get() - .build() + val request = + Request.Builder() + .url(url) + .get() + .build() - val handler = CoroutineExceptionHandler { _, exception -> - callback.invoke(null, exception) - } + val handler = + CoroutineExceptionHandler { _, exception -> + callback.invoke(null, exception) + } scope.launch(handler) { val response = client.newCall(request).await() @@ -135,14 +141,21 @@ class MyAnimeList() : API("https://api.jikan.moe/v3/") { } class Anilist() : API("https://graphql.anilist.co/") { - private fun countOccurrence(arr: JsonArray, search: String): Int { + private fun countOccurrence( + arr: JsonArray, + search: String + ): Int { return arr.count { val synonym = it.string synonym.contains(search, true) } } - private fun languageContains(obj: JsonObject, language: String, search: String): Boolean { + private fun languageContains( + obj: JsonObject, + language: String, + search: String + ): Boolean { return obj["title"].obj[language].nullString?.contains(search, true) == true } @@ -187,22 +200,26 @@ class Anilist() : API("https://graphql.anilist.co/") { |} |} |} - |""".trimMargin() + | + """.trimMargin() val variables = jsonObject("search" to search) - val payload = jsonObject( - "query" to query, - "variables" to variables - ) + val payload = + jsonObject( + "query" to query, + "variables" to variables + ) val payloadBody = payload.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) - val request = Request.Builder() - .url(endpoint) - .post(payloadBody) - .build() + val request = + Request.Builder() + .url(endpoint) + .post(payloadBody) + .build() - val handler = CoroutineExceptionHandler { _, exception -> - callback.invoke(null, exception) - } + val handler = + CoroutineExceptionHandler { _, exception -> + callback.invoke(null, exception) + } scope.launch(handler) { val response = client.newCall(request).await() @@ -210,35 +227,38 @@ class Anilist() : API("https://graphql.anilist.co/") { if (body.isEmpty()) { throw Exception("Null Response") } - val data = JsonParser.parseString(body).obj["data"].nullObj - ?: throw Exception("Unexpected response") + val data = + JsonParser.parseString(body).obj["data"].nullObj + ?: throw Exception("Unexpected response") val page = data["Page"].obj val media = page["media"].array if (media.size() <= 0) { throw Exception("'$search' not found") } - val result = media.sortedWith( - compareBy( - { languageContains(it.obj, "romaji", search) }, - { languageContains(it.obj, "english", search) }, - { languageContains(it.obj, "native", search) }, - { countOccurrence(it.obj["synonyms"].array, search) > 0 } - ) - ).last().obj + val result = + media.sortedWith( + compareBy( + { languageContains(it.obj, "romaji", search) }, + { languageContains(it.obj, "english", search) }, + { languageContains(it.obj, "native", search) }, + { countOccurrence(it.obj["synonyms"].array, search) > 0 } + ) + ).last().obj Timber.tag("RECOMMENDATIONS") .d("ANILIST > FOUND TITLE > %s", getTitle(result)) val recommendations = result["recommendations"].obj["edges"].array - val recs = recommendations.map { - val rec = it["node"]["mediaRecommendation"].obj - Timber.tag("RECOMMENDATIONS") - .d("ANILIST: FOUND RECOMMENDATION: %s", getTitle(rec)) - SMangaImpl().apply { - this.title = getTitle(rec) - this.thumbnail_url = rec["coverImage"].obj["large"].string - this.initialized = true - this.url = rec["siteUrl"].string + val recs = + recommendations.map { + val rec = it["node"]["mediaRecommendation"].obj + Timber.tag("RECOMMENDATIONS") + .d("ANILIST: FOUND RECOMMENDATION: %s", getTitle(rec)) + SMangaImpl().apply { + this.title = getTitle(rec) + this.thumbnail_url = rec["coverImage"].obj["large"].string + this.initialized = true + this.url = rec["siteUrl"].string + } } - } callback.invoke(recs, null) } } @@ -257,11 +277,12 @@ open class RecommendsPager( Timber.tag("RECOMMENDATIONS").e("%s > Couldn't find any", currentApi.toString()) apiList.remove(currentApi) val list = apiList.toList() - currentApi = if (list.isEmpty()) { - null - } else { - apiList.toList().first().first - } + currentApi = + if (list.isEmpty()) { + null + } else { + apiList.toList().first().first + } if (currentApi != null) { getRecs(currentApi!!) @@ -303,10 +324,11 @@ open class RecommendsPager( } companion object { - val API_MAP = mapOf( - API.MYANIMELIST to MyAnimeList(), - API.ANILIST to Anilist() - ) + val API_MAP = + mapOf( + API.MYANIMELIST to MyAnimeList(), + API.ANILIST to Anilist() + ) enum class API { MYANIMELIST, ANILIST } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceComfortableGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceComfortableGridHolder.kt index e30ee057c1ed..99adbc9c6e75 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceComfortableGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceComfortableGridHolder.kt @@ -19,8 +19,8 @@ import eu.kanade.tachiyomi.widget.StateImageViewTarget */ class SourceComfortableGridHolder(private val view: View, private val adapter: FlexibleAdapter<*>) : SourceHolder(view, adapter) { - override val binding = SourceComfortableGridItemBinding.bind(view) + /** * Method called from [CatalogueAdapter.onBindViewHolder]. It updates the data for this * holder with the given manga. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt index b99fbc713998..f2f172153d5a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt @@ -20,12 +20,14 @@ import eu.kanade.tachiyomi.util.view.inflate import eu.kanade.tachiyomi.widget.SimpleNavigationView import exh.savedsearches.EXHSavedSearch -class SourceFilterSheet @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class SourceFilterSheet +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : SimpleNavigationView(context, attrs) { - - val adapter: FlexibleAdapter> = FlexibleAdapter>(null) - .setDisplayHeadersAtStartUp(true) - .setStickyHeaders(true) + val adapter: FlexibleAdapter> = + FlexibleAdapter>(null) + .setDisplayHeadersAtStartUp(true) + .setStickyHeaders(true) var onSearchClicked = {} @@ -47,6 +49,7 @@ class SourceFilterSheet @JvmOverloads constructor(context: Context, attrs: Attri private val searchBtn: Button private val resetBtn: Button private val savedSearches: LinearLayout + init { recycler.adapter = adapter recycler.setHasFixedSize(true) @@ -87,7 +90,10 @@ class SourceFilterSheet @JvmOverloads constructor(context: Context, attrs: Attri restoreBtn.setBackgroundResource(outValue.resourceId) restoreBtn.setPadding(8.dpToPx, 8.dpToPx, 8.dpToPx, 8.dpToPx) restoreBtn.setOnClickListener { onSavedSearchClicked(index) } - restoreBtn.setOnLongClickListener { onSavedSearchDeleteClicked(index, search.name); true } + restoreBtn.setOnLongClickListener { + onSavedSearchDeleteClicked(index, search.name) + true + } savedSearches.addView(restoreBtn) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceGridHolder.kt index 97a73e0a75da..34dbbc98f09d 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceGridHolder.kt @@ -23,7 +23,6 @@ import uy.kohesive.injekt.api.get */ open class SourceGridHolder(private val view: View, private val adapter: FlexibleAdapter<*>) : SourceHolder(view, adapter) { - override val binding = SourceCompactGridItemBinding.bind(view) private val preferences: PreferencesHelper = Injekt.get() @@ -46,11 +45,12 @@ open class SourceGridHolder(private val view: View, private val adapter: Flexibl override fun setImage(manga: Manga) { // Setting this via XML doesn't work binding.card.clipToOutline = true - binding.card.radius = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - preferences.eh_library_corner_radius().get().toFloat(), - view.context.resources.displayMetrics - ) + binding.card.radius = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + preferences.eh_library_corner_radius().get().toFloat(), + view.context.resources.displayMetrics + ) GlideApp.with(view.context).clear(binding.thumbnail) if (!manga.thumbnail_url.isNullOrEmpty()) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceHolder.kt index 0ec59581ccc8..f0ca28b171ad 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceHolder.kt @@ -14,8 +14,8 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder */ abstract class SourceHolder(view: View, adapter: FlexibleAdapter<*>) : BaseFlexibleViewHolder(view, adapter) { - abstract val binding: VB + /** * Method called from [CatalogueAdapter.onBindViewHolder]. It updates the data for this * holder with the given manga. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceItem.kt index 201781f82d6e..48507ff9773c 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceItem.kt @@ -19,7 +19,6 @@ import eu.kanade.tachiyomi.widget.AutofitRecyclerView class SourceItem(val manga: Manga, private val catalogueDisplayMode: Preference) : AbstractFlexibleItem>() { - override fun getLayoutRes(): Int { return when (catalogueDisplayMode.get()) { DisplayMode.COMPACT_GRID -> R.layout.source_compact_grid_item @@ -38,12 +37,17 @@ class SourceItem(val manga: Manga, private val catalogueDisplayMode: Preference< val parent = adapter.recyclerView as AutofitRecyclerView val coverHeight = parent.itemWidth / 3 * 4 view.apply { - binding.card.layoutParams = FrameLayout.LayoutParams( - MATCH_PARENT, coverHeight - ) - binding.gradient.layoutParams = FrameLayout.LayoutParams( - MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM - ) + binding.card.layoutParams = + FrameLayout.LayoutParams( + MATCH_PARENT, + coverHeight + ) + binding.gradient.layoutParams = + FrameLayout.LayoutParams( + MATCH_PARENT, + coverHeight / 2, + Gravity.BOTTOM + ) } SourceGridHolder(view, adapter) } @@ -52,9 +56,11 @@ class SourceItem(val manga: Manga, private val catalogueDisplayMode: Preference< val parent = adapter.recyclerView as AutofitRecyclerView val coverHeight = parent.itemWidth / 3 * 4 view.apply { - binding.card.layoutParams = ConstraintLayout.LayoutParams( - MATCH_PARENT, coverHeight - ) + binding.card.layoutParams = + ConstraintLayout.LayoutParams( + MATCH_PARENT, + coverHeight + ) } SourceComfortableGridHolder(view, adapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceListHolder.kt index bb1e21b98b57..1730f532d9ce 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceListHolder.kt @@ -20,7 +20,6 @@ import eu.kanade.tachiyomi.util.system.getResourceColor */ class SourceListHolder(private val view: View, adapter: FlexibleAdapter<*>) : SourceHolder(view, adapter) { - override val binding = SourceListItemBinding.bind(view) private val favoriteColor = view.context.getResourceColor(R.attr.colorOnSurface, 0.38f) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourcePager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourcePager.kt index e58e7decbb5c..00e4584d3481 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourcePager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourcePager.kt @@ -3,16 +3,17 @@ package eu.kanade.tachiyomi.ui.source.browse import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.util.lang.awaitSingle -open class SourcePager(val source: CatalogueSource, val query: String, val filters: FilterList) : Pager() { +open class SourcePager(val source: CatalogueSource, val query: String, val filters: FilterList) : Pager() { override suspend fun requestNextPage() { val page = currentPage - val observable = if (query.isBlank() && filters.isEmpty()) { - source.fetchPopularManga(page) - } else { - source.fetchSearchManga(page, query, filters) - } + val observable = + if (query.isBlank() && filters.isEmpty()) { + source.fetchPopularManga(page) + } else { + source.fetchSearchManga(page, query, filters) + } val mangasPage = observable.awaitSingle() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/CheckboxItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/CheckboxItem.kt index 4eb1e82869fe..1f57ea5e3180 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/CheckboxItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/CheckboxItem.kt @@ -10,16 +10,23 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.model.Filter open class CheckboxItem(val filter: Filter.CheckBox) : AbstractFlexibleItem() { - override fun getLayoutRes(): Int { return R.layout.navigation_view_checkbox } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: List? + ) { val view = holder.check view.text = filter.name view.isChecked = filter.state diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/GroupItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/GroupItem.kt index 4d3ad852c251..8ced69e6b5a7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/GroupItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/GroupItem.kt @@ -16,7 +16,6 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem>() { - init { // --> EH isExpanded = Injekt.get().eh_expandFilters().get() @@ -31,11 +30,19 @@ class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: List? + ) { holder.title.text = filter.name holder.icon.setVectorCompat( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/HeaderItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/HeaderItem.kt index 22eed0b1c6cc..56274b4a1707 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/HeaderItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/HeaderItem.kt @@ -11,17 +11,24 @@ import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.source.model.Filter class HeaderItem(val filter: Filter.Header) : AbstractHeaderItem() { - @SuppressLint("PrivateResource") override fun getLayoutRes(): Int { return R.layout.design_navigation_item_subheader } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: List? + ) { val view = holder.itemView as TextView view.text = filter.name } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/HelpDialogItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/HelpDialogItem.kt index 9cfea32e2e46..eb83f8bfb9a3 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/HelpDialogItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/HelpDialogItem.kt @@ -23,11 +23,19 @@ class HelpDialogItem(val filter: Filter.HelpDialog) : AbstractHeaderItem>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: List? + ) { val view = holder.button as TextView view.text = filter.name view.setOnClickListener { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SectionItems.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SectionItems.kt index ab98dc5111c7..b87dcc3b79cb 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SectionItems.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SectionItems.kt @@ -4,7 +4,6 @@ import eu.davidea.flexibleadapter.items.ISectionable import eu.kanade.tachiyomi.source.model.Filter class TriStateSectionItem(filter: Filter.TriState) : TriStateItem(filter), ISectionable { - private var head: GroupItem? = null override fun getHeader(): GroupItem? = head @@ -28,7 +27,6 @@ class TriStateSectionItem(filter: Filter.TriState) : TriStateItem(filter), ISect } class TextSectionItem(filter: Filter.Text) : TextItem(filter), ISectionable { - private var head: GroupItem? = null override fun getHeader(): GroupItem? = head @@ -52,7 +50,6 @@ class TextSectionItem(filter: Filter.Text) : TextItem(filter), ISectionable { - private var head: GroupItem? = null override fun getHeader(): GroupItem? = head @@ -76,7 +73,6 @@ class CheckboxSectionItem(filter: Filter.CheckBox) : CheckboxItem(filter), ISect } class SelectSectionItem(filter: Filter.Select<*>) : SelectItem(filter), ISectionable { - private var head: GroupItem? = null override fun getHeader(): GroupItem? = head diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SelectItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SelectItem.kt index 1046ed1fdc05..23e9aff07410 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SelectItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SelectItem.kt @@ -13,29 +13,39 @@ import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem() { - override fun getLayoutRes(): Int { return R.layout.navigation_view_spinner } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: List? + ) { holder.text.text = filter.name + ": " val spinner = holder.spinner spinner.prompt = filter.name - spinner.adapter = ArrayAdapter( - holder.itemView.context, - android.R.layout.simple_spinner_item, filter.values - ).apply { - setDropDownViewResource(R.layout.common_spinner_item) - } - spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { pos -> - filter.state = pos - } + spinner.adapter = + ArrayAdapter( + holder.itemView.context, + android.R.layout.simple_spinner_item, + filter.values + ).apply { + setDropDownViewResource(R.layout.common_spinner_item) + } + spinner.onItemSelectedListener = + IgnoreFirstSpinnerListener { pos -> + filter.state = pos + } spinner.setSelection(filter.state) } @@ -50,7 +60,6 @@ open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem) : FlexibleViewHolder(view, adapter) { - val text: TextView = itemView.findViewById(R.id.nav_view_item_text) val spinner: Spinner = itemView.findViewById(R.id.nav_view_item) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SeparatorItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SeparatorItem.kt index 8b07d08380a9..2c6d46d3e054 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SeparatorItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SeparatorItem.kt @@ -11,17 +11,24 @@ import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.source.model.Filter class SeparatorItem(val filter: Filter.Separator) : AbstractHeaderItem() { - @SuppressLint("PrivateResource") override fun getLayoutRes(): Int { return R.layout.design_navigation_item_separator } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: List? + ) { } override fun equals(other: Any?): Boolean { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SortGroup.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SortGroup.kt index c1142581d38a..d2a20b35e186 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SortGroup.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SortGroup.kt @@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.util.view.setVectorCompat class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem>() { - init { isExpanded = false } @@ -23,11 +22,19 @@ class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: List? + ) { holder.title.text = filter.name holder.icon.setVectorCompat( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SortItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SortItem.kt index 19224062a4c8..9f2f29307cc5 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SortItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/SortItem.kt @@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.util.system.getResourceColor class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem(group) { - override fun getLayoutRes(): Int { return R.layout.navigation_view_checkedtext } @@ -22,26 +21,35 @@ class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem return 102 } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: List? + ) { val view = holder.text view.text = name val filter = group.filter val i = filter.values.indexOf(name) - fun getIcon() = when (filter.state) { - Filter.Sort.Selection(i, false) -> - VectorDrawableCompat.create(view.resources, R.drawable.ic_arrow_down_white_32dp, null) - ?.apply { setTint(view.context.getResourceColor(R.attr.colorAccent)) } - Filter.Sort.Selection(i, true) -> - VectorDrawableCompat.create(view.resources, R.drawable.ic_arrow_up_white_32dp, null) - ?.apply { setTint(view.context.getResourceColor(R.attr.colorAccent)) } - else -> ContextCompat.getDrawable(view.context, R.drawable.empty_drawable_32dp) - } + fun getIcon() = + when (filter.state) { + Filter.Sort.Selection(i, false) -> + VectorDrawableCompat.create(view.resources, R.drawable.ic_arrow_down_white_32dp, null) + ?.apply { setTint(view.context.getResourceColor(R.attr.colorAccent)) } + Filter.Sort.Selection(i, true) -> + VectorDrawableCompat.create(view.resources, R.drawable.ic_arrow_up_white_32dp, null) + ?.apply { setTint(view.context.getResourceColor(R.attr.colorAccent)) } + else -> ContextCompat.getDrawable(view.context, R.drawable.empty_drawable_32dp) + } view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null) holder.itemView.setOnClickListener { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/TextItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/TextItem.kt index 86a0c60b104e..ba6c76c88baa 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/TextItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/TextItem.kt @@ -18,18 +18,25 @@ import kotlinx.coroutines.flow.onEach import reactivecircus.flowbinding.android.widget.textChanges open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem() { - private val scope = CoroutineScope(Job() + Dispatchers.Main) override fun getLayoutRes(): Int { return R.layout.navigation_view_text } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: List? + ) { holder.wrapper.hint = filter.name holder.edit.setText(filter.state) holder.edit.textChanges() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/TriStateItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/TriStateItem.kt index 529fa935e2ab..d519ddbbaf07 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/TriStateItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/filter/TriStateItem.kt @@ -9,13 +9,12 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.viewholders.FlexibleViewHolder -import eu.kanade.tachiyomi.R as TR import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.getResourceColor +import eu.kanade.tachiyomi.R as TR open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem() { - override fun getLayoutRes(): Int { return TR.layout.navigation_view_checkedtext } @@ -24,32 +23,42 @@ open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem>): Holder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): Holder { return Holder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: Holder, position: Int, payloads: List?) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: Holder, + position: Int, + payloads: List? + ) { val view = holder.text view.text = filter.name - fun getIcon() = VectorDrawableCompat.create( - view.resources, - when (filter.state) { - Filter.TriState.STATE_IGNORE -> TR.drawable.ic_check_box_outline_blank_24dp - Filter.TriState.STATE_INCLUDE -> TR.drawable.ic_check_box_24dp - Filter.TriState.STATE_EXCLUDE -> TR.drawable.ic_check_box_x_24dp - else -> throw Exception("Unknown state") - }, - null - )?.apply { - val color = if (filter.state == Filter.TriState.STATE_INCLUDE) { - view.context.getResourceColor(R.attr.colorAccent) - } else { - view.context.getResourceColor(R.attr.colorOnBackground, 0.38f) - } + fun getIcon() = + VectorDrawableCompat.create( + view.resources, + when (filter.state) { + Filter.TriState.STATE_IGNORE -> TR.drawable.ic_check_box_outline_blank_24dp + Filter.TriState.STATE_INCLUDE -> TR.drawable.ic_check_box_24dp + Filter.TriState.STATE_EXCLUDE -> TR.drawable.ic_check_box_x_24dp + else -> throw Exception("Unknown state") + }, + null + )?.apply { + val color = + if (filter.state == Filter.TriState.STATE_INCLUDE) { + view.context.getResourceColor(R.attr.colorAccent) + } else { + view.context.getResourceColor(R.attr.colorOnBackground, 0.38f) + } - setTint(color) - } + setTint(color) + } view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null) holder.itemView.setOnClickListener { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchAdapter.kt index 6486ae516017..9c716cd7d4dd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchAdapter.kt @@ -14,7 +14,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource */ class GlobalSearchAdapter(val controller: GlobalSearchController) : FlexibleAdapter(null, controller, true) { - /** * Listen for more button clicks. */ @@ -25,7 +24,11 @@ class GlobalSearchAdapter(val controller: GlobalSearchController) : */ private var bundle = Bundle() - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List) { + override fun onBindViewHolder( + holder: RecyclerView.ViewHolder, + position: Int, + payloads: List + ) { super.onBindViewHolder(holder, position, payloads) restoreHolderState(holder) } @@ -53,7 +56,10 @@ class GlobalSearchAdapter(val controller: GlobalSearchController) : * @param holder The holder to save. * @param outState The bundle where the state is saved. */ - private fun saveHolderState(holder: RecyclerView.ViewHolder, outState: Bundle) { + private fun saveHolderState( + holder: RecyclerView.ViewHolder, + outState: Bundle + ) { val key = "holder_${holder.bindingAdapterPosition}" val holderState = SparseArray() holder.itemView.saveHierarchyState(holderState) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchCardAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchCardAdapter.kt index f0ca3e11b255..cfc872b84bee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchCardAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchCardAdapter.kt @@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga */ class GlobalSearchCardAdapter(controller: GlobalSearchController) : FlexibleAdapter(null, controller, true) { - /** * Listen for browse item clicks. */ @@ -22,6 +21,7 @@ class GlobalSearchCardAdapter(controller: GlobalSearchController) : */ interface OnMangaClickListener { fun onMangaClick(manga: Manga) + fun onMangaLongClick(manga: Manga) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchCardHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchCardHolder.kt index dfc2712bb0b5..cf4e624de723 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchCardHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchCardHolder.kt @@ -8,10 +8,11 @@ import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.widget.StateImageViewTarget + class GlobalSearchCardHolder(view: View, adapter: GlobalSearchCardAdapter) : BaseFlexibleViewHolder(view, adapter) { - private val binding = GlobalSearchControllerCardItemBinding.bind(view) + init { // Call onMangaClickListener when item is pressed. itemView.setOnClickListener { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchCardItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchCardItem.kt index 4fd8fe358b63..ebfea503cfb7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchCardItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchCardItem.kt @@ -9,12 +9,14 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga class GlobalSearchCardItem(val manga: Manga) : AbstractFlexibleItem() { - override fun getLayoutRes(): Int { return R.layout.global_search_controller_card_item } - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): GlobalSearchCardHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): GlobalSearchCardHolder { return GlobalSearchCardHolder(view, adapter as GlobalSearchCardAdapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchController.kt index caac605dddf0..00292b11a906 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchController.kt @@ -36,7 +36,6 @@ open class GlobalSearchController( ) : NucleusController(), GlobalSearchCardAdapter.OnMangaClickListener, GlobalSearchAdapter.OnMoreClickListener { - /** * Application preferences. */ @@ -58,7 +57,10 @@ open class GlobalSearchController( * @param container containing parent views. * @return inflated view */ - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = GlobalSearchControllerBinding.inflate(inflater) return binding.root } @@ -102,7 +104,10 @@ open class GlobalSearchController( * @param menu menu containing options. * @param inflater used to load the menu xml. */ - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + override fun onCreateOptionsMenu( + menu: Menu, + inflater: MenuInflater + ) { // Inflate menu. inflater.inflate(R.menu.global_search, menu) @@ -111,17 +116,19 @@ open class GlobalSearchController( val searchView = searchItem.actionView as SearchView searchView.maxWidth = Int.MAX_VALUE - searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { - override fun onMenuItemActionExpand(item: MenuItem): Boolean { - searchView.onActionViewExpanded() // Required to show the query in the view - searchView.setQuery(presenter.query, false) - return true - } - - override fun onMenuItemActionCollapse(item: MenuItem): Boolean { - return true + searchItem.setOnActionExpandListener( + object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem): Boolean { + searchView.onActionViewExpanded() // Required to show the query in the view + searchView.setQuery(presenter.query, false) + return true + } + + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + return true + } } - }) + ) searchView.queryTextEvents() .filterIsInstance() @@ -153,12 +160,18 @@ open class GlobalSearchController( super.onDestroyView(view) } - override fun onSaveViewState(view: View, outState: Bundle) { + override fun onSaveViewState( + view: View, + outState: Bundle + ) { super.onSaveViewState(view, outState) adapter?.onSaveInstanceState(outState) } - override fun onRestoreViewState(view: View, savedViewState: Bundle) { + override fun onRestoreViewState( + view: View, + savedViewState: Bundle + ) { super.onRestoreViewState(view, savedViewState) adapter?.onRestoreInstanceState(savedViewState) } @@ -196,7 +209,10 @@ open class GlobalSearchController( * * @param manga the initialized manga. */ - fun onMangaInitialized(source: CatalogueSource, manga: Manga) { + fun onMangaInitialized( + source: CatalogueSource, + manga: Manga + ) { getHolder(source)?.setImage(manga) } @@ -207,7 +223,10 @@ open class GlobalSearchController( /** * Opens a catalogue with the given controller. */ - private fun openCatalogue(source: CatalogueSource, controller: BrowseSourceController) { + private fun openCatalogue( + source: CatalogueSource, + controller: BrowseSourceController + ) { preferences.lastUsedCatalogueSource().set(source.id) router.pushController(controller.withFadeTransaction()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchHolder.kt index 39442e6cca65..988f02dbf81b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchHolder.kt @@ -16,8 +16,8 @@ import eu.kanade.tachiyomi.util.view.visible */ class GlobalSearchHolder(view: View, val adapter: GlobalSearchAdapter) : BaseFlexibleViewHolder(view, adapter) { - private val binding = GlobalSearchControllerCardBinding.bind(view) + /** * Adapter containing manga from search results. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchItem.kt index 34dd955030d4..b2c01862375c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchItem.kt @@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource */ class GlobalSearchItem(val source: CatalogueSource, val results: List?, val highlighted: Boolean = false) : AbstractFlexibleItem() { - /** * Set view. * @@ -32,7 +31,10 @@ class GlobalSearchItem(val source: CatalogueSource, val results: List>): GlobalSearchHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): GlobalSearchHolder { return GlobalSearchHolder(view, adapter as GlobalSearchAdapter) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt index 1a81f3236818..b914fc68dbc1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt @@ -42,7 +42,6 @@ open class GlobalSearchPresenter( val db: DatabaseHelper = Injekt.get(), val preferences: PreferencesHelper = Injekt.get() ) : BasePresenter() { - /** * Enabled sources. */ @@ -109,10 +108,11 @@ open class GlobalSearchPresenter( val hiddenCatalogues = preferences.hiddenCatalogues().get() val pinnedCatalogues = preferences.pinnedCatalogues().get() - val list = sourceManager.getVisibleCatalogueSources() - .filter { it.lang in languages } - .filterNot { it.id.toString() in hiddenCatalogues } - .sortedBy { "(${it.lang}) ${it.name}" } + val list = + sourceManager.getVisibleCatalogueSources() + .filter { it.lang in languages } + .filterNot { it.id.toString() in hiddenCatalogues } + .sortedBy { "(${it.lang}) ${it.name}" } return if (preferences.searchPinnedSourcesOnly()) { list.filter { it.id.toString() in pinnedCatalogues } @@ -128,11 +128,12 @@ open class GlobalSearchPresenter( var filteredSources: List? = null if (!filter.isNullOrEmpty()) { - filteredSources = extensionManager.installedExtensions - .filter { it.pkgName == filter } - .flatMap { it.sources } - .filter { it in enabledSources } - .filterIsInstance() + filteredSources = + extensionManager.installedExtensions + .filter { it.pkgName == filter } + .flatMap { it.sources } + .filter { it in enabledSources } + .filterIsInstance() } if (filteredSources != null && filteredSources.isNotEmpty()) { @@ -149,7 +150,10 @@ open class GlobalSearchPresenter( /** * Creates a catalogue search item */ - protected open fun createCatalogueSearchItem(source: CatalogueSource, results: List?): GlobalSearchItem { + protected open fun createCatalogueSearchItem( + source: CatalogueSource, + results: List? + ): GlobalSearchItem { return GlobalSearchItem(source, results) } @@ -175,46 +179,47 @@ open class GlobalSearchPresenter( val pinnedSourceIds = preferences.pinnedCatalogues().get() fetchSourcesSubscription?.unsubscribe() - fetchSourcesSubscription = Observable.from(sources) - .flatMap( - { source -> - Observable.defer { source.fetchSearchManga(1, query, FilterList()) } - .subscribeOn(Schedulers.io()) - .onErrorReturn { MangasPage(emptyList(), false) } // Ignore timeouts or other exceptions - .map { it.mangas.take(10) } // Get at most 10 manga from search result. - .map { list -> list.map { networkToLocalManga(it, source.id) } } // Convert to local manga. - .doOnNext { fetchImage(it, source) } // Load manga covers. - .map { list -> createCatalogueSearchItem(source, list.map { GlobalSearchCardItem(it) }) } - }, - 5 - ) - .observeOn(AndroidSchedulers.mainThread()) - // Update matching source with the obtained results - .map { result -> - items - .map { item -> if (item.source == result.source) result else item } - .sortedWith( - compareBy( - // Bubble up sources that actually have results - { it.results.isNullOrEmpty() }, - // Same as initial sort, i.e. pinned first then alphabetically - { it.source.id.toString() !in pinnedSourceIds }, - { "${it.source.name} (${it.source.lang})" } + fetchSourcesSubscription = + Observable.from(sources) + .flatMap( + { source -> + Observable.defer { source.fetchSearchManga(1, query, FilterList()) } + .subscribeOn(Schedulers.io()) + .onErrorReturn { MangasPage(emptyList(), false) } // Ignore timeouts or other exceptions + .map { it.mangas.take(10) } // Get at most 10 manga from search result. + .map { list -> list.map { networkToLocalManga(it, source.id) } } // Convert to local manga. + .doOnNext { fetchImage(it, source) } // Load manga covers. + .map { list -> createCatalogueSearchItem(source, list.map { GlobalSearchCardItem(it) }) } + }, + 5 + ) + .observeOn(AndroidSchedulers.mainThread()) + // Update matching source with the obtained results + .map { result -> + items + .map { item -> if (item.source == result.source) result else item } + .sortedWith( + compareBy( + // Bubble up sources that actually have results + { it.results.isNullOrEmpty() }, + // Same as initial sort, i.e. pinned first then alphabetically + { it.source.id.toString() !in pinnedSourceIds }, + { "${it.source.name} (${it.source.lang})" } + ) ) - ) - } - // Update current state - .doOnNext { items = it } - // Deliver initial state - .startWith(initialItems) - .subscribeLatestCache( - { view, manga -> - view.setItems(manga) - }, - { _, error -> - Timber.e(error) } - ) + // Update current state + .doOnNext { items = it } + // Deliver initial state + .startWith(initialItems) + .subscribeLatestCache( + { view, manga -> + view.setItems(manga) + }, + { _, error -> + Timber.e(error) + } + ) } /** @@ -222,7 +227,10 @@ open class GlobalSearchPresenter( * * @param manga the list of manga to initialize. */ - private fun fetchImage(manga: List, source: Source) { + private fun fetchImage( + manga: List, + source: Source + ) { fetchImageSubject.onNext(Pair(manga, source)) } @@ -231,25 +239,26 @@ open class GlobalSearchPresenter( */ private fun initializeFetchImageSubscription() { fetchImageSubscription?.unsubscribe() - fetchImageSubscription = fetchImageSubject.observeOn(Schedulers.io()) - .flatMap { pair -> - val source = pair.second - Observable.from(pair.first).filter { it.thumbnail_url == null && !it.initialized } - .map { Pair(it, source) } - .concatMap { getMangaDetailsObservable(it.first, it.second) } - .map { Pair(source as CatalogueSource, it) } - } - .onBackpressureBuffer() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { (source, manga) -> - @Suppress("DEPRECATION") - view?.onMangaInitialized(source, manga) - }, - { error -> - Timber.e(error) + fetchImageSubscription = + fetchImageSubject.observeOn(Schedulers.io()) + .flatMap { pair -> + val source = pair.second + Observable.from(pair.first).filter { it.thumbnail_url == null && !it.initialized } + .map { Pair(it, source) } + .concatMap { getMangaDetailsObservable(it.first, it.second) } + .map { Pair(source as CatalogueSource, it) } } - ) + .onBackpressureBuffer() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { (source, manga) -> + @Suppress("DEPRECATION") + view?.onMangaInitialized(source, manga) + }, + { error -> + Timber.e(error) + } + ) } /** @@ -258,7 +267,10 @@ open class GlobalSearchPresenter( * @param manga the manga to initialize. * @return an observable of the manga to initialize */ - private fun getMangaDetailsObservable(manga: Manga, source: Source): Observable { + private fun getMangaDetailsObservable( + manga: Manga, + source: Source + ): Observable { return runAsObservable({ val networkManga = source.getMangaDetails(manga.toMangaInfo()) manga.copyFrom(networkManga.toSManga()) @@ -276,7 +288,10 @@ open class GlobalSearchPresenter( * @param sManga the manga from the source. * @return a manga from the database. */ - protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { + protected open fun networkToLocalManga( + sManga: SManga, + sourceId: Long + ): Manga { var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking() if (localManga == null) { val newManga = Manga.create(sManga.url, sManga.title, sourceId) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/ExhLatestUpdatesPager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/ExhLatestUpdatesPager.kt index 2126efab7d46..b26a2a20169f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/ExhLatestUpdatesPager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/ExhLatestUpdatesPager.kt @@ -9,7 +9,6 @@ import eu.kanade.tachiyomi.util.lang.awaitSingle * ExhLatestUpdatesPager inherited from the Exh Pager. */ class ExhLatestUpdatesPager(val source: CatalogueSource) : Pager() { - override suspend fun requestNextPage() { val mangasPage = source.fetchLatestUpdates(currentPage).awaitSingle() onPageReceived(mangasPage) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/LatestUpdatesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/LatestUpdatesController.kt index ed6e81e30bf0..db1b71ed0286 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/LatestUpdatesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/LatestUpdatesController.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.ui.source.browse.BrowseSourcePresenter * Controller that shows the latest manga from the catalogue. Inherit [BrowseSourceController]. */ class LatestUpdatesController(bundle: Bundle) : BrowseSourceController(bundle) { - constructor(source: CatalogueSource) : this( Bundle().apply { putLong(SOURCE_ID_KEY, source.id) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/LatestUpdatesPager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/LatestUpdatesPager.kt index 4a036ab81265..573cb450e021 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/LatestUpdatesPager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/LatestUpdatesPager.kt @@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.util.lang.awaitSingle * LatestUpdatesPager inherited from the general Pager. */ class LatestUpdatesPager(val source: CatalogueSource) : Pager() { - override suspend fun requestNextPage() { val mangasPage = source.fetchLatestUpdates(currentPage).awaitSingle() onPageReceived(mangasPage) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/LatestUpdatesPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/LatestUpdatesPresenter.kt index bf2557e6c71b..4bea75321aab 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/LatestUpdatesPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/latest/LatestUpdatesPresenter.kt @@ -9,8 +9,14 @@ import exh.isEhBasedSource * Presenter of [LatestUpdatesController]. Inherit BrowseCataloguePresenter. */ class LatestUpdatesPresenter(sourceId: Long) : BrowseSourcePresenter(sourceId) { - - override fun createPager(query: String, filters: FilterList): Pager { - return if (source.isEhBasedSource()) { ExhLatestUpdatesPager(source) } else { LatestUpdatesPager(source) } + override fun createPager( + query: String, + filters: FilterList + ): Pager { + return if (source.isEhBasedSource()) { + ExhLatestUpdatesPager(source) + } else { + LatestUpdatesPager(source) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/video/VideoActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/video/VideoActivity.kt index 656f19336558..98d115730393 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/video/VideoActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/video/VideoActivity.kt @@ -40,7 +40,11 @@ class VideoActivity : BaseRxActivity() { companion object { @Suppress("unused") - fun newIntent(context: Context, anime: Manga, episode: Chapter): Intent { + fun newIntent( + context: Context, + anime: Manga, + episode: Chapter + ): Intent { return Intent(context, VideoActivity::class.java).apply { putExtra("anime", anime.id) putExtra("episode", episode.id) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/video/VideoPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/video/VideoPresenter.kt index 98ef310f1c36..f544ea0825b5 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/video/VideoPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/video/VideoPresenter.kt @@ -39,6 +39,7 @@ class VideoPresenter( VideoActivity::initError ) } + fun init(initEpisode: Chapter) { episode = initEpisode } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/BaseWebViewActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/BaseWebViewActivity.kt index 0c295e301803..e4b08aecf29e 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/BaseWebViewActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/BaseWebViewActivity.kt @@ -20,7 +20,6 @@ import reactivecircus.flowbinding.appcompat.navigationClicks import reactivecircus.flowbinding.swiperefreshlayout.refreshes open class BaseWebViewActivity : BaseActivity() { - internal var bundle: Bundle? = null internal var isRefreshing: Boolean = false @@ -65,16 +64,20 @@ open class BaseWebViewActivity : BaseActivity() { WebView.setWebContentsDebuggingEnabled(true) } - binding.webview.webChromeClient = object : WebChromeClient() { - override fun onProgressChanged(view: WebView?, newProgress: Int) { - binding.progressBar.isVisible = true - binding.progressBar.progress = newProgress - if (newProgress == 100) { - binding.progressBar.isInvisible = true + binding.webview.webChromeClient = + object : WebChromeClient() { + override fun onProgressChanged( + view: WebView?, + newProgress: Int + ) { + binding.progressBar.isVisible = true + binding.progressBar.progress = newProgress + if (newProgress == 100) { + binding.progressBar.isInvisible = true + } + super.onProgressChanged(view, newProgress) } - super.onProgressChanged(view, newProgress) } - } } else { binding.webview.restoreState(bundle ?: return) } @@ -86,8 +89,11 @@ open class BaseWebViewActivity : BaseActivity() { } override fun onBackPressed() { - if (binding.webview.canGoBack()) binding.webview.goBack() - else super.onBackPressed() + if (binding.webview.canGoBack()) { + binding.webview.goBack() + } else { + super.onBackPressed() + } } fun refreshPage() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt index ec66f8e16dbe..d2200585e2b6 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt @@ -26,7 +26,6 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import uy.kohesive.injekt.injectLazy class WebViewActivity : BaseWebViewActivity() { - private val sourceManager by injectLazy() private val network: NetworkHelper by injectLazy() @@ -52,47 +51,62 @@ class WebViewActivity : BaseWebViewActivity() { WebView.setWebContentsDebuggingEnabled(true) } - binding.webview.webChromeClient = object : WebChromeClient() { - override fun onProgressChanged(view: WebView?, newProgress: Int) { - binding.progressBar.isVisible = true - binding.progressBar.progress = newProgress - if (newProgress == 100) { - binding.progressBar.isInvisible = true + binding.webview.webChromeClient = + object : WebChromeClient() { + override fun onProgressChanged( + view: WebView?, + newProgress: Int + ) { + binding.progressBar.isVisible = true + binding.progressBar.progress = newProgress + if (newProgress == 100) { + binding.progressBar.isInvisible = true + } + super.onProgressChanged(view, newProgress) } - super.onProgressChanged(view, newProgress) } - } - binding.webview.webViewClient = object : WebViewClientCompat() { - override fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean { - if (url.startsWith("blob:http")) { - return false + binding.webview.webViewClient = + object : WebViewClientCompat() { + override fun shouldOverrideUrlCompat( + view: WebView, + url: String + ): Boolean { + if (url.startsWith("blob:http")) { + return false + } + + view.loadUrl(url, headers) + return true } - view.loadUrl(url, headers) - return true - } - - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { - super.onPageStarted(view, url, favicon) - invalidateOptionsMenu() - } + override fun onPageStarted( + view: WebView?, + url: String?, + favicon: Bitmap? + ) { + super.onPageStarted(view, url, favicon) + invalidateOptionsMenu() + } - override fun onPageFinished(view: WebView?, url: String?) { - super.onPageFinished(view, url) - invalidateOptionsMenu() - title = view?.title - supportActionBar?.subtitle = url - binding.swipeRefresh.isEnabled = true - binding.swipeRefresh.isRefreshing = false - - // Reset to top when page refreshes - if (isRefreshing) { - view?.scrollTo(0, 0) - isRefreshing = false + override fun onPageFinished( + view: WebView?, + url: String? + ) { + super.onPageFinished(view, url) + invalidateOptionsMenu() + title = view?.title + supportActionBar?.subtitle = url + binding.swipeRefresh.isEnabled = true + binding.swipeRefresh.isRefreshing = false + + // Reset to top when page refreshes + if (isRefreshing) { + view?.scrollTo(0, 0) + isRefreshing = false + } } } - } binding.webview.loadUrl(url, headers) } @@ -138,10 +152,11 @@ class WebViewActivity : BaseWebViewActivity() { private fun shareWebpage() { try { - val intent = Intent(Intent.ACTION_SEND).apply { - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, binding.webview.url) - } + val intent = + Intent(Intent.ACTION_SEND).apply { + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, binding.webview.url) + } startActivity(Intent.createChooser(intent, getString(R.string.action_share))) } catch (e: Exception) { toast(e.message) @@ -162,7 +177,12 @@ class WebViewActivity : BaseWebViewActivity() { private const val SOURCE_KEY = "source_key" private const val TITLE_KEY = "title_key" - fun newIntent(context: Context, url: String, sourceId: Long? = null, title: String? = null): Intent { + fun newIntent( + context: Context, + url: String, + sourceId: Long? = null, + title: String? = null + ): Intent { return Intent(context, WebViewActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) putExtra(URL_KEY, url) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt index a6ee5ea81055..4d7ab2fef01d 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt @@ -13,10 +13,10 @@ import java.io.File import java.io.IOException class CrashLogUtil(private val context: Context) { - - private val notificationBuilder = context.notificationBuilder(Notifications.CHANNEL_CRASH_LOGS) { - setSmallIcon(R.drawable.ic_tachi) - } + private val notificationBuilder = + context.notificationBuilder(Notifications.CHANNEL_CRASH_LOGS) { + setSmallIcon(R.drawable.ic_tachi) + } fun dumpLogs() { try { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt b/app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt index 2f589518855e..8e2f8a7a6608 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.sync.withLock * @author nulldev */ class DeferredField { - @Volatile var content: T? = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt index 6c166448a5a0..48e9f52c34fa 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/JsoupExtensions.kt @@ -5,11 +5,17 @@ import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element -fun Element.selectText(css: String, defaultValue: String? = null): String? { +fun Element.selectText( + css: String, + defaultValue: String? = null +): String? { return select(css).first()?.text() ?: defaultValue } -fun Element.selectInt(css: String, defaultValue: Int = 0): Int { +fun Element.selectInt( + css: String, + defaultValue: Int = 0 +): Int { return select(css).first()?.text()?.toInt() ?: defaultValue } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt index d1d1f80875e5..362288c8486f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt @@ -13,7 +13,11 @@ fun Manga.isLocal() = source == LocalSource.ID /** * Call before updating [Manga.thumbnail_url] to ensure old cover can be cleared from cache */ -fun Manga.prepUpdateCover(coverCache: CoverCache, remoteManga: SManga, refreshSameUrl: Boolean) { +fun Manga.prepUpdateCover( + coverCache: CoverCache, + remoteManga: SManga, + refreshSameUrl: Boolean +) { // Never refresh covers if the new url is null, as the current url has possibly become invalid val newUrl = remoteManga.thumbnail_url ?: return @@ -52,7 +56,10 @@ fun Manga.updateCoverLastModified(db: DatabaseHelper) { db.updateMangaCoverLastModified(this).executeAsBlocking() } -fun Manga.shouldDownloadNewChapters(db: DatabaseHelper, prefs: PreferencesHelper): Boolean { +fun Manga.shouldDownloadNewChapters( + db: DatabaseHelper, + prefs: PreferencesHelper +): Boolean { if (!favorite) return false // Boolean to determine if user wants to automatically download new chapters. diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt index dd44f42be8e6..242e2c8923f3 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt @@ -37,7 +37,10 @@ object ChapterRecognition { */ private val unwantedWhiteSpace = Regex("""(\s)(extra|special|omake)""") - fun parseChapterNumber(chapter: SChapter, manga: SManga) { + fun parseChapterNumber( + chapter: SChapter, + manga: SManga + ) { // If chapter number is known return. if (chapter.chapter_number == -2f || chapter.chapter_number > -1f) { return @@ -96,7 +99,10 @@ object ChapterRecognition { * @param chapter chapter object * @return true if volume is found */ - private fun updateChapter(match: MatchResult?, chapter: SChapter): Boolean { + private fun updateChapter( + match: MatchResult?, + chapter: SChapter + ): Boolean { match?.let { val initial = it.groups[1]?.value?.toFloat()!! val subChapterDecimal = it.groups[2]?.value @@ -114,7 +120,10 @@ object ChapterRecognition { * @param alpha alpha value of regex * @return decimal/alpha float value */ - private fun checkForDecimal(decimal: String?, alpha: String?): Float { + private fun checkForDecimal( + decimal: String?, + alpha: String? + ): Float { if (!decimal.isNullOrEmpty()) { return decimal.toFloat() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt index fb17e98d6745..fbf11a43c13b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt @@ -9,10 +9,10 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.online.HttpSource import exh.EH_SOURCE_ID import exh.EXH_SOURCE_ID -import java.util.Date -import java.util.TreeSet import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.Date +import java.util.TreeSet /** * Helper method for syncing the list of chapters from the source with the ones from the database. @@ -38,15 +38,16 @@ fun syncChaptersWithSource( // Chapters from db. val dbChapters = db.getChapters(manga).executeAsBlocking() - val sourceChapters = rawSourceChapters - .distinctBy { it.url } - .mapIndexed { i, sChapter -> - Chapter.create().apply { - copyFrom(sChapter) - manga_id = manga.id - source_order = i + val sourceChapters = + rawSourceChapters + .distinctBy { it.url } + .mapIndexed { i, sChapter -> + Chapter.create().apply { + copyFrom(sChapter) + manga_id = manga.id + source_order = i + } } - } // Chapters from the source not in db. val toAdd = mutableListOf() @@ -90,11 +91,12 @@ fun syncChaptersWithSource( } // Chapters from the db not in the source. - val toDelete = dbChapters.filterNot { dbChapter -> - sourceChapters.any { sourceChapter -> - dbChapter.url == sourceChapter.url + val toDelete = + dbChapters.filterNot { dbChapter -> + sourceChapters.any { sourceChapter -> + dbChapter.url == sourceChapter.url + } } - } // Return if there's nothing to add, delete or change, avoiding unnecessary db transactions. if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) { @@ -169,7 +171,10 @@ fun syncChaptersWithSource( } // checks if the chapter in db needs updated -private fun shouldUpdateDbChapter(dbChapter: Chapter, sourceChapter: SChapter): Boolean { +private fun shouldUpdateDbChapter( + dbChapter: Chapter, + sourceChapter: SChapter +): Boolean { return dbChapter.scanlator != sourceChapter.scanlator || dbChapter.name != sourceChapter.name || dbChapter.date_upload != sourceChapter.date_upload || dbChapter.chapter_number != sourceChapter.chapter_number diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/CloseableExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/CloseableExtensions.kt index 647eaab3962f..63ab2af2306f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/CloseableExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/CloseableExtensions.kt @@ -19,13 +19,14 @@ inline fun Array.use(block: () -> Unit) { } finally { when (blockException) { null -> forEach { it?.close() } - else -> forEach { - try { - it?.close() - } catch (closeException: Throwable) { - blockException.addSuppressed(closeException) + else -> + forEach { + try { + it?.close() + } catch (closeException: Throwable) { + blockException.addSuppressed(closeException) + } } - } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/CoroutinesExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/CoroutinesExtensions.kt index c126c028a0f1..076e60caec87 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/CoroutinesExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/CoroutinesExtensions.kt @@ -8,12 +8,9 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch -fun launchUI(block: suspend CoroutineScope.() -> Unit): Job = - GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT, block) +fun launchUI(block: suspend CoroutineScope.() -> Unit): Job = GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT, block) -fun launchIO(block: suspend CoroutineScope.() -> Unit): Job = - GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT, block) +fun launchIO(block: suspend CoroutineScope.() -> Unit): Job = GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT, block) @OptIn(ExperimentalCoroutinesApi::class) -fun launchNow(block: suspend CoroutineScope.() -> Unit): Job = - GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED, block) +fun launchNow(block: suspend CoroutineScope.() -> Unit): Job = GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED, block) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/Hash.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/Hash.kt index a89063208096..176890ae836e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/Hash.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/Hash.kt @@ -3,11 +3,11 @@ package eu.kanade.tachiyomi.util.lang import java.security.MessageDigest object Hash { - - private val chars = charArrayOf( - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'a', 'b', 'c', 'd', 'e', 'f' - ) + private val chars = + charArrayOf( + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f' + ) private val MD5 get() = MessageDigest.getInstance("MD5") diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/RetryWithDelay.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/RetryWithDelay.kt index a14825a4b5ef..e81a3f80b75d 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/RetryWithDelay.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/RetryWithDelay.kt @@ -1,25 +1,25 @@ package eu.kanade.tachiyomi.util.lang -import java.util.concurrent.TimeUnit.MILLISECONDS import rx.Observable import rx.Scheduler import rx.functions.Func1 import rx.schedulers.Schedulers +import java.util.concurrent.TimeUnit.MILLISECONDS class RetryWithDelay( private val maxRetries: Int = 1, private val retryStrategy: (Int) -> Int = { 1000 }, private val scheduler: Scheduler = Schedulers.computation() ) : Func1, Observable<*>> { - private var retryCount = 0 - override fun call(attempts: Observable) = attempts.flatMap { error -> - val count = ++retryCount - if (count <= maxRetries) { - Observable.timer(retryStrategy(count).toLong(), MILLISECONDS, scheduler) - } else { - Observable.error(error as Throwable) + override fun call(attempts: Observable) = + attempts.flatMap { error -> + val count = ++retryCount + if (count <= maxRetries) { + Observable.timer(retryStrategy(count).toLong(), MILLISECONDS, scheduler) + } else { + Observable.error(error as Throwable) + } } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/RxCoroutineBridge.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/RxCoroutineBridge.kt index 275e61fcdc79..fa65d2cf8a76 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/RxCoroutineBridge.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/RxCoroutineBridge.kt @@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.util.lang import com.pushtorefresh.storio.operations.PreparedOperation import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetObject -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineStart @@ -27,6 +25,8 @@ import rx.Single import rx.SingleSubscriber import rx.Subscriber import rx.Subscription +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException /* * Util functions for bridging RxJava and coroutines. Taken from TachiyomiEH/SY. @@ -37,18 +37,19 @@ suspend fun Single.await(subscribeOn: Scheduler? = null): T { return suspendCancellableCoroutine { continuation -> val self = if (subscribeOn != null) subscribeOn(subscribeOn) else this lateinit var sub: Subscription - sub = self.subscribe( - { - continuation.resume(it) { - sub.unsubscribe() - } - }, - { - if (!continuation.isCancelled) { - continuation.resumeWithException(it) + sub = + self.subscribe( + { + continuation.resume(it) { + sub.unsubscribe() + } + }, + { + if (!continuation.isCancelled) { + continuation.resumeWithException(it) + } } - } - ) + ) continuation.invokeOnCancellation { sub.unsubscribe() @@ -57,6 +58,7 @@ suspend fun Single.await(subscribeOn: Scheduler? = null): T { } suspend fun PreparedOperation.await(): T = asRxSingle().await() + suspend fun PreparedGetObject.await(): T? = asRxSingle().await() @ExperimentalCoroutinesApi @@ -64,18 +66,19 @@ suspend fun Completable.awaitSuspending(subscribeOn: Scheduler? = null) { return suspendCancellableCoroutine { continuation -> val self = if (subscribeOn != null) subscribeOn(subscribeOn) else this lateinit var sub: Subscription - sub = self.subscribe( - { - continuation.resume(Unit) { - sub.unsubscribe() - } - }, - { - if (!continuation.isCancelled) { - continuation.resumeWithException(it) + sub = + self.subscribe( + { + continuation.resume(Unit) { + sub.unsubscribe() + } + }, + { + if (!continuation.isCancelled) { + continuation.resumeWithException(it) + } } - } - ) + ) continuation.invokeOnCancellation { sub.unsubscribe() @@ -83,39 +86,41 @@ suspend fun Completable.awaitSuspending(subscribeOn: Scheduler? = null) { } } -suspend fun Completable.awaitCompleted(): Unit = suspendCancellableCoroutine { cont -> - subscribe( - object : CompletableSubscriber { - override fun onSubscribe(s: Subscription) { - cont.unsubscribeOnCancellation(s) - } +suspend fun Completable.awaitCompleted(): Unit = + suspendCancellableCoroutine { cont -> + subscribe( + object : CompletableSubscriber { + override fun onSubscribe(s: Subscription) { + cont.unsubscribeOnCancellation(s) + } - override fun onCompleted() { - cont.resume(Unit) - } + override fun onCompleted() { + cont.resume(Unit) + } - override fun onError(e: Throwable) { - cont.resumeWithException(e) + override fun onError(e: Throwable) { + cont.resumeWithException(e) + } } - } - ) -} + ) + } -suspend fun Single.await(): T = suspendCancellableCoroutine { cont -> - cont.unsubscribeOnCancellation( - subscribe( - object : SingleSubscriber() { - override fun onSuccess(t: T) { - cont.resume(t) - } +suspend fun Single.await(): T = + suspendCancellableCoroutine { cont -> + cont.unsubscribeOnCancellation( + subscribe( + object : SingleSubscriber() { + override fun onSuccess(t: T) { + cont.resume(t) + } - override fun onError(error: Throwable) { - cont.resumeWithException(error) + override fun onError(error: Throwable) { + cont.resumeWithException(error) + } } - } + ) ) - ) -} + } @OptIn(InternalCoroutinesApi::class, ExperimentalCoroutinesApi::class) suspend fun Observable.awaitFirst(): T = first().awaitOne() @@ -127,11 +132,12 @@ suspend fun Observable.awaitFirstOrDefault(default: T): T = firstOrDefaul suspend fun Observable.awaitFirstOrNull(): T? = firstOrDefault(null).awaitOne() @OptIn(InternalCoroutinesApi::class, ExperimentalCoroutinesApi::class) -suspend fun Observable.awaitFirstOrElse(defaultValue: () -> T): T = switchIfEmpty( - Observable.fromCallable( - defaultValue - ) -).first().awaitOne() +suspend fun Observable.awaitFirstOrElse(defaultValue: () -> T): T = + switchIfEmpty( + Observable.fromCallable( + defaultValue + ) + ).first().awaitOne() @OptIn(InternalCoroutinesApi::class, ExperimentalCoroutinesApi::class) suspend fun Observable.awaitLast(): T = last().awaitOne() @@ -144,85 +150,90 @@ suspend fun Observable.awaitSingleOrDefault(default: T): T = singleOrDefa suspend fun Observable.awaitSingleOrNull(): T? = singleOrDefault(null).awaitOne() @OptIn(InternalCoroutinesApi::class, ExperimentalCoroutinesApi::class) -private suspend fun Observable.awaitOne(): T = suspendCancellableCoroutine { cont -> - cont.unsubscribeOnCancellation( - subscribe( - object : Subscriber() { - override fun onStart() { - request(1) - } +private suspend fun Observable.awaitOne(): T = + suspendCancellableCoroutine { cont -> + cont.unsubscribeOnCancellation( + subscribe( + object : Subscriber() { + override fun onStart() { + request(1) + } - override fun onNext(t: T) { - cont.resume(t) - } + override fun onNext(t: T) { + cont.resume(t) + } - override fun onCompleted() { - if (cont.isActive) cont.resumeWithException( - IllegalStateException( - "Should have invoked onNext" - ) - ) - } + override fun onCompleted() { + if (cont.isActive) { + cont.resumeWithException( + IllegalStateException( + "Should have invoked onNext" + ) + ) + } + } - override fun onError(e: Throwable) { + override fun onError(e: Throwable) { /* - * Rx1 observable throws NoSuchElementException if cancellation happened before - * element emission. To mitigate this we try to atomically resume continuation with exception: - * if resume failed, then we know that continuation successfully cancelled itself - */ - val token = cont.tryResumeWithException(e) - if (token != null) { - cont.completeResume(token) + * Rx1 observable throws NoSuchElementException if cancellation happened before + * element emission. To mitigate this we try to atomically resume continuation with exception: + * if resume failed, then we know that continuation successfully cancelled itself + */ + val token = cont.tryResumeWithException(e) + if (token != null) { + cont.completeResume(token) + } } } - } + ) ) - ) -} + } -internal fun CancellableContinuation.unsubscribeOnCancellation(sub: Subscription) = - invokeOnCancellation { sub.unsubscribe() } +internal fun CancellableContinuation.unsubscribeOnCancellation(sub: Subscription) = invokeOnCancellation { sub.unsubscribe() } @ExperimentalCoroutinesApi -fun Observable.asFlow(): Flow = callbackFlow { - val observer = object : Observer { - override fun onNext(t: T) { - trySend(t) - } +fun Observable.asFlow(): Flow = + callbackFlow { + val observer = + object : Observer { + override fun onNext(t: T) { + trySend(t) + } - override fun onError(e: Throwable) { - close(e) - } + override fun onError(e: Throwable) { + close(e) + } - override fun onCompleted() { - close() - } + override fun onCompleted() { + close() + } + } + val subscription = subscribe(observer) + awaitClose { subscription.unsubscribe() } } - val subscription = subscribe(observer) - awaitClose { subscription.unsubscribe() } -} @ExperimentalCoroutinesApi fun Flow.asObservable(backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE): Observable { return Observable.create( { emitter -> /* - * ATOMIC is used here to provide stable behaviour of subscribe+dispose pair even if - * asObservable is already invoked from unconfined - */ - val job = GlobalScope.launch(Dispatchers.Unconfined, start = CoroutineStart.ATOMIC) { - try { - collect { emitter.onNext(it) } - emitter.onCompleted() - } catch (e: Throwable) { - // Ignore `CancellationException` as error, since it indicates "normal cancellation" - if (e !is CancellationException) { - emitter.onError(e) - } else { + * ATOMIC is used here to provide stable behaviour of subscribe+dispose pair even if + * asObservable is already invoked from unconfined + */ + val job = + GlobalScope.launch(Dispatchers.Unconfined, start = CoroutineStart.ATOMIC) { + try { + collect { emitter.onNext(it) } emitter.onCompleted() + } catch (e: Throwable) { + // Ignore `CancellationException` as error, since it indicates "normal cancellation" + if (e !is CancellationException) { + emitter.onError(e) + } else { + emitter.onCompleted() + } } } - } emitter.setCancellation { job.cancel() } }, backpressureMode @@ -236,19 +247,20 @@ fun runAsObservable( ): Observable { return Observable.create( { emitter -> - val job = GlobalScope.launch(Dispatchers.Unconfined, start = CoroutineStart.ATOMIC) { - try { - emitter.onNext(block()) - emitter.onCompleted() - } catch (e: Throwable) { - // Ignore `CancellationException` as error, since it indicates "normal cancellation" - if (e !is CancellationException) { - emitter.onError(e) - } else { + val job = + GlobalScope.launch(Dispatchers.Unconfined, start = CoroutineStart.ATOMIC) { + try { + emitter.onNext(block()) emitter.onCompleted() + } catch (e: Throwable) { + // Ignore `CancellationException` as error, since it indicates "normal cancellation" + if (e !is CancellationException) { + emitter.onError(e) + } else { + emitter.onCompleted() + } } } - } emitter.setCancellation { job.cancel() } }, backpressureMode diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/RxExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/RxExtensions.kt index b9fb8e7eb2fd..feee86de59b2 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/RxExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/RxExtensions.kt @@ -8,7 +8,10 @@ fun Subscription?.isNullOrUnsubscribed() = this == null || isUnsubscribed operator fun CompositeSubscription.plusAssign(subscription: Subscription) = add(subscription) -fun Observable.combineLatest(o2: Observable, combineFn: (T, U) -> R): Observable { +fun Observable.combineLatest( + o2: Observable, + combineFn: (T, U) -> R +): Observable { return Observable.combineLatest(this, o2, combineFn) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt index d6018e8fa4be..0d216fcaaff4 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt @@ -1,13 +1,16 @@ package eu.kanade.tachiyomi.util.lang -import kotlin.math.floor import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator +import kotlin.math.floor /** * Replaces the given string to have at most [count] characters using [replacement] at its end. * If [replacement] is longer than [count] an exception will be thrown when `length > count`. */ -fun String.chop(count: Int, replacement: String = "..."): String { +fun String.chop( + count: Int, + replacement: String = "..." +): String { return if (length > count) { take(count - replacement.length) + replacement } else { @@ -23,7 +26,10 @@ fun String.removeArticles(): String { * Replaces the given string to have at most [count] characters using [replacement] near the center. * If [replacement] is longer than [count] an exception will be thrown when `length > count`. */ -fun String.truncateCenter(count: Int, replacement: String = "..."): String { +fun String.truncateCenter( + count: Int, + replacement: String = "..." +): String { if (length <= count) { return this } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceDSL.kt b/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceDSL.kt index 52a0e2c2f2d6..021d40102e24 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceDSL.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceDSL.kt @@ -32,7 +32,9 @@ inline fun PreferenceGroup.preference(block: (@DSL Preference).() -> Unit): Pref return initThenAdd(Preference(context), block) } -inline fun PreferenceGroup.infoPreference(@StringRes infoRes: Int): Preference { +inline fun PreferenceGroup.infoPreference( + @StringRes infoRes: Int +): Preference { return initThenAdd( Preference(context), { @@ -97,16 +99,31 @@ fun initDialog(dialogPreference: DialogPreference) { } } -inline fun

PreferenceGroup.initThenAdd(p: P, block: P.() -> Unit): P { - return p.apply { block(); addPreference(this); } +inline fun

PreferenceGroup.initThenAdd( + p: P, + block: P.() -> Unit +): P { + return p.apply { + block() + addPreference(this) + } } -inline fun

PreferenceGroup.addThenInit(p: P, block: P.() -> Unit): P { - return p.apply { addPreference(this); block() } +inline fun

PreferenceGroup.addThenInit( + p: P, + block: P.() -> Unit +): P { + return p.apply { + addPreference(this) + block() + } } inline fun Preference.onClick(crossinline block: () -> Unit) { - setOnPreferenceClickListener { block(); true } + setOnPreferenceClickListener { + block() + true + } } inline fun Preference.onChange(crossinline block: (Any?) -> Boolean) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt index c582d87a6f1a..12d44ebf04dd 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt @@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.util.lang.Hash import java.io.File object DiskUtil { - fun hashKeyForDisk(key: String): String { return Hash.md5(key) } @@ -46,17 +45,18 @@ object DiskUtil { */ fun getExternalStorages(context: Context): Collection { val directories = mutableSetOf() - directories += ContextCompat.getExternalFilesDirs(context, null) - .filterNotNull() - .mapNotNull { - val file = File(it.absolutePath.substringBefore("/Android/")) - val state = EnvironmentCompat.getStorageState(file) - if (state == Environment.MEDIA_MOUNTED || state == Environment.MEDIA_MOUNTED_READ_ONLY) { - file - } else { - null + directories += + ContextCompat.getExternalFilesDirs(context, null) + .filterNotNull() + .mapNotNull { + val file = File(it.absolutePath.substringBefore("/Android/")) + val state = EnvironmentCompat.getStorageState(file) + if (state == Environment.MEDIA_MOUNTED || state == Environment.MEDIA_MOUNTED_READ_ONLY) { + file + } else { + null + } } - } return directories } @@ -64,7 +64,10 @@ object DiskUtil { /** * Don't display downloaded chapters in gallery apps creating `.nomedia`. */ - fun createNoMediaFile(dir: UniFile?, context: Context?) { + fun createNoMediaFile( + dir: UniFile?, + context: Context? + ) { if (dir != null && dir.exists()) { val nomedia = dir.findFile(".nomedia") if (nomedia == null) { @@ -77,14 +80,20 @@ object DiskUtil { /** * Scans the given file so that it can be shown in gallery apps, for example. */ - fun scanMedia(context: Context, file: File) { + fun scanMedia( + context: Context, + file: File + ) { scanMedia(context, Uri.fromFile(file)) } /** * Scans the given file so that it can be shown in gallery apps, for example. */ - fun scanMedia(context: Context, uri: Uri) { + fun scanMedia( + context: Context, + uri: Uri + ) { val action = Intent.ACTION_MEDIA_SCANNER_SCAN_FILE val mediaScanIntent = Intent(action) mediaScanIntent.data = uri diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt b/app/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt index 5f9fb0cc56a4..c6106d823451 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt @@ -2,6 +2,10 @@ package eu.kanade.tachiyomi.util.storage import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipFile +import org.jsoup.Jsoup +import org.jsoup.nodes.Document import java.io.Closeable import java.io.File import java.io.InputStream @@ -9,16 +13,11 @@ import java.nio.channels.SeekableByteChannel import java.text.ParseException import java.text.SimpleDateFormat import java.util.Locale -import org.apache.commons.compress.archivers.zip.ZipArchiveEntry -import org.apache.commons.compress.archivers.zip.ZipFile -import org.jsoup.Jsoup -import org.jsoup.nodes.Document /** * Wrapper over ZipFile to load files in epub format. */ class EpubFile(private val zip: ZipFile) : Closeable { - constructor(channel: SeekableByteChannel) : this(ZipFile(channel)) constructor(file: File) : this(ZipFile(file)) @@ -137,9 +136,10 @@ class EpubFile(private val zip: ZipFile) : Closeable { * Returns all the pages from the epub. */ private fun getPagesFromDocument(document: Document): List { - val pages = document.select("manifest > item") - .filter { "application/xhtml+xml" == it.attr("media-type") } - .associateBy { it.attr("id") } + val pages = + document.select("manifest > item") + .filter { "application/xhtml+xml" == it.attr("media-type") } + .associateBy { it.attr("id") } val spine = document.select("spine > itemref").map { it.attr("idref") } return spine.mapNotNull { pages[it] }.map { it.attr("href") } @@ -148,7 +148,10 @@ class EpubFile(private val zip: ZipFile) : Closeable { /** * Returns all the images contained in every page from the epub. */ - private fun getImagesFromPages(pages: List, packageHref: String): List { + private fun getImagesFromPages( + pages: List, + packageHref: String + ): List { val result = ArrayList() val basePath = getParentDirectory(packageHref) pages.forEach { page -> @@ -184,7 +187,10 @@ class EpubFile(private val zip: ZipFile) : Closeable { /** * Resolves a zip path from base and relative components and a path separator. */ - private fun resolveZipPath(basePath: String, relativePath: String): String { + private fun resolveZipPath( + basePath: String, + relativePath: String + ): String { if (relativePath.startsWith(pathSeparator)) { // Path is absolute, so return as-is. return relativePath diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFileCompat.kt b/app/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFileCompat.kt index 7a5d638c8d11..0a08039e3437 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFileCompat.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFileCompat.kt @@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.util.storage import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga +import org.jsoup.Jsoup +import org.jsoup.nodes.Document import java.io.Closeable import java.io.File import java.io.InputStream @@ -10,8 +12,6 @@ import java.text.SimpleDateFormat import java.util.Locale import java.util.zip.ZipEntry import java.util.zip.ZipFile -import org.jsoup.Jsoup -import org.jsoup.nodes.Document /** * Wrapper over ZipFile to load files in epub format. @@ -134,9 +134,10 @@ class EpubFileCompat(private val zip: ZipFile) : Closeable { * Returns all the pages from the epub. */ private fun getPagesFromDocument(document: Document): List { - val pages = document.select("manifest > item") - .filter { "application/xhtml+xml" == it.attr("media-type") } - .associateBy { it.attr("id") } + val pages = + document.select("manifest > item") + .filter { "application/xhtml+xml" == it.attr("media-type") } + .associateBy { it.attr("id") } val spine = document.select("spine > itemref").map { it.attr("idref") } return spine.mapNotNull { pages[it] }.map { it.attr("href") } @@ -145,7 +146,10 @@ class EpubFileCompat(private val zip: ZipFile) : Closeable { /** * Returns all the images contained in every page from the epub. */ - private fun getImagesFromPages(pages: List, packageHref: String): List { + private fun getImagesFromPages( + pages: List, + packageHref: String + ): List { val result = ArrayList() val basePath = getParentDirectory(packageHref) pages.forEach { page -> @@ -181,7 +185,10 @@ class EpubFileCompat(private val zip: ZipFile) : Closeable { /** * Resolves a zip path from base and relative components and a path separator. */ - private fun resolveZipPath(basePath: String, relativePath: String): String { + private fun resolveZipPath( + basePath: String, + relativePath: String + ): String { if (relativePath.startsWith(pathSeparator)) { // Path is absolute, so return as-is. return relativePath diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/storage/OkioExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/storage/OkioExtensions.kt index b3fd6193a08a..a4301d9b91b0 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/storage/OkioExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/storage/OkioExtensions.kt @@ -1,10 +1,10 @@ package eu.kanade.tachiyomi.util.storage -import java.io.File -import java.io.OutputStream import okio.BufferedSource import okio.buffer import okio.sink +import java.io.File +import java.io.OutputStream /** * Saves the given source to a file and closes it. Directories will be created if needed. diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ChildFirstPathClassLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ChildFirstPathClassLoader.kt index b63dfd032851..441fe2e78e6f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ChildFirstPathClassLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ChildFirstPathClassLoader.kt @@ -17,24 +17,28 @@ class ChildFirstPathClassLoader( librarySearchPath: String?, parent: ClassLoader ) : PathClassLoader(dexPath, librarySearchPath, parent) { - private val systemClassLoader: ClassLoader? = getSystemClassLoader() - override fun loadClass(name: String?, resolve: Boolean): Class<*> { + override fun loadClass( + name: String?, + resolve: Boolean + ): Class<*> { var c = findLoadedClass(name) if (c == null && systemClassLoader != null) { try { c = systemClassLoader.loadClass(name) - } catch (_: ClassNotFoundException) {} + } catch (_: ClassNotFoundException) { + } } if (c == null) { - c = try { - findClass(name) - } catch (_: ClassNotFoundException) { - super.loadClass(name, resolve) - } + c = + try { + findClass(name) + } catch (_: ClassNotFoundException) { + super.loadClass(name, resolve) + } } if (resolve) { @@ -54,24 +58,26 @@ class ChildFirstPathClassLoader( val systemUrls = systemClassLoader?.getResources(name) val localUrls = findResources(name) val parentUrls = parent?.getResources(name) - val urls = buildList { - while (systemUrls?.hasMoreElements() == true) { - add(systemUrls.nextElement()) - } + val urls = + buildList { + while (systemUrls?.hasMoreElements() == true) { + add(systemUrls.nextElement()) + } - while (localUrls?.hasMoreElements() == true) { - add(localUrls.nextElement()) - } + while (localUrls?.hasMoreElements() == true) { + add(localUrls.nextElement()) + } - while (parentUrls?.hasMoreElements() == true) { - add(parentUrls.nextElement()) + while (parentUrls?.hasMoreElements() == true) { + add(parentUrls.nextElement()) + } } - } return object : Enumeration { val iterator = urls.iterator() override fun hasMoreElements() = iterator.hasNext() + override fun nextElement() = iterator.next() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index 63e57cdcda8b..ce06a13e51a2 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -35,10 +35,10 @@ import com.nononsenseapps.filepicker.FilePickerActivity import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.lang.truncateCenter import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity -import kotlin.math.roundToInt import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import kotlin.math.roundToInt /** * Display a toast in this context. @@ -46,7 +46,10 @@ import kotlinx.coroutines.launch * @param resource the text resource. * @param duration the duration of the toast. Defaults to short. */ -fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) { +fun Context.toast( + @StringRes resource: Int, + duration: Int = Toast.LENGTH_SHORT +) { GlobalScope.launch(Dispatchers.Main) { Toast.makeText(this@toast, resource, duration).show() } @@ -58,7 +61,10 @@ fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) * @param text the text to display. * @param duration the duration of the toast. Defaults to short. */ -fun Context.toast(text: String?, duration: Int = Toast.LENGTH_SHORT) { +fun Context.toast( + text: String?, + duration: Int = Toast.LENGTH_SHORT +) { GlobalScope.launch(Dispatchers.Main) { Toast.makeText(this@toast, text.orEmpty(), duration).show() } @@ -70,7 +76,10 @@ fun Context.toast(text: String?, duration: Int = Toast.LENGTH_SHORT) { * @param label Label to show to the user describing the content * @param content the actual text to copy to the board */ -fun Context.copyToClipboard(label: String, content: String) { +fun Context.copyToClipboard( + label: String, + content: String +) { if (content.isBlank()) return val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @@ -86,9 +95,13 @@ fun Context.copyToClipboard(label: String, content: String) { * @param block the function that will execute inside the builder. * @return a notification to be displayed or updated. */ -fun Context.notificationBuilder(channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null): NotificationCompat.Builder { - val builder = NotificationCompat.Builder(this, channelId) - .setColor(ContextCompat.getColor(this, R.color.colorPrimary)) +fun Context.notificationBuilder( + channelId: String, + block: (NotificationCompat.Builder.() -> Unit)? = null +): NotificationCompat.Builder { + val builder = + NotificationCompat.Builder(this, channelId) + .setColor(ContextCompat.getColor(this, R.color.colorPrimary)) if (block != null) { builder.block() } @@ -102,7 +115,10 @@ fun Context.notificationBuilder(channelId: String, block: (NotificationCompat.Bu * @param block the function that will execute inside the builder. * @return a notification to be displayed or updated. */ -fun Context.notification(channelId: String, block: (NotificationCompat.Builder.() -> Unit)?): Notification { +fun Context.notification( + channelId: String, + block: (NotificationCompat.Builder.() -> Unit)? +): Notification { val builder = notificationBuilder(channelId, block) return builder.build() } @@ -134,7 +150,10 @@ fun Context.hasPermission(permission: String) = ContextCompat.checkSelfPermissio * @param resource the attribute. * @param alphaFactor the alpha number [0,1]. */ -@ColorInt fun Context.getResourceColor(@AttrRes resource: Int, alphaFactor: Float = 1f): Int { +@ColorInt fun Context.getResourceColor( + @AttrRes resource: Int, + alphaFactor: Float = 1f +): Int { val typedArray = obtainStyledAttributes(intArrayOf(resource)) val color = typedArray.getColor(0, 0) typedArray.recycle() @@ -226,7 +245,10 @@ fun Context.sendLocalBroadcastSync(intent: Intent) { * * @param receiver receiver that gets registered. */ -fun Context.registerLocalReceiver(receiver: BroadcastReceiver, filter: IntentFilter) { +fun Context.registerLocalReceiver( + receiver: BroadcastReceiver, + filter: IntentFilter +) { LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filter) } @@ -256,9 +278,10 @@ fun Context.isServiceRunning(serviceClass: Class<*>): Boolean { fun Context.openInBrowser(url: String) { try { val parsedUrl = Uri.parse(url) - val intent = CustomTabsIntent.Builder() - .setToolbarColor(getResourceColor(R.attr.colorPrimary)) - .build() + val intent = + CustomTabsIntent.Builder() + .setToolbarColor(getResourceColor(R.attr.colorPrimary)) + .build() intent.launchUrl(this, parsedUrl) } catch (e: Exception) { toast(e.message) @@ -299,11 +322,12 @@ fun Context.isOnline(): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val networkCapabilities = connectivityManager.activeNetwork ?: return false val actNw = connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false - val maxTransport = when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> NetworkCapabilities.TRANSPORT_LOWPAN - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> NetworkCapabilities.TRANSPORT_WIFI_AWARE - else -> NetworkCapabilities.TRANSPORT_VPN - } + val maxTransport = + when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> NetworkCapabilities.TRANSPORT_LOWPAN + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> NetworkCapabilities.TRANSPORT_WIFI_AWARE + else -> NetworkCapabilities.TRANSPORT_VPN + } return (NetworkCapabilities.TRANSPORT_CELLULAR..maxTransport).any(actNw::hasTransport) } else { val networkInfo = connectivityManager.activeNetworkInfo ?: return false diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt index 08f335668f0c..aba012b88e22 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt @@ -8,20 +8,15 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -fun launchUI(block: suspend CoroutineScope.() -> Unit): Job = - GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT, block) +fun launchUI(block: suspend CoroutineScope.() -> Unit): Job = GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT, block) -fun launchIO(block: suspend CoroutineScope.() -> Unit): Job = - GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT, block) +fun launchIO(block: suspend CoroutineScope.() -> Unit): Job = GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT, block) -fun launchNow(block: suspend CoroutineScope.() -> Unit): Job = - GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED, block) +fun launchNow(block: suspend CoroutineScope.() -> Unit): Job = GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED, block) -fun CoroutineScope.launchIO(block: suspend CoroutineScope.() -> Unit): Job = - launch(Dispatchers.IO, block = block) +fun CoroutineScope.launchIO(block: suspend CoroutineScope.() -> Unit): Job = launch(Dispatchers.IO, block = block) -fun CoroutineScope.launchUI(block: suspend CoroutineScope.() -> Unit): Job = - launch(Dispatchers.Main, block = block) +fun CoroutineScope.launchUI(block: suspend CoroutineScope.() -> Unit): Job = launch(Dispatchers.Main, block = block) suspend fun withUIContext(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.Main, block) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt index c66b5f25e774..33c2a29c75a4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt @@ -12,13 +12,16 @@ import java.net.URLConnection import kotlin.math.abs object ImageUtil { - - fun isImage(name: String, openStream: (() -> InputStream)? = null): Boolean { - val contentType = try { - URLConnection.guessContentTypeFromName(name) - } catch (e: Exception) { - null - } ?: openStream?.let { findImageType(it)?.mime } + fun isImage( + name: String, + openStream: (() -> InputStream)? = null + ): Boolean { + val contentType = + try { + URLConnection.guessContentTypeFromName(name) + } catch (e: Exception) { + null + } ?: openStream?.let { findImageType(it)?.mime } return contentType?.startsWith("image/") ?: false } @@ -30,12 +33,13 @@ object ImageUtil { try { val bytes = ByteArray(12) - val length = if (stream.markSupported()) { - stream.mark(bytes.size) - stream.read(bytes, 0, bytes.size).also { stream.reset() } - } else { - stream.read(bytes, 0, bytes.size) - } + val length = + if (stream.markSupported()) { + stream.mark(bytes.size) + stream.read(bytes, 0, bytes.size).also { stream.reset() } + } else { + stream.read(bytes, 0, bytes.size) + } if (length == -1) { return null @@ -54,11 +58,11 @@ object ImageUtil { return ImageType.WEBP } if (bytes.comparesWithAnyOf( - listOf( - charByteArrayOf(0xFF, 0x0A), - charByteArrayOf(0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A) + listOf( + charByteArrayOf(0xFF, 0x0A), + charByteArrayOf(0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A) + ) ) - ) ) { return ImageType.JXL } @@ -66,12 +70,12 @@ object ImageUtil { if (bytes.compareWith("avi".toByteArray(), 8)) { return ImageType.AVIF } else if (bytes.getSlice(8, 4).comparesWithAnyOf( - listOf( - "hei".toByteArray(), - "mif1".toByteArray(), - "hev".toByteArray() + listOf( + "hei".toByteArray(), + "mif1".toByteArray(), + "hev".toByteArray() + ) ) - ) ) { return ImageType.HEIF } @@ -89,16 +93,24 @@ object ImageUtil { } return false } - private fun ByteArray.compareWith(magic: ByteArray, offset: Int = 0): Boolean { + + private fun ByteArray.compareWith( + magic: ByteArray, + offset: Int = 0 + ): Boolean { for (i in magic.indices) { if (this[i + offset] != magic[i]) return false } return true } - private fun ByteArray.getSlice(offset: Int, length: Int): ByteArray { + private fun ByteArray.getSlice( + offset: Int, + length: Int + ): ByteArray { return this.slice(IntRange(offset, offset + length - 1)).toByteArray() } + private fun charByteArrayOf(vararg bytes: Int): ByteArray { return ByteArray(bytes.size).apply { for (i in bytes.indices) { @@ -118,10 +130,17 @@ object ImageUtil { } // SY --> - fun autoSetBackground(image: Bitmap?, alwaysUseWhite: Boolean, context: Context): Drawable { - val backgroundColor = if (alwaysUseWhite) Color.WHITE else { - context.getResourceColor(R.attr.colorPrimary) - } + fun autoSetBackground( + image: Bitmap?, + alwaysUseWhite: Boolean, + context: Context + ): Drawable { + val backgroundColor = + if (alwaysUseWhite) { + Color.WHITE + } else { + context.getResourceColor(R.attr.colorPrimary) + } if (image == null) return ColorDrawable(backgroundColor) if (image.width < 50 || image.height < 50) { return ColorDrawable(backgroundColor) @@ -142,8 +161,9 @@ object ImageUtil { val botLeftIsDark = isDark(image.getPixel(left, bot)) val botRightIsDark = isDark(image.getPixel(right, bot)) - var darkBG = (topLeftIsDark && (botLeftIsDark || botRightIsDark || topRightIsDark || midLeftIsDark || topMidIsDark)) || - (topRightIsDark && (botRightIsDark || botLeftIsDark || midRightIsDark || topMidIsDark)) + var darkBG = + (topLeftIsDark && (botLeftIsDark || botRightIsDark || topRightIsDark || midLeftIsDark || topMidIsDark)) || + (topRightIsDark && (botRightIsDark || botLeftIsDark || midRightIsDark || topMidIsDark)) if (!isWhite(image.getPixel(left, top)) && pixelIsClose(image.getPixel(left, top), image.getPixel(midX, top)) && !isWhite(image.getPixel(midX, top)) && pixelIsClose(image.getPixel(midX, top), image.getPixel(right, top)) && @@ -163,13 +183,14 @@ object ImageUtil { darkBG = false } - var blackPixel = when { - topLeftIsDark -> image.getPixel(left, top) - topRightIsDark -> image.getPixel(right, top) - botLeftIsDark -> image.getPixel(left, bot) - botRightIsDark -> image.getPixel(right, bot) - else -> backgroundColor - } + var blackPixel = + when { + topLeftIsDark -> image.getPixel(left, top) + topRightIsDark -> image.getPixel(right, top) + botLeftIsDark -> image.getPixel(left, bot) + botRightIsDark -> image.getPixel(right, bot) + else -> backgroundColor + } var overallWhitePixels = 0 var overallBlackPixels = 0 @@ -227,11 +248,12 @@ object ImageUtil { when { blackPixels > 22 -> { if (x == right || x == right + offsetX) { - blackPixel = when { - topRightIsDark -> image.getPixel(right, top) - botRightIsDark -> image.getPixel(right, bot) - else -> blackPixel - } + blackPixel = + when { + topRightIsDark -> image.getPixel(right, top) + botRightIsDark -> image.getPixel(right, bot) + else -> blackPixel + } } darkBG = true overallWhitePixels = 0 @@ -240,11 +262,12 @@ object ImageUtil { blackStreak -> { darkBG = true if (x == right || x == right + offsetX) { - blackPixel = when { - topRightIsDark -> image.getPixel(right, top) - botRightIsDark -> image.getPixel(right, bot) - else -> blackPixel - } + blackPixel = + when { + topRightIsDark -> image.getPixel(right, top) + botRightIsDark -> image.getPixel(right, bot) + else -> blackPixel + } } if (blackPixels > 18) { overallWhitePixels = 0 @@ -274,7 +297,9 @@ object ImageUtil { GradientDrawable.Orientation.TOP_BOTTOM, intArrayOf(backgroundColor, backgroundColor, blackPixel, blackPixel) ) - } else ColorDrawable(blackPixel) + } else { + ColorDrawable(blackPixel) + } } if (topIsBlackStreak || ( topLeftIsDark && topRightIsDark && @@ -311,7 +336,10 @@ object ImageUtil { private fun Boolean.toInt() = if (this) 1 else 0 - private fun pixelIsClose(color1: Int, color2: Int): Boolean { + private fun pixelIsClose( + color1: Int, + color2: Int + ): Boolean { return abs(Color.red(color1) - Color.red(color2)) < 30 && abs(Color.green(color1) - Color.green(color2)) < 30 && abs(Color.blue(color1) - Color.blue(color2)) < 30 diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/LocaleHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/LocaleHelper.kt index 26e921afa108..93aec796ef93 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/LocaleHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/LocaleHelper.kt @@ -8,15 +8,14 @@ import android.view.ContextThemeWrapper import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.source.SourcePresenter -import java.util.Locale import uy.kohesive.injekt.injectLazy +import java.util.Locale /** * Utility class to change the application's language in runtime. */ @Suppress("DEPRECATION") object LocaleHelper { - private val preferences: PreferencesHelper by injectLazy() private var systemLocale: Locale? = null @@ -47,7 +46,10 @@ object LocaleHelper { /** * Returns Display name of a string language code */ - fun getSourceDisplayName(lang: String?, context: Context): String { + fun getSourceDisplayName( + lang: String?, + context: Context + ): String { return when (lang) { "" -> context.getString(R.string.other_source) SourcePresenter.LAST_USED_KEY -> context.getString(R.string.last_used_source) @@ -108,7 +110,11 @@ object LocaleHelper { /** * Updates the app's language to the application. */ - fun updateConfiguration(app: Application, config: Configuration, configChange: Boolean = false) { + fun updateConfiguration( + app: Application, + config: Configuration, + configChange: Boolean = false + ) { if (systemLocale == null) { systemLocale = getConfigLocale(config) } @@ -141,7 +147,10 @@ object LocaleHelper { /** * Returns a new configuration with the given locale applied. */ - private fun updateConfigLocale(config: Configuration, locale: Locale): Configuration { + private fun updateConfigLocale( + config: Configuration, + locale: Locale + ): Configuration { val newConfig = Configuration(config) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { newConfig.locale = locale diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/MiuiUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/MiuiUtil.kt index 2aee8e3b2ffe..57a6b9abf900 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/MiuiUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/MiuiUtil.kt @@ -4,7 +4,6 @@ import android.annotation.SuppressLint import android.util.Log object MiuiUtil { - fun isMiui(): Boolean { return getSystemProperty("ro.miui.ui.version.name")?.isNotEmpty() ?: false } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt index d32cc11410a9..218b3a5504be 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt @@ -10,12 +10,17 @@ import android.webkit.WebViewClient @Suppress("OverridingDeprecatedMember") abstract class WebViewClientCompat : WebViewClient() { - - open fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean { + open fun shouldOverrideUrlCompat( + view: WebView, + url: String + ): Boolean { return false } - open fun shouldInterceptRequestCompat(view: WebView, url: String): WebResourceResponse? { + open fun shouldInterceptRequestCompat( + view: WebView, + url: String + ): WebResourceResponse? { return null } @@ -36,7 +41,10 @@ abstract class WebViewClientCompat : WebViewClient() { return shouldOverrideUrlCompat(view, request.url.toString()) } - final override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + final override fun shouldOverrideUrlLoading( + view: WebView, + url: String + ): Boolean { return shouldOverrideUrlCompat(view, url) } @@ -61,8 +69,11 @@ abstract class WebViewClientCompat : WebViewClient() { error: WebResourceError ) { onReceivedErrorCompat( - view, error.errorCode, error.description?.toString(), - request.url.toString(), request.isForMainFrame + view, + error.errorCode, + error.description?.toString(), + request.url.toString(), + request.isForMainFrame ) } @@ -82,7 +93,9 @@ abstract class WebViewClientCompat : WebViewClient() { error: WebResourceResponse ) { onReceivedErrorCompat( - view, error.statusCode, error.reasonPhrase, + view, + error.statusCode, + error.reasonPhrase, request.url .toString(), request.isForMainFrame diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt index d0ca441f356a..3ec27129898a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/WebViewUtil.kt @@ -51,11 +51,12 @@ private fun getWebViewMajorVersion(webview: WebView): Int { webview.settings.userAgentString = null val uaRegexMatch = WebViewUtil.WEBVIEW_UA_VERSION_REGEX.matchEntire(webview.settings.userAgentString) - val webViewVersion: Int = if (uaRegexMatch != null && uaRegexMatch.groupValues.size > 1) { - uaRegexMatch.groupValues[1].toInt() - } else { - 0 - } + val webViewVersion: Int = + if (uaRegexMatch != null && uaRegexMatch.groupValues.size > 1) { + uaRegexMatch.groupValues[1].toInt() + } else { + 0 + } // Revert to original UA string webview.settings.userAgentString = originalUA diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ImageViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ImageViewExtensions.kt index 69335bc5b541..99da4a3ba17e 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ImageViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ImageViewExtensions.kt @@ -9,7 +9,10 @@ import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat * * @param drawable id of drawable resource */ -fun ImageView.setVectorCompat(@DrawableRes drawable: Int, tint: Int? = null) { +fun ImageView.setVectorCompat( + @DrawableRes drawable: Int, + tint: Int? = null +) { val vector = VectorDrawableCompat.create(resources, drawable, context.theme) if (tint != null) { vector?.mutate() diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 82130b8de054..2b8cac0b7c18 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -33,7 +33,11 @@ fun View.getCoordinates() = Point((left + right) / 2, (top + bottom) / 2) * @param length the duration of the snack. * @param f a function to execute in the snack, allowing for example to define a custom action. */ -inline fun View.snack(message: String, length: Int = Snackbar.LENGTH_LONG, f: Snackbar.() -> Unit): Snackbar { +inline fun View.snack( + message: String, + length: Int = Snackbar.LENGTH_LONG, + f: Snackbar.() -> Unit +): Snackbar { val snack = Snackbar.make(this, message, length) val textView: TextView = snack.view.findViewById(com.google.android.material.R.id.snackbar_text) textView.setTextColor(Color.WHITE) @@ -49,7 +53,11 @@ inline fun View.snack(message: String, length: Int = Snackbar.LENGTH_LONG, f: Sn * @param initMenu function to execute when the menu after is inflated. * @param onMenuItemClick function to execute when a menu item is clicked. */ -fun View.popupMenu(@MenuRes menuRes: Int, initMenu: (Menu.() -> Unit)? = null, onMenuItemClick: MenuItem.() -> Boolean) { +fun View.popupMenu( + @MenuRes menuRes: Int, + initMenu: (Menu.() -> Unit)? = null, + onMenuItemClick: MenuItem.() -> Boolean +) { val popup = PopupMenu(context, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0) popup.menuInflater.inflate(menuRes, popup.menu) @@ -87,15 +95,21 @@ inline fun View.toggle() { * @param recycler [RecyclerView] that the FAB should shrink/extend in response to. */ fun ExtendedFloatingActionButton.shrinkOnScroll(recycler: RecyclerView) { - recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - if (dy <= 0) { - extend() - } else { - shrink() + recycler.addOnScrollListener( + object : RecyclerView.OnScrollListener() { + override fun onScrolled( + recyclerView: RecyclerView, + dx: Int, + dy: Int + ) { + if (dy <= 0) { + extend() + } else { + shrink() + } } } - }) + ) } /** @@ -104,14 +118,18 @@ fun ExtendedFloatingActionButton.shrinkOnScroll(recycler: RecyclerView) { * @param items List of strings that are shown as individual chips. * @param onClick Optional on click listener for each chip. */ -fun ChipGroup.setChips(items: List?, onClick: (item: String) -> Unit = {}) { +fun ChipGroup.setChips( + items: List?, + onClick: (item: String) -> Unit = {} +) { removeAllViews() items?.forEach { item -> - val chip = Chip(context).apply { - text = item - setOnClickListener { onClick(item) } - } + val chip = + Chip(context).apply { + text = item + setOnClickListener { onClick(item) } + } addView(chip) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewGroupExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewGroupExtensions.kt index 64c71200a07d..74a1834a6ea7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewGroupExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewGroupExtensions.kt @@ -10,6 +10,9 @@ import androidx.annotation.LayoutRes * @param layout the layout to inflate. * @param attachToRoot whether to attach the view to the root or not. Defaults to false. */ -fun ViewGroup.inflate(@LayoutRes layout: Int, attachToRoot: Boolean = false): View { +fun ViewGroup.inflate( + @LayoutRes layout: Int, + attachToRoot: Boolean = false +): View { return LayoutInflater.from(context).inflate(layout, this, attachToRoot) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/WindowExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/WindowExtensions.kt index 3ae882af3d85..e35d9a83f5dc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/WindowExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/WindowExtensions.kt @@ -4,18 +4,20 @@ import android.view.View import android.view.Window fun Window.showBar() { - val uiFlags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_LAYOUT_STABLE + val uiFlags = + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_LAYOUT_STABLE decorView.systemUiVisibility = uiFlags } fun Window.hideBar() { - val uiFlags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + val uiFlags = + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_FULLSCREEN or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY decorView.systemUiVisibility = uiFlags } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt index 565dfbe3c48f..5cf84da61e24 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/AutofitRecyclerView.kt @@ -6,9 +6,10 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlin.math.max -class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class AutofitRecyclerView +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : RecyclerView(context, attrs) { - private val manager = GridLayoutManager(context, 1) private var columnWidth = -1 @@ -35,7 +36,10 @@ class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: Att layoutManager = manager } - override fun onMeasure(widthSpec: Int, heightSpec: Int) { + override fun onMeasure( + widthSpec: Int, + heightSpec: Int + ) { super.onMeasure(widthSpec, heightSpec) if (spanCount == 0 && columnWidth > 0) { val count = max(1, measuredWidth / columnWidth) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/CustomLayoutPicker.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/CustomLayoutPicker.kt index fd871976aed8..cc1857496c1e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/CustomLayoutPicker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/CustomLayoutPicker.kt @@ -10,17 +10,23 @@ import eu.kanade.tachiyomi.util.view.inflate import java.io.File class CustomLayoutPickerActivity : FilePickerActivity() { - - override fun getFragment(startPath: String?, mode: Int, allowMultiple: Boolean, allowCreateDir: Boolean): - AbstractFilePickerFragment { - val fragment = CustomLayoutFilePickerFragment() - fragment.setArgs(startPath, mode, allowMultiple, allowCreateDir) - return fragment - } + override fun getFragment( + startPath: String?, + mode: Int, + allowMultiple: Boolean, + allowCreateDir: Boolean + ): AbstractFilePickerFragment { + val fragment = CustomLayoutFilePickerFragment() + fragment.setArgs(startPath, mode, allowMultiple, allowCreateDir) + return fragment + } } class CustomLayoutFilePickerFragment : FilePickerFragment() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): androidx.recyclerview.widget.RecyclerView.ViewHolder { return when (viewType) { LogicHandler.VIEWTYPE_DIR -> { val view = parent.inflate(R.layout.common_listitem_dir) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCheckboxView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCheckboxView.kt index 07920c120e08..8416e1edb5bd 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCheckboxView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCheckboxView.kt @@ -7,20 +7,26 @@ import android.widget.LinearLayout import androidx.annotation.StringRes import eu.kanade.tachiyomi.databinding.CommonDialogWithCheckboxBinding -class DialogCheckboxView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class DialogCheckboxView +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) { - private val binding: CommonDialogWithCheckboxBinding + init { binding = CommonDialogWithCheckboxBinding.inflate(LayoutInflater.from(context), this, false) addView(binding.root) } - fun setDescription(@StringRes id: Int) { + fun setDescription( + @StringRes id: Int + ) { binding.description.text = context.getString(id) } - fun setOptionDescription(@StringRes id: Int) { + fun setOptionDescription( + @StringRes id: Int + ) { binding.checkboxOption.text = context.getString(id) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCustomDownloadView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCustomDownloadView.kt index 0db08f1951fb..65da1b4d65cc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCustomDownloadView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/DialogCustomDownloadView.kt @@ -18,9 +18,10 @@ import timber.log.Timber /** * Custom dialog to select how many chapters to download. */ -class DialogCustomDownloadView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class DialogCustomDownloadView +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) { - private val scope = CoroutineScope(Job() + Dispatchers.Main) /** @@ -40,6 +41,7 @@ class DialogCustomDownloadView @JvmOverloads constructor(context: Context, attrs private var max = 0 private val binding: DownloadCustomAmountBinding + init { // Add view to stack binding = DownloadCustomAmountBinding.inflate(LayoutInflater.from(context), this, false) @@ -95,7 +97,10 @@ class DialogCustomDownloadView @JvmOverloads constructor(context: Context, attrs * @param min minimal downloads * @param max maximal downloads */ - fun setMinMax(min: Int, max: Int) { + fun setMinMax( + min: Int, + max: Int + ) { this.min = min this.max = max } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/DrawerSwipeCloseListener.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/DrawerSwipeCloseListener.kt index dad74a771a88..76ce25fbccbb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/DrawerSwipeCloseListener.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/DrawerSwipeCloseListener.kt @@ -8,7 +8,6 @@ class DrawerSwipeCloseListener( private val drawer: androidx.drawerlayout.widget.DrawerLayout, private val navigationView: ViewGroup ) : androidx.drawerlayout.widget.DrawerLayout.SimpleDrawerListener() { - override fun onDrawerOpened(drawerView: View) { if (drawerView == navigationView) { drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED, drawerView) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt index 838bcab8e8a6..134205fd7ae2 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ElevationAppBarLayout.kt @@ -7,11 +7,12 @@ import android.util.AttributeSet import com.google.android.material.R import com.google.android.material.appbar.AppBarLayout -class ElevationAppBarLayout @JvmOverloads constructor( +class ElevationAppBarLayout +@JvmOverloads +constructor( context: Context, attrs: AttributeSet? = null ) : AppBarLayout(context, attrs) { - private var origStateAnimator: StateListAnimator? = null init { @@ -23,20 +24,21 @@ class ElevationAppBarLayout @JvmOverloads constructor( } fun disableElevation() { - stateListAnimator = StateListAnimator().apply { - val objAnimator = ObjectAnimator.ofFloat(this, "elevation", 0f) - - // Enabled and collapsible, but not collapsed means not elevated - addState( - intArrayOf(android.R.attr.enabled, R.attr.state_collapsible, -R.attr.state_collapsed), - objAnimator - ) - - // Default enabled state - addState(intArrayOf(android.R.attr.enabled), objAnimator) - - // Disabled state - addState(IntArray(0), objAnimator) - } + stateListAnimator = + StateListAnimator().apply { + val objAnimator = ObjectAnimator.ofFloat(this, "elevation", 0f) + + // Enabled and collapsible, but not collapsed means not elevated + addState( + intArrayOf(android.R.attr.enabled, R.attr.state_collapsible, -R.attr.state_collapsed), + objAnimator + ) + + // Default enabled state + addState(intArrayOf(android.R.attr.enabled), objAnimator) + + // Disabled state + addState(IntArray(0), objAnimator) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt index 2ddd04e22473..f98bfd2e9ed7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt @@ -12,10 +12,12 @@ import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.visible import kotlin.random.Random -class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class EmptyView +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : RelativeLayout(context, attrs) { - private val binding: CommonViewEmptyBinding + init { binding = CommonViewEmptyBinding.inflate(LayoutInflater.from(context), this, true) } @@ -31,26 +33,34 @@ class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? * Show the information view * @param textResource text of information view */ - fun show(@StringRes textResource: Int, actions: List? = null) { + fun show( + @StringRes textResource: Int, + actions: List? = null + ) { show(context.getString(textResource), actions) } - fun show(message: String, actions: List? = null) { + fun show( + message: String, + actions: List? = null + ) { binding.textFace.text = getRandomErrorFace() binding.textLabel.text = message binding.actionsContainer.removeAllViews() if (!actions.isNullOrEmpty()) { actions.forEach { - val button = AppCompatButton(context).apply { - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ) + val button = + AppCompatButton(context).apply { + layoutParams = + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) - setText(it.resId) - setOnClickListener(it.listener) - } + setText(it.resId) + setOnClickListener(it.listener) + } binding.actionsContainer.addView(button) } @@ -60,16 +70,17 @@ class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? } companion object { - private val ERROR_FACES = listOf( - "(・o・;)", - "Σ(ಠ_ಠ)", - "ಥ_ಥ", - "(˘・_・˘)", - "(; ̄Д ̄)", - "(・Д・。", - "ᗜ˰ᗜ", - "ᗜˬᗜ" - ) + private val ERROR_FACES = + listOf( + "(・o・;)", + "Σ(ಠ_ಠ)", + "ಥ_ಥ", + "(˘・_・˘)", + "(; ̄Д ̄)", + "(・Д・。", + "ᗜ˰ᗜ", + "ᗜˬᗜ" + ) fun getRandomErrorFace(): String { return ERROR_FACES[Random.nextInt(ERROR_FACES.size)] diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt index 59598e0fa4fc..63c80eb639c7 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt @@ -14,13 +14,14 @@ import eu.kanade.tachiyomi.util.system.getResourceColor * An alternative implementation of [com.google.android.material.navigation.NavigationView], without menu * inflation and allowing customizable items (multiple selections, custom views, etc). */ -open class ExtendedNavigationView @JvmOverloads constructor( +open class ExtendedNavigationView +@JvmOverloads +constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : SimpleNavigationView(context, attrs, defStyleAttr) { - /** * Every item of the nav view. Generic items must belong to this list, custom items could be * implemented by an abstract class. If more customization is needed in the future, this can be @@ -59,7 +60,6 @@ open class ExtendedNavigationView @JvmOverloads constructor( * An item with which needs more than two states (selected/deselected). */ abstract class MultiState(val resTitle: Int, var state: Int = 0) : Item() { - /** * Returns the drawable associated to every possible each state. */ @@ -71,7 +71,10 @@ open class ExtendedNavigationView @JvmOverloads constructor( * @param context any context. * @param resId the vector resource to load and tint */ - fun tintVector(context: Context, resId: Int): Drawable { + fun tintVector( + context: Context, + resId: Int + ): Drawable { return VectorDrawableCompat.create(context.resources, resId, context.theme)!!.apply { setTint(context.getResourceColor(R.attr.colorAccent)) } @@ -83,7 +86,11 @@ open class ExtendedNavigationView @JvmOverloads constructor( * @param context any context. * @param resId the vector resource to load and tint */ - fun tintVector(context: Context, resId: Int, colorId: Int): Drawable { + fun tintVector( + context: Context, + resId: Int, + colorId: Int + ): Drawable { return VectorDrawableCompat.create(context.resources, resId, context.theme)!!.apply { setTint(context.getResourceColor(colorId)) } @@ -101,7 +108,6 @@ open class ExtendedNavigationView @JvmOverloads constructor( * A multistate item for sorting lists (unselected, ascending, descending). */ class MultiSort(resId: Int, group: Group) : MultiStateGroup(resId, group) { - companion object { const val SORT_NONE = 0 const val SORT_ASC = 1 @@ -119,7 +125,6 @@ open class ExtendedNavigationView @JvmOverloads constructor( } class TriStateGroup(resId: Int, group: Group) : MultiStateGroup(resId, group) { - companion object { const val STATE_IGNORE = 0 const val STATE_INCLUDE = 1 @@ -129,14 +134,18 @@ open class ExtendedNavigationView @JvmOverloads constructor( override fun getStateDrawable(context: Context): Drawable? { return when (state) { STATE_INCLUDE -> tintVector(context, R.drawable.ic_check_box_24dp) - STATE_EXCLUDE -> tintVector( - context, R.drawable.ic_check_box_x_24dp, - android.R.attr.textColorSecondary - ) - else -> tintVector( - context, R.drawable.ic_check_box_outline_blank_24dp, - android.R.attr.textColorSecondary - ) + STATE_EXCLUDE -> + tintVector( + context, + R.drawable.ic_check_box_x_24dp, + android.R.attr.textColorSecondary + ) + else -> + tintVector( + context, + R.drawable.ic_check_box_outline_blank_24dp, + android.R.attr.textColorSecondary + ) } } } @@ -153,7 +162,6 @@ open class ExtendedNavigationView @JvmOverloads constructor( * A group containing a list of items. */ interface Group { - /** * An optional header for the group, typically a [Item.Header]. */ @@ -193,12 +201,12 @@ open class ExtendedNavigationView @JvmOverloads constructor( * [Item]. */ abstract inner class Adapter(private val items: List) : androidx.recyclerview.widget.RecyclerView.Adapter() { - - private val onClick = OnClickListener { - val pos = recycler.getChildAdapterPosition(it) - val item = items[pos] - onItemClicked(item) - } + private val onClick = + OnClickListener { + val pos = recycler.getChildAdapterPosition(it) + val item = items[pos] + onItemClicked(item) + } fun notifyItemChanged(item: Item) { val pos = items.indexOf(item) @@ -221,7 +229,10 @@ open class ExtendedNavigationView @JvmOverloads constructor( } @CallSuper - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): Holder { return when (viewType) { VIEW_TYPE_HEADER -> HeaderHolder(parent) VIEW_TYPE_SEPARATOR -> SeparatorHolder(parent) @@ -233,7 +244,10 @@ open class ExtendedNavigationView @JvmOverloads constructor( } @CallSuper - override fun onBindViewHolder(holder: Holder, position: Int) { + override fun onBindViewHolder( + holder: Holder, + position: Int + ) { when (holder) { is HeaderHolder -> { val item = items[position] as Item.Header diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/IgnoreFirstSpinnerListener.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/IgnoreFirstSpinnerListener.kt index 107e6fa55879..22d6653ebde8 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/IgnoreFirstSpinnerListener.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/IgnoreFirstSpinnerListener.kt @@ -5,10 +5,14 @@ import android.widget.AdapterView import android.widget.AdapterView.OnItemSelectedListener class IgnoreFirstSpinnerListener(private val block: (Int) -> Unit) : OnItemSelectedListener { - private var firstEvent = true - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { if (!firstEvent) { block(position) } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/LimitedScrollView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/LimitedScrollView.kt index 3b22a1980dc9..8c9e7ab0d7cf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/LimitedScrollView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/LimitedScrollView.kt @@ -9,7 +9,6 @@ import android.widget.ScrollView import eu.kanade.tachiyomi.R class LimitedScrollView(context: Context, attrs: AttributeSet) : ScrollView(context, attrs) { - var maxHeight: Int init { @@ -17,11 +16,17 @@ class LimitedScrollView(context: Context, attrs: AttributeSet) : ScrollView(cont maxHeight = a.getInt(R.styleable.LimitedScrollView_max_height, 100) } - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + override fun onMeasure( + widthMeasureSpec: Int, + heightMeasureSpec: Int + ) { super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(dpToPx(getResources(), maxHeight), MeasureSpec.AT_MOST)) } - private fun dpToPx(res: Resources, dp: Int): Int { + private fun dpToPx( + res: Resources, + dp: Int + ): Int { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), res.getDisplayMetrics()).toInt() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/MinMaxNumberPicker.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/MinMaxNumberPicker.kt index a041db442b20..c3a2f510f252 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/MinMaxNumberPicker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/MinMaxNumberPicker.kt @@ -5,9 +5,10 @@ import android.util.AttributeSet import android.widget.NumberPicker import eu.kanade.tachiyomi.R -class MinMaxNumberPicker @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class MinMaxNumberPicker +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : NumberPicker(context, attrs) { - init { if (attrs != null) { val ta = context.obtainStyledAttributes(attrs, R.styleable.MinMaxNumberPicker, 0, 0) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt index 10f4f129677e..520e4dc77e96 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt @@ -8,18 +8,22 @@ import androidx.appcompat.widget.AppCompatSeekBar import eu.kanade.tachiyomi.R import kotlin.math.abs -class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class NegativeSeekBar +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : AppCompatSeekBar(context, attrs) { - private var minValue: Int = 0 private var maxValue: Int = 0 private var listener: OnSeekBarChangeListener? = null init { - val styledAttributes = context.obtainStyledAttributes( - attrs, - R.styleable.NegativeSeekBar, 0, 0 - ) + val styledAttributes = + context.obtainStyledAttributes( + attrs, + R.styleable.NegativeSeekBar, + 0, + 0 + ) try { setMinSeek(styledAttributes.getInt(R.styleable.NegativeSeekBar_min_seek, 0)) @@ -28,19 +32,25 @@ class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: Attribu styledAttributes.recycle() } - super.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar?, value: Int, fromUser: Boolean) { - listener?.onProgressChanged(seekBar, minValue + value, fromUser) - } + super.setOnSeekBarChangeListener( + object : OnSeekBarChangeListener { + override fun onProgressChanged( + seekBar: SeekBar?, + value: Int, + fromUser: Boolean + ) { + listener?.onProgressChanged(seekBar, minValue + value, fromUser) + } - override fun onStartTrackingTouch(p0: SeekBar?) { - listener?.onStartTrackingTouch(p0) - } + override fun onStartTrackingTouch(p0: SeekBar?) { + listener?.onStartTrackingTouch(p0) + } - override fun onStopTrackingTouch(p0: SeekBar?) { - listener?.onStopTrackingTouch(p0) + override fun onStopTrackingTouch(p0: SeekBar?) { + listener?.onStopTrackingTouch(p0) + } } - }) + ) } override fun setProgress(progress: Int) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/OutlineSpan.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/OutlineSpan.kt index b7ab3a82fbeb..036e3d799db1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/OutlineSpan.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/OutlineSpan.kt @@ -15,7 +15,6 @@ class OutlineSpan( @ColorInt private val strokeColor: Int, @Dimension private val strokeWidth: Float ) : ReplacementSpan() { - override fun getSize( paint: Paint, text: CharSequence, diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt index 2e1d51c2f5f6..ad72797acca4 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/RecyclerViewPagerAdapter.kt @@ -6,7 +6,6 @@ import com.nightlynexus.viewstatepageradapter.ViewStatePagerAdapter import java.util.Stack abstract class RecyclerViewPagerAdapter : ViewStatePagerAdapter() { - private val pool = Stack() var recycle = true @@ -17,17 +16,30 @@ abstract class RecyclerViewPagerAdapter : ViewStatePagerAdapter() { protected abstract fun createView(container: ViewGroup): View - protected abstract fun bindView(view: View, position: Int) + protected abstract fun bindView( + view: View, + position: Int + ) - protected open fun recycleView(view: View, position: Int) {} + protected open fun recycleView( + view: View, + position: Int + ) {} - override fun createView(container: ViewGroup, position: Int): View { + override fun createView( + container: ViewGroup, + position: Int + ): View { val view = if (pool.isNotEmpty()) pool.pop() else createView(container) bindView(view, position) return view } - override fun destroyView(container: ViewGroup, position: Int, view: View) { + override fun destroyView( + container: ViewGroup, + position: Int, + view: View + ) { recycleView(view, position) if (recycle) pool.push(view) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/RevealAnimationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/RevealAnimationView.kt index b8aa1764f5ed..81b22019a3bf 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/RevealAnimationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/RevealAnimationView.kt @@ -9,9 +9,10 @@ import android.view.ViewAnimationUtils import eu.kanade.tachiyomi.util.view.invisible import eu.kanade.tachiyomi.util.view.visible -class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class RevealAnimationView +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : View(context, attrs) { - /** * Hides the animation view with a animation * @@ -19,25 +20,36 @@ class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: Att * @param centerY y starting point * @param initialRadius size of radius of animation */ - fun hideRevealEffect(centerX: Int, centerY: Int, initialRadius: Int) { + fun hideRevealEffect( + centerX: Int, + centerY: Int, + initialRadius: Int + ) { // Make the view visible. this.visible() // Create the animation (the final radius is zero). - val anim = ViewAnimationUtils.createCircularReveal( - this, centerX, centerY, initialRadius.toFloat(), 0f - ) + val anim = + ViewAnimationUtils.createCircularReveal( + this, + centerX, + centerY, + initialRadius.toFloat(), + 0f + ) // Set duration of animation. anim.duration = 500 // make the view invisible when the animation is done - anim.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - super.onAnimationEnd(animation) - this@RevealAnimationView.invisible() + anim.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + super.onAnimationEnd(animation) + this@RevealAnimationView.invisible() + } } - }) + ) anim.start() } @@ -51,15 +63,24 @@ class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: Att * * @return sdk version lower then 21 */ - fun showRevealEffect(centerX: Int, centerY: Int, listener: Animator.AnimatorListener): Boolean { + fun showRevealEffect( + centerX: Int, + centerY: Int, + listener: Animator.AnimatorListener + ): Boolean { this.visible() val height = this.height // Create animation - val anim = ViewAnimationUtils.createCircularReveal( - this, centerX, centerY, 0f, height.toFloat() - ) + val anim = + ViewAnimationUtils.createCircularReveal( + this, + centerX, + centerY, + 0f, + height.toFloat() + ) // Set duration of animation anim.duration = 350 diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt index f65bb3118405..afd91f65c6ed 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt @@ -16,19 +16,20 @@ import androidx.core.view.ViewCompat import com.google.android.material.R import com.google.android.material.internal.ScrimInsetsFrameLayout import com.google.android.material.textfield.TextInputLayout -import eu.kanade.tachiyomi.R as TR import eu.kanade.tachiyomi.util.view.inflate import kotlin.math.min +import eu.kanade.tachiyomi.R as TR @Suppress("LeakingThis") @SuppressLint("PrivateResource", "RestrictedApi") -open class SimpleNavigationView @JvmOverloads constructor( +open class SimpleNavigationView +@JvmOverloads +constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ScrimInsetsFrameLayout(context, attrs, defStyleAttr) { - /** * Max width of the navigation view. */ @@ -41,21 +42,26 @@ open class SimpleNavigationView @JvmOverloads constructor( init { // Custom attributes - val a = TintTypedArray.obtainStyledAttributes( - context, attrs, - R.styleable.NavigationView, defStyleAttr, - R.style.Widget_Design_NavigationView - ) + val a = + TintTypedArray.obtainStyledAttributes( + context, + attrs, + R.styleable.NavigationView, + defStyleAttr, + R.style.Widget_Design_NavigationView + ) ViewCompat.setBackground( - this, a.getDrawable(R.styleable.NavigationView_android_background) + this, + a.getDrawable(R.styleable.NavigationView_android_background) ) if (a.hasValue(R.styleable.NavigationView_elevation)) { ViewCompat.setElevation( this, a.getDimensionPixelSize( - R.styleable.NavigationView_elevation, 0 + R.styleable.NavigationView_elevation, + 0 ).toFloat() ) } @@ -77,14 +83,20 @@ open class SimpleNavigationView @JvmOverloads constructor( * Overriden to measure the width of the navigation view. */ @SuppressLint("SwitchIntDef") - override fun onMeasure(widthSpec: Int, heightSpec: Int) { - val width = when (MeasureSpec.getMode(widthSpec)) { - MeasureSpec.AT_MOST -> MeasureSpec.makeMeasureSpec( - min(MeasureSpec.getSize(widthSpec), maxWidth), MeasureSpec.EXACTLY - ) - MeasureSpec.UNSPECIFIED -> MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY) - else -> widthSpec - } + override fun onMeasure( + widthSpec: Int, + heightSpec: Int + ) { + val width = + when (MeasureSpec.getMode(widthSpec)) { + MeasureSpec.AT_MOST -> + MeasureSpec.makeMeasureSpec( + min(MeasureSpec.getSize(widthSpec), maxWidth), + MeasureSpec.EXACTLY + ) + MeasureSpec.UNSPECIFIED -> MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY) + else -> widthSpec + } // Let super sort out the height super.onMeasure(width, heightSpec) } @@ -105,7 +117,6 @@ open class SimpleNavigationView @JvmOverloads constructor( */ class HeaderHolder(parent: ViewGroup) : Holder(parent.inflate(TR.layout.navigation_view_group)) { - val title: TextView = itemView.findViewById(TR.id.title) } @@ -123,7 +134,6 @@ open class SimpleNavigationView @JvmOverloads constructor( */ class RadioHolder(parent: ViewGroup, listener: OnClickListener?) : ClickableHolder(parent.inflate(TR.layout.navigation_view_radio), listener) { - val radio: RadioButton = itemView.findViewById(TR.id.nav_view_item) } @@ -132,7 +142,6 @@ open class SimpleNavigationView @JvmOverloads constructor( */ class CheckboxHolder(parent: ViewGroup, listener: OnClickListener?) : ClickableHolder(parent.inflate(TR.layout.navigation_view_checkbox), listener) { - val check: CheckBox = itemView.findViewById(TR.id.nav_view_item) } @@ -141,20 +150,17 @@ open class SimpleNavigationView @JvmOverloads constructor( */ class MultiStateHolder(parent: ViewGroup, listener: OnClickListener?) : ClickableHolder(parent.inflate(TR.layout.navigation_view_checkedtext), listener) { - val text: CheckedTextView = itemView.findViewById(TR.id.nav_view_item) } class SpinnerHolder(parent: ViewGroup, listener: OnClickListener? = null) : ClickableHolder(parent.inflate(TR.layout.navigation_view_spinner), listener) { - val text: TextView = itemView.findViewById(TR.id.nav_view_item_text) val spinner: Spinner = itemView.findViewById(TR.id.nav_view_item) } class EditTextHolder(parent: ViewGroup) : Holder(parent.inflate(TR.layout.navigation_view_text)) { - val wrapper: TextInputLayout = itemView.findViewById(TR.id.nav_view_item_wrapper) val edit: EditText = itemView.findViewById(TR.id.nav_view_item) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt index fdade75f1924..405432717cca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt @@ -3,7 +3,11 @@ package eu.kanade.tachiyomi.widget import android.widget.SeekBar open class SimpleSeekBarListener : SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { + override fun onProgressChanged( + seekBar: SeekBar, + value: Int, + fromUser: Boolean + ) { } override fun onStartTrackingTouch(seekBar: SeekBar) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt index 7228c6d94157..74a4c88a949f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt @@ -27,7 +27,6 @@ class StateImageViewTarget( private val errorDrawableRes: Int = R.drawable.ic_broken_image_grey_24dp, private val errorScaleType: ScaleType = ScaleType.CENTER ) : ImageViewTarget(view) { - private var resource: Drawable? = null private val imageScaleType = view.scaleType @@ -55,7 +54,10 @@ class StateImageViewTarget( super.onLoadCleared(placeholder) } - override fun onResourceReady(resource: Drawable, transition: Transition?) { + override fun onResourceReady( + resource: Drawable, + transition: Transition? + ) { progress?.gone() view.scaleType = imageScaleType super.onResourceReady(resource, transition) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ThemedSwipeRefreshLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ThemedSwipeRefreshLayout.kt index 45d6d01cad1a..d6dc9b0c380c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/ThemedSwipeRefreshLayout.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ThemedSwipeRefreshLayout.kt @@ -7,9 +7,10 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.getResourceColor -class ThemedSwipeRefreshLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class ThemedSwipeRefreshLayout +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : SwipeRefreshLayout(context, attrs) { - init { setColors() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt index d2c2a634e41f..1fd102ddfe36 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ViewPagerAdapter.kt @@ -4,25 +4,41 @@ import android.view.View import android.view.ViewGroup abstract class ViewPagerAdapter : androidx.viewpager.widget.PagerAdapter() { - - protected abstract fun createView(container: ViewGroup, position: Int): View - - protected open fun destroyView(container: ViewGroup, position: Int, view: View) { + protected abstract fun createView( + container: ViewGroup, + position: Int + ): View + + protected open fun destroyView( + container: ViewGroup, + position: Int, + view: View + ) { } - override fun instantiateItem(container: ViewGroup, position: Int): Any { + override fun instantiateItem( + container: ViewGroup, + position: Int + ): Any { val view = createView(container, position) container.addView(view) return view } - override fun destroyItem(container: ViewGroup, position: Int, obj: Any) { + override fun destroyItem( + container: ViewGroup, + position: Int, + obj: Any + ) { val view = obj as View destroyView(container, position, view) container.removeView(view) } - override fun isViewFromObject(view: View, obj: Any): Boolean { + override fun isViewFromObject( + view: View, + obj: Any + ): Boolean { return view === obj } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/BadgePreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/BadgePreference.kt index 73c3a07abd05..c15efaa531ce 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/BadgePreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/BadgePreference.kt @@ -9,9 +9,10 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.visible -class BadgePreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class BadgePreference +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : Preference(context, attrs) { - private var badgeNumber: Int = 0 init { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt index 63b52eb2a9e6..28e9d4e234c0 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt @@ -4,9 +4,10 @@ import android.content.Context import android.util.AttributeSet import androidx.preference.ListPreference -class IntListPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class IntListPreference +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : ListPreference(context, attrs) { - override fun persistString(value: String?): Boolean { return value != null && persistInt(value.toInt()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt index 62958889ced0..0a31b7beb77c 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt @@ -23,7 +23,6 @@ abstract class LoginDialogPreference( @StringRes private val usernameLabelRes: Int? = null, bundle: Bundle? = null ) : DialogController(bundle) { - var v: View? = null private set @@ -34,9 +33,10 @@ abstract class LoginDialogPreference( var requestSubscription: Subscription? = null override fun onCreateDialog(savedViewState: Bundle?): Dialog { - var dialog = MaterialDialog(activity!!) - .customView(R.layout.pref_account_login) - .negativeButton(android.R.string.cancel) + var dialog = + MaterialDialog(activity!!) + .customView(R.layout.pref_account_login) + .negativeButton(android.R.string.cancel) binding = PrefAccountLoginBinding.bind(dialog.getCustomView()) @@ -50,19 +50,23 @@ abstract class LoginDialogPreference( } fun onViewCreated(view: View) { - v = view.apply { - if (usernameLabelRes != null) { - binding.usernameLabel.hint = context.getString(usernameLabelRes) - } + v = + view.apply { + if (usernameLabelRes != null) { + binding.usernameLabel.hint = context.getString(usernameLabelRes) + } - binding.login.setMode(ActionProcessButton.Mode.ENDLESS) - binding.login.setOnClickListener { checkLogin() } + binding.login.setMode(ActionProcessButton.Mode.ENDLESS) + binding.login.setOnClickListener { checkLogin() } - setCredentialsOnView(this) - } + setCredentialsOnView(this) + } } - override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { + override fun onChangeStarted( + handler: ControllerChangeHandler, + type: ControllerChangeType + ) { super.onChangeStarted(handler, type) if (!type.isEnter) { onDialogClosed() diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginPreference.kt index c3008d8f3d82..f1ab689cd92f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginPreference.kt @@ -7,9 +7,10 @@ import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import eu.kanade.tachiyomi.R -class LoginPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class LoginPreference +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : Preference(context, attrs) { - init { widgetLayoutResource = R.layout.pref_widget_imageview } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt index 15d64392a65a..25422833ed82 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt @@ -13,7 +13,9 @@ import androidx.preference.PreferenceViewHolder import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.getResourceColor -class SwitchPreferenceCategory @JvmOverloads constructor( +class SwitchPreferenceCategory +@JvmOverloads +constructor( context: Context, attrs: AttributeSet? = null ) : @@ -23,7 +25,6 @@ class SwitchPreferenceCategory @JvmOverloads constructor( R.attr.switchPreferenceCompatStyle ), CompoundButton.OnCheckedChangeListener { - private var mChecked = false private var mCheckedSet = false @@ -57,7 +58,10 @@ class SwitchPreferenceCategory @JvmOverloads constructor( } } - override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { + override fun onCheckedChanged( + buttonView: CompoundButton, + isChecked: Boolean + ) { if (!callChangeListener(isChecked)) { buttonView.isChecked = !isChecked } else { @@ -110,11 +114,17 @@ class SwitchPreferenceCategory @JvmOverloads constructor( return false } - override fun onGetDefaultValue(a: TypedArray, index: Int): Any { + override fun onGetDefaultValue( + a: TypedArray, + index: Int + ): Any { return a.getBoolean(index, false) } - override fun onSetInitialValue(restoreValue: Boolean, defaultValue: Any?) { + override fun onSetInitialValue( + restoreValue: Boolean, + defaultValue: Any? + ) { setChecked( if (restoreValue) { getPersistedBoolean(mChecked) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt index 2878498411bf..2fe3662bf0e1 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt @@ -18,12 +18,14 @@ class TrackLoginDialog( @StringRes usernameLabelRes: Int? = null, bundle: Bundle? = null ) : LoginDialogPreference(titleRes, titleFormatArgs, usernameLabelRes, bundle) { - private val service = Injekt.get().getService(args.getInt("key"))!! constructor(service: TrackService) : this(service, null) - constructor(service: TrackService, @StringRes usernameLabelRes: Int?) : + constructor( + service: TrackService, + @StringRes usernameLabelRes: Int? + ) : this(R.string.login_title, service.name, usernameLabelRes, Bundle().apply { putInt("key", service.id) }) override fun setCredentialsOnView(view: View) { @@ -43,20 +45,21 @@ class TrackLoginDialog( val user = binding.username.text.toString() val pass = binding.password.text.toString() - requestSubscription = service.login(user, pass) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - dialog?.dismiss() - context.toast(R.string.login_success) - }, - { error -> - binding!!.login.progress = -1 - binding!!.login.setText(R.string.unknown_error) - error.message?.let { context.toast(it) } - } - ) + requestSubscription = + service.login(user, pass) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + dialog?.dismiss() + context.toast(R.string.login_success) + }, + { error -> + binding!!.login.progress = -1 + binding!!.login.setText(R.string.unknown_error) + error.message?.let { context.toast(it) } + } + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLogoutDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLogoutDialog.kt index 740a3681b35b..ec1db00947c4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLogoutDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLogoutDialog.kt @@ -12,7 +12,6 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class TrackLogoutDialog(bundle: Bundle? = null) : DialogController(bundle) { - private val service = Injekt.get().getService(args.getInt("key"))!! constructor(service: TrackService) : this(Bundle().apply { putInt("key", service.id) }) diff --git a/app/src/main/java/exh/EHSourceHelpers.kt b/app/src/main/java/exh/EHSourceHelpers.kt index 6a664460bb77..ebb055d3c556 100755 --- a/app/src/main/java/exh/EHSourceHelpers.kt +++ b/app/src/main/java/exh/EHSourceHelpers.kt @@ -24,20 +24,22 @@ const val MERGED_SOURCE_ID = LEWD_SOURCE_SERIES + 69 const val EIGHTMUSES_SOURCE_ID = 1802675169972965535 const val HITOMI_SOURCE_ID = 690123758188633713 -private val DELEGATED_LEWD_SOURCES = listOf( - HentaiCafe::class, - Pururin::class, - Tsumino::class -) - -val LIBRARY_UPDATE_EXCLUDED_SOURCES = listOf( - EH_SOURCE_ID, - EXH_SOURCE_ID, - NHENTAI_SOURCE_ID, - HENTAI_CAFE_SOURCE_ID, - TSUMINO_SOURCE_ID, - PURURIN_SOURCE_ID -) +private val DELEGATED_LEWD_SOURCES = + listOf( + HentaiCafe::class, + Pururin::class, + Tsumino::class + ) + +val LIBRARY_UPDATE_EXCLUDED_SOURCES = + listOf( + EH_SOURCE_ID, + EXH_SOURCE_ID, + NHENTAI_SOURCE_ID, + HENTAI_CAFE_SOURCE_ID, + TSUMINO_SOURCE_ID, + PURURIN_SOURCE_ID + ) private inline fun delegatedSourceId(): Long { return SourceManager.DELEGATED_SOURCES.entries.find { @@ -46,14 +48,17 @@ private inline fun delegatedSourceId(): Long { } // Used to speed up isLewdSource -val lewdDelegatedSourceIds = SourceManager.DELEGATED_SOURCES.filter { - it.value.newSourceClass in DELEGATED_LEWD_SOURCES -}.map { it.value.sourceId }.sorted() +val lewdDelegatedSourceIds = + SourceManager.DELEGATED_SOURCES.filter { + it.value.newSourceClass in DELEGATED_LEWD_SOURCES + }.map { it.value.sourceId }.sorted() // This method MUST be fast! -fun isLewdSource(source: Long) = source in 6900..6999 || - lewdDelegatedSourceIds.binarySearch(source) >= 0 +fun isLewdSource(source: Long) = + source in 6900..6999 || + lewdDelegatedSourceIds.binarySearch(source) >= 0 fun Source.isEhBasedSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID -fun Source.isNamespaceSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID || id == NHENTAI_SOURCE_ID || id == PURURIN_SOURCE_ID || id == TSUMINO_SOURCE_ID +fun Source.isNamespaceSource() = + id == EH_SOURCE_ID || id == EXH_SOURCE_ID || id == NHENTAI_SOURCE_ID || id == PURURIN_SOURCE_ID || id == TSUMINO_SOURCE_ID diff --git a/app/src/main/java/exh/EXHMigrations.kt b/app/src/main/java/exh/EXHMigrations.kt index a99a30e70640..69449839653b 100755 --- a/app/src/main/java/exh/EXHMigrations.kt +++ b/app/src/main/java/exh/EXHMigrations.kt @@ -21,12 +21,12 @@ import eu.kanade.tachiyomi.data.updater.UpdaterJob import eu.kanade.tachiyomi.extension.ExtensionUpdateJob import eu.kanade.tachiyomi.util.system.jobScheduler import exh.source.BlacklistedSources -import java.io.File -import java.net.URI -import java.net.URISyntaxException import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.io.File +import java.net.URI +import java.net.URISyntaxException object EXHMigrations { private val db: DatabaseHelper by injectLazy() @@ -61,16 +61,17 @@ object EXHMigrations { ) // Migrate nhentai URLs - val nhentaiManga = db.db.get() - .listOfObjects(Manga::class.java) - .withQuery( - Query.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_SOURCE} = $NHENTAI_SOURCE_ID") - .build() - ) - .prepare() - .executeAsBlocking() + val nhentaiManga = + db.db.get() + .listOfObjects(Manga::class.java) + .withQuery( + Query.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_SOURCE} = $NHENTAI_SOURCE_ID") + .build() + ) + .prepare() + .executeAsBlocking() nhentaiManga.forEach { it.url = getUrlWithoutDomain(it.url) @@ -130,16 +131,17 @@ object EXHMigrations { if (oldVersion < 8409) { db.inTransaction { // Migrate tsumino URLs - val tsuminoManga = db.db.get() - .listOfObjects(Manga::class.java) - .withQuery( - Query.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_SOURCE} = $TSUMINO_SOURCE_ID") - .build() - ) - .prepare() - .executeAsBlocking() + val tsuminoManga = + db.db.get() + .listOfObjects(Manga::class.java) + .withQuery( + Query.builder() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_SOURCE} = $TSUMINO_SOURCE_ID") + .build() + ) + .prepare() + .executeAsBlocking() tsuminoManga.forEach { it.url = "/entry/" + it.url.split("/").last() } @@ -257,7 +259,10 @@ object EXHMigrations { return manga } - private fun backupDatabase(context: Context, oldMigrationVersion: Int) { + private fun backupDatabase( + context: Context, + oldMigrationVersion: Int + ) { val backupLocation = File(File(context.filesDir, "exh_db_bck"), "$oldMigrationVersion.bck.db") if (backupLocation.exists()) return // Do not backup same version twice diff --git a/app/src/main/java/exh/GalleryAdder.kt b/app/src/main/java/exh/GalleryAdder.kt index 5846ca7d71ff..da77317e5844 100755 --- a/app/src/main/java/exh/GalleryAdder.kt +++ b/app/src/main/java/exh/GalleryAdder.kt @@ -12,11 +12,10 @@ import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.lang.runAsObservable -import java.util.Date import uy.kohesive.injekt.injectLazy +import java.util.Date class GalleryAdder { - private val db: DatabaseHelper by injectLazy() private val sourceManager: SourceManager by injectLazy() @@ -32,58 +31,65 @@ class GalleryAdder { val uri = Uri.parse(url) // Find matching source - val source = if (forceSource != null) { - try { - if (forceSource.matchesUri(uri)) forceSource - else return GalleryAddEvent.Fail.UnknownType(url) - } catch (e: Exception) { - XLog.e("Source URI match check error!", e) - return GalleryAddEvent.Fail.UnknownType(url) - } - } else { - sourceManager.getVisibleCatalogueSources() - .filterIsInstance() - .find { - try { - it.matchesUri(uri) - } catch (e: Exception) { - XLog.e("Source URI match check error!", e) - false - } - } ?: sourceManager.getDelegatedCatalogueSources() - .filterIsInstance() - .find { - try { - it.matchesUri(uri) - } catch (e: Exception) { - XLog.e("Source URI match check error!", e) - false + val source = + if (forceSource != null) { + try { + if (forceSource.matchesUri(uri)) { + forceSource + } else { + return GalleryAddEvent.Fail.UnknownType(url) } - } ?: return GalleryAddEvent.Fail.UnknownType(url) - } + } catch (e: Exception) { + XLog.e("Source URI match check error!", e) + return GalleryAddEvent.Fail.UnknownType(url) + } + } else { + sourceManager.getVisibleCatalogueSources() + .filterIsInstance() + .find { + try { + it.matchesUri(uri) + } catch (e: Exception) { + XLog.e("Source URI match check error!", e) + false + } + } ?: sourceManager.getDelegatedCatalogueSources() + .filterIsInstance() + .find { + try { + it.matchesUri(uri) + } catch (e: Exception) { + XLog.e("Source URI match check error!", e) + false + } + } ?: return GalleryAddEvent.Fail.UnknownType(url) + } // Map URL to manga URL - val realUrl = try { - source.mapUrlToMangaUrl(uri) - } catch (e: Exception) { - XLog.e("Source URI map-to-manga error!", e) - null - } ?: return GalleryAddEvent.Fail.UnknownType(url) + val realUrl = + try { + source.mapUrlToMangaUrl(uri) + } catch (e: Exception) { + XLog.e("Source URI map-to-manga error!", e) + null + } ?: return GalleryAddEvent.Fail.UnknownType(url) // Clean URL - val cleanedUrl = try { - source.cleanMangaUrl(realUrl) - } catch (e: Exception) { - XLog.e("Source URI clean error!", e) - null - } ?: return GalleryAddEvent.Fail.UnknownType(url) + val cleanedUrl = + try { + source.cleanMangaUrl(realUrl) + } catch (e: Exception) { + XLog.e("Source URI clean error!", e) + null + } ?: return GalleryAddEvent.Fail.UnknownType(url) // Use manga in DB if possible, otherwise, make a new manga - val manga = db.getManga(cleanedUrl, source.id).executeAsBlocking() - ?: Manga.create(source.id).apply { - this.url = cleanedUrl - title = realUrl - } + val manga = + db.getManga(cleanedUrl, source.id).executeAsBlocking() + ?: Manga.create(source.id).apply { + this.url = cleanedUrl + title = realUrl + } // Insert created manga if not in DB before fetching details // This allows us to keep the metadata when fetching details @@ -107,11 +113,12 @@ class GalleryAdder { // Fetch and copy chapters try { - val chapterListObs = if (source is EHentai) { - source.fetchChapterList(manga, throttleFunc) - } else { - runAsObservable({ source.getChapterList(manga.toMangaInfo()).map { it.toSChapter() } }) - } + val chapterListObs = + if (source is EHentai) { + source.fetchChapterList(manga, throttleFunc) + } else { + runAsObservable({ source.getChapterList(manga.toMangaInfo()).map { it.toSChapter() } }) + } chapterListObs.map { syncChaptersWithSource(db, it, manga, source) }.toBlocking().first() diff --git a/app/src/main/java/exh/StringBuilderExtensions.kt b/app/src/main/java/exh/StringBuilderExtensions.kt index 1dffedbb01d0..b991e1254b31 100755 --- a/app/src/main/java/exh/StringBuilderExtensions.kt +++ b/app/src/main/java/exh/StringBuilderExtensions.kt @@ -1,3 +1,5 @@ package exh -operator fun StringBuilder.plusAssign(other: String) { append(other) } +operator fun StringBuilder.plusAssign(other: String) { + append(other) +} diff --git a/app/src/main/java/exh/debug/DebugFunctions.kt b/app/src/main/java/exh/debug/DebugFunctions.kt index d503ec239515..45117c51104d 100644 --- a/app/src/main/java/exh/debug/DebugFunctions.kt +++ b/app/src/main/java/exh/debug/DebugFunctions.kt @@ -45,12 +45,13 @@ object DebugFunctions { runBlocking { val metadataManga = db.getFavoriteMangaWithMetadata().await() - val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga -> - if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) { - return@mapNotNull null - } - manga - }.toList() + val allManga = + metadataManga.asFlow().cancellable().mapNotNull { manga -> + if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) { + return@mapNotNull null + } + manga + }.toList() for (manga in allManga) { val meta = db.getFlatMetadataForManga(manga.id!!).await()?.raise() @@ -62,6 +63,7 @@ object DebugFunctions { } } } + private val throttleManager = EHentaiThrottleManager() fun ResetEHGalleriesForUpdater() { @@ -69,12 +71,13 @@ object DebugFunctions { runBlocking { val metadataManga = db.getFavoriteMangaWithMetadata().await() - val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga -> - if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) { - return@mapNotNull null - } - manga - }.toList() + val allManga = + metadataManga.asFlow().cancellable().mapNotNull { manga -> + if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) { + return@mapNotNull null + } + manga + }.toList() val eh = sourceManager.getOrStub(EH_SOURCE_ID) val ex = sourceManager.getOrStub(EXH_SOURCE_ID) @@ -102,12 +105,13 @@ object DebugFunctions { runBlocking { val metadataManga = db.getFavoriteMangaWithMetadata().await() - val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga -> - if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) { - return@mapNotNull null - } - manga - }.toList() + val allManga = + metadataManga.asFlow().cancellable().mapNotNull { manga -> + if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) { + return@mapNotNull null + } + manga + }.toList() for (manga in allManga) { val meta = db.getFlatMetadataForManga(manga.id!!).await()?.raise() @@ -125,12 +129,13 @@ object DebugFunctions { runBlocking { val metadataManga = db.getFavoriteMangaWithMetadata().await() - val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga -> - if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) { - return@mapNotNull null - } - manga - }.toList() + val allManga = + metadataManga.asFlow().cancellable().mapNotNull { manga -> + if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) { + return@mapNotNull null + } + manga + }.toList() for (manga in allManga) { val meta = db.getFlatMetadataForManga(manga.id!!).await()?.raise() @@ -167,26 +172,32 @@ object DebugFunctions { fun countMetadataInDatabase() = db.getSearchMetadata().executeAsBlocking().size - fun countMangaInLibraryWithMissingMetadata() = db.getMangas().executeAsBlocking().count { - it.favorite && db.getSearchMetadataForManga(it.id!!).executeAsBlocking() == null - } + fun countMangaInLibraryWithMissingMetadata() = + db.getMangas().executeAsBlocking().count { + it.favorite && db.getSearchMetadataForManga(it.id!!).executeAsBlocking() == null + } fun clearSavedSearches() = prefs.eh_savedSearches().set(emptySet()) - fun listAllSources() = sourceManager.getCatalogueSources().joinToString("\n") { - "${it.id}: ${it.name} (${it.lang.uppercase()})" - } + fun listAllSources() = + sourceManager.getCatalogueSources().joinToString("\n") { + "${it.id}: ${it.name} (${it.lang.uppercase()})" + } - fun listFilteredSources() = sourceManager.getVisibleCatalogueSources().joinToString("\n") { - "${it.id}: ${it.name} (${it.lang.uppercase()})" - } + fun listFilteredSources() = + sourceManager.getVisibleCatalogueSources().joinToString("\n") { + "${it.id}: ${it.name} (${it.lang.uppercase()})" + } - fun listAllHttpSources() = sourceManager.getOnlineSources().joinToString("\n") { - "${it.id}: ${it.name} (${it.lang.uppercase()})" - } - fun listFilteredHttpSources() = sourceManager.getVisibleOnlineSources().joinToString("\n") { - "${it.id}: ${it.name} (${it.lang.uppercase()})" - } + fun listAllHttpSources() = + sourceManager.getOnlineSources().joinToString("\n") { + "${it.id}: ${it.name} (${it.lang.uppercase()})" + } + + fun listFilteredHttpSources() = + sourceManager.getVisibleOnlineSources().joinToString("\n") { + "${it.id}: ${it.name} (${it.lang.uppercase()})" + } fun convertAllEhentaiGalleriesToExhentai() = convertSources(EH_SOURCE_ID, EXH_SOURCE_ID) @@ -200,20 +211,24 @@ object DebugFunctions { EHentaiUpdateWorker.scheduleBackground(app) } - fun listScheduledJobs() = app.jobScheduler.allPendingJobs.map { j -> - """ - { - info: ${j.id}, - isPeriod: ${j.isPeriodic}, - isPersisted: ${j.isPersisted}, - intervalMillis: ${j.intervalMillis}, - } - """.trimIndent() - }.joinToString(",\n") + fun listScheduledJobs() = + app.jobScheduler.allPendingJobs.map { j -> + """ + { + info: ${j.id}, + isPeriod: ${j.isPeriodic}, + isPersisted: ${j.isPersisted}, + intervalMillis: ${j.intervalMillis}, + } + """.trimIndent() + }.joinToString(",\n") fun cancelAllScheduledJobs() = app.jobScheduler.cancelAll() - private fun convertSources(from: Long, to: Long) { + private fun convertSources( + from: Long, + to: Long + ) { db.lowLevel().executeSQL( RawQuery.builder() .query( diff --git a/app/src/main/java/exh/debug/DebugToggles.kt b/app/src/main/java/exh/debug/DebugToggles.kt index 842419fa7784..519bd73701a7 100644 --- a/app/src/main/java/exh/debug/DebugToggles.kt +++ b/app/src/main/java/exh/debug/DebugToggles.kt @@ -6,14 +6,19 @@ import uy.kohesive.injekt.injectLazy enum class DebugToggles(val default: Boolean) { // Redirect to master version of gallery when encountering a gallery that has a parent/child that is already in the library ENABLE_EXH_ROOT_REDIRECT(true), + // Enable debug overlay (only available in debug builds) ENABLE_DEBUG_OVERLAY(true), + // Convert non-root galleries into root galleries when loading them PULL_TO_ROOT_WHEN_LOADING_EXH_MANGA_DETAILS(true), + // Do not update the same gallery too often RESTRICT_EXH_GALLERY_UPDATE_CHECK_FREQUENCY(true), + // Pretend that all galleries only have a single version - INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS(false); + INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS(false) + ; val prefKey = "eh_debug_toggle_${name.lowercase()}" diff --git a/app/src/main/java/exh/debug/SettingsDebugController.kt b/app/src/main/java/exh/debug/SettingsDebugController.kt index 9ae71f57c79a..25d6aa2a45d7 100644 --- a/app/src/main/java/exh/debug/SettingsDebugController.kt +++ b/app/src/main/java/exh/debug/SettingsDebugController.kt @@ -20,58 +20,61 @@ import kotlin.reflect.full.declaredFunctions class SettingsDebugController : SettingsController() { @SuppressLint("SetTextI18n") - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { - title = "DEBUG MENU" + override fun setupPreferenceScreen(screen: PreferenceScreen) = + with(screen) { + title = "DEBUG MENU" - preferenceCategory { - title = "Functions" + preferenceCategory { + title = "Functions" - DebugFunctions::class.declaredFunctions.filter { - it.visibility == KVisibility.PUBLIC - }.forEach { - preference { - title = it.name.replace(Regex("(.)(\\p{Upper})"), "$1 $2").lowercase() - .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } - isPersistent = false + DebugFunctions::class.declaredFunctions.filter { + it.visibility == KVisibility.PUBLIC + }.forEach { + preference { + title = + it.name.replace(Regex("(.)(\\p{Upper})"), "$1 $2").lowercase() + .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } + isPersistent = false - onClick { - val view = TextView(context) - view.setHorizontallyScrolling(true) - view.setTextIsSelectable(true) + onClick { + val view = TextView(context) + view.setHorizontallyScrolling(true) + view.setTextIsSelectable(true) - val hView = HorizontalScrollView(context) - hView.addView(view) + val hView = HorizontalScrollView(context) + hView.addView(view) - try { - val result = it.call(DebugFunctions) - view.text = "Function returned result:\n\n$result" - MaterialDialog(context) - .customView(view = hView, scrollable = true) - } catch (t: Throwable) { - view.text = "Function threw exception:\n\n${Log.getStackTraceString(t)}" - MaterialDialog(context) - .customView(view = hView, scrollable = true) - }.show() + try { + val result = it.call(DebugFunctions) + view.text = "Function returned result:\n\n$result" + MaterialDialog(context) + .customView(view = hView, scrollable = true) + } catch (t: Throwable) { + view.text = "Function threw exception:\n\n${Log.getStackTraceString(t)}" + MaterialDialog(context) + .customView(view = hView, scrollable = true) + }.show() + } } } } - } - preferenceCategory { - title = "Toggles" + preferenceCategory { + title = "Toggles" - DebugToggles.values().forEach { - switchPreference { - title = it.name.replace('_', ' ').lowercase() - .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } - key = it.prefKey - defaultValue = it.default - summaryOn = if (it.default) "" else MODIFIED_TEXT - summaryOff = if (it.default) MODIFIED_TEXT else "" + DebugToggles.values().forEach { + switchPreference { + title = + it.name.replace('_', ' ').lowercase() + .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } + key = it.prefKey + defaultValue = it.default + summaryOn = if (it.default) "" else MODIFIED_TEXT + summaryOff = if (it.default) MODIFIED_TEXT else "" + } } } } - } override fun onActivityStopped(activity: Activity) { super.onActivityStopped(activity) diff --git a/app/src/main/java/exh/eh/EHentaiUpdateHelper.kt b/app/src/main/java/exh/eh/EHentaiUpdateHelper.kt index e61f5ebd5f55..05f3388308c0 100755 --- a/app/src/main/java/exh/eh/EHentaiUpdateHelper.kt +++ b/app/src/main/java/exh/eh/EHentaiUpdateHelper.kt @@ -8,11 +8,11 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.MangaCategory import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.base.getFlatMetadataForManga -import java.io.File -import java.util.Date import rx.Observable import rx.Single import uy.kohesive.injekt.injectLazy +import java.io.File +import java.util.Date data class ChapterChain(val manga: Manga, val chapters: List) @@ -29,35 +29,40 @@ class EHentaiUpdateHelper(context: Context) { * * @return Triple */ - fun findAcceptedRootAndDiscardOthers(sourceId: Long, chapters: List): Single, Boolean>> { + fun findAcceptedRootAndDiscardOthers( + sourceId: Long, + chapters: List + ): Single, Boolean>> { // Find other chains - val chainsObservable = Observable.merge( - chapters.map { chapter -> - db.getChapters(chapter.url).asRxSingle().toObservable() - } - ).toList().map { allChapters -> - allChapters.flatMap { innerChapters -> innerChapters.map { it.manga_id!! } }.distinct() - }.flatMap { mangaIds -> + val chainsObservable = Observable.merge( - mangaIds.map { mangaId -> - Single.zip( - db.getManga(mangaId).asRxSingle(), - db.getChaptersByMangaId(mangaId).asRxSingle() - ) { manga, chapters -> - ChapterChain(manga, chapters) - }.toObservable().filter { - it.manga.source == sourceId - } + chapters.map { chapter -> + db.getChapters(chapter.url).asRxSingle().toObservable() } - ) - }.toList() + ).toList().map { allChapters -> + allChapters.flatMap { innerChapters -> innerChapters.map { it.manga_id!! } }.distinct() + }.flatMap { mangaIds -> + Observable.merge( + mangaIds.map { mangaId -> + Single.zip( + db.getManga(mangaId).asRxSingle(), + db.getChaptersByMangaId(mangaId).asRxSingle() + ) { manga, chapters -> + ChapterChain(manga, chapters) + }.toObservable().filter { + it.manga.source == sourceId + } + } + ) + }.toList() // Accept oldest chain - val chainsWithAccepted = chainsObservable.map { chains -> - val acceptedChain = chains.minByOrNull { it.manga.id!! }!! + val chainsWithAccepted = + chainsObservable.map { chains -> + val acceptedChain = chains.minByOrNull { it.manga.id!! }!! - acceptedChain to chains - } + acceptedChain to chains + } return chainsWithAccepted.map { (accepted, chains) -> val toDiscard = chains.filter { it.manga.favorite && it.manga.id != accepted.manga.id } @@ -68,67 +73,71 @@ class EHentaiUpdateHelper(context: Context) { var new = false // Copy chain chapters to curChapters - val newChapters = toDiscard - .flatMap { chain -> - val meta by lazy { - db.getFlatMetadataForManga(chain.manga.id!!) - .executeAsBlocking() - ?.raise() - } + val newChapters = + toDiscard + .flatMap { chain -> + val meta by lazy { + db.getFlatMetadataForManga(chain.manga.id!!) + .executeAsBlocking() + ?.raise() + } - chain.chapters.map { chapter -> - // Convert old style chapters to new style chapters if possible - if (chapter.date_upload <= 0 && - meta?.datePosted != null && - meta?.title != null - ) { - chapter.name = meta!!.title!! - chapter.date_upload = meta!!.datePosted!! + chain.chapters.map { chapter -> + // Convert old style chapters to new style chapters if possible + if (chapter.date_upload <= 0 && + meta?.datePosted != null && + meta?.title != null + ) { + chapter.name = meta!!.title!! + chapter.date_upload = meta!!.datePosted!! + } + chapter } - chapter } - } - .fold(accepted.chapters) { curChapters, chapter -> - val existing = curChapters.find { it.url == chapter.url } + .fold(accepted.chapters) { curChapters, chapter -> + val existing = curChapters.find { it.url == chapter.url } - val newLastPageRead = chainsAsChapters.maxByOrNull { it.last_page_read }?.last_page_read + val newLastPageRead = chainsAsChapters.maxByOrNull { it.last_page_read }?.last_page_read - if (existing != null) { - existing.read = existing.read || chapter.read - existing.last_page_read = existing.last_page_read.coerceAtLeast(chapter.last_page_read) - if (newLastPageRead != null && existing.last_page_read <= 0) { - existing.last_page_read = newLastPageRead - } - existing.bookmark = existing.bookmark || chapter.bookmark - curChapters - } else if (chapter.date_upload > 0) { // Ignore chapters using the old system - new = true - curChapters + ChapterImpl().apply { - manga_id = accepted.manga.id - url = chapter.url - name = chapter.name - read = chapter.read - bookmark = chapter.bookmark - - last_page_read = chapter.last_page_read - if (newLastPageRead != null && last_page_read <= 0) { - last_page_read = newLastPageRead + if (existing != null) { + existing.read = existing.read || chapter.read + existing.last_page_read = existing.last_page_read.coerceAtLeast(chapter.last_page_read) + if (newLastPageRead != null && existing.last_page_read <= 0) { + existing.last_page_read = newLastPageRead } - - date_fetch = chapter.date_fetch - date_upload = chapter.date_upload + existing.bookmark = existing.bookmark || chapter.bookmark + curChapters + } else if (chapter.date_upload > 0) { // Ignore chapters using the old system + new = true + curChapters + + ChapterImpl().apply { + manga_id = accepted.manga.id + url = chapter.url + name = chapter.name + read = chapter.read + bookmark = chapter.bookmark + + last_page_read = chapter.last_page_read + if (newLastPageRead != null && last_page_read <= 0) { + last_page_read = newLastPageRead + } + + date_fetch = chapter.date_fetch + date_upload = chapter.date_upload + } + } else { + curChapters + } + } + .filter { it.date_upload > 0 } // Ignore chapters using the old system (filter after to prevent dupes from insert) + .sortedBy { it.date_upload } + .apply { + mapIndexed { index, chapter -> + chapter.name = "v${index + 1}: " + chapter.name.substringAfter(" ") + chapter.chapter_number = index + 1f + chapter.source_order = lastIndex - index } - } else curChapters - } - .filter { it.date_upload > 0 } // Ignore chapters using the old system (filter after to prevent dupes from insert) - .sortedBy { it.date_upload } - .apply { - mapIndexed { index, chapter -> - chapter.name = "v${index + 1}: " + chapter.name.substringAfter(" ") - chapter.chapter_number = index + 1f - chapter.source_order = lastIndex - index } - } toDiscard.forEach { it.manga.favorite = false @@ -146,16 +155,19 @@ class EHentaiUpdateHelper(context: Context) { // Insert new chapters for accepted manga db.insertChapters(newAccepted.chapters).executeAsBlocking() // Copy categories from all chains to accepted manga - val newCategories = rootsToMutate.flatMap { - db.getCategoriesForManga(it.manga).executeAsBlocking() - }.distinctBy { it.id }.map { - MangaCategory.create(newAccepted.manga, it) - } + val newCategories = + rootsToMutate.flatMap { + db.getCategoriesForManga(it.manga).executeAsBlocking() + }.distinctBy { it.id }.map { + MangaCategory.create(newAccepted.manga, it) + } db.setMangaCategories(newCategories, rootsToMutate.map { it.manga }) } Triple(newAccepted, toDiscard, new) - } else Triple(accepted, emptyList(), false) + } else { + Triple(accepted, emptyList(), false) + } }.toSingle() } } diff --git a/app/src/main/java/exh/eh/EHentaiUpdateWorker.kt b/app/src/main/java/exh/eh/EHentaiUpdateWorker.kt index b939b1b04c5f..8acbaa97dc10 100755 --- a/app/src/main/java/exh/eh/EHentaiUpdateWorker.kt +++ b/app/src/main/java/exh/eh/EHentaiUpdateWorker.kt @@ -29,8 +29,6 @@ import exh.metadata.metadata.base.insertFlatMetadata import exh.util.await import exh.util.cancellable import exh.util.jobScheduler -import java.util.ArrayList -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -44,6 +42,8 @@ import rx.schedulers.Schedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.util.ArrayList +import kotlin.coroutines.CoroutineContext class EHentaiUpdateWorker : JobService(), CoroutineScope { override val coroutineContext: CoroutineContext @@ -136,27 +136,30 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { logger.d("Filtering manga and raising metadata...") val curTime = System.currentTimeMillis() - val allMeta = metadataManga.asFlow().cancellable().mapNotNull { manga -> - if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) { - return@mapNotNull null - } + val allMeta = + metadataManga.asFlow().cancellable().mapNotNull { manga -> + if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) { + return@mapNotNull null + } - val meta = db.getFlatMetadataForManga(manga.id!!).asRxSingle().await() - ?: return@mapNotNull null + val meta = + db.getFlatMetadataForManga(manga.id!!).asRxSingle().await() + ?: return@mapNotNull null - val raisedMeta = meta.raise() + val raisedMeta = meta.raise() - // Don't update galleries too frequently - if (raisedMeta.aged || (curTime - raisedMeta.lastUpdateCheck < MIN_BACKGROUND_UPDATE_FREQ && DebugToggles.RESTRICT_EXH_GALLERY_UPDATE_CHECK_FREQUENCY.enabled)) { - return@mapNotNull null - } + // Don't update galleries too frequently + if (raisedMeta.aged || (curTime - raisedMeta.lastUpdateCheck < MIN_BACKGROUND_UPDATE_FREQ && DebugToggles.RESTRICT_EXH_GALLERY_UPDATE_CHECK_FREQUENCY.enabled)) { + return@mapNotNull null + } - val chapter = db.getChaptersByMangaId(manga.id!!).asRxSingle().await().minByOrNull { - it.date_upload - } + val chapter = + db.getChaptersByMangaId(manga.id!!).asRxSingle().await().minByOrNull { + it.date_upload + } - UpdateEntry(manga, raisedMeta, chapter) - }.toList().sortedBy { it.meta.lastUpdateCheck } + UpdateEntry(manga, raisedMeta, chapter) + }.toList().sortedBy { it.meta.lastUpdateCheck } logger.d("Found %s manga to update, starting updates!", allMeta.size) val mangaMetaToUpdateThisIter = allMeta.take(UPDATES_PER_ITERATION) @@ -191,25 +194,26 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { continue } - val (new, chapters) = try { - updateEntryAndGetChapters(manga) - } catch (e: GalleryNotUpdatedException) { - if (e.network) { - failuresThisIteration++ - - logger.e("> Network error while updating gallery!", e) - logger.e( - "> (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)", - manga.id, - meta.gId, - meta.gToken, - failuresThisIteration - ) + val (new, chapters) = + try { + updateEntryAndGetChapters(manga) + } catch (e: GalleryNotUpdatedException) { + if (e.network) { + failuresThisIteration++ + + logger.e("> Network error while updating gallery!", e) + logger.e( + "> (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)", + manga.id, + meta.gId, + meta.gToken, + failuresThisIteration + ) + } + + continue } - continue - } - if (chapters.isEmpty()) { logger.e( "No chapters found for gallery (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)!", @@ -255,8 +259,9 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { // New, current suspend fun updateEntryAndGetChapters(manga: Manga): Pair, List> { - val source = sourceManager.get(manga.source) as? EHentai - ?: throw GalleryNotUpdatedException(false, IllegalStateException("Missing EH-based source (${manga.source})!")) + val source = + sourceManager.get(manga.source) as? EHentai + ?: throw GalleryNotUpdatedException(false, IllegalStateException("Missing EH-based source (${manga.source})!")) try { val updatedManga = source.fetchMangaDetails(manga).toSingle().await(Schedulers.io()) @@ -297,8 +302,11 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { private fun Context.baseBackgroundJobInfo(isTest: Boolean): JobInfo.Builder { return JobInfo.Builder( - if (isTest) JOB_ID_UPDATE_BACKGROUND_TEST - else JOB_ID_UPDATE_BACKGROUND, + if (isTest) { + JOB_ID_UPDATE_BACKGROUND_TEST + } else { + JOB_ID_UPDATE_BACKGROUND + }, componentName() ) } @@ -312,8 +320,11 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { .setPeriodic(period) .setPersisted(true) .setRequiredNetworkType( - if (requireUnmetered) JobInfo.NETWORK_TYPE_UNMETERED - else JobInfo.NETWORK_TYPE_ANY + if (requireUnmetered) { + JobInfo.NETWORK_TYPE_UNMETERED + } else { + JobInfo.NETWORK_TYPE_ANY + } ) .apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -348,7 +359,10 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { } } - fun scheduleBackground(context: Context, prefInterval: Int? = null) { + fun scheduleBackground( + context: Context, + prefInterval: Int? = null + ) { cancelBackground(context) val preferences = Injekt.get() @@ -358,11 +372,12 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope { val acRestriction = "ac" in restrictions val wifiRestriction = "wifi" in restrictions - val jobInfo = context.periodicBackgroundJobInfo( - interval.hours.inMilliseconds.longValue, - acRestriction, - wifiRestriction - ) + val jobInfo = + context.periodicBackgroundJobInfo( + interval.hours.inMilliseconds.longValue, + acRestriction, + wifiRestriction + ) if (context.jobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) { logger.e("Failed to schedule background update job!") diff --git a/app/src/main/java/exh/eh/MemAutoFlushingLookupTable.kt b/app/src/main/java/exh/eh/MemAutoFlushingLookupTable.kt index 911949289d4a..89b91bc20b88 100644 --- a/app/src/main/java/exh/eh/MemAutoFlushingLookupTable.kt +++ b/app/src/main/java/exh/eh/MemAutoFlushingLookupTable.kt @@ -3,13 +3,6 @@ package exh.eh import android.util.SparseArray import androidx.core.util.AtomicFile import com.elvishew.xlog.XLog -import java.io.Closeable -import java.io.File -import java.io.FileNotFoundException -import java.io.InputStream -import java.nio.ByteBuffer -import kotlin.concurrent.thread -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -22,6 +15,13 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext +import java.io.Closeable +import java.io.File +import java.io.FileNotFoundException +import java.io.InputStream +import java.nio.ByteBuffer +import kotlin.concurrent.thread +import kotlin.coroutines.CoroutineContext /** * In memory Int -> Obj lookup table implementation that @@ -52,14 +52,16 @@ class MemAutoFlushingLookupTable( // Used to debounce @Volatile private var writeCounter = Long.MIN_VALUE + @Volatile private var flushed = true private val atomicFile = AtomicFile(file) - private val shutdownHook = thread(start = false) { - if (!flushed) writeSynchronously() - } + private val shutdownHook = + thread(start = false) { + if (!flushed) writeSynchronously() + } init { initialLoad() @@ -67,7 +69,10 @@ class MemAutoFlushingLookupTable( Runtime.getRuntime().addShutdownHook(shutdownHook) } - private fun InputStream.requireBytes(targetArray: ByteArray, byteCount: Int): Boolean { + private fun InputStream.requireBytes( + targetArray: ByteArray, + byteCount: Int + ): Boolean { var readIter = 0 while (true) { val readThisIter = read(targetArray, readIter, byteCount - readIter) @@ -143,7 +148,10 @@ class MemAutoFlushingLookupTable( } } - suspend fun put(key: Int, value: T) { + suspend fun put( + key: Int, + value: T + ) { mutex.withLock { table.put(key, value) } tryWrite() } diff --git a/app/src/main/java/exh/favorites/FavoritesIntroDialog.kt b/app/src/main/java/exh/favorites/FavoritesIntroDialog.kt index 1889e63ccce6..2c1b4b4bbac2 100644 --- a/app/src/main/java/exh/favorites/FavoritesIntroDialog.kt +++ b/app/src/main/java/exh/favorites/FavoritesIntroDialog.kt @@ -9,14 +9,15 @@ import uy.kohesive.injekt.injectLazy class FavoritesIntroDialog { private val prefs: PreferencesHelper by injectLazy() - fun show(context: Context) = MaterialDialog(context) - .title(text = "IMPORTANT FAVORITES SYNC NOTES") - .message(text = HtmlCompat.fromHtml(FAVORITES_INTRO_TEXT, HtmlCompat.FROM_HTML_MODE_LEGACY)) - .positiveButton(android.R.string.ok) { - prefs.eh_showSyncIntro().set(false) - } - .cancelable(false) - .show() + fun show(context: Context) = + MaterialDialog(context) + .title(text = "IMPORTANT FAVORITES SYNC NOTES") + .message(text = HtmlCompat.fromHtml(FAVORITES_INTRO_TEXT, HtmlCompat.FROM_HTML_MODE_LEGACY)) + .positiveButton(android.R.string.ok) { + prefs.eh_showSyncIntro().set(false) + } + .cancelable(false) + .show() private val FAVORITES_INTRO_TEXT = """ diff --git a/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt b/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt index fc4aa52f9a82..19e935bfe1a7 100644 --- a/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt +++ b/app/src/main/java/exh/favorites/FavoritesSyncHelper.kt @@ -23,13 +23,13 @@ import exh.eh.EHentaiThrottleManager import exh.eh.EHentaiUpdateWorker import exh.util.ignore import exh.util.trans -import kotlin.concurrent.thread import okhttp3.FormBody import okhttp3.Request import rx.subjects.BehaviorSubject import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import kotlin.concurrent.thread class FavoritesSyncHelper(val context: Context) { private val db: DatabaseHelper by injectLazy() @@ -93,33 +93,36 @@ class FavoritesSyncHelper(val context: Context) { } // Download remote favorites - val favorites = try { - status.onNext(FavoritesSyncStatus.Processing("Downloading favorites from remote server")) - exh.fetchFavorites() - } catch (e: Exception) { - status.onNext(FavoritesSyncStatus.Error("Failed to fetch favorites from remote server!")) - logger.e("Could not fetch favorites!", e) - return - } + val favorites = + try { + status.onNext(FavoritesSyncStatus.Processing("Downloading favorites from remote server")) + exh.fetchFavorites() + } catch (e: Exception) { + status.onNext(FavoritesSyncStatus.Error("Failed to fetch favorites from remote server!")) + logger.e("Could not fetch favorites!", e) + return + } val errorList = mutableListOf() try { // Take wake + wifi locks ignore { wakeLock?.release() } - wakeLock = ignore { - context.powerManager.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, - "teh:ExhFavoritesSyncWakelock" - ) - } + wakeLock = + ignore { + context.powerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, + "teh:ExhFavoritesSyncWakelock" + ) + } ignore { wifiLock?.release() } - wifiLock = ignore { - context.wifiManager.createWifiLock( - WifiManager.WIFI_MODE_FULL, - "teh:ExhFavoritesSyncWifi" - ) - } + wifiLock = + ignore { + context.wifiManager.createWifiLock( + WifiManager.WIFI_MODE_FULL, + "teh:ExhFavoritesSyncWifi" + ) + } // Do not update galleries while syncing favorites EHentaiUpdateWorker.cancelBackground(context) @@ -129,12 +132,13 @@ class FavoritesSyncHelper(val context: Context) { db.inTransaction { status.onNext(FavoritesSyncStatus.Processing("Calculating remote changes")) val remoteChanges = storage.getChangedRemoteEntries(realm, favorites.first) - val localChanges = if (prefs.eh_readOnlySync().get()) { - null // Do not build local changes if they are not going to be applied - } else { - status.onNext(FavoritesSyncStatus.Processing("Calculating local changes")) - storage.getChangedDbEntries(realm) - } + val localChanges = + if (prefs.eh_readOnlySync().get()) { + null // Do not build local changes if they are not going to be applied + } else { + status.onNext(FavoritesSyncStatus.Processing("Calculating local changes")) + storage.getChangedDbEntries(realm) + } // Apply remote categories status.onNext(FavoritesSyncStatus.Processing("Updating category names")) @@ -186,7 +190,10 @@ class FavoritesSyncHelper(val context: Context) { } } - private fun applyRemoteCategories(errorList: MutableList, categories: List) { + private fun applyRemoteCategories( + errorList: MutableList, + categories: List + ) { val localCategories = db.getCategories().executeAsBlocking() val newLocalCategories = localCategories.toMutableList() @@ -194,18 +201,19 @@ class FavoritesSyncHelper(val context: Context) { var changed = false categories.forEachIndexed { index, remote -> - val local = localCategories.getOrElse(index) { - changed = true + val local = + localCategories.getOrElse(index) { + changed = true - Category.create(remote).apply { - order = index + Category.create(remote).apply { + order = index - // Going through categories list from front to back - // If category does not exist, list size <= category index - // Thus, we can just add it here and not worry about indexing - newLocalCategories += this + // Going through categories list from front to back + // If category does not exist, list size <= category index + // Thus, we can just add it here and not worry about indexing + newLocalCategories += this + } } - } if (local.name != remote) { changed = true @@ -229,20 +237,24 @@ class FavoritesSyncHelper(val context: Context) { } } - private fun addGalleryRemote(errorList: MutableList, gallery: FavoriteEntry) { + private fun addGalleryRemote( + errorList: MutableList, + gallery: FavoriteEntry + ) { val url = "${exh.baseUrl}/gallerypopups.php?gid=${gallery.gid}&t=${gallery.token}&act=addfav" - val request = Request.Builder() - .url(url) - .post( - FormBody.Builder() - .add("favcat", gallery.category.toString()) - .add("favnote", "") - .add("apply", "Add to Favorites") - .add("update", "1") - .build() - ) - .build() + val request = + Request.Builder() + .url(url) + .post( + FormBody.Builder() + .add("favcat", gallery.category.toString()) + .add("favnote", "") + .add("apply", "Add to Favorites") + .add("update", "1") + .build() + ) + .build() if (!explicitlyRetryExhRequest(10, request)) { val errorString = "Unable to add gallery to remote server: '${gallery.title}' (GID: ${gallery.gid})!" @@ -256,7 +268,10 @@ class FavoritesSyncHelper(val context: Context) { } } - private fun explicitlyRetryExhRequest(retryCount: Int, request: Request): Boolean { + private fun explicitlyRetryExhRequest( + retryCount: Int, + request: Request + ): Boolean { var success = false for (i in 1..retryCount) { @@ -275,24 +290,29 @@ class FavoritesSyncHelper(val context: Context) { return success } - private fun applyChangeSetToRemote(errorList: MutableList, changeSet: ChangeSet) { + private fun applyChangeSetToRemote( + errorList: MutableList, + changeSet: ChangeSet + ) { // Apply removals if (changeSet.removed.isNotEmpty()) { status.onNext(FavoritesSyncStatus.Processing("Removing ${changeSet.removed.size} galleries from remote server")) - val formBody = FormBody.Builder() - .add("ddact", "delete") - .add("apply", "Apply") + val formBody = + FormBody.Builder() + .add("ddact", "delete") + .add("apply", "Apply") // Add change set to form changeSet.removed.forEach { formBody.add("modifygids[]", it.gid) } - val request = Request.Builder() - .url("https://exhentai.org/favorites.php") - .post(formBody.build()) - .build() + val request = + Request.Builder() + .url("https://exhentai.org/favorites.php") + .post(formBody.build()) + .build() if (!explicitlyRetryExhRequest(10, request)) { val errorString = "Unable to delete galleries from the remote servers!" @@ -322,7 +342,10 @@ class FavoritesSyncHelper(val context: Context) { } } - private fun applyChangeSetToLocal(errorList: MutableList, changeSet: ChangeSet) { + private fun applyChangeSetToLocal( + errorList: MutableList, + changeSet: ChangeSet + ) { val removedManga = mutableListOf() // Apply removals @@ -367,12 +390,13 @@ class FavoritesSyncHelper(val context: Context) { throttleManager.throttle() // Import using gallery adder - val result = galleryAdder.addGallery( - "${exh.baseUrl}${it.getUrl()}", - true, - exh, - throttleManager::throttle - ) + val result = + galleryAdder.addGallery( + "${exh.baseUrl}${it.getUrl()}", + true, + exh, + throttleManager::throttle + ) if (result is GalleryAddEvent.Fail) { if (result is GalleryAddEvent.Fail.NotFound) { @@ -381,10 +405,12 @@ class FavoritesSyncHelper(val context: Context) { return@forEachIndexed } - val errorString = "Failed to add gallery to local database: " + when (result) { - is GalleryAddEvent.Fail.Error -> "'${it.title}' ${result.logMessage}" - is GalleryAddEvent.Fail.UnknownType -> "'${it.title}' (${result.galleryUrl}) is not a valid gallery!" - } + val errorString = + "Failed to add gallery to local database: " + + when (result) { + is GalleryAddEvent.Fail.Error -> "'${it.title}' ${result.logMessage}" + is GalleryAddEvent.Fail.UnknownType -> "'${it.title}' (${result.galleryUrl}) is not a valid gallery!" + } if (prefs.eh_lenientSync().get()) { errorList += errorString @@ -408,8 +434,7 @@ class FavoritesSyncHelper(val context: Context) { } } - fun needWarnThrottle() = - throttleManager.throttleTime >= THROTTLE_WARN + fun needWarnThrottle() = throttleManager.throttleTime >= THROTTLE_WARN class IgnoredException : RuntimeException() @@ -420,7 +445,9 @@ class FavoritesSyncHelper(val context: Context) { sealed class FavoritesSyncStatus(val message: String) { class Error(message: String) : FavoritesSyncStatus(message) + class Idle : FavoritesSyncStatus("Waiting for sync to start") + sealed class BadLibraryState(message: String) : FavoritesSyncStatus(message) { class MangaInMultipleCategories( val manga: Manga, @@ -428,7 +455,9 @@ sealed class FavoritesSyncStatus(val message: String) { ) : BadLibraryState("The gallery: ${manga.title} is in more than one category (${categories.joinToString { it.name }})!") } + class Initializing : FavoritesSyncStatus("Initializing sync") + class Processing(message: String, isThrottle: Boolean = false) : FavoritesSyncStatus( if (isThrottle) { "$message\n\nSync is currently throttling (to avoid being banned from ExHentai) and may take a long time to complete." @@ -436,5 +465,6 @@ sealed class FavoritesSyncStatus(val message: String) { message } ) + class CompleteWithErrors(messages: List) : FavoritesSyncStatus(messages.joinToString("\n")) } diff --git a/app/src/main/java/exh/favorites/LocalFavoritesStorage.kt b/app/src/main/java/exh/favorites/LocalFavoritesStorage.kt index ab3e0d2e65b8..f11334051a13 100644 --- a/app/src/main/java/exh/favorites/LocalFavoritesStorage.kt +++ b/app/src/main/java/exh/favorites/LocalFavoritesStorage.kt @@ -8,16 +8,17 @@ import exh.EXH_SOURCE_ID import exh.metadata.metadata.EHentaiSearchMetadata import io.realm.Realm import io.realm.RealmConfiguration -import java.util.Date import uy.kohesive.injekt.injectLazy +import java.util.Date class LocalFavoritesStorage { private val db: DatabaseHelper by injectLazy() - private val realmConfig = RealmConfiguration.Builder() - .name("fav-sync") - .deleteRealmIfMigrationNeeded() - .build() + private val realmConfig = + RealmConfiguration.Builder() + .name("fav-sync") + .deleteRealmIfMigrationNeeded() + .build() fun getRealm() = Realm.getInstance(realmConfig) @@ -33,30 +34,33 @@ class LocalFavoritesStorage { ) ) - fun getChangedRemoteEntries(realm: Realm, entries: List) = - getChangedEntries( - realm, - parseToFavoriteEntries( - entries.asSequence().map { - Pair( - it.fav, - it.manga.apply { - favorite = true - date_added = Date().time - } - ) - } - ) + fun getChangedRemoteEntries( + realm: Realm, + entries: List + ) = getChangedEntries( + realm, + parseToFavoriteEntries( + entries.asSequence().map { + Pair( + it.fav, + it.manga.apply { + favorite = true + date_added = Date().time + } + ) + } ) + ) fun snapshotEntries(realm: Realm) { - val dbMangas = parseToFavoriteEntries( - loadDbCategories( - db.getFavoriteMangas() - .executeAsBlocking() - .asSequence() + val dbMangas = + parseToFavoriteEntries( + loadDbCategories( + db.getFavoriteMangas() + .executeAsBlocking() + .asSequence() + ) ) - ) // Delete old snapshot realm.delete(FavoriteEntry::class.java) @@ -69,21 +73,26 @@ class LocalFavoritesStorage { realm.delete(FavoriteEntry::class.java) } - private fun getChangedEntries(realm: Realm, entries: Sequence): ChangeSet { + private fun getChangedEntries( + realm: Realm, + entries: Sequence + ): ChangeSet { val terminated = entries.toList() - val added = terminated.filter { - realm.queryRealmForEntry(it) == null - } - - val removed = realm.where(FavoriteEntry::class.java) - .findAll() - .filter { - queryListForEntry(terminated, it) == null - }.map { - realm.copyFromRealm(it) + val added = + terminated.filter { + realm.queryRealmForEntry(it) == null } + val removed = + realm.where(FavoriteEntry::class.java) + .findAll() + .filter { + queryListForEntry(terminated, it) == null + }.map { + realm.copyFromRealm(it) + } + return ChangeSet(added, removed) } @@ -94,12 +103,14 @@ class LocalFavoritesStorage { .equalTo(FavoriteEntry::category.name, entry.category) .findFirst() - private fun queryListForEntry(list: List, entry: FavoriteEntry) = - list.find { - it.gid == entry.gid && - it.token == entry.token && - it.category == entry.category - } + private fun queryListForEntry( + list: List, + entry: FavoriteEntry + ) = list.find { + it.gid == entry.gid && + it.token == entry.token && + it.category == entry.category + } private fun loadDbCategories(manga: Sequence): Sequence> { val dbCategories = db.getCategories().executeAsBlocking() @@ -133,8 +144,7 @@ class LocalFavoritesStorage { } } - private fun validateDbManga(manga: Manga) = - manga.favorite && (manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID) + private fun validateDbManga(manga: Manga) = manga.favorite && (manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID) companion object { const val MAX_CATEGORIES = 9 diff --git a/app/src/main/java/exh/log/CrashlyticsPrinter.kt b/app/src/main/java/exh/log/CrashlyticsPrinter.kt index fc21f714e03d..148c0bcac936 100644 --- a/app/src/main/java/exh/log/CrashlyticsPrinter.kt +++ b/app/src/main/java/exh/log/CrashlyticsPrinter.kt @@ -12,7 +12,11 @@ class CrashlyticsPrinter(private val logLevel: Int) : Printer { * @param tag the tag of log * @param msg the msg of log */ - override fun println(logLevel: Int, tag: String?, msg: String?) { + override fun println( + logLevel: Int, + tag: String?, + msg: String? + ) { if (logLevel >= this.logLevel) { try { val crashlytics = FirebaseCrashlytics.getInstance() diff --git a/app/src/main/java/exh/log/EHDebugModeOverlay.kt b/app/src/main/java/exh/log/EHDebugModeOverlay.kt index d5a3699c75c2..e4d696a7a9ce 100644 --- a/app/src/main/java/exh/log/EHDebugModeOverlay.kt +++ b/app/src/main/java/exh/log/EHDebugModeOverlay.kt @@ -19,32 +19,44 @@ class EHDebugModeOverlay(private val context: Context) : OverlayModule(n private val prefs: PreferencesHelper by injectLazy() override fun start() {} + override fun stop() {} + override fun notifyObservers() {} + override fun addObserver(observer: DataObserver) { observer.onDataAvailable(buildInfo()) } + override fun removeObserver(observer: DataObserver) {} + override fun onDataAvailable(data: String?) { textView?.text = HtmlCompat.fromHtml(data!!, HtmlCompat.FROM_HTML_MODE_LEGACY) } - override fun createView(root: ViewGroup, textColor: Int, textSize: Float, textAlpha: Float): View { + override fun createView( + root: ViewGroup, + textColor: Int, + textSize: Float, + textAlpha: Float + ): View { val view = LinearLayout(root.context) - view.layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) + view.layoutParams = + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) view.setPadding(4.dpToPx, 0, 4.dpToPx, 4.dpToPx) val textView = TextView(view.context) textView.setTextColor(textColor) textView.textSize = textSize textView.alpha = textAlpha textView.text = HtmlCompat.fromHtml(buildInfo(), HtmlCompat.FROM_HTML_MODE_LEGACY) - textView.layoutParams = LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) + textView.layoutParams = + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) view.addView(textView) this.textView = textView return view diff --git a/app/src/main/java/exh/log/EHLogLevel.kt b/app/src/main/java/exh/log/EHLogLevel.kt index cab571749b24..ed3ce16592b7 100644 --- a/app/src/main/java/exh/log/EHLogLevel.kt +++ b/app/src/main/java/exh/log/EHLogLevel.kt @@ -7,7 +7,8 @@ import eu.kanade.tachiyomi.data.preference.PreferenceKeys enum class EHLogLevel(val description: String) { MINIMAL("critical errors only"), EXTRA("log everything"), - EXTREME("network inspection mode"); + EXTREME("network inspection mode") + ; companion object { private var curLogLevel: Int? = null @@ -15,8 +16,9 @@ enum class EHLogLevel(val description: String) { val currentLogLevel get() = values()[curLogLevel!!] fun init(context: Context) { - curLogLevel = PreferenceManager.getDefaultSharedPreferences(context) - .getInt(PreferenceKeys.eh_logLevel, 0) + curLogLevel = + PreferenceManager.getDefaultSharedPreferences(context) + .getInt(PreferenceKeys.eh_logLevel, 0) } fun shouldLog(requiredLogLevel: EHLogLevel): Boolean { diff --git a/app/src/main/java/exh/log/EHNetworkLogging.kt b/app/src/main/java/exh/log/EHNetworkLogging.kt index 8a4229e8a4f0..0bec10067729 100644 --- a/app/src/main/java/exh/log/EHNetworkLogging.kt +++ b/app/src/main/java/exh/log/EHNetworkLogging.kt @@ -7,16 +7,17 @@ import okhttp3.logging.HttpLoggingInterceptor fun OkHttpClient.Builder.maybeInjectEHLogger(): OkHttpClient.Builder { if (EHLogLevel.shouldLog(EHLogLevel.EXTREME)) { - val logger: HttpLoggingInterceptor.Logger = object : HttpLoggingInterceptor.Logger { - override fun log(message: String) { - try { - Gson().fromJson(message, Any::class.java) - XLog.tag("||EH-NETWORK-JSON").nst().json(message) - } catch (ex: Exception) { - XLog.tag("||EH-NETWORK").nb().nst().d(message) + val logger: HttpLoggingInterceptor.Logger = + object : HttpLoggingInterceptor.Logger { + override fun log(message: String) { + try { + Gson().fromJson(message, Any::class.java) + XLog.tag("||EH-NETWORK-JSON").nst().json(message) + } catch (ex: Exception) { + XLog.tag("||EH-NETWORK").nb().nst().d(message) + } } } - } return addInterceptor(HttpLoggingInterceptor(logger).apply { level = HttpLoggingInterceptor.Level.BODY }) } return this diff --git a/app/src/main/java/exh/metadata/MetadataUtil.kt b/app/src/main/java/exh/metadata/MetadataUtil.kt index 83d8608a6275..69d48b5e8736 100755 --- a/app/src/main/java/exh/metadata/MetadataUtil.kt +++ b/app/src/main/java/exh/metadata/MetadataUtil.kt @@ -6,7 +6,10 @@ import java.util.Locale /** * Metadata utils */ -fun humanReadableByteCount(bytes: Long, si: Boolean): String { +fun humanReadableByteCount( + bytes: Long, + si: Boolean +): String { val unit = if (si) 1000 else 1024 if (bytes < unit) return "$bytes B" val exp = (Math.log(bytes.toDouble()) / Math.log(unit.toDouble())).toInt() @@ -35,32 +38,34 @@ fun parseHumanReadableByteCount(arg0: String): Double? { return null } -fun String?.nullIfBlank(): String? = if (isNullOrBlank()) { - null -} else { - this -} +fun String?.nullIfBlank(): String? = + if (isNullOrBlank()) { + null + } else { + this + } fun Set>.forEach(action: (K, V) -> Unit) { forEach { action(it.key, it.value) } } -val ONGOING_SUFFIX = arrayOf( - "[ongoing]", - "(ongoing)", - "{ongoing}", - "", - "ongoing", - "[incomplete]", - "(incomplete)", - "{incomplete}", - "", - "incomplete", - "[wip]", - "(wip)", - "{wip}", - "", - "wip" -) +val ONGOING_SUFFIX = + arrayOf( + "[ongoing]", + "(ongoing)", + "{ongoing}", + "", + "ongoing", + "[incomplete]", + "(incomplete)", + "{incomplete}", + "", + "incomplete", + "[wip]", + "(wip)", + "{wip}", + "", + "wip" + ) val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US) diff --git a/app/src/main/java/exh/metadata/metadata/EHentaiSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/EHentaiSearchMetadata.kt index 7f219e0dbf2e..03b95c526130 100755 --- a/app/src/main/java/exh/metadata/metadata/EHentaiSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/EHentaiSearchMetadata.kt @@ -7,16 +7,18 @@ import exh.metadata.EX_DATE_FORMAT import exh.metadata.ONGOING_SUFFIX import exh.metadata.humanReadableByteCount import exh.plusAssign -import java.util.Date import kotlinx.serialization.Serializable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.Date @Serializable class EHentaiSearchMetadata : RaisedSearchMetadata() { var gId: String? get() = indexedExtra - set(value) { indexedExtra = value } + set(value) { + indexedExtra = value + } var gToken: String? = null var exh: Boolean? = null @@ -50,11 +52,12 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { thumbnailUrl?.let { manga.thumbnail_url = it } // No title bug? - val titleObj = if (Injekt.get().useJapaneseTitle().get()) { - altTitle ?: title - } else { - title - } + val titleObj = + if (Injekt.get().useJapaneseTitle().get()) { + altTitle ?: title + } else { + title + } titleObj?.let { manga.title = it } // Set artist (if we can find one) @@ -102,9 +105,10 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { val tagsDesc = tagsToDescription() - manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + manga.description = + listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { @@ -120,23 +124,24 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() { private fun splitGalleryUrl(url: String) = url.let { // Only parse URL if is full URL - val pathSegments = if (it.startsWith("http")) { - Uri.parse(it).pathSegments - } else { - it.split('/') - } + val pathSegments = + if (it.startsWith("http")) { + Uri.parse(it).pathSegments + } else { + it.split('/') + } pathSegments.filterNot(String::isNullOrBlank) } fun galleryId(url: String) = splitGalleryUrl(url)[1] - fun galleryToken(url: String) = - splitGalleryUrl(url)[2] + fun galleryToken(url: String) = splitGalleryUrl(url)[2] - fun normalizeUrl(url: String) = - idAndTokenToUrl(galleryId(url), galleryToken(url)) + fun normalizeUrl(url: String) = idAndTokenToUrl(galleryId(url), galleryToken(url)) - fun idAndTokenToUrl(id: String, token: String) = - "/g/$id/$token/?nw=always" + fun idAndTokenToUrl( + id: String, + token: String + ) = "/g/$id/$token/?nw=always" } } diff --git a/app/src/main/java/exh/metadata/metadata/HentaiCafeSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/HentaiCafeSearchMetadata.kt index fe78de15c3be..5246b7c59580 100755 --- a/app/src/main/java/exh/metadata/metadata/HentaiCafeSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/HentaiCafeSearchMetadata.kt @@ -31,16 +31,18 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() { // Not available manga.status = SManga.UNKNOWN - val detailsDesc = "Title: $title\n" + - "Artist: $artist\n" + val detailsDesc = + "Title: $title\n" + + "Artist: $artist\n" val tagsDesc = tagsToDescription() manga.genre = tagsToGenreString() - manga.description = listOf(detailsDesc, tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + manga.description = + listOf(detailsDesc, tagsDesc.toString()) + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { @@ -50,7 +52,6 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() { const val BASE_URL = "https://hentai.cafe" - fun hcIdFromUrl(url: String) = - url.split("/").last { it.isNotBlank() } + fun hcIdFromUrl(url: String) = url.split("/").last { it.isNotBlank() } } } diff --git a/app/src/main/java/exh/metadata/metadata/NHentaiSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/NHentaiSearchMetadata.kt index c93a934039ed..c8d172e5fcd3 100755 --- a/app/src/main/java/exh/metadata/metadata/NHentaiSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/NHentaiSearchMetadata.kt @@ -6,10 +6,10 @@ import exh.metadata.EX_DATE_FORMAT import exh.metadata.ONGOING_SUFFIX import exh.metadata.nullIfBlank import exh.plusAssign -import java.util.Date import kotlinx.serialization.Serializable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.Date @Serializable class NHentaiSearchMetadata : RaisedSearchMetadata() { @@ -92,9 +92,10 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() { val tagsDesc = tagsToDescription() - manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + manga.description = + listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { @@ -117,8 +118,7 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() { else -> null } - fun nhUrlToId(url: String) = - url.split("/").last { it.isNotBlank() }.toLong() + fun nhUrlToId(url: String) = url.split("/").last { it.isNotBlank() }.toLong() fun nhIdToPath(id: Long) = "/g/$id/" } diff --git a/app/src/main/java/exh/metadata/metadata/PururinSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/PururinSearchMetadata.kt index a56c20694e08..4c305354baaa 100755 --- a/app/src/main/java/exh/metadata/metadata/PururinSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/PururinSearchMetadata.kt @@ -55,9 +55,10 @@ class PururinSearchMetadata : RaisedSearchMetadata() { val tagsDesc = tagsToDescription() - manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + manga.description = + listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString()) + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { diff --git a/app/src/main/java/exh/metadata/metadata/RaisedSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/RaisedSearchMetadata.kt index 9fa62dd3c48a..68aa2a15ddde 100755 --- a/app/src/main/java/exh/metadata/metadata/RaisedSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/RaisedSearchMetadata.kt @@ -9,12 +9,12 @@ import exh.metadata.sql.models.SearchMetadata import exh.metadata.sql.models.SearchTag import exh.metadata.sql.models.SearchTitle import exh.plusAssign -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty @Serializable sealed class RaisedSearchMetadata { @@ -35,7 +35,10 @@ sealed class RaisedSearchMetadata { fun getTitleOfType(type: Int): String? = titles.find { it.type == type }?.title - fun replaceTitleOfType(type: Int, newTitle: String?) { + fun replaceTitleOfType( + type: Int, + newTitle: String? + ) { titles.removeAll { it.type == type } if (newTitle != null) titles += RaisedTitle(newTitle, type) } @@ -49,9 +52,10 @@ sealed class RaisedSearchMetadata { fun tagsToDescription() = StringBuilder("Tags:\n").apply { // BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags' - val groupedTags = tags.filter { it.type != TAG_TYPE_VIRTUAL }.groupBy { - it.namespace - }.entries + val groupedTags = + tags.filter { it.type != TAG_TYPE_VIRTUAL }.groupBy { + it.namespace + }.entries groupedTags.forEach { namespace, tags -> if (tags.isNotEmpty()) { @@ -109,42 +113,51 @@ sealed class RaisedSearchMetadata { indexedExtra = metadata.metadata.indexedExtra this.tags.clear() - this.tags += metadata.tags.map { - RaisedTag(it.namespace, it.name, it.type) - } + this.tags += + metadata.tags.map { + RaisedTag(it.namespace, it.name, it.type) + } this.titles.clear() - this.titles += metadata.titles.map { - RaisedTitle(it.title, it.type) - } + this.titles += + metadata.titles.map { + RaisedTitle(it.title, it.type) + } } companion object { // Virtual tags allow searching of otherwise unindexed fields const val TAG_TYPE_VIRTUAL = -2 - val raiseFlattenJson = Json { - ignoreUnknownKeys = true - } + val raiseFlattenJson = + Json { + ignoreUnknownKeys = true + } - fun titleDelegate(type: Int) = object : ReadWriteProperty { - /** - * Returns the value of the property for the given object. - * @param thisRef the object for which the value is requested. - * @param property the metadata for the property. - * @return the property value. - */ - override fun getValue(thisRef: RaisedSearchMetadata, property: KProperty<*>) = - thisRef.getTitleOfType(type) - - /** - * Sets the value of the property for the given object. - * @param thisRef the object for which the value is requested. - * @param property the metadata for the property. - * @param value the value to set. - */ - override fun setValue(thisRef: RaisedSearchMetadata, property: KProperty<*>, value: String?) = - thisRef.replaceTitleOfType(type, value) - } + fun titleDelegate(type: Int) = + object : ReadWriteProperty { + /** + * Returns the value of the property for the given object. + * @param thisRef the object for which the value is requested. + * @param property the metadata for the property. + * @return the property value. + */ + override fun getValue( + thisRef: RaisedSearchMetadata, + property: KProperty<*> + ) = thisRef.getTitleOfType(type) + + /** + * Sets the value of the property for the given object. + * @param thisRef the object for which the value is requested. + * @param property the metadata for the property. + * @param value the value to set. + */ + override fun setValue( + thisRef: RaisedSearchMetadata, + property: KProperty<*>, + value: String? + ) = thisRef.replaceTitleOfType(type, value) + } } } diff --git a/app/src/main/java/exh/metadata/metadata/TsuminoSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/TsuminoSearchMetadata.kt index c25ee4bd8710..b568cfe46cbe 100755 --- a/app/src/main/java/exh/metadata/metadata/TsuminoSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/TsuminoSearchMetadata.kt @@ -4,8 +4,8 @@ import android.net.Uri import eu.kanade.tachiyomi.source.model.SManga import exh.metadata.EX_DATE_FORMAT import exh.plusAssign -import java.util.Date import kotlinx.serialization.Serializable +import java.util.Date @Serializable class TsuminoSearchMetadata : RaisedSearchMetadata() { @@ -65,9 +65,10 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() { val tagsDesc = tagsToDescription() - manga.description = listOf(titleDesc, detailsDesc.toString(), tagsDesc.toString()) - .filter(String::isNotBlank) - .joinToString(separator = "\n") + manga.description = + listOf(titleDesc, detailsDesc.toString(), tagsDesc.toString()) + .filter(String::isNotBlank) + .joinToString(separator = "\n") } companion object { @@ -77,8 +78,7 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() { val BASE_URL = "https://www.tsumino.com" - fun tmIdFromUrl(url: String) = - Uri.parse(url).lastPathSegment + fun tmIdFromUrl(url: String) = Uri.parse(url).lastPathSegment fun mangaUrlFromId(id: String) = "/Book/Info/$id" diff --git a/app/src/main/java/exh/metadata/metadata/base/FlatMetadata.kt b/app/src/main/java/exh/metadata/metadata/base/FlatMetadata.kt index a0b921bd1b4a..6d481247eb6e 100755 --- a/app/src/main/java/exh/metadata/metadata/base/FlatMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/base/FlatMetadata.kt @@ -6,12 +6,12 @@ import exh.metadata.metadata.RaisedSearchMetadata import exh.metadata.sql.models.SearchMetadata import exh.metadata.sql.models.SearchTag import exh.metadata.sql.models.SearchTitle -import kotlin.reflect.KClass import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.Serializable import kotlinx.serialization.serializer import rx.Completable import rx.Single +import kotlin.reflect.KClass @Serializable data class FlatMetadata( @@ -31,15 +31,18 @@ data class FlatMetadata( fun DatabaseHelper.getFlatMetadataForManga(mangaId: Long): PreparedOperation { // We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions - val single = Single.fromCallable { - val meta = getSearchMetadataForManga(mangaId).executeAsBlocking() - if (meta != null) { - val tags = getSearchTagsForManga(mangaId).executeAsBlocking() - val titles = getSearchTitlesForManga(mangaId).executeAsBlocking() + val single = + Single.fromCallable { + val meta = getSearchMetadataForManga(mangaId).executeAsBlocking() + if (meta != null) { + val tags = getSearchTagsForManga(mangaId).executeAsBlocking() + val titles = getSearchTitlesForManga(mangaId).executeAsBlocking() - FlatMetadata(meta, tags, titles) - } else null - } + FlatMetadata(meta, tags, titles) + } else { + null + } + } return preparedOperationFromSingle(single) } @@ -90,12 +93,13 @@ private fun preparedOperationFromSingle(single: Single): PreparedOperatio } } -fun DatabaseHelper.insertFlatMetadata(flatMetadata: FlatMetadata): Completable = Completable.fromCallable { - require(flatMetadata.metadata.mangaId != -1L) +fun DatabaseHelper.insertFlatMetadata(flatMetadata: FlatMetadata): Completable = + Completable.fromCallable { + require(flatMetadata.metadata.mangaId != -1L) - inTransaction { - insertSearchMetadata(flatMetadata.metadata).executeAsBlocking() - setSearchTagsForManga(flatMetadata.metadata.mangaId, flatMetadata.tags) - setSearchTitlesForManga(flatMetadata.metadata.mangaId, flatMetadata.titles) + inTransaction { + insertSearchMetadata(flatMetadata.metadata).executeAsBlocking() + setSearchTagsForManga(flatMetadata.metadata.mangaId, flatMetadata.tags) + setSearchTitlesForManga(flatMetadata.metadata.mangaId, flatMetadata.titles) + } } -} diff --git a/app/src/main/java/exh/metadata/sql/mappers/SearchMetadataTypeMapping.kt b/app/src/main/java/exh/metadata/sql/mappers/SearchMetadataTypeMapping.kt index 43ca0c31ce83..a6350c32ffb9 100644 --- a/app/src/main/java/exh/metadata/sql/mappers/SearchMetadataTypeMapping.kt +++ b/app/src/main/java/exh/metadata/sql/mappers/SearchMetadataTypeMapping.kt @@ -24,28 +24,29 @@ class SearchMetadataTypeMapping : SQLiteTypeMapping( ) class SearchMetadataPutResolver : DefaultPutResolver() { + override fun mapToInsertQuery(obj: SearchMetadata) = + InsertQuery.builder() + .table(TABLE) + .build() - override fun mapToInsertQuery(obj: SearchMetadata) = InsertQuery.builder() - .table(TABLE) - .build() + override fun mapToUpdateQuery(obj: SearchMetadata) = + UpdateQuery.builder() + .table(TABLE) + .where("$COL_MANGA_ID = ?") + .whereArgs(obj.mangaId) + .build() - override fun mapToUpdateQuery(obj: SearchMetadata) = UpdateQuery.builder() - .table(TABLE) - .where("$COL_MANGA_ID = ?") - .whereArgs(obj.mangaId) - .build() - - override fun mapToContentValues(obj: SearchMetadata) = ContentValues(5).apply { - put(COL_MANGA_ID, obj.mangaId) - put(COL_UPLOADER, obj.uploader) - put(COL_EXTRA, obj.extra) - put(COL_INDEXED_EXTRA, obj.indexedExtra) - put(COL_EXTRA_VERSION, obj.extraVersion) - } + override fun mapToContentValues(obj: SearchMetadata) = + ContentValues(5).apply { + put(COL_MANGA_ID, obj.mangaId) + put(COL_UPLOADER, obj.uploader) + put(COL_EXTRA, obj.extra) + put(COL_INDEXED_EXTRA, obj.indexedExtra) + put(COL_EXTRA_VERSION, obj.extraVersion) + } } class SearchMetadataGetResolver : DefaultGetResolver() { - override fun mapFromCursor(cursor: Cursor): SearchMetadata = SearchMetadata( mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)), @@ -57,10 +58,10 @@ class SearchMetadataGetResolver : DefaultGetResolver() { } class SearchMetadataDeleteResolver : DefaultDeleteResolver() { - - override fun mapToDeleteQuery(obj: SearchMetadata) = DeleteQuery.builder() - .table(TABLE) - .where("$COL_MANGA_ID = ?") - .whereArgs(obj.mangaId) - .build() + override fun mapToDeleteQuery(obj: SearchMetadata) = + DeleteQuery.builder() + .table(TABLE) + .where("$COL_MANGA_ID = ?") + .whereArgs(obj.mangaId) + .build() } diff --git a/app/src/main/java/exh/metadata/sql/mappers/SearchTagTypeMapping.kt b/app/src/main/java/exh/metadata/sql/mappers/SearchTagTypeMapping.kt index ff4fdf99f107..513861e7f05c 100755 --- a/app/src/main/java/exh/metadata/sql/mappers/SearchTagTypeMapping.kt +++ b/app/src/main/java/exh/metadata/sql/mappers/SearchTagTypeMapping.kt @@ -24,42 +24,44 @@ class SearchTagTypeMapping : SQLiteTypeMapping( ) class SearchTagPutResolver : DefaultPutResolver() { + override fun mapToInsertQuery(obj: SearchTag) = + InsertQuery.builder() + .table(TABLE) + .build() - override fun mapToInsertQuery(obj: SearchTag) = InsertQuery.builder() - .table(TABLE) - .build() + override fun mapToUpdateQuery(obj: SearchTag) = + UpdateQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() - override fun mapToUpdateQuery(obj: SearchTag) = UpdateQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() - - override fun mapToContentValues(obj: SearchTag) = ContentValues(5).apply { - put(COL_ID, obj.id) - put(COL_MANGA_ID, obj.mangaId) - put(COL_NAMESPACE, obj.namespace) - put(COL_NAME, obj.name) - put(COL_TYPE, obj.type) - } + override fun mapToContentValues(obj: SearchTag) = + ContentValues(5).apply { + put(COL_ID, obj.id) + put(COL_MANGA_ID, obj.mangaId) + put(COL_NAMESPACE, obj.namespace) + put(COL_NAME, obj.name) + put(COL_TYPE, obj.type) + } } class SearchTagGetResolver : DefaultGetResolver() { - - override fun mapFromCursor(cursor: Cursor): SearchTag = SearchTag( - id = cursor.getLong(cursor.getColumnIndex(COL_ID)), - mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)), - namespace = cursor.getString(cursor.getColumnIndex(COL_NAMESPACE)), - name = cursor.getString(cursor.getColumnIndex(COL_NAME)), - type = cursor.getInt(cursor.getColumnIndex(COL_TYPE)) - ) + override fun mapFromCursor(cursor: Cursor): SearchTag = + SearchTag( + id = cursor.getLong(cursor.getColumnIndex(COL_ID)), + mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)), + namespace = cursor.getString(cursor.getColumnIndex(COL_NAMESPACE)), + name = cursor.getString(cursor.getColumnIndex(COL_NAME)), + type = cursor.getInt(cursor.getColumnIndex(COL_TYPE)) + ) } class SearchTagDeleteResolver : DefaultDeleteResolver() { - - override fun mapToDeleteQuery(obj: SearchTag) = DeleteQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() + override fun mapToDeleteQuery(obj: SearchTag) = + DeleteQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() } diff --git a/app/src/main/java/exh/metadata/sql/mappers/SearchTitleTypeMapping.kt b/app/src/main/java/exh/metadata/sql/mappers/SearchTitleTypeMapping.kt index a1f5d0ba293c..59b423428bc5 100755 --- a/app/src/main/java/exh/metadata/sql/mappers/SearchTitleTypeMapping.kt +++ b/app/src/main/java/exh/metadata/sql/mappers/SearchTitleTypeMapping.kt @@ -23,40 +23,42 @@ class SearchTitleTypeMapping : SQLiteTypeMapping( ) class SearchTitlePutResolver : DefaultPutResolver() { + override fun mapToInsertQuery(obj: SearchTitle) = + InsertQuery.builder() + .table(TABLE) + .build() - override fun mapToInsertQuery(obj: SearchTitle) = InsertQuery.builder() - .table(TABLE) - .build() + override fun mapToUpdateQuery(obj: SearchTitle) = + UpdateQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() - override fun mapToUpdateQuery(obj: SearchTitle) = UpdateQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() - - override fun mapToContentValues(obj: SearchTitle) = ContentValues(4).apply { - put(COL_ID, obj.id) - put(COL_MANGA_ID, obj.mangaId) - put(COL_TITLE, obj.title) - put(COL_TYPE, obj.type) - } + override fun mapToContentValues(obj: SearchTitle) = + ContentValues(4).apply { + put(COL_ID, obj.id) + put(COL_MANGA_ID, obj.mangaId) + put(COL_TITLE, obj.title) + put(COL_TYPE, obj.type) + } } class SearchTitleGetResolver : DefaultGetResolver() { - - override fun mapFromCursor(cursor: Cursor): SearchTitle = SearchTitle( - id = cursor.getLong(cursor.getColumnIndex(COL_ID)), - mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)), - title = cursor.getString(cursor.getColumnIndex(COL_TITLE)), - type = cursor.getInt(cursor.getColumnIndex(COL_TYPE)) - ) + override fun mapFromCursor(cursor: Cursor): SearchTitle = + SearchTitle( + id = cursor.getLong(cursor.getColumnIndex(COL_ID)), + mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)), + title = cursor.getString(cursor.getColumnIndex(COL_TITLE)), + type = cursor.getInt(cursor.getColumnIndex(COL_TYPE)) + ) } class SearchTitleDeleteResolver : DefaultDeleteResolver() { - - override fun mapToDeleteQuery(obj: SearchTitle) = DeleteQuery.builder() - .table(TABLE) - .where("$COL_ID = ?") - .whereArgs(obj.id) - .build() + override fun mapToDeleteQuery(obj: SearchTitle) = + DeleteQuery.builder() + .table(TABLE) + .where("$COL_ID = ?") + .whereArgs(obj.id) + .build() } diff --git a/app/src/main/java/exh/metadata/sql/models/SearchMetadata.kt b/app/src/main/java/exh/metadata/sql/models/SearchMetadata.kt index 54551afd3aa3..66491406e507 100755 --- a/app/src/main/java/exh/metadata/sql/models/SearchMetadata.kt +++ b/app/src/main/java/exh/metadata/sql/models/SearchMetadata.kt @@ -6,16 +6,12 @@ import kotlinx.serialization.Serializable data class SearchMetadata( // Manga ID this gallery is linked to val mangaId: Long, - // Gallery uploader val uploader: String?, - // Extra data attached to this metadata, in JSON format val extra: String, - // Indexed extra data attached to this metadata val indexedExtra: String?, - // The version of this metadata's extra. Used to track changes to the 'extra' field's schema val extraVersion: Int ) { diff --git a/app/src/main/java/exh/metadata/sql/models/SearchTag.kt b/app/src/main/java/exh/metadata/sql/models/SearchTag.kt index c4d6348edde3..805e255717b6 100755 --- a/app/src/main/java/exh/metadata/sql/models/SearchTag.kt +++ b/app/src/main/java/exh/metadata/sql/models/SearchTag.kt @@ -5,16 +5,12 @@ import kotlinx.serialization.Serializable data class SearchTag( // Tag identifier, unique val id: Long?, - // Metadata this tag is attached to val mangaId: Long, - // Tag namespace val namespace: String?, - // Tag name val name: String, - // Tag type val type: Int ) diff --git a/app/src/main/java/exh/metadata/sql/models/SearchTitle.kt b/app/src/main/java/exh/metadata/sql/models/SearchTitle.kt index baec226f02f6..cc4163d204d3 100755 --- a/app/src/main/java/exh/metadata/sql/models/SearchTitle.kt +++ b/app/src/main/java/exh/metadata/sql/models/SearchTitle.kt @@ -5,13 +5,10 @@ import kotlinx.serialization.Serializable data class SearchTitle( // Title identifier, unique val id: Long?, - // Metadata this title is attached to val mangaId: Long, - // Title val title: String, - // Title type, useful for distinguishing between main/alt titles val type: Int ) diff --git a/app/src/main/java/exh/metadata/sql/queries/SearchMetadataQueries.kt b/app/src/main/java/exh/metadata/sql/queries/SearchMetadataQueries.kt index 4e30d3928fa5..3556ba84ac02 100644 --- a/app/src/main/java/exh/metadata/sql/queries/SearchMetadataQueries.kt +++ b/app/src/main/java/exh/metadata/sql/queries/SearchMetadataQueries.kt @@ -7,46 +7,49 @@ import exh.metadata.sql.models.SearchMetadata import exh.metadata.sql.tables.SearchMetadataTable interface SearchMetadataQueries : DbProvider { + fun getSearchMetadataForManga(mangaId: Long) = + db.get() + .`object`(SearchMetadata::class.java) + .withQuery( + Query.builder() + .table(SearchMetadataTable.TABLE) + .where("${SearchMetadataTable.COL_MANGA_ID} = ?") + .whereArgs(mangaId) + .build() + ) + .prepare() + + fun getSearchMetadata() = + db.get() + .listOfObjects(SearchMetadata::class.java) + .withQuery( + Query.builder() + .table(SearchMetadataTable.TABLE) + .build() + ) + .prepare() + + fun getSearchMetadataByIndexedExtra(extra: String) = + db.get() + .listOfObjects(SearchMetadata::class.java) + .withQuery( + Query.builder() + .table(SearchMetadataTable.TABLE) + .where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?") + .whereArgs(extra) + .build() + ) + .prepare() - fun getSearchMetadataForManga(mangaId: Long) = db.get() - .`object`(SearchMetadata::class.java) - .withQuery( - Query.builder() - .table(SearchMetadataTable.TABLE) - .where("${SearchMetadataTable.COL_MANGA_ID} = ?") - .whereArgs(mangaId) - .build() - ) - .prepare() + fun insertSearchMetadata(metadata: SearchMetadata) = db.put().`object`(metadata).prepare() - fun getSearchMetadata() = db.get() - .listOfObjects(SearchMetadata::class.java) - .withQuery( - Query.builder() - .table(SearchMetadataTable.TABLE) - .build() - ) - .prepare() + fun deleteSearchMetadata(metadata: SearchMetadata) = db.delete().`object`(metadata).prepare() - fun getSearchMetadataByIndexedExtra(extra: String) = db.get() - .listOfObjects(SearchMetadata::class.java) - .withQuery( - Query.builder() + fun deleteAllSearchMetadata() = + db.delete().byQuery( + DeleteQuery.builder() .table(SearchMetadataTable.TABLE) - .where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?") - .whereArgs(extra) .build() ) - .prepare() - - fun insertSearchMetadata(metadata: SearchMetadata) = db.put().`object`(metadata).prepare() - - fun deleteSearchMetadata(metadata: SearchMetadata) = db.delete().`object`(metadata).prepare() - - fun deleteAllSearchMetadata() = db.delete().byQuery( - DeleteQuery.builder() - .table(SearchMetadataTable.TABLE) - .build() - ) - .prepare() + .prepare() } diff --git a/app/src/main/java/exh/metadata/sql/queries/SearchTagQueries.kt b/app/src/main/java/exh/metadata/sql/queries/SearchTagQueries.kt index 11a12e2b9b3d..aa4d606d5d22 100755 --- a/app/src/main/java/exh/metadata/sql/queries/SearchTagQueries.kt +++ b/app/src/main/java/exh/metadata/sql/queries/SearchTagQueries.kt @@ -8,26 +8,28 @@ import exh.metadata.sql.models.SearchTag import exh.metadata.sql.tables.SearchTagTable interface SearchTagQueries : DbProvider { - fun getSearchTagsForManga(mangaId: Long) = db.get() - .listOfObjects(SearchTag::class.java) - .withQuery( - Query.builder() - .table(SearchTagTable.TABLE) - .where("${SearchTagTable.COL_MANGA_ID} = ?") - .whereArgs(mangaId) - .build() - ) - .prepare() - - fun deleteSearchTagsForManga(mangaId: Long) = db.delete() - .byQuery( - DeleteQuery.builder() - .table(SearchTagTable.TABLE) - .where("${SearchTagTable.COL_MANGA_ID} = ?") - .whereArgs(mangaId) - .build() - ) - .prepare() + fun getSearchTagsForManga(mangaId: Long) = + db.get() + .listOfObjects(SearchTag::class.java) + .withQuery( + Query.builder() + .table(SearchTagTable.TABLE) + .where("${SearchTagTable.COL_MANGA_ID} = ?") + .whereArgs(mangaId) + .build() + ) + .prepare() + + fun deleteSearchTagsForManga(mangaId: Long) = + db.delete() + .byQuery( + DeleteQuery.builder() + .table(SearchTagTable.TABLE) + .where("${SearchTagTable.COL_MANGA_ID} = ?") + .whereArgs(mangaId) + .build() + ) + .prepare() fun insertSearchTag(searchTag: SearchTag) = db.put().`object`(searchTag).prepare() @@ -35,14 +37,18 @@ interface SearchTagQueries : DbProvider { fun deleteSearchTag(searchTag: SearchTag) = db.delete().`object`(searchTag).prepare() - fun deleteAllSearchTags() = db.delete().byQuery( - DeleteQuery.builder() - .table(SearchTagTable.TABLE) - .build() - ) - .prepare() + fun deleteAllSearchTags() = + db.delete().byQuery( + DeleteQuery.builder() + .table(SearchTagTable.TABLE) + .build() + ) + .prepare() - fun setSearchTagsForManga(mangaId: Long, tags: List) { + fun setSearchTagsForManga( + mangaId: Long, + tags: List + ) { db.inTransaction { deleteSearchTagsForManga(mangaId).executeAsBlocking() tags.chunked(100) { chunk -> diff --git a/app/src/main/java/exh/metadata/sql/queries/SearchTitleQueries.kt b/app/src/main/java/exh/metadata/sql/queries/SearchTitleQueries.kt index 29ba48e16ed7..3aafabc9da1e 100755 --- a/app/src/main/java/exh/metadata/sql/queries/SearchTitleQueries.kt +++ b/app/src/main/java/exh/metadata/sql/queries/SearchTitleQueries.kt @@ -8,26 +8,28 @@ import exh.metadata.sql.models.SearchTitle import exh.metadata.sql.tables.SearchTitleTable interface SearchTitleQueries : DbProvider { - fun getSearchTitlesForManga(mangaId: Long) = db.get() - .listOfObjects(SearchTitle::class.java) - .withQuery( - Query.builder() - .table(SearchTitleTable.TABLE) - .where("${SearchTitleTable.COL_MANGA_ID} = ?") - .whereArgs(mangaId) - .build() - ) - .prepare() - - fun deleteSearchTitlesForManga(mangaId: Long) = db.delete() - .byQuery( - DeleteQuery.builder() - .table(SearchTitleTable.TABLE) - .where("${SearchTitleTable.COL_MANGA_ID} = ?") - .whereArgs(mangaId) - .build() - ) - .prepare() + fun getSearchTitlesForManga(mangaId: Long) = + db.get() + .listOfObjects(SearchTitle::class.java) + .withQuery( + Query.builder() + .table(SearchTitleTable.TABLE) + .where("${SearchTitleTable.COL_MANGA_ID} = ?") + .whereArgs(mangaId) + .build() + ) + .prepare() + + fun deleteSearchTitlesForManga(mangaId: Long) = + db.delete() + .byQuery( + DeleteQuery.builder() + .table(SearchTitleTable.TABLE) + .where("${SearchTitleTable.COL_MANGA_ID} = ?") + .whereArgs(mangaId) + .build() + ) + .prepare() fun insertSearchTitle(searchTitle: SearchTitle) = db.put().`object`(searchTitle).prepare() @@ -35,14 +37,18 @@ interface SearchTitleQueries : DbProvider { fun deleteSearchTitle(searchTitle: SearchTitle) = db.delete().`object`(searchTitle).prepare() - fun deleteAllSearchTitle() = db.delete().byQuery( - DeleteQuery.builder() - .table(SearchTitleTable.TABLE) - .build() - ) - .prepare() + fun deleteAllSearchTitle() = + db.delete().byQuery( + DeleteQuery.builder() + .table(SearchTitleTable.TABLE) + .build() + ) + .prepare() - fun setSearchTitlesForManga(mangaId: Long, titles: List) { + fun setSearchTitlesForManga( + mangaId: Long, + titles: List + ) { db.inTransaction { deleteSearchTitlesForManga(mangaId).executeAsBlocking() titles.chunked(100) { chunk -> diff --git a/app/src/main/java/exh/patch/MangaDexLogin.kt b/app/src/main/java/exh/patch/MangaDexLogin.kt index 3221295a9d14..5677bfe0163c 100644 --- a/app/src/main/java/exh/patch/MangaDexLogin.kt +++ b/app/src/main/java/exh/patch/MangaDexLogin.kt @@ -40,47 +40,50 @@ val MANGADEX_LOGIN_PATCH: EHInterceptor = { request, response, sourceId -> ) } } - } else response + } else { + response + } } -val MANGADEX_SOURCE_IDS = listOf( - 2499283573021220255, - 8033579885162383068, - 1952071260038453057, - 2098905203823335614, - 5098537545549490547, - 4505830566611664829, - 9194073792736219759, - 6400665728063187402, - 4938773340256184018, - 5860541308324630662, - 5189216366882819742, - 2655149515337070132, - 1145824452519314725, - 3846770256925560569, - 3807502156582598786, - 4284949320785450865, - 5463447640980279236, - 8578871918181236609, - 6750440049024086587, - 3339599426223341161, - 5148895169070562838, - 1493666528525752601, - 1713554459881080228, - 4150470519566206911, - 1347402746269051958, - 3578612018159256808, - 425785191804166217, - 8254121249433835847, - 3260701926561129943, - 1411768577036936240, - 3285208643537017688, - 737986167355114438, - 1471784905273036181, - 5967745367608513818, - 3781216447842245147, - 4774459486579224459, - 4710920497926776490, - 5779037855201976894 -) +val MANGADEX_SOURCE_IDS = + listOf( + 2499283573021220255, + 8033579885162383068, + 1952071260038453057, + 2098905203823335614, + 5098537545549490547, + 4505830566611664829, + 9194073792736219759, + 6400665728063187402, + 4938773340256184018, + 5860541308324630662, + 5189216366882819742, + 2655149515337070132, + 1145824452519314725, + 3846770256925560569, + 3807502156582598786, + 4284949320785450865, + 5463447640980279236, + 8578871918181236609, + 6750440049024086587, + 3339599426223341161, + 5148895169070562838, + 1493666528525752601, + 1713554459881080228, + 4150470519566206911, + 1347402746269051958, + 3578612018159256808, + 425785191804166217, + 8254121249433835847, + 3260701926561129943, + 1411768577036936240, + 3285208643537017688, + 737986167355114438, + 1471784905273036181, + 5967745367608513818, + 3781216447842245147, + 4774459486579224459, + 4710920497926776490, + 5779037855201976894 + ) const val MANGADEX_DOMAIN = "mangadex.org" diff --git a/app/src/main/java/exh/patch/NetworkPatches.kt b/app/src/main/java/exh/patch/NetworkPatches.kt index 6d3a7fe56070..11823b82d230 100644 --- a/app/src/main/java/exh/patch/NetworkPatches.kt +++ b/app/src/main/java/exh/patch/NetworkPatches.kt @@ -31,13 +31,14 @@ fun List.merge(): EHInterceptor { } private const val EH_UNIVERSAL_INTERCEPTOR = -1L -private val EH_INTERCEPTORS: Map> = mapOf( - EH_UNIVERSAL_INTERCEPTOR to listOf( - CAPTCHA_DETECTION_PATCH // Auto captcha detection - ), - - // MangaDex login support - *MANGADEX_SOURCE_IDS.map { id -> - id to listOf(MANGADEX_LOGIN_PATCH) - }.toTypedArray() -) +private val EH_INTERCEPTORS: Map> = + mapOf( + EH_UNIVERSAL_INTERCEPTOR to + listOf( + CAPTCHA_DETECTION_PATCH // Auto captcha detection + ), + // MangaDex login support + *MANGADEX_SOURCE_IDS.map { id -> + id to listOf(MANGADEX_LOGIN_PATCH) + }.toTypedArray() + ) diff --git a/app/src/main/java/exh/patch/UniversalCaptchaDetection.kt b/app/src/main/java/exh/patch/UniversalCaptchaDetection.kt index 7ed25280c6ba..7d63a44d25f6 100644 --- a/app/src/main/java/exh/patch/UniversalCaptchaDetection.kt +++ b/app/src/main/java/exh/patch/UniversalCaptchaDetection.kt @@ -10,7 +10,12 @@ val CAPTCHA_DETECTION_PATCH: EHInterceptor = { request, response, sourceId -> if (!response.isSuccessful) { response.interceptAsHtml { doc -> // Find captcha - if ((doc.getElementsByClass("g-recaptcha").isNotEmpty() && !("mangaeden" in request.url.host)) || doc.getElementsByClass("h-captcha").isNotEmpty()) { + if (( + doc.getElementsByClass( + "g-recaptcha" + ).isNotEmpty() && !("mangaeden" in request.url.host) + ) || doc.getElementsByClass("h-captcha").isNotEmpty() + ) { // Found it, allow the user to solve this thing BrowserActionActivity.launchUniversal( Injekt.get(), @@ -19,5 +24,7 @@ val CAPTCHA_DETECTION_PATCH: EHInterceptor = { request, response, sourceId -> ) } } - } else response + } else { + response + } } diff --git a/app/src/main/java/exh/search/SearchEngine.kt b/app/src/main/java/exh/search/SearchEngine.kt index 7999b96a3c49..1ce4f89b23d7 100755 --- a/app/src/main/java/exh/search/SearchEngine.kt +++ b/app/src/main/java/exh/search/SearchEngine.kt @@ -11,20 +11,22 @@ class SearchEngine { namespace: String?, component: Text? ): Pair>? { - val maybeLenientComponent = component?.let { - if (!it.exact) { - it.asLenientTagQueries() - } else { - listOf(it.asQuery()) + val maybeLenientComponent = + component?.let { + if (!it.exact) { + it.asLenientTagQueries() + } else { + listOf(it.asQuery()) + } + } + val componentTagQuery = + maybeLenientComponent?.let { + val params = mutableListOf() + it.joinToString(separator = " OR ", prefix = "(", postfix = ")") { q -> + params += q + "${SearchTagTable.TABLE}.${SearchTagTable.COL_NAME} LIKE ?" + } to params } - } - val componentTagQuery = maybeLenientComponent?.let { - val params = mutableListOf() - it.joinToString(separator = " OR ", prefix = "(", postfix = ")") { q -> - params += q - "${SearchTagTable.TABLE}.${SearchTagTable.COL_NAME} LIKE ?" - } to params - } return when { namespace != null -> { var query = @@ -70,23 +72,26 @@ class SearchEngine { val exclude = mutableListOf>>() for (component in q) { - val query = if (component is Text) { - textToSubQueries(null, component) - } else if (component is Namespace) { - if (component.namespace == "uploader") { - wheres += "meta.${SearchMetadataTable.COL_UPLOADER} LIKE ?" - whereParams += component.tag!!.rawTextEscapedForLike() - null - } else { - if (component.tag!!.components.size > 0) { - // Match namespace + tags - textToSubQueries(component.namespace, component.tag) + val query = + if (component is Text) { + textToSubQueries(null, component) + } else if (component is Namespace) { + if (component.namespace == "uploader") { + wheres += "meta.${SearchMetadataTable.COL_UPLOADER} LIKE ?" + whereParams += component.tag!!.rawTextEscapedForLike() + null } else { - // Perform namespace search - textToSubQueries(component.namespace, null) + if (component.tag!!.components.size > 0) { + // Match namespace + tags + textToSubQueries(component.namespace, component.tag) + } else { + // Perform namespace search + textToSubQueries(component.namespace, null) + } } + } else { + error("Unknown query component!") } - } else error("Unknown query component!") if (query != null) { (if (component.excluded) exclude else include) += query @@ -111,9 +116,10 @@ class SearchEngine { } exclude.forEach { - wheres += """ - (meta.${SearchMetadataTable.COL_MANGA_ID} NOT IN ${it.first}) - """.trimIndent() + wheres += + """ + (meta.${SearchMetadataTable.COL_MANGA_ID} NOT IN ${it.first}) + """.trimIndent() whereParams += it.second } if (wheres.isNotEmpty()) { @@ -126,7 +132,10 @@ class SearchEngine { return baseQuery to completeParams } - fun parseQuery(query: String, enableWildcard: Boolean = true) = queryCache.getOrPut(query) { + fun parseQuery( + query: String, + enableWildcard: Boolean = true + ) = queryCache.getOrPut(query) { val res = mutableListOf() var inQuotes = false @@ -144,18 +153,20 @@ class SearchEngine { } } - fun flushToText() = Text().apply { - components += queuedText - queuedText.clear() - } + fun flushToText() = + Text().apply { + components += queuedText + queuedText.clear() + } fun flushAll() { flushText() if (queuedText.isNotEmpty() || namespace != null) { - val component = namespace?.apply { - tag = flushToText() - namespace = null - } ?: flushToText() + val component = + namespace?.apply { + tag = flushToText() + namespace = null + } ?: flushToText() component.excluded = nextIsExcluded component.exact = nextIsExact res += component @@ -179,17 +190,18 @@ class SearchEngine { flushText() var flushed = flushToText().rawTextOnly() // Map tag aliases - flushed = when (flushed) { - "a" -> "artist" - "c", "char" -> "character" - "f" -> "female" - "g", "creator", "circle" -> "group" - "l", "lang" -> "language" - "m" -> "male" - "p", "series" -> "parody" - "r" -> "reclass" - else -> flushed - } + flushed = + when (flushed) { + "a" -> "artist" + "c", "char" -> "character" + "f" -> "female" + "g", "creator", "circle" -> "group" + "l", "lang" -> "language" + "m" -> "male" + "p", "series" -> "parody" + "r" -> "reclass" + else -> flushed + } namespace = Namespace(flushed, null) } else if (char == ' ' && !inQuotes) { flushAll() diff --git a/app/src/main/java/exh/search/Text.kt b/app/src/main/java/exh/search/Text.kt index eccf75547b1e..8b5025ea3b68 100755 --- a/app/src/main/java/exh/search/Text.kt +++ b/app/src/main/java/exh/search/Text.kt @@ -27,15 +27,16 @@ class Text : QueryComponent() { fun asLenientTagQueries(): List { if (lenientTagQueries == null) { - lenientTagQueries = listOf( - // Match beginning of tag - rBaseBuilder().append("%").toString(), - // Tag word matcher (that matches multiple words) - // Can't make it match a single word in Realm :( - StringBuilder(" ").append(rBaseBuilder()).append(" ").toString(), - StringBuilder(" ").append(rBaseBuilder()).toString(), - rBaseBuilder().append(" ").toString() - ) + lenientTagQueries = + listOf( + // Match beginning of tag + rBaseBuilder().append("%").toString(), + // Tag word matcher (that matches multiple words) + // Can't make it match a single word in Realm :( + StringBuilder(" ").append(rBaseBuilder()).append(" ").toString(), + StringBuilder(" ").append(rBaseBuilder()).toString(), + rBaseBuilder().append(" ").toString() + ) } return lenientTagQueries!! } @@ -52,13 +53,15 @@ class Text : QueryComponent() { return builder } - fun rawTextOnly() = if (rawText != null) { - rawText!! - } else { - rawText = components - .joinToString(separator = "", transform = { it.rawText }) - rawText!! - } + fun rawTextOnly() = + if (rawText != null) { + rawText!! + } else { + rawText = + components + .joinToString(separator = "", transform = { it.rawText }) + rawText!! + } fun rawTextEscapedForLike() = escapeLike(rawTextOnly()) } diff --git a/app/src/main/java/exh/smartsearch/SmartSearchEngine.kt b/app/src/main/java/exh/smartsearch/SmartSearchEngine.kt index aa7af314684c..3f5ae06d6e91 100755 --- a/app/src/main/java/exh/smartsearch/SmartSearchEngine.kt +++ b/app/src/main/java/exh/smartsearch/SmartSearchEngine.kt @@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.SManga import exh.util.await import info.debatty.java.stringsimilarity.NormalizedLevenshtein -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -15,6 +14,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.supervisorScope import rx.schedulers.Schedulers import uy.kohesive.injekt.injectLazy +import kotlin.coroutines.CoroutineContext class SmartSearchEngine( parentContext: CoroutineContext, @@ -26,53 +26,68 @@ class SmartSearchEngine( private val normalizedLevenshtein = NormalizedLevenshtein() - suspend fun smartSearch(source: CatalogueSource, title: String): SManga? { + suspend fun smartSearch( + source: CatalogueSource, + title: String + ): SManga? { val cleanedTitle = cleanSmartSearchTitle(title) val queries = getSmartSearchQueries(cleanedTitle) - val eligibleManga = supervisorScope { - queries.map { query -> - async(Dispatchers.Default) { - val builtQuery = if (extraSearchParams != null) { - "$query ${extraSearchParams.trim()}" - } else query - - val searchResults = source.fetchSearchManga(1, builtQuery, FilterList()) - .toSingle().await(Schedulers.io()) - - searchResults.mangas.map { - val cleanedMangaTitle = cleanSmartSearchTitle(it.title) - val normalizedDistance = normalizedLevenshtein.similarity(cleanedTitle, cleanedMangaTitle) - SearchEntry(it, normalizedDistance) - }.filter { (_, normalizedDistance) -> - normalizedDistance >= MIN_SMART_ELIGIBLE_THRESHOLD + val eligibleManga = + supervisorScope { + queries.map { query -> + async(Dispatchers.Default) { + val builtQuery = + if (extraSearchParams != null) { + "$query ${extraSearchParams.trim()}" + } else { + query + } + + val searchResults = + source.fetchSearchManga(1, builtQuery, FilterList()) + .toSingle().await(Schedulers.io()) + + searchResults.mangas.map { + val cleanedMangaTitle = cleanSmartSearchTitle(it.title) + val normalizedDistance = normalizedLevenshtein.similarity(cleanedTitle, cleanedMangaTitle) + SearchEntry(it, normalizedDistance) + }.filter { (_, normalizedDistance) -> + normalizedDistance >= MIN_SMART_ELIGIBLE_THRESHOLD + } } - } - }.flatMap { it.await() } - } + }.flatMap { it.await() } + } return eligibleManga.maxByOrNull { it.dist }?.manga } - suspend fun normalSearch(source: CatalogueSource, title: String): SManga? { - val eligibleManga = supervisorScope { - val searchQuery = if (extraSearchParams != null) { - "$title ${extraSearchParams.trim()}" - } else title - val searchResults = source.fetchSearchManga(1, searchQuery, FilterList()).toSingle().await(Schedulers.io()) + suspend fun normalSearch( + source: CatalogueSource, + title: String + ): SManga? { + val eligibleManga = + supervisorScope { + val searchQuery = + if (extraSearchParams != null) { + "$title ${extraSearchParams.trim()}" + } else { + title + } + val searchResults = source.fetchSearchManga(1, searchQuery, FilterList()).toSingle().await(Schedulers.io()) - if (searchResults.mangas.size == 1) { - return@supervisorScope listOf(SearchEntry(searchResults.mangas.first(), 0.0)) - } + if (searchResults.mangas.size == 1) { + return@supervisorScope listOf(SearchEntry(searchResults.mangas.first(), 0.0)) + } - searchResults.mangas.map { - val normalizedDistance = normalizedLevenshtein.similarity(title, it.title) - SearchEntry(it, normalizedDistance) - }.filter { (_, normalizedDistance) -> - normalizedDistance >= MIN_NORMAL_ELIGIBLE_THRESHOLD + searchResults.mangas.map { + val normalizedDistance = normalizedLevenshtein.similarity(title, it.title) + SearchEntry(it, normalizedDistance) + }.filter { (_, normalizedDistance) -> + normalizedDistance >= MIN_NORMAL_ELIGIBLE_THRESHOLD + } } - } return eligibleManga.maxByOrNull { it.dist }?.manga } @@ -91,13 +106,14 @@ class SmartSearchEngine( // Search first two words // Search first word - val searchQueries = listOf( - listOf(cleanedTitle), - splitSortedByLargest.take(2), - splitSortedByLargest.take(1), - splitCleanedTitle.take(2), - splitCleanedTitle.take(1) - ) + val searchQueries = + listOf( + listOf(cleanedTitle), + splitSortedByLargest.take(2), + splitSortedByLargest.take(1), + splitCleanedTitle.take(2), + splitCleanedTitle.take(1) + ) return searchQueries.map { it.joinToString(" ").trim() @@ -122,19 +138,25 @@ class SmartSearchEngine( return cleanedTitle } - private fun removeTextInBrackets(text: String, readForward: Boolean): String { - val bracketPairs = listOf( - '(' to ')', - '[' to ']', - '<' to '>', - '{' to '}' - ) - var openingBracketPairs = bracketPairs.mapIndexed { index, (opening, _) -> - opening to index - }.toMap() - var closingBracketPairs = bracketPairs.mapIndexed { index, (_, closing) -> - closing to index - }.toMap() + private fun removeTextInBrackets( + text: String, + readForward: Boolean + ): String { + val bracketPairs = + listOf( + '(' to ')', + '[' to ']', + '<' to '>', + '{' to '}' + ) + var openingBracketPairs = + bracketPairs.mapIndexed { index, (opening, _) -> + opening to index + }.toMap() + var closingBracketPairs = + bracketPairs.mapIndexed { index, (_, closing) -> + closing to index + }.toMap() // Reverse pairs if reading backwards if (!readForward) { @@ -174,7 +196,10 @@ class SmartSearchEngine( * @param sManga the manga from the source. * @return a manga from the database. */ - suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { + suspend fun networkToLocalManga( + sManga: SManga, + sourceId: Long + ): Manga { var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking() if (localManga == null) { val newManga = Manga.create(sManga.url, sManga.title, sourceId) diff --git a/app/src/main/java/exh/source/BlacklistedSources.kt b/app/src/main/java/exh/source/BlacklistedSources.kt index 08566591b1b1..3112003d39aa 100644 --- a/app/src/main/java/exh/source/BlacklistedSources.kt +++ b/app/src/main/java/exh/source/BlacklistedSources.kt @@ -3,40 +3,45 @@ package exh.source import exh.MERGED_SOURCE_ID object BlacklistedSources { - val NHENTAI_EXT_SOURCES = listOf( - 3122156392225024195, - 4726175775739752699, - 2203215402871965477 - ) - val EHENTAI_EXT_SOURCES = listOf( - 8100626124886895451, - 57122881048805941, - 4678440076103929247, - 1876021963378735852, - 3955189842350477641, - 4348288691341764259, - 773611868725221145, - 5759417018342755550, - 825187715438990384, - 6116711405602166104, - 7151438547982231541, - 2171445159732592630, - 3032959619549451093, - 5980349886941016589, - 6073266008352078708, - 5499077866612745456, - 6140480779421365791 - ) + val NHENTAI_EXT_SOURCES = + listOf( + 3122156392225024195, + 4726175775739752699, + 2203215402871965477 + ) + val EHENTAI_EXT_SOURCES = + listOf( + 8100626124886895451, + 57122881048805941, + 4678440076103929247, + 1876021963378735852, + 3955189842350477641, + 4348288691341764259, + 773611868725221145, + 5759417018342755550, + 825187715438990384, + 6116711405602166104, + 7151438547982231541, + 2171445159732592630, + 3032959619549451093, + 5980349886941016589, + 6073266008352078708, + 5499077866612745456, + 6140480779421365791 + ) - val BLACKLISTED_EXT_SOURCES = NHENTAI_EXT_SOURCES + - EHENTAI_EXT_SOURCES + val BLACKLISTED_EXT_SOURCES = + NHENTAI_EXT_SOURCES + + EHENTAI_EXT_SOURCES - val BLACKLISTED_EXTENSIONS = listOf( - "eu.kanade.tachiyomi.extension.all.ehentai", - "eu.kanade.tachiyomi.extension.all.nhentai" - ) + val BLACKLISTED_EXTENSIONS = + listOf( + "eu.kanade.tachiyomi.extension.all.ehentai", + "eu.kanade.tachiyomi.extension.all.nhentai" + ) - var HIDDEN_SOURCES = listOf( - MERGED_SOURCE_ID - ) + var HIDDEN_SOURCES = + listOf( + MERGED_SOURCE_ID + ) } diff --git a/app/src/main/java/exh/source/DelegatedHttpSource.kt b/app/src/main/java/exh/source/DelegatedHttpSource.kt index 8e39ef422e04..6424d79db7e7 100755 --- a/app/src/main/java/exh/source/DelegatedHttpSource.kt +++ b/app/src/main/java/exh/source/DelegatedHttpSource.kt @@ -22,16 +22,14 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * * @param page the page number to retrieve. */ - override fun popularMangaRequest(page: Int) = - throw UnsupportedOperationException("Should never be called!") + override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a [MangasPage] object. * * @param response the response from the site. */ - override fun popularMangaParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun popularMangaParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Returns the request for the search manga given the page. @@ -40,64 +38,60 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param query the search query. * @param filters the list of filters to apply. */ - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = - throw UnsupportedOperationException("Should never be called!") + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList + ) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a [MangasPage] object. * * @param response the response from the site. */ - override fun searchMangaParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun searchMangaParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Returns the request for latest manga given the page. * * @param page the page number to retrieve. */ - override fun latestUpdatesRequest(page: Int) = - throw UnsupportedOperationException("Should never be called!") + override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a [MangasPage] object. * * @param response the response from the site. */ - override fun latestUpdatesParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns the details of a manga. * * @param response the response from the site. */ - override fun mangaDetailsParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a list of chapters. * * @param response the response from the site. */ - override fun chapterListParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun chapterListParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a list of pages. * * @param response the response from the site. */ - override fun pageListParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun pageListParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns the absolute url to the source image. * * @param response the response from the site. */ - override fun imageUrlParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Base url of the website without the trailing slash, like: http://mysite.com @@ -108,6 +102,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * Whether the source has support for latest updates. */ override val supportsLatest get() = delegate.supportsLatest + /** * Name of the source. */ @@ -121,6 +116,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * Note the generated id sets the sign bit to 0. */ override val id get() = delegate.id + /** * Default network client for doing requests. */ @@ -157,7 +153,11 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param query the search query. * @param filters the list of filters to apply. */ - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList + ): Observable { ensureDelegateCompatible() return delegate.fetchSearchManga(page, query, filters) } @@ -233,7 +233,10 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { * @param chapter the chapter to be added. * @param manga the manga of the chapter. */ - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { + override fun prepareNewChapter( + chapter: SChapter, + manga: SManga + ) { ensureDelegateCompatible() return delegate.prepareNewChapter(chapter, manga) } @@ -247,7 +250,9 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() { if (versionId != delegate.versionId || lang != delegate.lang ) { - throw IncompatibleDelegateException("Delegate source is not compatible (versionId: $versionId <=> ${delegate.versionId}, lang: $lang <=> ${delegate.lang})!") + throw IncompatibleDelegateException( + "Delegate source is not compatible (versionId: $versionId <=> ${delegate.versionId}, lang: $lang <=> ${delegate.lang})!" + ) } } diff --git a/app/src/main/java/exh/source/EnhancedHttpSource.kt b/app/src/main/java/exh/source/EnhancedHttpSource.kt index 81abcd960e07..89e29c1cfefb 100755 --- a/app/src/main/java/exh/source/EnhancedHttpSource.kt +++ b/app/src/main/java/exh/source/EnhancedHttpSource.kt @@ -22,16 +22,14 @@ class EnhancedHttpSource( * * @param page the page number to retrieve. */ - override fun popularMangaRequest(page: Int) = - throw UnsupportedOperationException("Should never be called!") + override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a [MangasPage] object. * * @param response the response from the site. */ - override fun popularMangaParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun popularMangaParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Returns the request for the search manga given the page. @@ -40,64 +38,60 @@ class EnhancedHttpSource( * @param query the search query. * @param filters the list of filters to apply. */ - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = - throw UnsupportedOperationException("Should never be called!") + override fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList + ) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a [MangasPage] object. * * @param response the response from the site. */ - override fun searchMangaParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun searchMangaParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Returns the request for latest manga given the page. * * @param page the page number to retrieve. */ - override fun latestUpdatesRequest(page: Int) = - throw UnsupportedOperationException("Should never be called!") + override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a [MangasPage] object. * * @param response the response from the site. */ - override fun latestUpdatesParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns the details of a manga. * * @param response the response from the site. */ - override fun mangaDetailsParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a list of chapters. * * @param response the response from the site. */ - override fun chapterListParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun chapterListParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns a list of pages. * * @param response the response from the site. */ - override fun pageListParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun pageListParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Parses the response from the site and returns the absolute url to the source image. * * @param response the response from the site. */ - override fun imageUrlParse(response: Response) = - throw UnsupportedOperationException("Should never be called!") + override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Should never be called!") /** * Base url of the website without the trailing slash, like: http://mysite.com @@ -108,6 +102,7 @@ class EnhancedHttpSource( * Whether the source has support for latest updates. */ override val supportsLatest get() = source().supportsLatest + /** * Name of the source. */ @@ -126,6 +121,7 @@ class EnhancedHttpSource( * Note the generated id sets the sign bit to 0. */ override val id get() = source().id + /** * Default network client for doing requests. */ @@ -152,8 +148,11 @@ class EnhancedHttpSource( * @param query the search query. * @param filters the list of filters to apply. */ - override fun fetchSearchManga(page: Int, query: String, filters: FilterList) = - source().fetchSearchManga(page, query, filters) + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList + ) = source().fetchSearchManga(page, query, filters) /** * Returns an observable containing a page with a list of latest manga updates. @@ -208,8 +207,10 @@ class EnhancedHttpSource( * @param chapter the chapter to be added. * @param manga the manga of the chapter. */ - override fun prepareNewChapter(chapter: SChapter, manga: SManga) = - source().prepareNewChapter(chapter, manga) + override fun prepareNewChapter( + chapter: SChapter, + manga: SManga + ) = source().prepareNewChapter(chapter, manga) /** * Returns the list of filters for the source. @@ -225,8 +226,9 @@ class EnhancedHttpSource( } } -fun Source.getMainSource(): Source = if (this is EnhancedHttpSource) { - this.source() -} else { - this -} +fun Source.getMainSource(): Source = + if (this is EnhancedHttpSource) { + this.source() + } else { + this + } diff --git a/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt b/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt index 5bc9a8f6b57a..99868ee16a3b 100644 --- a/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt +++ b/app/src/main/java/exh/uconfig/ConfiguringDialogController.kt @@ -7,8 +7,8 @@ import com.afollestad.materialdialogs.MaterialDialog import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.system.toast -import kotlin.concurrent.thread import timber.log.Timber +import kotlin.concurrent.thread class ConfiguringDialogController : DialogController() { private var materialDialog: MaterialDialog? = null diff --git a/app/src/main/java/exh/uconfig/EHConfigurator.kt b/app/src/main/java/exh/uconfig/EHConfigurator.kt index efad31b2067c..ef0aa26b9c21 100644 --- a/app/src/main/java/exh/uconfig/EHConfigurator.kt +++ b/app/src/main/java/exh/uconfig/EHConfigurator.kt @@ -17,32 +17,33 @@ class EHConfigurator { private val prefs: PreferencesHelper by injectLazy() private val sources: SourceManager by injectLazy() - private val configuratorClient = OkHttpClient.Builder() - .maybeInjectEHLogger() - .build() + private val configuratorClient = + OkHttpClient.Builder() + .maybeInjectEHLogger() + .build() - private fun EHentai.requestWithCreds(sp: Int = 1) = Request.Builder() - .addHeader("Cookie", cookiesHeader(sp)) + private fun EHentai.requestWithCreds(sp: Int = 1) = + Request.Builder() + .addHeader("Cookie", cookiesHeader(sp)) private fun EHentai.execProfileActions( action: String, name: String, set: String, sp: Int - ) = - configuratorClient.newCall( - requestWithCreds(sp) - .url(uconfigUrl) - .post( - FormBody.Builder() - .add("profile_action", action) - .add("profile_name", name) - .add("profile_set", set) - .build() - ) - .build() - ) - .execute() + ) = configuratorClient.newCall( + requestWithCreds(sp) + .url(uconfigUrl) + .post( + FormBody.Builder() + .add("profile_action", action) + .add("profile_name", name) + .add("profile_set", set) + .build() + ) + .build() + ) + .execute() private val EHentai.uconfigUrl get() = baseUrl + UCONFIG_URL @@ -51,12 +52,13 @@ class EHConfigurator { val exhSource = sources.get(EXH_SOURCE_ID) as EHentai // Get hath perks - val perksPage = configuratorClient.newCall( - ehSource.requestWithCreds() - .url(HATH_PERKS_URL) - .build() - ) - .execute().asJsoup() + val perksPage = + configuratorClient.newCall( + ehSource.requestWithCreds() + .url(HATH_PERKS_URL) + .build() + ) + .execute().asJsoup() val hathPerks = EHHathPerksResponse() @@ -83,7 +85,10 @@ class EHConfigurator { configure(exhSource, hathPerks) } - fun configure(source: EHentai, hathPerks: EHHathPerksResponse) { + fun configure( + source: EHentai, + hathPerks: EHHathPerksResponse + ) { // Delete old app profiles val scanReq = source.requestWithCreds().url(source.uconfigUrl).build() val resp = configuratorClient.newCall(scanReq).execute().asJsoup() @@ -109,12 +114,13 @@ class EHConfigurator { // Create profile in available slot val slot = availableProfiles.first() - val response = source.execProfileActions( - "create", - PROFILE_NAME, - slot.toString(), - 1 - ) + val response = + source.execProfileActions( + "create", + PROFILE_NAME, + slot.toString(), + 1 + ) // Build new profile val form = EhUConfigBuilder().build(hathPerks) @@ -130,15 +136,18 @@ class EHConfigurator { // Persist slot + sk source.spPref().set(slot) - val keyCookie = response.headers.toMultimap()["Set-Cookie"]?.find { - it.startsWith("sk=") - }?.removePrefix("sk=")?.substringBefore(';') - val sessionCookie = response.headers.toMultimap()["Set-Cookie"]?.find { - it.startsWith("s=") - }?.removePrefix("s=")?.substringBefore(';') - val hathPerksCookie = response.headers.toMultimap()["Set-Cookie"]?.find { - it.startsWith("hath_perks=") - }?.removePrefix("hath_perks=")?.substringBefore(';') + val keyCookie = + response.headers.toMultimap()["Set-Cookie"]?.find { + it.startsWith("sk=") + }?.removePrefix("sk=")?.substringBefore(';') + val sessionCookie = + response.headers.toMultimap()["Set-Cookie"]?.find { + it.startsWith("s=") + }?.removePrefix("s=")?.substringBefore(';') + val hathPerksCookie = + response.headers.toMultimap()["Set-Cookie"]?.find { + it.startsWith("hath_perks=") + }?.removePrefix("hath_perks=")?.substringBefore(';') if (keyCookie != null) { prefs.eh_settingsKey().set(keyCookie) @@ -154,6 +163,7 @@ class EHConfigurator { companion object { private const val PROFILE_NAME = "TachiyomiEH App" private const val UCONFIG_URL = "/uconfig.php" + // Always use E-H here as EXH does not have a perks page private const val HATH_PERKS_URL = "https://e-hentai.org/hathperks.php" private const val PROFILE_SELECTOR = "[name=profile_set] > option" diff --git a/app/src/main/java/exh/uconfig/EhUConfigBuilder.kt b/app/src/main/java/exh/uconfig/EhUConfigBuilder.kt index f8f08a734be6..d09d2a43a4e7 100644 --- a/app/src/main/java/exh/uconfig/EhUConfigBuilder.kt +++ b/app/src/main/java/exh/uconfig/EhUConfigBuilder.kt @@ -10,51 +10,57 @@ class EhUConfigBuilder { fun build(hathPerks: EHHathPerksResponse): FormBody { val configItems = mutableListOf() - configItems += when ( - prefs.imageQuality() - .get() - .lowercase() - ) { - "ovrs_2400" -> Entry.ImageSize.`2400` - "ovrs_1600" -> Entry.ImageSize.`1600` - "high" -> Entry.ImageSize.`1280` - "med" -> Entry.ImageSize.`980` - "low" -> Entry.ImageSize.`780` - "auto" -> Entry.ImageSize.AUTO - else -> Entry.ImageSize.AUTO - } - - configItems += when (prefs.useHentaiAtHome().get()) { - 2 -> Entry.UseHentaiAtHome.NO - 1 -> Entry.UseHentaiAtHome.DEFAULTONLY - else -> Entry.UseHentaiAtHome.ANY - } - - configItems += if (prefs.useJapaneseTitle().get()) { - Entry.TitleDisplayLanguage.JAPANESE - } else { - Entry.TitleDisplayLanguage.DEFAULT - } - - configItems += if (prefs.eh_useOriginalImages().get()) { - Entry.UseOriginalImages.YES - } else { - Entry.UseOriginalImages.NO - } - - configItems += when { - hathPerks.allThumbs -> Entry.ThumbnailRows.`40` - hathPerks.thumbsUp -> Entry.ThumbnailRows.`20` - hathPerks.moreThumbs -> Entry.ThumbnailRows.`10` - else -> Entry.ThumbnailRows.`4` - } - - configItems += when { - hathPerks.pagingEnlargementIII -> Entry.SearchResultsCount.`200` - hathPerks.pagingEnlargementII -> Entry.SearchResultsCount.`100` - hathPerks.pagingEnlargementI -> Entry.SearchResultsCount.`50` - else -> Entry.SearchResultsCount.`25` - } + configItems += + when ( + prefs.imageQuality() + .get() + .lowercase() + ) { + "ovrs_2400" -> Entry.ImageSize.`2400` + "ovrs_1600" -> Entry.ImageSize.`1600` + "high" -> Entry.ImageSize.`1280` + "med" -> Entry.ImageSize.`980` + "low" -> Entry.ImageSize.`780` + "auto" -> Entry.ImageSize.AUTO + else -> Entry.ImageSize.AUTO + } + + configItems += + when (prefs.useHentaiAtHome().get()) { + 2 -> Entry.UseHentaiAtHome.NO + 1 -> Entry.UseHentaiAtHome.DEFAULTONLY + else -> Entry.UseHentaiAtHome.ANY + } + + configItems += + if (prefs.useJapaneseTitle().get()) { + Entry.TitleDisplayLanguage.JAPANESE + } else { + Entry.TitleDisplayLanguage.DEFAULT + } + + configItems += + if (prefs.eh_useOriginalImages().get()) { + Entry.UseOriginalImages.YES + } else { + Entry.UseOriginalImages.NO + } + + configItems += + when { + hathPerks.allThumbs -> Entry.ThumbnailRows.`40` + hathPerks.thumbsUp -> Entry.ThumbnailRows.`20` + hathPerks.moreThumbs -> Entry.ThumbnailRows.`10` + else -> Entry.ThumbnailRows.`4` + } + + configItems += + when { + hathPerks.pagingEnlargementIII -> Entry.SearchResultsCount.`200` + hathPerks.pagingEnlargementII -> Entry.SearchResultsCount.`100` + hathPerks.pagingEnlargementI -> Entry.SearchResultsCount.`50` + else -> Entry.SearchResultsCount.`25` + } configItems += Entry.DisplayMode() configItems += Entry.UseMPV() @@ -81,7 +87,8 @@ object Entry { enum class UseHentaiAtHome(override val value: String) : ConfigItem { ANY("0"), DEFAULTONLY("1"), - NO("2"); + NO("2") + ; override val key = "uh" } @@ -92,14 +99,16 @@ object Entry { `1600`("4"), `1280`("3"), `980`("2"), - `780`("1"); + `780`("1") + ; override val key = "xr" } enum class TitleDisplayLanguage(override val value: String) : ConfigItem { DEFAULT("0"), - JAPANESE("1"); + JAPANESE("1") + ; override val key = "tl" } @@ -114,7 +123,8 @@ object Entry { `25`("0"), `50`("1"), `100`("2"), - `200`("3"); + `200`("3") + ; override val key = "rc" } @@ -123,14 +133,16 @@ object Entry { `4`("0"), `10`("1"), `20`("2"), - `40`("3"); + `40`("3") + ; override val key = "tr" } enum class UseOriginalImages(override val value: String) : ConfigItem { NO("0"), - YES("1"); + YES("1") + ; override val key = "oi" } @@ -158,7 +170,6 @@ object Entry { } class Categories() { - fun categoryConfigs(list: List): List { return listOf( Doujinshi(list[0]), @@ -178,38 +189,47 @@ object Entry { override val value = if (exclude) "1" else "0" override val key = "ct_doujinshi" } + private class Manga(exclude: Boolean) : ConfigItem { override val value = if (exclude) "1" else "0" override val key = "ct_manga" } + private class ArtistCG(exclude: Boolean) : ConfigItem { override val value = if (exclude) "1" else "0" override val key = "ct_artistcg" } + private class GameCG(exclude: Boolean) : ConfigItem { override val value = if (exclude) "1" else "0" override val key = "ct_gamecg" } + private class Western(exclude: Boolean) : ConfigItem { override val value = if (exclude) "1" else "0" override val key = "ct_western" } + private class NonH(exclude: Boolean) : ConfigItem { override val value = if (exclude) "1" else "0" override val key = "ct_non-h" } + private class ImageSet(exclude: Boolean) : ConfigItem { override val value = if (exclude) "1" else "0" override val key = "ct_imageset" } + private class Cosplay(exclude: Boolean) : ConfigItem { override val value = if (exclude) "1" else "0" override val key = "ct_cosplay" } + private class AsianPorn(exclude: Boolean) : ConfigItem { override val value = if (exclude) "1" else "0" override val key = "ct_asianporn" } + private class Misc(exclude: Boolean) : ConfigItem { override val value = if (exclude) "1" else "0" override val key = "ct_misc_div" @@ -217,7 +237,6 @@ object Entry { } class LanguageSystem { - fun getLanguages(values: List): List { return Japanese(values[0].split("*").map { it.toBoolean() }).configs + English(values[1].split("*").map { it.toBoolean() }).configs + @@ -239,58 +258,65 @@ object Entry { } private class Japanese(values: List) { - - val configs = listOf( - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Translated(values[1]), + Rewrite(values[2]) + ) class Translated(value: Boolean) : ConfigItem { override val key = "xl_1024" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2048" override val value = if (value) "checked" else "" } } - private class English(values: List) { - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + private class English(values: List) { + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_1" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1025" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2049" override val value = if (value) "checked" else "" } } - private class Chinese(values: List) { - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + private class Chinese(values: List) { + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_10" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1034" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2058" override val value = if (value) "checked" else "" @@ -298,21 +324,23 @@ object Entry { } private class Dutch(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_20" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1044" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2068" override val value = if (value) "checked" else "" @@ -320,21 +348,23 @@ object Entry { } private class French(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_30" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1054" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2078" override val value = if (value) "checked" else "" @@ -342,21 +372,23 @@ object Entry { } private class German(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_40" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1064" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2088" override val value = if (value) "checked" else "" @@ -364,21 +396,23 @@ object Entry { } private class Hungarian(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_50" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1074" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2098" override val value = if (value) "checked" else "" @@ -386,21 +420,23 @@ object Entry { } private class Italian(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_60" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1084" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2108" override val value = if (value) "checked" else "" @@ -408,21 +444,23 @@ object Entry { } private class Korean(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_70" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1094" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2118" override val value = if (value) "checked" else "" @@ -430,21 +468,23 @@ object Entry { } private class Polish(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_80" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1104" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2128" override val value = if (value) "checked" else "" @@ -452,21 +492,23 @@ object Entry { } private class Portuguese(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_90" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1114" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2138" override val value = if (value) "checked" else "" @@ -474,21 +516,23 @@ object Entry { } private class Russian(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_100" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1124" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2148" override val value = if (value) "checked" else "" @@ -496,21 +540,23 @@ object Entry { } private class Spanish(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_110" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1134" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2158" override val value = if (value) "checked" else "" @@ -518,21 +564,23 @@ object Entry { } private class Thai(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_120" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1144" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2168" override val value = if (value) "checked" else "" @@ -540,21 +588,23 @@ object Entry { } private class Vietnamese(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_130" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1154" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2178" override val value = if (value) "checked" else "" @@ -562,21 +612,23 @@ object Entry { } private class NotAvailable(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_254" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1278" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2302" override val value = if (value) "checked" else "" @@ -584,21 +636,23 @@ object Entry { } private class Other(values: List) { - - val configs = listOf( - Original(values[0]), - Translated(values[1]), - Rewrite(values[2]) - ) + val configs = + listOf( + Original(values[0]), + Translated(values[1]), + Rewrite(values[2]) + ) class Original(value: Boolean) : ConfigItem { override val key = "xl_255" override val value = if (value) "checked" else "" } + class Translated(value: Boolean) : ConfigItem { override val key = "xl_1279" override val value = if (value) "checked" else "" } + class Rewrite(value: Boolean) : ConfigItem { override val key = "xl_2303" override val value = if (value) "checked" else "" diff --git a/app/src/main/java/exh/uconfig/WarnConfigureDialogController.kt b/app/src/main/java/exh/uconfig/WarnConfigureDialogController.kt index 8dcbd9ab4b92..33090503100e 100644 --- a/app/src/main/java/exh/uconfig/WarnConfigureDialogController.kt +++ b/app/src/main/java/exh/uconfig/WarnConfigureDialogController.kt @@ -12,16 +12,17 @@ import uy.kohesive.injekt.injectLazy class WarnConfigureDialogController : DialogController() { private val prefs: PreferencesHelper by injectLazy() + override fun onCreateDialog(savedViewState: Bundle?): Dialog { return MaterialDialog(activity!!) .title(text = "Settings profile note") .message( text = - """ + """ The app will now add a new settings profile on E-Hentai and ExHentai to optimize app performance. Please ensure that you have less than three profiles on both sites. If you have no idea what settings profiles are, then it probably doesn't matter, just hit 'OK'. - """.trimIndent() + """.trimIndent() ) .positiveButton(android.R.string.ok) { prefs.eh_showSettingsUploadWarning().set(false) diff --git a/app/src/main/java/exh/ui/LoaderManager.kt b/app/src/main/java/exh/ui/LoaderManager.kt index f30318e19ded..a7731907c469 100644 --- a/app/src/main/java/exh/ui/LoaderManager.kt +++ b/app/src/main/java/exh/ui/LoaderManager.kt @@ -1,11 +1,11 @@ package exh.ui -import java.util.UUID -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.util.UUID +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext typealias LoadingHandle = String @@ -19,11 +19,12 @@ class LoaderManager(parentContext: CoroutineContext = EmptyCoroutineContext) : C var loadingChangeListener: (suspend (newIsLoading: Boolean) -> Unit)? = null fun openProgressBar(): LoadingHandle { - val (handle, shouldUpdateLoadingStatus) = synchronized(this) { - val handle = UUID.randomUUID().toString() - openLoadingHandles += handle - handle to (openLoadingHandles.size == 1) - } + val (handle, shouldUpdateLoadingStatus) = + synchronized(this) { + val handle = UUID.randomUUID().toString() + openLoadingHandles += handle + handle to (openLoadingHandles.size == 1) + } if (shouldUpdateLoadingStatus) { launch { @@ -38,9 +39,10 @@ class LoaderManager(parentContext: CoroutineContext = EmptyCoroutineContext) : C fun closeProgressBar(handle: LoadingHandle?) { if (handle == null) return - val shouldUpdateLoadingStatus = synchronized(this) { - openLoadingHandles.remove(handle) && openLoadingHandles.isEmpty() - } + val shouldUpdateLoadingStatus = + synchronized(this) { + openLoadingHandles.remove(handle) && openLoadingHandles.isEmpty() + } if (shouldUpdateLoadingStatus) { launch { diff --git a/app/src/main/java/exh/ui/base/BaseExhController.kt b/app/src/main/java/exh/ui/base/BaseExhController.kt index 6d5edb872afc..08e5689c79a8 100644 --- a/app/src/main/java/exh/ui/base/BaseExhController.kt +++ b/app/src/main/java/exh/ui/base/BaseExhController.kt @@ -7,11 +7,11 @@ import android.view.ViewGroup import androidx.annotation.LayoutRes import androidx.viewbinding.ViewBinding import eu.kanade.tachiyomi.ui.base.controller.BaseController -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel +import kotlin.coroutines.CoroutineContext abstract class BaseExhController(bundle: Bundle? = null) : BaseController(bundle), CoroutineScope { abstract val layoutId: Int @@ -19,7 +19,10 @@ abstract class BaseExhController(bundle: Bundle? = null) : Bas override val coroutineContext: CoroutineContext = Job() + Dispatchers.Default - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { return inflater.inflate(layoutId, container, false) } diff --git a/app/src/main/java/exh/ui/batchadd/BatchAddController.kt b/app/src/main/java/exh/ui/batchadd/BatchAddController.kt index 79998068dbb5..c3cd648ef885 100755 --- a/app/src/main/java/exh/ui/batchadd/BatchAddController.kt +++ b/app/src/main/java/exh/ui/batchadd/BatchAddController.kt @@ -19,7 +19,10 @@ import rx.subscriptions.CompositeSubscription * Batch add screen */ class BatchAddController : NucleusController() { - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = EhFragmentBatchAddBinding.inflate(inflater) return binding.root } @@ -53,36 +56,39 @@ class BatchAddController : NucleusController - // Show hide dismiss button - binding.progressDismissBtn.visibility = - if (progress == total) { - View.VISIBLE - } else { - View.GONE - } - - formatProgress(progress, total) - }.subscribeUntilDestroy { - binding.progressText.text = it - } - - progressSubscriptions += presenter.progressTotalRelay - .onBackpressureBuffer() - .observeOn(AndroidSchedulers.mainThread()) - .subscribeUntilDestroy { - binding.progressBar.max = it - } - - progressSubscriptions += presenter.progressRelay - .onBackpressureBuffer() - .observeOn(AndroidSchedulers.mainThread()) - .subscribeUntilDestroy { - binding.progressBar.progress = it - } + progressSubscriptions += + presenter.progressRelay + .onBackpressureBuffer() + .observeOn(AndroidSchedulers.mainThread()) + .combineLatest(presenter.progressTotalRelay) { progress, total -> + // Show hide dismiss button + binding.progressDismissBtn.visibility = + if (progress == total) { + View.VISIBLE + } else { + View.GONE + } + + formatProgress(progress, total) + }.subscribeUntilDestroy { + binding.progressText.text = it + } + + progressSubscriptions += + presenter.progressTotalRelay + .onBackpressureBuffer() + .observeOn(AndroidSchedulers.mainThread()) + .subscribeUntilDestroy { + binding.progressBar.max = it + } + + progressSubscriptions += + presenter.progressRelay + .onBackpressureBuffer() + .observeOn(AndroidSchedulers.mainThread()) + .subscribeUntilDestroy { + binding.progressBar.progress = it + } presenter.eventRelay ?.onBackpressureBuffer() @@ -90,8 +96,8 @@ class BatchAddController : NucleusController.visibility: Int get() = throw UnsupportedOperationException() - set(v) { forEach { it.visibility = v } } + set(v) { + forEach { it.visibility = v } + } private fun showProgress(target: View? = view) { target?.apply { @@ -140,7 +150,10 @@ class BatchAddController : NucleusController() { - private val galleryAdder by lazy { GalleryAdder() } val progressTotalRelay = BehaviorRelay.create(0)!! @@ -27,23 +26,25 @@ class BatchAddPresenter : BasePresenter() { """[0-9]*?\.[a-z0-9]*?:""".toRegex() val testedGalleries: String - testedGalleries = if (regex.containsMatchIn(galleries)) { - regex.findAll(galleries).map { galleryKeys -> - val LinkParts = galleryKeys.value.split(".") - val Link = "${if (Injekt.get().enableExhentai().get()) { - "https://exhentai.org/g/" - } else { - "https://e-hentai.org/g/" - }}${LinkParts[0]}/${LinkParts[1].replace(":", "")}" - Log.d("Batch Add", Link) - Link - }.joinToString(separator = "\n") - } else { - galleries - } - val splitGalleries = testedGalleries.split("\n").mapNotNull { - it.trim().nullIfBlank() - } + testedGalleries = + if (regex.containsMatchIn(galleries)) { + regex.findAll(galleries).map { galleryKeys -> + val LinkParts = galleryKeys.value.split(".") + val Link = "${if (Injekt.get().enableExhentai().get()) { + "https://exhentai.org/g/" + } else { + "https://e-hentai.org/g/" + }}${LinkParts[0]}/${LinkParts[1].replace(":", "")}" + Log.d("Batch Add", Link) + Link + }.joinToString(separator = "\n") + } else { + galleries + } + val splitGalleries = + testedGalleries.split("\n").mapNotNull { + it.trim().nullIfBlank() + } progressRelay.call(0) progressTotalRelay.call(splitGalleries.size) diff --git a/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt b/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt index 386efa38b708..5eeae8c1d7ec 100644 --- a/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt +++ b/app/src/main/java/exh/ui/captcha/AutoSolvingWebViewClient.kt @@ -5,9 +5,9 @@ import android.webkit.WebResourceResponse import android.webkit.WebView import eu.kanade.tachiyomi.util.asJsoup import exh.ui.captcha.BrowserActionActivity.Companion.CROSS_WINDOW_SCRIPT_INNER -import java.nio.charset.Charset import org.jsoup.nodes.DataNode import org.jsoup.nodes.Element +import java.nio.charset.Charset class AutoSolvingWebViewClient( activity: BrowserActionActivity, @@ -16,8 +16,10 @@ class AutoSolvingWebViewClient( headers: Map ) : HeadersInjectingWebViewClient(activity, verifyComplete, injectScript, headers) { - - override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { + override fun shouldInterceptRequest( + view: WebView, + request: WebResourceRequest + ): WebResourceResponse? { // Inject our custom script into the recaptcha iframes val lastPathSegment = request.url.pathSegments.lastOrNull() if (lastPathSegment == "anchor" || lastPathSegment == "bframe") { diff --git a/app/src/main/java/exh/ui/captcha/BasicWebViewClient.kt b/app/src/main/java/exh/ui/captcha/BasicWebViewClient.kt index bed8f1c1abee..0069848499e2 100644 --- a/app/src/main/java/exh/ui/captcha/BasicWebViewClient.kt +++ b/app/src/main/java/exh/ui/captcha/BasicWebViewClient.kt @@ -8,7 +8,10 @@ open class BasicWebViewClient( protected val verifyComplete: (String) -> Boolean, private val injectScript: String? ) : WebViewClient() { - override fun onPageFinished(view: WebView, url: String) { + override fun onPageFinished( + view: WebView, + url: String + ) { super.onPageFinished(view, url) if (verifyComplete(url)) { diff --git a/app/src/main/java/exh/ui/captcha/BrowserActionActivity.kt b/app/src/main/java/exh/ui/captcha/BrowserActionActivity.kt index 059ba70ad389..19f519f94472 100644 --- a/app/src/main/java/exh/ui/captcha/BrowserActionActivity.kt +++ b/app/src/main/java/exh/ui/captcha/BrowserActionActivity.kt @@ -25,9 +25,6 @@ import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.online.HttpSource import exh.source.DelegatedHttpSource import exh.util.melt -import java.io.Serializable -import java.net.URL -import java.util.UUID import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody @@ -38,6 +35,9 @@ import rx.Single import rx.schedulers.Schedulers import timber.log.Timber import uy.kohesive.injekt.injectLazy +import java.io.Serializable +import java.net.URL +import java.util.UUID class BrowserActionActivity : AppCompatActivity() { private val sourceManager: SourceManager by injectLazy() @@ -53,6 +53,7 @@ class BrowserActionActivity : AppCompatActivity() { lateinit var credentialsObservable: Observable private lateinit var binding: EhActivityCaptchaBinding + @SuppressLint("SetJavaScriptEnabled") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -63,20 +64,24 @@ class BrowserActionActivity : AppCompatActivity() { val sourceId = intent.getLongExtra(SOURCE_ID_EXTRA, -1) val originalSource = if (sourceId != -1L) sourceManager.get(sourceId) else null - val source = if (originalSource != null) { - originalSource as? ActionCompletionVerifier - ?: run { - (originalSource as? HttpSource)?.let { - NoopActionCompletionVerifier(it) + val source = + if (originalSource != null) { + originalSource as? ActionCompletionVerifier + ?: run { + (originalSource as? HttpSource)?.let { + NoopActionCompletionVerifier(it) + } } - } - } else null + } else { + null + } - val headers = ( - (source as? HttpSource)?.headers?.toMultimap()?.mapValues { - it.value.joinToString(",") - } ?: emptyMap() - ) + (intent.getSerializableExtra(HEADERS_EXTRA) as? HashMap ?: emptyMap()) + val headers = + ( + (source as? HttpSource)?.headers?.toMultimap()?.mapValues { + it.value.joinToString(",") + } ?: emptyMap() + ) + (intent.getSerializableExtra(HEADERS_EXTRA) as? HashMap ?: emptyMap()) val cookies: HashMap? = intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap @@ -85,9 +90,12 @@ class BrowserActionActivity : AppCompatActivity() { val actionName = intent.getStringExtra(ACTION_NAME_EXTRA) @Suppress("NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE") - val verifyComplete = if (source != null) { - source::verifyComplete!! - } else intent.getSerializableExtra(VERIFY_LAMBDA_EXTRA) as? (String) -> Boolean + val verifyComplete = + if (source != null) { + source::verifyComplete!! + } else { + intent.getSerializableExtra(VERIFY_LAMBDA_EXTRA) as? (String) -> Boolean + } if (verifyComplete == null || url == null) { finish() @@ -96,9 +104,12 @@ class BrowserActionActivity : AppCompatActivity() { val actionStr = actionName ?: "Solve captcha" - binding.toolbar.title = if (source != null) { - "${source.name}: $actionStr" - } else actionStr + binding.toolbar.title = + if (source != null) { + "${source.name}: $actionStr" + } else { + actionStr + } val parsedUrl = URL(url) @@ -117,53 +128,61 @@ class BrowserActionActivity : AppCompatActivity() { var loadedInners = 0 - binding.webview.webChromeClient = object : WebChromeClient() { - override fun onJsAlert(view: WebView?, url: String?, message: String, result: JsResult): Boolean { - if (message.startsWith("exh-")) { - loadedInners++ - // Wait for both inner scripts to be loaded - if (loadedInners >= 2) { - // Attempt to autosolve captcha - if (preferencesHelper.eh_autoSolveCaptchas().get()) { - binding.webview.post { - // 10 seconds to auto-solve captcha - strictValidationStartTime = System.currentTimeMillis() + 1000 * 10 - beginSolveLoop() - beginValidateCaptchaLoop() - binding.webview.evaluateJavascript(SOLVE_UI_SCRIPT_HIDE) { - binding.webview.evaluateJavascript(SOLVE_UI_SCRIPT_SHOW, null) + binding.webview.webChromeClient = + object : WebChromeClient() { + override fun onJsAlert( + view: WebView?, + url: String?, + message: String, + result: JsResult + ): Boolean { + if (message.startsWith("exh-")) { + loadedInners++ + // Wait for both inner scripts to be loaded + if (loadedInners >= 2) { + // Attempt to autosolve captcha + if (preferencesHelper.eh_autoSolveCaptchas().get()) { + binding.webview.post { + // 10 seconds to auto-solve captcha + strictValidationStartTime = System.currentTimeMillis() + 1000 * 10 + beginSolveLoop() + beginValidateCaptchaLoop() + binding.webview.evaluateJavascript(SOLVE_UI_SCRIPT_HIDE) { + binding.webview.evaluateJavascript(SOLVE_UI_SCRIPT_SHOW, null) + } } } } + result.confirm() + return true } - result.confirm() - return true + return false } - return false } - } - binding.webview.webViewClient = if (actionName == null && preferencesHelper.eh_autoSolveCaptchas().get()) { - // Fetch auto-solve credentials early for speed - credentialsObservable = httpClient.newCall( - Request.Builder() - // Rob demo credentials - .url("https://speech-to-text-demo.ng.bluemix.net/api/v1/credentials") - .build() - ) - .asObservableSuccess() - .subscribeOn(Schedulers.io()) - .map { - val json = JsonParser.parseString(it.body.string()) - it.close() - json["token"].string - }.melt() - - binding.webview.addJavascriptInterface(this@BrowserActionActivity, "exh") - AutoSolvingWebViewClient(this, verifyComplete, script, headers) - } else { - HeadersInjectingWebViewClient(this, verifyComplete, script, headers) - } + binding.webview.webViewClient = + if (actionName == null && preferencesHelper.eh_autoSolveCaptchas().get()) { + // Fetch auto-solve credentials early for speed + credentialsObservable = + httpClient.newCall( + Request.Builder() + // Rob demo credentials + .url("https://speech-to-text-demo.ng.bluemix.net/api/v1/credentials") + .build() + ) + .asObservableSuccess() + .subscribeOn(Schedulers.io()) + .map { + val json = JsonParser.parseString(it.body.string()) + it.close() + json["token"].string + }.melt() + + binding.webview.addJavascriptInterface(this@BrowserActionActivity, "exh") + AutoSolvingWebViewClient(this, verifyComplete, script, headers) + } else { + HeadersInjectingWebViewClient(this, verifyComplete, script, headers) + } binding.webview.loadUrl(url, headers) @@ -194,7 +213,11 @@ class BrowserActionActivity : AppCompatActivity() { } @JavascriptInterface - fun callback(result: String?, loopId: String, stage: Int) { + fun callback( + result: String?, + loopId: String, + stage: Int + ) { if (loopId != currentLoopId) return when (stage) { @@ -412,7 +435,10 @@ class BrowserActionActivity : AppCompatActivity() { ) } - fun typeResult(loopId: String, result: String) { + fun typeResult( + loopId: String, + result: String + ) { binding.webview.evaluateJavascript( """ (function() { @@ -450,7 +476,10 @@ class BrowserActionActivity : AppCompatActivity() { } @JavascriptInterface - fun validateCaptchaCallback(result: Boolean, loopId: String) { + fun validateCaptchaCallback( + result: Boolean, + loopId: String + ) { if (loopId != validateCurrentLoopId) return if (result) { @@ -517,22 +546,27 @@ class BrowserActionActivity : AppCompatActivity() { runValidateCaptcha(loopId) } - private fun simulateClick(x: Float, y: Float) { + private fun simulateClick( + x: Float, + y: Float + ) { val downTime = SystemClock.uptimeMillis() val eventTime = SystemClock.uptimeMillis() val properties = arrayOfNulls(1) - val pp1 = MotionEvent.PointerProperties().apply { - id = 0 - toolType = MotionEvent.TOOL_TYPE_FINGER - } + val pp1 = + MotionEvent.PointerProperties().apply { + id = 0 + toolType = MotionEvent.TOOL_TYPE_FINGER + } properties[0] = pp1 val pointerCoords = arrayOfNulls(1) - val pc1 = MotionEvent.PointerCoords().apply { - this.x = x - this.y = y - pressure = 1f - size = 1f - } + val pc1 = + MotionEvent.PointerCoords().apply { + this.x = x + this.y = y + pressure = 1f + size = 1f + } pointerCoords[0] = pc1 var motionEvent = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, 1, properties, pointerCoords, 0, 0, 1f, 1f, 0, 0, 0, 0) dispatchTouchEvent(motionEvent) @@ -674,13 +708,14 @@ class BrowserActionActivity : AppCompatActivity() { url: String, autoSolveSubmitBtnSelector: String? = null ) { - val intent = baseIntent(context).apply { - putExtra(SOURCE_ID_EXTRA, source.id) - putExtra(COOKIES_EXTRA, HashMap(cookies)) - putExtra(SCRIPT_EXTRA, script) - putExtra(URL_EXTRA, url) - putExtra(ASBTN_EXTRA, autoSolveSubmitBtnSelector) - } + val intent = + baseIntent(context).apply { + putExtra(SOURCE_ID_EXTRA, source.id) + putExtra(COOKIES_EXTRA, HashMap(cookies)) + putExtra(SCRIPT_EXTRA, script) + putExtra(URL_EXTRA, url) + putExtra(ASBTN_EXTRA, autoSolveSubmitBtnSelector) + } context.startActivity(intent) } @@ -690,10 +725,11 @@ class BrowserActionActivity : AppCompatActivity() { source: HttpSource, url: String ) { - val intent = baseIntent(context).apply { - putExtra(SOURCE_ID_EXTRA, source.id) - putExtra(URL_EXTRA, url) - } + val intent = + baseIntent(context).apply { + putExtra(SOURCE_ID_EXTRA, source.id) + putExtra(URL_EXTRA, url) + } context.startActivity(intent) } @@ -703,10 +739,11 @@ class BrowserActionActivity : AppCompatActivity() { sourceId: Long, url: String ) { - val intent = baseIntent(context).apply { - putExtra(SOURCE_ID_EXTRA, sourceId) - putExtra(URL_EXTRA, url) - } + val intent = + baseIntent(context).apply { + putExtra(SOURCE_ID_EXTRA, sourceId) + putExtra(URL_EXTRA, url) + } context.startActivity(intent) } @@ -718,12 +755,13 @@ class BrowserActionActivity : AppCompatActivity() { url: String, actionName: String ) { - val intent = baseIntent(context).apply { - putExtra(SOURCE_ID_EXTRA, completionVerifier.id) - putExtra(SCRIPT_EXTRA, script) - putExtra(URL_EXTRA, url) - putExtra(ACTION_NAME_EXTRA, actionName) - } + val intent = + baseIntent(context).apply { + putExtra(SOURCE_ID_EXTRA, completionVerifier.id) + putExtra(SCRIPT_EXTRA, script) + putExtra(URL_EXTRA, url) + putExtra(ACTION_NAME_EXTRA, actionName) + } context.startActivity(intent) } @@ -736,13 +774,14 @@ class BrowserActionActivity : AppCompatActivity() { actionName: String, headers: Map? = emptyMap() ) { - val intent = baseIntent(context).apply { - putExtra(HEADERS_EXTRA, HashMap(headers!!)) - putExtra(VERIFY_LAMBDA_EXTRA, completionVerifier as Serializable) - putExtra(SCRIPT_EXTRA, script) - putExtra(URL_EXTRA, url) - putExtra(ACTION_NAME_EXTRA, actionName) - } + val intent = + baseIntent(context).apply { + putExtra(HEADERS_EXTRA, HashMap(headers!!)) + putExtra(VERIFY_LAMBDA_EXTRA, completionVerifier as Serializable) + putExtra(SCRIPT_EXTRA, script) + putExtra(URL_EXTRA, url) + putExtra(ACTION_NAME_EXTRA, actionName) + } context.startActivity(intent) } diff --git a/app/src/main/java/exh/ui/captcha/HeadersInjectingWebViewClient.kt b/app/src/main/java/exh/ui/captcha/HeadersInjectingWebViewClient.kt index 192fa95a8c83..d70c75241dce 100644 --- a/app/src/main/java/exh/ui/captcha/HeadersInjectingWebViewClient.kt +++ b/app/src/main/java/exh/ui/captcha/HeadersInjectingWebViewClient.kt @@ -11,8 +11,10 @@ open class HeadersInjectingWebViewClient( private val headers: Map ) : BasicWebViewClient(activity, verifyComplete, injectScript) { - - override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { + override fun shouldInterceptRequest( + view: WebView, + request: WebResourceRequest + ): WebResourceResponse? { // Temp disabled as it's unreliable /*if(headers.isNotEmpty()) { val response = activity.httpClient.newCall(request.toOkHttpRequest() @@ -36,44 +38,45 @@ open class HeadersInjectingWebViewClient( } companion object { - private val FALLBACK_REASON_PHRASES = mapOf( - 100 to "Continue", - 101 to "Switching Protocols", - 200 to "OK", - 201 to "Created", - 202 to "Accepted", - 203 to "Non-Authoritative Information", - 204 to "No Content", - 205 to "Reset Content", - 206 to "Partial Content", - 300 to "Multiple Choices", - 301 to "Moved Permanently", - 302 to "Moved Temporarily", - 303 to "See Other", - 304 to "Not Modified", - 305 to "Use Proxy", - 400 to "Bad Request", - 401 to "Unauthorized", - 402 to "Payment Required", - 403 to "Forbidden", - 404 to "Not Found", - 405 to "Method Not Allowed", - 406 to "Not Acceptable", - 407 to "Proxy Authentication Required", - 408 to "Request Time-out", - 409 to "Conflict", - 410 to "Gone", - 411 to "Length Required", - 412 to "Precondition Failed", - 413 to "Request Entity Too Large", - 414 to "Request-URI Too Large", - 415 to "Unsupported Media Type", - 500 to "Internal Server Error", - 501 to "Not Implemented", - 502 to "Bad Gateway", - 503 to "Service Unavailable", - 504 to "Gateway Time-out", - 505 to "HTTP Version not supported" - ) + private val FALLBACK_REASON_PHRASES = + mapOf( + 100 to "Continue", + 101 to "Switching Protocols", + 200 to "OK", + 201 to "Created", + 202 to "Accepted", + 203 to "Non-Authoritative Information", + 204 to "No Content", + 205 to "Reset Content", + 206 to "Partial Content", + 300 to "Multiple Choices", + 301 to "Moved Permanently", + 302 to "Moved Temporarily", + 303 to "See Other", + 304 to "Not Modified", + 305 to "Use Proxy", + 400 to "Bad Request", + 401 to "Unauthorized", + 402 to "Payment Required", + 403 to "Forbidden", + 404 to "Not Found", + 405 to "Method Not Allowed", + 406 to "Not Acceptable", + 407 to "Proxy Authentication Required", + 408 to "Request Time-out", + 409 to "Conflict", + 410 to "Gone", + 411 to "Length Required", + 412 to "Precondition Failed", + 413 to "Request Entity Too Large", + 414 to "Request-URI Too Large", + 415 to "Unsupported Media Type", + 500 to "Internal Server Error", + 501 to "Not Implemented", + 502 to "Bad Gateway", + 503 to "Service Unavailable", + 504 to "Gateway Time-out", + 505 to "HTTP Version not supported" + ) } } diff --git a/app/src/main/java/exh/ui/captcha/WebViewUtil.kt b/app/src/main/java/exh/ui/captcha/WebViewUtil.kt index fa4ce3693f0b..8ece19e6491f 100644 --- a/app/src/main/java/exh/ui/captcha/WebViewUtil.kt +++ b/app/src/main/java/exh/ui/captcha/WebViewUtil.kt @@ -4,9 +4,10 @@ import android.webkit.WebResourceRequest import okhttp3.Request fun WebResourceRequest.toOkHttpRequest(): Request { - val request = Request.Builder() - .url(url.toString()) - .method(method, null) + val request = + Request.Builder() + .url(url.toString()) + .method(method, null) requestHeaders.entries.forEach { (t, u) -> request.addHeader(t, u) diff --git a/app/src/main/java/exh/ui/intercept/InterceptActivity.kt b/app/src/main/java/exh/ui/intercept/InterceptActivity.kt index 8dfd72db7eec..e05548fd4bcb 100755 --- a/app/src/main/java/exh/ui/intercept/InterceptActivity.kt +++ b/app/src/main/java/exh/ui/intercept/InterceptActivity.kt @@ -15,10 +15,10 @@ import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.visible import exh.GalleryAddEvent import exh.GalleryAdder -import kotlin.concurrent.thread import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.subjects.BehaviorSubject +import kotlin.concurrent.thread class InterceptActivity : BaseActivity() { private var statusSubscription: Subscription? = null @@ -55,37 +55,38 @@ class InterceptActivity : BaseActivity() { override fun onStart() { super.onStart() statusSubscription?.unsubscribe() - statusSubscription = status - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - when (it) { - is InterceptResult.Success -> { - binding.interceptProgress.gone() - binding.interceptStatus.text = "Launching app..." - onBackPressed() - startActivity( - Intent(this, MainActivity::class.java) - .setAction(MainActivity.SHORTCUT_MANGA) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - .putExtra(MangaController.MANGA_EXTRA, it.mangaId) - ) + statusSubscription = + status + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + when (it) { + is InterceptResult.Success -> { + binding.interceptProgress.gone() + binding.interceptStatus.text = "Launching app..." + onBackPressed() + startActivity( + Intent(this, MainActivity::class.java) + .setAction(MainActivity.SHORTCUT_MANGA) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + .putExtra(MangaController.MANGA_EXTRA, it.mangaId) + ) + } + is InterceptResult.Failure -> { + binding.interceptProgress.gone() + binding.interceptStatus.text = "Error: ${it.reason}" + MaterialDialog(this) + .title(text = "Error") + .message(text = "Could not open this gallery:\n\n${it.reason}") + .cancelable(true) + .cancelOnTouchOutside(true) + .positiveButton(android.R.string.ok) + .onCancel { onBackPressed() } + .onDismiss { onBackPressed() } + .show() + } + else -> {} } - is InterceptResult.Failure -> { - binding.interceptProgress.gone() - binding.interceptStatus.text = "Error: ${it.reason}" - MaterialDialog(this) - .title(text = "Error") - .message(text = "Could not open this gallery:\n\n${it.reason}") - .cancelable(true) - .cancelOnTouchOutside(true) - .positiveButton(android.R.string.ok) - .onCancel { onBackPressed() } - .onDismiss { onBackPressed() } - .show() - } - else -> {} } - } } override fun onStop() { @@ -109,9 +110,10 @@ class InterceptActivity : BaseActivity() { status.onNext( when (result) { - is GalleryAddEvent.Success -> result.manga.id?.let { - InterceptResult.Success(it) - } ?: InterceptResult.Failure("Manga ID is null!") + is GalleryAddEvent.Success -> + result.manga.id?.let { + InterceptResult.Success(it) + } ?: InterceptResult.Failure("Manga ID is null!") is GalleryAddEvent.Fail -> InterceptResult.Failure(result.logMessage) } ) @@ -122,7 +124,10 @@ class InterceptActivity : BaseActivity() { sealed class InterceptResult { class Idle : InterceptResult() + class Loading : InterceptResult() + data class Success(val mangaId: Long) : InterceptResult() + data class Failure(val reason: String) : InterceptResult() } diff --git a/app/src/main/java/exh/ui/lock/FingerLockPreference.kt b/app/src/main/java/exh/ui/lock/FingerLockPreference.kt index cf57097b3e6c..ce6c5920d973 100644 --- a/app/src/main/java/exh/ui/lock/FingerLockPreference.kt +++ b/app/src/main/java/exh/ui/lock/FingerLockPreference.kt @@ -23,19 +23,22 @@ import exh.util.dpToPx import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.injectLazy -class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class FingerLockPreference +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : SwitchPreferenceCompat(context, attrs) { - val prefs: PreferencesHelper by injectLazy() val fingerprintSupported - get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - Reprint.isHardwarePresent() && - Reprint.hasFingerprintRegistered() + get() = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + Reprint.isHardwarePresent() && + Reprint.hasFingerprintRegistered() val useFingerprint - get() = fingerprintSupported && - prefs.eh_lockUseFingerprint().get() + get() = + fingerprintSupported && + prefs.eh_lockUseFingerprint().get() @SuppressLint("NewApi") override fun onAttached() { @@ -53,103 +56,116 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At } else { title = "Fingerprint unsupported" shouldDisableView = true - summary = if (!Reprint.hasFingerprintRegistered()) { - "No fingerprints enrolled!" - } else { - "Fingerprint unlock is unsupported on this device!" - } + summary = + if (!Reprint.hasFingerprintRegistered()) { + "No fingerprints enrolled!" + } else { + "Fingerprint unlock is unsupported on this device!" + } onChange { false } } } private fun updateSummary() { isChecked = useFingerprint - title = if (isChecked) { - "Fingerprint enabled" - } else { - "Fingerprint disabled" - } + title = + if (isChecked) { + "Fingerprint enabled" + } else { + "Fingerprint disabled" + } } @TargetApi(Build.VERSION_CODES.M) fun tryChange() { - val statusTextView = TextView(context).apply { - text = "Please touch the fingerprint sensor" - val size = ViewGroup.LayoutParams.WRAP_CONTENT - layoutParams = ( - layoutParams ?: ViewGroup.LayoutParams( - size, size - ) - ).apply { - width = size - height = size - setPadding(0, 0, dpToPx(context, 8), 0) - } - } - val iconView = SwirlView(context).apply { - val size = dpToPx(context, 30) - layoutParams = ( - layoutParams ?: ViewGroup.LayoutParams( - size, size - ) - ).apply { - width = size - height = size + val statusTextView = + TextView(context).apply { + text = "Please touch the fingerprint sensor" + val size = ViewGroup.LayoutParams.WRAP_CONTENT + layoutParams = + ( + layoutParams ?: ViewGroup.LayoutParams( + size, + size + ) + ).apply { + width = size + height = size + setPadding(0, 0, dpToPx(context, 8), 0) + } } - setState(SwirlView.State.OFF, false) - } - val linearLayout = LinearLayoutCompat(context).apply { - orientation = LinearLayoutCompat.HORIZONTAL - gravity = Gravity.CENTER_VERTICAL - val size = LinearLayoutCompat.LayoutParams.WRAP_CONTENT - layoutParams = ( - layoutParams ?: LinearLayoutCompat.LayoutParams( - size, size - ) - ).apply { - width = size - height = size - val pSize = dpToPx(context, 24) - setPadding(pSize, 0, pSize, 0) + val iconView = + SwirlView(context).apply { + val size = dpToPx(context, 30) + layoutParams = + ( + layoutParams ?: ViewGroup.LayoutParams( + size, + size + ) + ).apply { + width = size + height = size + } + setState(SwirlView.State.OFF, false) } + val linearLayout = + LinearLayoutCompat(context).apply { + orientation = LinearLayoutCompat.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + val size = LinearLayoutCompat.LayoutParams.WRAP_CONTENT + layoutParams = + ( + layoutParams ?: LinearLayoutCompat.LayoutParams( + size, + size + ) + ).apply { + width = size + height = size + val pSize = dpToPx(context, 24) + setPadding(pSize, 0, pSize, 0) + } - addView(statusTextView) - addView(iconView) - } - val dialog = MaterialDialog(context) - .title(text = "Fingerprint verification") - .customView(view = linearLayout) - .negativeButton(R.string.action_cancel) - .cancelable(true) - .cancelOnTouchOutside(true) + addView(statusTextView) + addView(iconView) + } + val dialog = + MaterialDialog(context) + .title(text = "Fingerprint verification") + .customView(view = linearLayout) + .negativeButton(R.string.action_cancel) + .cancelable(true) + .cancelOnTouchOutside(true) dialog.show() iconView.setState(SwirlView.State.ON) - val subscription = RxReprint.authenticate() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { result -> - when (result.status) { - AuthenticationResult.Status.SUCCESS -> { - iconView.setState(SwirlView.State.ON) - prefs.eh_lockUseFingerprint().set(true) - dialog.dismiss() - updateSummary() - } - AuthenticationResult.Status.NONFATAL_FAILURE -> { - iconView.setState(SwirlView.State.ERROR) - statusTextView.text = result.errorMessage - } - AuthenticationResult.Status.FATAL_FAILURE, null -> { - MaterialDialog(context) - .title(text = "Fingerprint verification failed!") - .message(text = result.errorMessage) - .positiveButton(android.R.string.ok) - .cancelable(true) - .cancelOnTouchOutside(false) - .show() - dialog.dismiss() + val subscription = + RxReprint.authenticate() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { result -> + when (result.status) { + AuthenticationResult.Status.SUCCESS -> { + iconView.setState(SwirlView.State.ON) + prefs.eh_lockUseFingerprint().set(true) + dialog.dismiss() + updateSummary() + } + AuthenticationResult.Status.NONFATAL_FAILURE -> { + iconView.setState(SwirlView.State.ERROR) + statusTextView.text = result.errorMessage + } + AuthenticationResult.Status.FATAL_FAILURE, null -> { + MaterialDialog(context) + .title(text = "Fingerprint verification failed!") + .message(text = result.errorMessage) + .positiveButton(android.R.string.ok) + .cancelable(true) + .cancelOnTouchOutside(false) + .show() + dialog.dismiss() + } } } - } dialog.setOnDismissListener { subscription.unsubscribe() } diff --git a/app/src/main/java/exh/ui/lock/LockActivityDelegate.kt b/app/src/main/java/exh/ui/lock/LockActivityDelegate.kt index 8870178e8a59..617a0b213ba2 100644 --- a/app/src/main/java/exh/ui/lock/LockActivityDelegate.kt +++ b/app/src/main/java/exh/ui/lock/LockActivityDelegate.kt @@ -18,7 +18,10 @@ object LockActivityDelegate { private val uiScope = CoroutineScope(Dispatchers.Main) - fun doLock(router: Router, animate: Boolean = false) { + fun doLock( + router: Router, + animate: Boolean = false + ) { router.pushController( RouterTransaction.with(LockController()) .popChangeHandler(LockChangeHandler(animate)) @@ -37,7 +40,10 @@ object LockActivityDelegate { .launchIn(uiScope) } - fun onResume(activity: FragmentActivity, router: Router) { + fun onResume( + activity: FragmentActivity, + router: Router + ) { if (lockEnabled() && !isAppLocked(router) && willLock && !preferences.eh_lockManually().get()) { doLock(router) willLock = false diff --git a/app/src/main/java/exh/ui/lock/LockChangeHandler.kt b/app/src/main/java/exh/ui/lock/LockChangeHandler.kt index b9c3dd5c1daf..b0feb9a0dca5 100644 --- a/app/src/main/java/exh/ui/lock/LockChangeHandler.kt +++ b/app/src/main/java/exh/ui/lock/LockChangeHandler.kt @@ -18,7 +18,13 @@ class LockChangeHandler : AnimatorChangeHandler { constructor(duration: Long, removesFromViewOnPush: Boolean) : super(duration, removesFromViewOnPush) - override fun getAnimator(container: ViewGroup, from: View?, to: View?, isPush: Boolean, toAddedToContainer: Boolean): Animator { + override fun getAnimator( + container: ViewGroup, + from: View?, + to: View?, + isPush: Boolean, + toAddedToContainer: Boolean + ): Animator { val animator = AnimatorSet() val viewAnimators = ArrayList() @@ -34,6 +40,5 @@ class LockChangeHandler : AnimatorChangeHandler { override fun resetFromView(from: View) {} - override fun copy(): ControllerChangeHandler = - LockChangeHandler(animationDuration, removesFromViewOnPush()) + override fun copy(): ControllerChangeHandler = LockChangeHandler(animationDuration, removesFromViewOnPush()) } diff --git a/app/src/main/java/exh/ui/lock/LockController.kt b/app/src/main/java/exh/ui/lock/LockController.kt index 7c0765667e4a..b25ececc182e 100755 --- a/app/src/main/java/exh/ui/lock/LockController.kt +++ b/app/src/main/java/exh/ui/lock/LockController.kt @@ -18,13 +18,16 @@ import exh.util.dpToPx import uy.kohesive.injekt.injectLazy class LockController : NucleusController() { - val prefs: PreferencesHelper by injectLazy() - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun inflateView( + inflater: LayoutInflater, + container: ViewGroup + ): View { binding = ActivityLockBinding.inflate(inflater) return binding.root } + override fun createPresenter() = LockPresenter() override fun getTitle() = "Application locked" @@ -42,27 +45,32 @@ class LockController : NucleusController() { binding.pinLockView.attachIndicatorDots(binding.indicatorDots) binding.pinLockView.pinLength = prefs.eh_lockLength().get() - binding.pinLockView.setPinLockListener(object : PinLockListener { - override fun onEmpty() {} - - override fun onComplete(pin: String) { - if (sha512(pin, prefs.eh_lockSalt().get()) == prefs.eh_lockHash().get()) { - // Yay! - closeLock() - } else { - MaterialDialog(context) - .title(text = "PIN code incorrect") - .message(text = "The PIN code you entered is incorrect. Please try again.") - .cancelable(true) - .cancelOnTouchOutside(true) - .positiveButton(android.R.string.ok) - .show() - binding.pinLockView.resetPinLockView() + binding.pinLockView.setPinLockListener( + object : PinLockListener { + override fun onEmpty() {} + + override fun onComplete(pin: String) { + if (sha512(pin, prefs.eh_lockSalt().get()) == prefs.eh_lockHash().get()) { + // Yay! + closeLock() + } else { + MaterialDialog(context) + .title(text = "PIN code incorrect") + .message(text = "The PIN code you entered is incorrect. Please try again.") + .cancelable(true) + .cancelOnTouchOutside(true) + .positiveButton(android.R.string.ok) + .show() + binding.pinLockView.resetPinLockView() + } } - } - override fun onPinChange(pinLength: Int, intermediatePin: String?) {} - }) + override fun onPinChange( + pinLength: Int, + intermediatePin: String? + ) {} + } + ) } } @@ -75,28 +83,31 @@ class LockController : NucleusController() { if (presenter.useFingerprint) { binding.swirlContainer.visibility = View.VISIBLE binding.swirlContainer.removeAllViews() - val icon = SwirlView(context).apply { - val size = dpToPx(context, 60) - layoutParams = ( - layoutParams ?: ViewGroup.LayoutParams( - size, size - ) - ).apply { - width = size - height = size - - val pSize = dpToPx(context, 8) - setPadding(pSize, pSize, pSize, pSize) - } - val lockColor = resolvColor(android.R.attr.windowBackground) - setBackgroundColor(lockColor) - val bgColor = resolvColor(android.R.attr.colorBackground) - // Disable elevation if lock color is same as background color - if (lockColor == bgColor) { - binding.swirlContainer.cardElevation = 0f + val icon = + SwirlView(context).apply { + val size = dpToPx(context, 60) + layoutParams = + ( + layoutParams ?: ViewGroup.LayoutParams( + size, + size + ) + ).apply { + width = size + height = size + + val pSize = dpToPx(context, 8) + setPadding(pSize, pSize, pSize, pSize) + } + val lockColor = resolvColor(android.R.attr.windowBackground) + setBackgroundColor(lockColor) + val bgColor = resolvColor(android.R.attr.colorBackground) + // Disable elevation if lock color is same as background color + if (lockColor == bgColor) { + binding.swirlContainer.cardElevation = 0f + } + setState(SwirlView.State.OFF, true) } - setState(SwirlView.State.OFF, true) - } binding.swirlContainer.addView(icon) icon.setState(SwirlView.State.ON) RxReprint.authenticate() diff --git a/app/src/main/java/exh/ui/lock/LockPreference.kt b/app/src/main/java/exh/ui/lock/LockPreference.kt index 454c2020508a..4c38b5cd5063 100755 --- a/app/src/main/java/exh/ui/lock/LockPreference.kt +++ b/app/src/main/java/exh/ui/lock/LockPreference.kt @@ -9,16 +9,17 @@ import com.afollestad.materialdialogs.input.input import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.util.preference.onChange -import java.math.BigInteger -import java.security.SecureRandom import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import uy.kohesive.injekt.injectLazy +import java.math.BigInteger +import java.security.SecureRandom -class LockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class LockPreference +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null) : SwitchPreferenceCompat(context, attrs) { - private val secureRandom by lazy { SecureRandom() } val prefs: PreferencesHelper by injectLazy() @@ -48,9 +49,10 @@ class LockPreference @JvmOverloads constructor(context: Context, attrs: Attribut .title(text = "Lock application") .message(text = "Enter a pin to lock the application. Enter nothing to disable the pin lock.") .input(maxLength = 10, inputType = InputType.TYPE_CLASS_NUMBER, allowEmpty = true) { _, c -> - val progressDialog = MaterialDialog(context) - .title(text = "Saving password") - .cancelable(false) + val progressDialog = + MaterialDialog(context) + .title(text = "Saving password") + .cancelable(false) progressDialog.show() Observable.fromCallable { savePassword(c.toString()) diff --git a/app/src/main/java/exh/ui/lock/LockPresenter.kt b/app/src/main/java/exh/ui/lock/LockPresenter.kt index e861314a6352..1ff9441428c2 100644 --- a/app/src/main/java/exh/ui/lock/LockPresenter.kt +++ b/app/src/main/java/exh/ui/lock/LockPresenter.kt @@ -10,8 +10,9 @@ class LockPresenter : BasePresenter() { val prefs: PreferencesHelper by injectLazy() val useFingerprint - get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - Reprint.isHardwarePresent() && - Reprint.hasFingerprintRegistered() && - prefs.eh_lockUseFingerprint().get() + get() = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + Reprint.isHardwarePresent() && + Reprint.hasFingerprintRegistered() && + prefs.eh_lockUseFingerprint().get() } diff --git a/app/src/main/java/exh/ui/lock/LockUtils.kt b/app/src/main/java/exh/ui/lock/LockUtils.kt index 802f108efb5c..051a90d1b67e 100755 --- a/app/src/main/java/exh/ui/lock/LockUtils.kt +++ b/app/src/main/java/exh/ui/lock/LockUtils.kt @@ -1,10 +1,10 @@ package exh.ui.lock import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import java.security.MessageDigest -import kotlin.experimental.and import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.security.MessageDigest +import kotlin.experimental.and /** * Password hashing utils @@ -13,7 +13,10 @@ import uy.kohesive.injekt.api.get /** * Yes, I know SHA512 is fast, but bcrypt on mobile devices is too slow apparently */ -fun sha512(passwordToHash: String, salt: String): String { +fun sha512( + passwordToHash: String, + salt: String +): String { val md = MessageDigest.getInstance("SHA-512") md.update(salt.toByteArray(charset("UTF-8"))) val bytes = md.digest(passwordToHash.toByteArray(charset("UTF-8"))) @@ -27,5 +30,4 @@ fun sha512(passwordToHash: String, salt: String): String { /** * Check if lock is enabled */ -fun lockEnabled(prefs: PreferencesHelper = Injekt.get()) = - prefs.eh_lockLength().get() != -1 +fun lockEnabled(prefs: PreferencesHelper = Injekt.get()) = prefs.eh_lockLength().get() != -1 diff --git a/app/src/main/java/exh/ui/login/LoginController.kt b/app/src/main/java/exh/ui/login/LoginController.kt index 73dd82fc1355..9fe92254b64e 100755 --- a/app/src/main/java/exh/ui/login/LoginController.kt +++ b/app/src/main/java/exh/ui/login/LoginController.kt @@ -18,9 +18,9 @@ import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.visible import exh.uconfig.WarnConfigureDialogController -import java.net.HttpCookie import timber.log.Timber import uy.kohesive.injekt.injectLazy +import java.net.HttpCookie /** * LoginController @@ -37,7 +37,10 @@ class LoginController : NucleusController(), CoroutineScope { - override val coroutineContext = Job() + Dispatchers.Main val smartSearchChannel = Channel() @@ -29,21 +28,22 @@ class SmartSearchPresenter(private val source: CatalogueSource?, private val con if (source != null && config != null) { launch(Dispatchers.Default) { - val result = try { - val resultManga = smartSearchEngine.smartSearch(source, config.origTitle) - if (resultManga != null) { - val localManga = smartSearchEngine.networkToLocalManga(resultManga, source.id) - SearchResults.Found(localManga) - } else { - SearchResults.NotFound - } - } catch (e: Exception) { - if (e is CancellationException) { - throw e - } else { - SearchResults.Error + val result = + try { + val resultManga = smartSearchEngine.smartSearch(source, config.origTitle) + if (resultManga != null) { + val localManga = smartSearchEngine.networkToLocalManga(resultManga, source.id) + SearchResults.Found(localManga) + } else { + SearchResults.NotFound + } + } catch (e: Exception) { + if (e is CancellationException) { + throw e + } else { + SearchResults.Error + } } - } smartSearchChannel.send(result) } @@ -60,7 +60,9 @@ class SmartSearchPresenter(private val source: CatalogueSource?, private val con sealed class SearchResults { data class Found(val manga: Manga) : SearchResults() + object NotFound : SearchResults() + object Error : SearchResults() } } diff --git a/app/src/main/java/exh/util/CoroutineUtil.kt b/app/src/main/java/exh/util/CoroutineUtil.kt index 39ec95bfbae4..f97acdb395a8 100644 --- a/app/src/main/java/exh/util/CoroutineUtil.kt +++ b/app/src/main/java/exh/util/CoroutineUtil.kt @@ -1,12 +1,13 @@ package exh.util -import kotlin.coroutines.coroutineContext import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.onEach +import kotlin.coroutines.coroutineContext @FlowPreview -fun Flow.cancellable() = onEach { - coroutineContext.ensureActive() -} +fun Flow.cancellable() = + onEach { + coroutineContext.ensureActive() + } diff --git a/app/src/main/java/exh/util/ExceptionUtil.kt b/app/src/main/java/exh/util/ExceptionUtil.kt index 3b73328a3e47..7e1e18c449c8 100644 --- a/app/src/main/java/exh/util/ExceptionUtil.kt +++ b/app/src/main/java/exh/util/ExceptionUtil.kt @@ -1,7 +1,11 @@ package exh.util inline fun ignore(expr: () -> T): T? { - return try { expr() } catch (t: Throwable) { null } + return try { + expr() + } catch (t: Throwable) { + null + } } fun T.withRootCause(cause: Throwable): T { diff --git a/app/src/main/java/exh/util/FakeMutables.kt b/app/src/main/java/exh/util/FakeMutables.kt index d91c47f32014..2d3334cc0ddf 100644 --- a/app/src/main/java/exh/util/FakeMutables.kt +++ b/app/src/main/java/exh/util/FakeMutables.kt @@ -136,6 +136,7 @@ private value class EntryShim(private val entry: Map.Entry) : FakeMu */ override val key: K get() = entry.key + /** * Returns the value of this key/value pair. */ @@ -149,6 +150,7 @@ private value class PairShim(private val pair: Pair) : FakeMutableEn * Returns the key of this key/value pair. */ override val key: K get() = pair.first + /** * Returns the value of this key/value pair. */ @@ -165,11 +167,15 @@ interface FakeMutableEntry : MutableMap.MutableEntry { fun fromPair(pair: Pair): FakeMutableEntry = PairShim(pair) - fun fromPair(key: K, value: V) = object : FakeMutableEntry { + fun fromPair( + key: K, + value: V + ) = object : FakeMutableEntry { /** * Returns the key of this key/value pair. */ override val key: K = key + /** * Returns the value of this key/value pair. */ diff --git a/app/src/main/java/exh/util/LewdMangaChecker.kt b/app/src/main/java/exh/util/LewdMangaChecker.kt index 176af447d774..0a5fff795b6e 100644 --- a/app/src/main/java/exh/util/LewdMangaChecker.kt +++ b/app/src/main/java/exh/util/LewdMangaChecker.kt @@ -7,9 +7,9 @@ import exh.EH_SOURCE_ID import exh.EXH_SOURCE_ID import exh.NHENTAI_SOURCE_ID import exh.lewdDelegatedSourceIds -import java.util.Locale import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.Locale fun Manga.isLewd(): Boolean { val sourceName = Injekt.get().getOrStub(source).name diff --git a/app/src/main/java/exh/util/LoggingRealmQuery.kt b/app/src/main/java/exh/util/LoggingRealmQuery.kt index d7bbf803d099..ff2ed6cb93f5 100644 --- a/app/src/main/java/exh/util/LoggingRealmQuery.kt +++ b/app/src/main/java/exh/util/LoggingRealmQuery.kt @@ -15,15 +15,16 @@ import java.util.Date inline fun RealmQuery.beginLog( clazz: Class? = E::class.java -): LoggingRealmQuery = - LoggingRealmQuery.fromQuery(this, clazz) +): LoggingRealmQuery = LoggingRealmQuery.fromQuery(this, clazz) class LoggingRealmQuery(val query: RealmQuery) { companion object { - fun fromQuery(q: RealmQuery, clazz: Class?) = - LoggingRealmQuery(q).apply { - log += "SELECT * FROM ${clazz?.name ?: "???"} WHERE" - } + fun fromQuery( + q: RealmQuery, + clazz: Class? + ) = LoggingRealmQuery(q).apply { + log += "SELECT * FROM ${clazz?.name ?: "???"} WHERE" + } } private val log = mutableListOf() @@ -46,422 +47,680 @@ class LoggingRealmQuery(val query: RealmQuery) { return query.isNotNull(fieldName) } - private fun appendEqualTo(fieldName: String, value: String, casing: Case? = null) { - log += sec( - "\"$fieldName\" == \"$value\"" + ( - casing?.let { - " CASE ${casing.name}" - } ?: "" - ) - ) - } - - fun equalTo(fieldName: String, value: String): RealmQuery { + private fun appendEqualTo( + fieldName: String, + value: String, + casing: Case? = null + ) { + log += + sec( + "\"$fieldName\" == \"$value\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) + } + + fun equalTo( + fieldName: String, + value: String + ): RealmQuery { appendEqualTo(fieldName, value) return query.equalTo(fieldName, value) } - fun equalTo(fieldName: String, value: String, casing: Case): RealmQuery { + fun equalTo( + fieldName: String, + value: String, + casing: Case + ): RealmQuery { appendEqualTo(fieldName, value, casing) return query.equalTo(fieldName, value, casing) } - fun equalTo(fieldName: String, value: Byte?): RealmQuery { + fun equalTo( + fieldName: String, + value: Byte? + ): RealmQuery { appendEqualTo(fieldName, value.toString()) return query.equalTo(fieldName, value) } - fun equalTo(fieldName: String, value: ByteArray): RealmQuery { + fun equalTo( + fieldName: String, + value: ByteArray + ): RealmQuery { appendEqualTo(fieldName, value.toString()) return query.equalTo(fieldName, value) } - fun equalTo(fieldName: String, value: Short?): RealmQuery { + fun equalTo( + fieldName: String, + value: Short? + ): RealmQuery { appendEqualTo(fieldName, value.toString()) return query.equalTo(fieldName, value) } - fun equalTo(fieldName: String, value: Int?): RealmQuery { + fun equalTo( + fieldName: String, + value: Int? + ): RealmQuery { appendEqualTo(fieldName, value.toString()) return query.equalTo(fieldName, value) } - fun equalTo(fieldName: String, value: Long?): RealmQuery { + fun equalTo( + fieldName: String, + value: Long? + ): RealmQuery { appendEqualTo(fieldName, value.toString()) return query.equalTo(fieldName, value) } - fun equalTo(fieldName: String, value: Double?): RealmQuery { + fun equalTo( + fieldName: String, + value: Double? + ): RealmQuery { appendEqualTo(fieldName, value.toString()) return query.equalTo(fieldName, value) } - fun equalTo(fieldName: String, value: Float?): RealmQuery { + fun equalTo( + fieldName: String, + value: Float? + ): RealmQuery { appendEqualTo(fieldName, value.toString()) return query.equalTo(fieldName, value) } - fun equalTo(fieldName: String, value: Boolean?): RealmQuery { + fun equalTo( + fieldName: String, + value: Boolean? + ): RealmQuery { appendEqualTo(fieldName, value.toString()) return query.equalTo(fieldName, value) } - fun equalTo(fieldName: String, value: Date): RealmQuery { + fun equalTo( + fieldName: String, + value: Date + ): RealmQuery { appendEqualTo(fieldName, value.toString()) return query.equalTo(fieldName, value) } - fun appendIn(fieldName: String, values: Array, casing: Case? = null) { - log += sec( - "[${values.joinToString( - separator = ", ", - transform = { - "\"$it\"" - } - )}] IN \"$fieldName\"" + ( - casing?.let { - " CASE ${casing.name}" - } ?: "" - ) - ) - } - - fun `in`(fieldName: String, values: Array): RealmQuery { + fun appendIn( + fieldName: String, + values: Array, + casing: Case? = null + ) { + log += + sec( + "[${values.joinToString( + separator = ", ", + transform = { + "\"$it\"" + } + )}] IN \"$fieldName\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) + } + + fun `in`( + fieldName: String, + values: Array + ): RealmQuery { appendIn(fieldName, values) return query.`in`(fieldName, values) } - fun `in`(fieldName: String, values: Array, casing: Case): RealmQuery { + fun `in`( + fieldName: String, + values: Array, + casing: Case + ): RealmQuery { appendIn(fieldName, values, casing) return query.`in`(fieldName, values, casing) } - fun `in`(fieldName: String, values: Array): RealmQuery { + fun `in`( + fieldName: String, + values: Array + ): RealmQuery { appendIn(fieldName, values) return query.`in`(fieldName, values) } - fun `in`(fieldName: String, values: Array): RealmQuery { + fun `in`( + fieldName: String, + values: Array + ): RealmQuery { appendIn(fieldName, values) return query.`in`(fieldName, values) } - fun `in`(fieldName: String, values: Array): RealmQuery { + fun `in`( + fieldName: String, + values: Array + ): RealmQuery { appendIn(fieldName, values) return query.`in`(fieldName, values) } - fun `in`(fieldName: String, values: Array): RealmQuery { + fun `in`( + fieldName: String, + values: Array + ): RealmQuery { appendIn(fieldName, values) return query.`in`(fieldName, values) } - fun `in`(fieldName: String, values: Array): RealmQuery { + fun `in`( + fieldName: String, + values: Array + ): RealmQuery { appendIn(fieldName, values) return query.`in`(fieldName, values) } - fun `in`(fieldName: String, values: Array): RealmQuery { + fun `in`( + fieldName: String, + values: Array + ): RealmQuery { appendIn(fieldName, values) return query.`in`(fieldName, values) } - fun `in`(fieldName: String, values: Array): RealmQuery { + fun `in`( + fieldName: String, + values: Array + ): RealmQuery { appendIn(fieldName, values) return query.`in`(fieldName, values) } - fun `in`(fieldName: String, values: Array): RealmQuery { + fun `in`( + fieldName: String, + values: Array + ): RealmQuery { appendIn(fieldName, values) return query.`in`(fieldName, values) } - private fun appendNotEqualTo(fieldName: String, value: Any?, casing: Case? = null) { - log += sec( - "\"$fieldName\" != \"$value\"" + ( - casing?.let { - " CASE ${casing.name}" - } ?: "" - ) - ) - } - - fun notEqualTo(fieldName: String, value: String): RealmQuery { + private fun appendNotEqualTo( + fieldName: String, + value: Any?, + casing: Case? = null + ) { + log += + sec( + "\"$fieldName\" != \"$value\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) + } + + fun notEqualTo( + fieldName: String, + value: String + ): RealmQuery { appendNotEqualTo(fieldName, value) return query.notEqualTo(fieldName, value) } - fun notEqualTo(fieldName: String, value: String, casing: Case): RealmQuery { + fun notEqualTo( + fieldName: String, + value: String, + casing: Case + ): RealmQuery { appendNotEqualTo(fieldName, value, casing) return query.notEqualTo(fieldName, value, casing) } - fun notEqualTo(fieldName: String, value: Byte?): RealmQuery { + fun notEqualTo( + fieldName: String, + value: Byte? + ): RealmQuery { appendNotEqualTo(fieldName, value) return query.notEqualTo(fieldName, value) } - fun notEqualTo(fieldName: String, value: ByteArray): RealmQuery { + fun notEqualTo( + fieldName: String, + value: ByteArray + ): RealmQuery { appendNotEqualTo(fieldName, value) return query.notEqualTo(fieldName, value) } - fun notEqualTo(fieldName: String, value: Short?): RealmQuery { + fun notEqualTo( + fieldName: String, + value: Short? + ): RealmQuery { appendNotEqualTo(fieldName, value) return query.notEqualTo(fieldName, value) } - fun notEqualTo(fieldName: String, value: Int?): RealmQuery { + fun notEqualTo( + fieldName: String, + value: Int? + ): RealmQuery { appendNotEqualTo(fieldName, value) return query.notEqualTo(fieldName, value) } - fun notEqualTo(fieldName: String, value: Long?): RealmQuery { + fun notEqualTo( + fieldName: String, + value: Long? + ): RealmQuery { appendNotEqualTo(fieldName, value) return query.notEqualTo(fieldName, value) } - fun notEqualTo(fieldName: String, value: Double?): RealmQuery { + fun notEqualTo( + fieldName: String, + value: Double? + ): RealmQuery { appendNotEqualTo(fieldName, value) return query.notEqualTo(fieldName, value) } - fun notEqualTo(fieldName: String, value: Float?): RealmQuery { + fun notEqualTo( + fieldName: String, + value: Float? + ): RealmQuery { appendNotEqualTo(fieldName, value) return query.notEqualTo(fieldName, value) } - fun notEqualTo(fieldName: String, value: Boolean?): RealmQuery { + fun notEqualTo( + fieldName: String, + value: Boolean? + ): RealmQuery { appendNotEqualTo(fieldName, value) return query.notEqualTo(fieldName, value) } - fun notEqualTo(fieldName: String, value: Date): RealmQuery { + fun notEqualTo( + fieldName: String, + value: Date + ): RealmQuery { appendNotEqualTo(fieldName, value) return query.notEqualTo(fieldName, value) } - private fun appendGreaterThan(fieldName: String, value: Any?) { + private fun appendGreaterThan( + fieldName: String, + value: Any? + ) { log += sec("\"$fieldName\" > $value") } - fun greaterThan(fieldName: String, value: Int): RealmQuery { + fun greaterThan( + fieldName: String, + value: Int + ): RealmQuery { appendGreaterThan(fieldName, value) return query.greaterThan(fieldName, value) } - fun greaterThan(fieldName: String, value: Long): RealmQuery { + fun greaterThan( + fieldName: String, + value: Long + ): RealmQuery { appendGreaterThan(fieldName, value) return query.greaterThan(fieldName, value) } - fun greaterThan(fieldName: String, value: Double): RealmQuery { + fun greaterThan( + fieldName: String, + value: Double + ): RealmQuery { appendGreaterThan(fieldName, value) return query.greaterThan(fieldName, value) } - fun greaterThan(fieldName: String, value: Float): RealmQuery { + fun greaterThan( + fieldName: String, + value: Float + ): RealmQuery { appendGreaterThan(fieldName, value) return query.greaterThan(fieldName, value) } - fun greaterThan(fieldName: String, value: Date): RealmQuery { + fun greaterThan( + fieldName: String, + value: Date + ): RealmQuery { appendGreaterThan(fieldName, value) return query.greaterThan(fieldName, value) } - private fun appendGreaterThanOrEqualTo(fieldName: String, value: Any?) { + private fun appendGreaterThanOrEqualTo( + fieldName: String, + value: Any? + ) { log += sec("\"$fieldName\" >= $value") } - fun greaterThanOrEqualTo(fieldName: String, value: Int): RealmQuery { + fun greaterThanOrEqualTo( + fieldName: String, + value: Int + ): RealmQuery { appendGreaterThanOrEqualTo(fieldName, value) return query.greaterThanOrEqualTo(fieldName, value) } - fun greaterThanOrEqualTo(fieldName: String, value: Long): RealmQuery { + fun greaterThanOrEqualTo( + fieldName: String, + value: Long + ): RealmQuery { appendGreaterThanOrEqualTo(fieldName, value) return query.greaterThanOrEqualTo(fieldName, value) } - fun greaterThanOrEqualTo(fieldName: String, value: Double): RealmQuery { + fun greaterThanOrEqualTo( + fieldName: String, + value: Double + ): RealmQuery { appendGreaterThanOrEqualTo(fieldName, value) return query.greaterThanOrEqualTo(fieldName, value) } - fun greaterThanOrEqualTo(fieldName: String, value: Float): RealmQuery { + fun greaterThanOrEqualTo( + fieldName: String, + value: Float + ): RealmQuery { appendGreaterThanOrEqualTo(fieldName, value) return query.greaterThanOrEqualTo(fieldName, value) } - fun greaterThanOrEqualTo(fieldName: String, value: Date): RealmQuery { + fun greaterThanOrEqualTo( + fieldName: String, + value: Date + ): RealmQuery { appendGreaterThanOrEqualTo(fieldName, value) return query.greaterThanOrEqualTo(fieldName, value) } - private fun appendLessThan(fieldName: String, value: Any?) { + private fun appendLessThan( + fieldName: String, + value: Any? + ) { log += sec("\"$fieldName\" < $value") } - fun lessThan(fieldName: String, value: Int): RealmQuery { + fun lessThan( + fieldName: String, + value: Int + ): RealmQuery { appendLessThan(fieldName, value) return query.lessThan(fieldName, value) } - fun lessThan(fieldName: String, value: Long): RealmQuery { + fun lessThan( + fieldName: String, + value: Long + ): RealmQuery { appendLessThan(fieldName, value) return query.lessThan(fieldName, value) } - fun lessThan(fieldName: String, value: Double): RealmQuery { + fun lessThan( + fieldName: String, + value: Double + ): RealmQuery { appendLessThan(fieldName, value) return query.lessThan(fieldName, value) } - fun lessThan(fieldName: String, value: Float): RealmQuery { + fun lessThan( + fieldName: String, + value: Float + ): RealmQuery { appendLessThan(fieldName, value) return query.lessThan(fieldName, value) } - fun lessThan(fieldName: String, value: Date): RealmQuery { + fun lessThan( + fieldName: String, + value: Date + ): RealmQuery { appendLessThan(fieldName, value) return query.lessThan(fieldName, value) } - private fun appendLessThanOrEqualTo(fieldName: String, value: Any?) { + private fun appendLessThanOrEqualTo( + fieldName: String, + value: Any? + ) { log += sec("\"$fieldName\" <= $value") } - fun lessThanOrEqualTo(fieldName: String, value: Int): RealmQuery { + fun lessThanOrEqualTo( + fieldName: String, + value: Int + ): RealmQuery { appendLessThanOrEqualTo(fieldName, value) return query.lessThanOrEqualTo(fieldName, value) } - fun lessThanOrEqualTo(fieldName: String, value: Long): RealmQuery { + fun lessThanOrEqualTo( + fieldName: String, + value: Long + ): RealmQuery { appendLessThanOrEqualTo(fieldName, value) return query.lessThanOrEqualTo(fieldName, value) } - fun lessThanOrEqualTo(fieldName: String, value: Double): RealmQuery { + fun lessThanOrEqualTo( + fieldName: String, + value: Double + ): RealmQuery { appendLessThanOrEqualTo(fieldName, value) return query.lessThanOrEqualTo(fieldName, value) } - fun lessThanOrEqualTo(fieldName: String, value: Float): RealmQuery { + fun lessThanOrEqualTo( + fieldName: String, + value: Float + ): RealmQuery { appendLessThanOrEqualTo(fieldName, value) return query.lessThanOrEqualTo(fieldName, value) } - fun lessThanOrEqualTo(fieldName: String, value: Date): RealmQuery { + fun lessThanOrEqualTo( + fieldName: String, + value: Date + ): RealmQuery { appendLessThanOrEqualTo(fieldName, value) return query.lessThanOrEqualTo(fieldName, value) } - private fun appendBetween(fieldName: String, from: Any?, to: Any?) { + private fun appendBetween( + fieldName: String, + from: Any?, + to: Any? + ) { log += sec("\"$fieldName\" BETWEEN $from - $to") } - fun between(fieldName: String, from: Int, to: Int): RealmQuery { + fun between( + fieldName: String, + from: Int, + to: Int + ): RealmQuery { appendBetween(fieldName, from, to) return query.between(fieldName, from, to) } - fun between(fieldName: String, from: Long, to: Long): RealmQuery { + fun between( + fieldName: String, + from: Long, + to: Long + ): RealmQuery { appendBetween(fieldName, from, to) return query.between(fieldName, from, to) } - fun between(fieldName: String, from: Double, to: Double): RealmQuery { + fun between( + fieldName: String, + from: Double, + to: Double + ): RealmQuery { appendBetween(fieldName, from, to) return query.between(fieldName, from, to) } - fun between(fieldName: String, from: Float, to: Float): RealmQuery { + fun between( + fieldName: String, + from: Float, + to: Float + ): RealmQuery { appendBetween(fieldName, from, to) return query.between(fieldName, from, to) } - fun between(fieldName: String, from: Date, to: Date): RealmQuery { + fun between( + fieldName: String, + from: Date, + to: Date + ): RealmQuery { appendBetween(fieldName, from, to) return query.between(fieldName, from, to) } - private fun appendContains(fieldName: String, value: Any?, casing: Case? = null) { - log += sec( - "\"$fieldName\" CONTAINS \"$value\"" + ( - casing?.let { - " CASE ${casing.name}" - } ?: "" - ) - ) - } - - fun contains(fieldName: String, value: String): RealmQuery { + private fun appendContains( + fieldName: String, + value: Any?, + casing: Case? = null + ) { + log += + sec( + "\"$fieldName\" CONTAINS \"$value\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) + } + + fun contains( + fieldName: String, + value: String + ): RealmQuery { appendContains(fieldName, value) return query.contains(fieldName, value) } - fun contains(fieldName: String, value: String, casing: Case): RealmQuery { + fun contains( + fieldName: String, + value: String, + casing: Case + ): RealmQuery { appendContains(fieldName, value, casing) return query.contains(fieldName, value, casing) } - private fun appendBeginsWith(fieldName: String, value: Any?, casing: Case? = null) { - log += sec( - "\"$fieldName\" BEGINS WITH \"$value\"" + ( - casing?.let { - " CASE ${casing.name}" - } ?: "" - ) - ) - } - - fun beginsWith(fieldName: String, value: String): RealmQuery { + private fun appendBeginsWith( + fieldName: String, + value: Any?, + casing: Case? = null + ) { + log += + sec( + "\"$fieldName\" BEGINS WITH \"$value\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) + } + + fun beginsWith( + fieldName: String, + value: String + ): RealmQuery { appendBeginsWith(fieldName, value) return query.beginsWith(fieldName, value) } - fun beginsWith(fieldName: String, value: String, casing: Case): RealmQuery { + fun beginsWith( + fieldName: String, + value: String, + casing: Case + ): RealmQuery { appendBeginsWith(fieldName, value, casing) return query.beginsWith(fieldName, value, casing) } - private fun appendEndsWith(fieldName: String, value: Any?, casing: Case? = null) { - log += sec( - "\"$fieldName\" ENDS WITH \"$value\"" + ( - casing?.let { - " CASE ${casing.name}" - } ?: "" - ) - ) - } - - fun endsWith(fieldName: String, value: String): RealmQuery { + private fun appendEndsWith( + fieldName: String, + value: Any?, + casing: Case? = null + ) { + log += + sec( + "\"$fieldName\" ENDS WITH \"$value\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) + } + + fun endsWith( + fieldName: String, + value: String + ): RealmQuery { appendEndsWith(fieldName, value) return query.endsWith(fieldName, value) } - fun endsWith(fieldName: String, value: String, casing: Case): RealmQuery { + fun endsWith( + fieldName: String, + value: String, + casing: Case + ): RealmQuery { appendEndsWith(fieldName, value, casing) return query.endsWith(fieldName, value, casing) } - private fun appendLike(fieldName: String, value: Any?, casing: Case? = null) { - log += sec( - "\"$fieldName\" LIKE \"$value\"" + ( - casing?.let { - " CASE ${casing.name}" - } ?: "" - ) - ) - } - - fun like(fieldName: String, value: String): RealmQuery { + private fun appendLike( + fieldName: String, + value: Any?, + casing: Case? = null + ) { + log += + sec( + "\"$fieldName\" LIKE \"$value\"" + ( + casing?.let { + " CASE ${casing.name}" + } ?: "" + ) + ) + } + + fun like( + fieldName: String, + value: String + ): RealmQuery { appendLike(fieldName, value) return query.like(fieldName, value) } - fun like(fieldName: String, value: String, casing: Case): RealmQuery { + fun like( + fieldName: String, + value: String, + casing: Case + ): RealmQuery { appendLike(fieldName, value, casing) return query.like(fieldName, value, casing) } diff --git a/app/src/main/java/exh/util/MangaType.kt b/app/src/main/java/exh/util/MangaType.kt index 6f14b123cf80..fb9d5ac7e77c 100644 --- a/app/src/main/java/exh/util/MangaType.kt +++ b/app/src/main/java/exh/util/MangaType.kt @@ -6,9 +6,9 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.reader.ReaderActivity -import java.util.Locale import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.util.Locale fun Manga.mangaType(context: Context): String { return context.getString( @@ -61,7 +61,9 @@ fun Manga.defaultReaderType(): Int? { ReaderActivity.WEBTOON /* } else if (type == MangaType.TYPE_MANHUA || (type == MangaType.TYPE_COMIC && !sourceName.contains("tapastic", ignoreCase = true))) { ReaderActivity.LEFT_TO_RIGHT*/ - } else null + } else { + null + } } private fun isMangaTag(tag: String): Boolean { diff --git a/app/src/main/java/exh/util/NakedTrie.kt b/app/src/main/java/exh/util/NakedTrie.kt index 5988b0d66e8a..e3dfef1d5c5d 100644 --- a/app/src/main/java/exh/util/NakedTrie.kt +++ b/app/src/main/java/exh/util/NakedTrie.kt @@ -11,7 +11,11 @@ class NakedTrieNode(val key: Int, var parent: NakedTrieNode?) { // Walks in ascending order // Consumer should return true to continue walking, false to stop walking - inline fun walk(prefix: String, consumer: (String, T) -> Boolean, leavesOnly: Boolean) { + inline fun walk( + prefix: String, + consumer: (String, T) -> Boolean, + leavesOnly: Boolean + ) { // Special case root if (hasData && (!leavesOnly || children.size() <= 0)) { if (!consumer(prefix, data!! as T)) return @@ -70,7 +74,10 @@ class NakedTrie : MutableMap { val root = NakedTrieNode(-1, null) private var version: Long = 0 - override fun put(key: String, value: T): T? { + override fun put( + key: String, + value: T + ): T? { // Traverse to node location in tree, making parent nodes if required var current = root for (c in key) { @@ -84,13 +91,14 @@ class NakedTrie : MutableMap { } // Add data to node or replace existing data - val previous = if (current.hasData) { - current.data - } else { - current.hasData = true - size++ - null - } + val previous = + if (current.hasData) { + current.data + } else { + current.hasData = true + size++ + null + } current.data = value version++ @@ -143,7 +151,9 @@ class NakedTrie : MutableMap { if (!curBottom.hasData && curBottom.children.size() <= 0) { // No data or child nodes, this node is useless, discard parent.children.remove(curBottom.key) - } else break + } else { + break + } } version++ @@ -170,11 +180,17 @@ class NakedTrie : MutableMap { // Walks in ascending order // Consumer should return true to continue walking, false to stop walking - inline fun walk(consumer: (String, T) -> Boolean, leavesOnly: Boolean) { + inline fun walk( + consumer: (String, T) -> Boolean, + leavesOnly: Boolean + ) { root.walk("", consumer, leavesOnly) } - fun getOrPut(key: String, producer: () -> T): T { + fun getOrPut( + key: String, + producer: () -> T + ): T { // Traverse to node location in tree, making parent nodes if required var current = root for (c in key) { @@ -199,7 +215,10 @@ class NakedTrie : MutableMap { } // Includes root - fun subMap(prefix: String, leavesOnly: Boolean = false): Map { + fun subMap( + prefix: String, + leavesOnly: Boolean = false + ): Map { val node = getAsNode(prefix) ?: return emptyMap() return object : Map { @@ -219,6 +238,7 @@ class NakedTrie : MutableMap { ) return out } + /** * Returns a read-only [Set] of all keys in this map. */ @@ -241,7 +261,10 @@ class NakedTrie : MutableMap { */ override val size: Int get() { var s = 0 - node.walk("", { _, _ -> s++; true }, leavesOnly) + node.walk("", { _, _ -> + s++ + true + }, leavesOnly) return s } @@ -331,38 +354,41 @@ class NakedTrie : MutableMap { * Returns a [MutableSet] of all key/value pairs in this map. */ override val entries: MutableSet> - get() = FakeMutableSet.fromSet( - mutableSetOf>().apply { - walk { k, v -> - this += FakeMutableEntry.fromPair(k, v) - true + get() = + FakeMutableSet.fromSet( + mutableSetOf>().apply { + walk { k, v -> + this += FakeMutableEntry.fromPair(k, v) + true + } } - } - ) + ) /** * Returns a [MutableSet] of all keys in this map. */ override val keys: MutableSet - get() = FakeMutableSet.fromSet( - mutableSetOf().apply { - walk { k, _ -> - this += k - true + get() = + FakeMutableSet.fromSet( + mutableSetOf().apply { + walk { k, _ -> + this += k + true + } } - } - ) + ) /** * Returns a [MutableCollection] of all values in this map. Note that this collection may contain duplicate values. */ override val values: MutableCollection - get() = FakeMutableCollection.fromCollection( - mutableListOf().apply { - walk { _, v -> - this += v - true + get() = + FakeMutableCollection.fromCollection( + mutableListOf().apply { + walk { _, v -> + this += v + true + } } - } - ) + ) } diff --git a/app/src/main/java/exh/util/OkHttpUtil.kt b/app/src/main/java/exh/util/OkHttpUtil.kt index cad425cf0d20..aee23a3e5889 100644 --- a/app/src/main/java/exh/util/OkHttpUtil.kt +++ b/app/src/main/java/exh/util/OkHttpUtil.kt @@ -12,9 +12,10 @@ fun Response.interceptAsHtml(block: (Document) -> Unit): Response { body.contentType()?.subtype == "html" ) { val bodyString = body.string() - val rebuiltResponse = newBuilder() - .body(bodyString.toResponseBody(body.contentType())) - .build() + val rebuiltResponse = + newBuilder() + .body(bodyString.toResponseBody(body.contentType())) + .build() try { // Search for captcha val parsed = asJsoup(html = bodyString) diff --git a/app/src/main/java/exh/util/RealmUtil.kt b/app/src/main/java/exh/util/RealmUtil.kt index 475bea2203e4..f266c42b9148 100644 --- a/app/src/main/java/exh/util/RealmUtil.kt +++ b/app/src/main/java/exh/util/RealmUtil.kt @@ -49,8 +49,6 @@ inline fun Realm.useTrans(block: (Realm) -> T): T { } } -fun Realm.createUUIDObj(clazz: Class) = - createObject(clazz, UUID.randomUUID().toString())!! +fun Realm.createUUIDObj(clazz: Class) = createObject(clazz, UUID.randomUUID().toString())!! -inline fun Realm.createUUIDObj() = - createUUIDObj(T::class.java) +inline fun Realm.createUUIDObj() = createUUIDObj(T::class.java) diff --git a/app/src/main/java/exh/util/RxUtil.kt b/app/src/main/java/exh/util/RxUtil.kt index 25be431305cd..e678b3913239 100644 --- a/app/src/main/java/exh/util/RxUtil.kt +++ b/app/src/main/java/exh/util/RxUtil.kt @@ -2,7 +2,6 @@ package exh.util import com.pushtorefresh.storio.operations.PreparedOperation import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetObject -import kotlin.coroutines.resumeWithException import kotlinx.coroutines.suspendCancellableCoroutine import rx.Completable import rx.Observable @@ -10,6 +9,7 @@ import rx.Scheduler import rx.Single import rx.Subscription import rx.subjects.ReplaySubject +import kotlin.coroutines.resumeWithException /** * Transform a cold single to a hot single @@ -37,18 +37,19 @@ suspend fun Single.await(subscribeOn: Scheduler? = null): T { return suspendCancellableCoroutine { continuation -> val self = if (subscribeOn != null) subscribeOn(subscribeOn) else this lateinit var sub: Subscription - sub = self.subscribe( - { - continuation.resume(it) { - sub.unsubscribe() - } - }, - { - if (!continuation.isCancelled) { - continuation.resumeWithException(it) + sub = + self.subscribe( + { + continuation.resume(it) { + sub.unsubscribe() + } + }, + { + if (!continuation.isCancelled) { + continuation.resumeWithException(it) + } } - } - ) + ) continuation.invokeOnCancellation { sub.unsubscribe() @@ -57,24 +58,26 @@ suspend fun Single.await(subscribeOn: Scheduler? = null): T { } suspend fun PreparedOperation.await(): T = asRxSingle().await() + suspend fun PreparedGetObject.await(): T? = asRxSingle().await() suspend fun Completable.awaitSuspending(subscribeOn: Scheduler? = null) { return suspendCancellableCoroutine { continuation -> val self = if (subscribeOn != null) subscribeOn(subscribeOn) else this lateinit var sub: Subscription - sub = self.subscribe( - { - continuation.resume(Unit) { - sub.unsubscribe() - } - }, - { - if (!continuation.isCancelled) { - continuation.resumeWithException(it) + sub = + self.subscribe( + { + continuation.resume(Unit) { + sub.unsubscribe() + } + }, + { + if (!continuation.isCancelled) { + continuation.resumeWithException(it) + } } - } - ) + ) continuation.invokeOnCancellation { sub.unsubscribe() diff --git a/app/src/main/java/exh/util/SearchOverride.kt b/app/src/main/java/exh/util/SearchOverride.kt index ca459372966b..8089f1722d90 100644 --- a/app/src/main/java/exh/util/SearchOverride.kt +++ b/app/src/main/java/exh/util/SearchOverride.kt @@ -13,22 +13,24 @@ private val galleryAdder by lazy { /** * A version of fetchSearchManga that supports URL importing */ -fun UrlImportableSource.urlImportFetchSearchManga(query: String, fail: () -> Observable) = - when { - query.startsWith("http://") || query.startsWith("https://") -> { - Observable.fromCallable { - val res = galleryAdder.addGallery(query, false, this) - MangasPage( - ( - if (res is GalleryAddEvent.Success) { - listOf(res.manga) - } else { - emptyList() - } - ), - false - ) - } +fun UrlImportableSource.urlImportFetchSearchManga( + query: String, + fail: () -> Observable +) = when { + query.startsWith("http://") || query.startsWith("https://") -> { + Observable.fromCallable { + val res = galleryAdder.addGallery(query, false, this) + MangasPage( + ( + if (res is GalleryAddEvent.Success) { + listOf(res.manga) + } else { + emptyList() + } + ), + false + ) } - else -> fail() } + else -> fail() +} diff --git a/app/src/main/java/exh/util/SourceTagsUtil.kt b/app/src/main/java/exh/util/SourceTagsUtil.kt index 8a0418e9aed2..19cf79314155 100644 --- a/app/src/main/java/exh/util/SourceTagsUtil.kt +++ b/app/src/main/java/exh/util/SourceTagsUtil.kt @@ -10,9 +10,21 @@ import exh.metadata.metadata.base.RaisedTag import java.util.Locale class SourceTagsUtil { - fun getWrappedTag(sourceId: Long, namespace: String? = null, tag: String? = null, fullTag: String? = null): String? { + fun getWrappedTag( + sourceId: Long, + namespace: String? = null, + tag: String? = null, + fullTag: String? = null + ): String? { return if (sourceId == EXH_SOURCE_ID || sourceId == EH_SOURCE_ID || sourceId == NHENTAI_SOURCE_ID) { - val parsed = if (fullTag != null) parseTag(fullTag) else if (namespace != null && tag != null) RaisedTag(namespace, tag, TAG_TYPE_DEFAULT) else null + val parsed = + if (fullTag != null) { + parseTag(fullTag) + } else if (namespace != null && tag != null) { + RaisedTag(namespace, tag, TAG_TYPE_DEFAULT) + } else { + null + } if (parsed?.namespace != null) { when (sourceId) { NHENTAI_SOURCE_ID -> wrapTagNHentai(parsed.namespace, parsed.name.substringBefore('|').trim()) @@ -20,16 +32,27 @@ class SourceTagsUtil { TSUMINO_SOURCE_ID -> parsed.name.substringBefore('|').trim() else -> wrapTag(parsed.namespace, parsed.name.substringBefore('|').trim()) } - } else null - } else null + } else { + null + } + } else { + null + } } - private fun wrapTag(namespace: String, tag: String) = if (tag.contains(' ')) { + private fun wrapTag( + namespace: String, + tag: String + ) = if (tag.contains(' ')) { "$namespace:\"$tag$\"" } else { "$namespace:$tag$" } - private fun wrapTagNHentai(namespace: String, tag: String) = if (tag.contains(' ')) { + + private fun wrapTagNHentai( + namespace: String, + tag: String + ) = if (tag.contains(' ')) { if (namespace == "tag") { "\"$tag\"" } else { @@ -38,20 +61,22 @@ class SourceTagsUtil { } else { "$namespace:$tag" } + companion object { fun Manga.getRaisedTags(genres: List? = null): List? = (genres ?: this.getGenres())?.map { parseTag(it) } - fun parseTag(tag: String) = RaisedTag( - ( - if (tag.startsWith("-")) { - tag.substringAfter("-") - } else { - tag - } - ).substringBefore(':', missingDelimiterValue = "").trim(), - tag.substringAfter(':', missingDelimiterValue = tag).trim(), - if (tag.startsWith("-")) TAG_TYPE_EXCLUDE else TAG_TYPE_DEFAULT - ) + fun parseTag(tag: String) = + RaisedTag( + ( + if (tag.startsWith("-")) { + tag.substringAfter("-") + } else { + tag + } + ).substringBefore(':', missingDelimiterValue = "").trim(), + tag.substringAfter(':', missingDelimiterValue = tag).trim(), + if (tag.startsWith("-")) TAG_TYPE_EXCLUDE else TAG_TYPE_DEFAULT + ) const val TAG_TYPE_EXCLUDE = 69 // why not @@ -66,23 +91,24 @@ class SourceTagsUtil { const val ASIAN_PORN_COLOR = "#9575cd" const val MISC_COLOR = "#f06292" - fun getLocaleSourceUtil(language: String?) = when (language) { - "english", "eng" -> Locale("en") - "chinese" -> Locale("zh") - "spanish" -> Locale("es") - "korean" -> Locale("ko") - "russian" -> Locale("ru") - "french" -> Locale("fr") - "portuguese" -> Locale("pt") - "thai" -> Locale("th") - "german" -> Locale("de") - "italian" -> Locale("it") - "vietnamese" -> Locale("vi") - "polish" -> Locale("pl") - "hungarian" -> Locale("hu") - "dutch" -> Locale("nl") - else -> null - } + fun getLocaleSourceUtil(language: String?) = + when (language) { + "english", "eng" -> Locale("en") + "chinese" -> Locale("zh") + "spanish" -> Locale("es") + "korean" -> Locale("ko") + "russian" -> Locale("ru") + "french" -> Locale("fr") + "portuguese" -> Locale("pt") + "thai" -> Locale("th") + "german" -> Locale("de") + "italian" -> Locale("it") + "vietnamese" -> Locale("vi") + "polish" -> Locale("pl") + "hungarian" -> Locale("hu") + "dutch" -> Locale("nl") + else -> null + } private const val TAG_TYPE_DEFAULT = 1 } diff --git a/app/src/main/java/exh/util/SparseArrayCollection.kt b/app/src/main/java/exh/util/SparseArrayCollection.kt index 10259fd295c5..f4b2c709499d 100644 --- a/app/src/main/java/exh/util/SparseArrayCollection.kt +++ b/app/src/main/java/exh/util/SparseArrayCollection.kt @@ -6,68 +6,71 @@ import java.util.AbstractMap class SparseArrayKeyCollection(val sparseArray: SparseArray, var reverse: Boolean = false) : AbstractCollection() { override val size get() = sparseArray.size() - override fun iterator() = object : Iterator { - private var index: Int = 0 + override fun iterator() = + object : Iterator { + private var index: Int = 0 - /** - * Returns `true` if the iteration has more elements. - */ - override fun hasNext() = index < sparseArray.size() + /** + * Returns `true` if the iteration has more elements. + */ + override fun hasNext() = index < sparseArray.size() - /** - * Returns the next element in the iteration. - */ - override fun next(): Int { - var idx = index++ - if (reverse) idx = sparseArray.size() - 1 - idx - return sparseArray.keyAt(idx) + /** + * Returns the next element in the iteration. + */ + override fun next(): Int { + var idx = index++ + if (reverse) idx = sparseArray.size() - 1 - idx + return sparseArray.keyAt(idx) + } } - } } class SparseArrayValueCollection(val sparseArray: SparseArray, var reverse: Boolean = false) : AbstractCollection() { override val size get() = sparseArray.size() - override fun iterator() = object : Iterator { - private var index: Int = 0 + override fun iterator() = + object : Iterator { + private var index: Int = 0 - /** - * Returns `true` if the iteration has more elements. - */ - override fun hasNext() = index < sparseArray.size() + /** + * Returns `true` if the iteration has more elements. + */ + override fun hasNext() = index < sparseArray.size() - /** - * Returns the next element in the iteration. - */ - override fun next(): E { - var idx = index++ - if (reverse) idx = sparseArray.size() - 1 - idx - return sparseArray.valueAt(idx) + /** + * Returns the next element in the iteration. + */ + override fun next(): E { + var idx = index++ + if (reverse) idx = sparseArray.size() - 1 - idx + return sparseArray.valueAt(idx) + } } - } } class SparseArrayCollection(val sparseArray: SparseArray, var reverse: Boolean = false) : AbstractCollection>() { override val size get() = sparseArray.size() - override fun iterator() = object : Iterator> { - private var index: Int = 0 + override fun iterator() = + object : Iterator> { + private var index: Int = 0 - /** - * Returns `true` if the iteration has more elements. - */ - override fun hasNext() = index < sparseArray.size() + /** + * Returns `true` if the iteration has more elements. + */ + override fun hasNext() = index < sparseArray.size() - /** - * Returns the next element in the iteration. - */ - override fun next(): Map.Entry { - var idx = index++ - if (reverse) idx = sparseArray.size() - 1 - idx - return AbstractMap.SimpleImmutableEntry( - sparseArray.keyAt(idx), - sparseArray.valueAt(idx) - ) + /** + * Returns the next element in the iteration. + */ + override fun next(): Map.Entry { + var idx = index++ + if (reverse) idx = sparseArray.size() - 1 - idx + return AbstractMap.SimpleImmutableEntry( + sparseArray.keyAt(idx), + sparseArray.valueAt(idx) + ) + } } - } } diff --git a/app/src/main/java/exh/util/StringUtil.kt b/app/src/main/java/exh/util/StringUtil.kt index a80054d396b8..d30e5fc35da2 100644 --- a/app/src/main/java/exh/util/StringUtil.kt +++ b/app/src/main/java/exh/util/StringUtil.kt @@ -1,5 +1,7 @@ package exh.util fun List.trimAll() = map { it.trim() } + fun List.dropBlank() = filter { it.isNotBlank() } + fun List.dropEmpty() = filter { it.isNotEmpty() } diff --git a/app/src/main/java/exh/util/ViewExtensions.kt b/app/src/main/java/exh/util/ViewExtensions.kt index 5a99f5f7bc06..db615d250d29 100644 --- a/app/src/main/java/exh/util/ViewExtensions.kt +++ b/app/src/main/java/exh/util/ViewExtensions.kt @@ -34,7 +34,10 @@ fun View.doOnApplyWindowInsets(f: (View, WindowInsets, ViewPaddingState) -> Unit } object ControllerViewWindowInsetsListener : View.OnApplyWindowInsetsListener { - override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { + override fun onApplyWindowInsets( + v: View, + insets: WindowInsets + ): WindowInsets { v.updateLayoutParams { val attrsArray = intArrayOf(android.R.attr.actionBarSize) val array = v.context.obtainStyledAttributes(attrsArray) @@ -49,13 +52,15 @@ fun View.requestApplyInsetsWhenAttached() { if (isAttachedToWindow) { requestApplyInsets() } else { - addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) { - v.requestApplyInsets() - } + addOnAttachStateChangeListener( + object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + v.requestApplyInsets() + } - override fun onViewDetachedFromWindow(v: View) = Unit - }) + override fun onViewDetachedFromWindow(v: View) = Unit + } + ) } } @@ -79,14 +84,15 @@ inline fun View.updatePaddingRelative( setPaddingRelative(start, top, end, bottom) } -private fun createStateForView(view: View) = ViewPaddingState( - view.paddingLeft, - view.paddingTop, - view.paddingRight, - view.paddingBottom, - view.paddingStart, - view.paddingEnd -) +private fun createStateForView(view: View) = + ViewPaddingState( + view.paddingLeft, + view.paddingTop, + view.paddingRight, + view.paddingBottom, + view.paddingStart, + view.paddingEnd + ) data class ViewPaddingState( val left: Int, @@ -98,7 +104,10 @@ data class ViewPaddingState( ) object RecyclerWindowInsetsListener : View.OnApplyWindowInsetsListener { - override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { + override fun onApplyWindowInsets( + v: View, + insets: WindowInsets + ): WindowInsets { v.updatePaddingRelative(bottom = insets.systemWindowInsetBottom) // v.updatePaddingRelative(bottom = v.paddingBottom + insets.systemWindowInsetBottom) return insets @@ -113,29 +122,35 @@ object RecyclerWindowInsetsListener : View.OnApplyWindowInsetsListener { * @param onLongClick Optional long click listener for each chip. * @param sourceId Optional source check to determine if we need special search functions for each chip. */ -fun ChipGroup.setChipsExtended(items: List?, onClick: (item: String) -> Unit = {}, onLongClick: (item: String) -> Unit = {}, sourceId: Long = 0L) { +fun ChipGroup.setChipsExtended( + items: List?, + onClick: (item: String) -> Unit = {}, + onLongClick: (item: String) -> Unit = {}, + sourceId: Long = 0L +) { removeAllViews() items?.forEach { item -> - val chip = Chip(context).apply { - text = item - var search = item - if (sourceId == EXH_SOURCE_ID || sourceId == EH_SOURCE_ID || sourceId == NHENTAI_SOURCE_ID) { - val parsed = parseTag(search) - if (sourceId == NHENTAI_SOURCE_ID) { - search = wrapTagNHentai(parsed.first, parsed.second.substringBefore('|').trim()) + val chip = + Chip(context).apply { + text = item + var search = item + if (sourceId == EXH_SOURCE_ID || sourceId == EH_SOURCE_ID || sourceId == NHENTAI_SOURCE_ID) { + val parsed = parseTag(search) + if (sourceId == NHENTAI_SOURCE_ID) { + search = wrapTagNHentai(parsed.first, parsed.second.substringBefore('|').trim()) + } else { + search = wrapTag(parsed.first, parsed.second.substringBefore('|').trim()) + } } else { - search = wrapTag(parsed.first, parsed.second.substringBefore('|').trim()) + search = wrapTag(search) + } + setOnClickListener { onClick(search) } + setOnLongClickListener { + onLongClick(search) + false } - } else { - search = wrapTag(search) - } - setOnClickListener { onClick(search) } - setOnLongClickListener { - onLongClick(search) - false } - } addView(chip) } @@ -143,19 +158,26 @@ fun ChipGroup.setChipsExtended(items: List?, onClick: (item: String) -> private fun parseTag(tag: String) = tag.substringBefore(':').trim() to tag.substringAfter(':').trim() -private fun wrapTag(tag: String) = if (tag.contains(' ')) { - "\"$tag\"" -} else { - "$tag" -} +private fun wrapTag(tag: String) = + if (tag.contains(' ')) { + "\"$tag\"" + } else { + "$tag" + } -private fun wrapTag(namespace: String, tag: String) = if (tag.contains(' ')) { +private fun wrapTag( + namespace: String, + tag: String +) = if (tag.contains(' ')) { "$namespace:\"$tag$\"" } else { "$namespace:$tag$" } -private fun wrapTagNHentai(namespace: String, tag: String) = if (tag.contains(' ')) { +private fun wrapTagNHentai( + namespace: String, + tag: String +) = if (tag.contains(' ')) { if (namespace == "tag") { "\"$tag\"" } else { diff --git a/app/src/main/java/exh/util/ViewUtil.kt b/app/src/main/java/exh/util/ViewUtil.kt index 4d4e98bc1554..5b1f23c796f5 100644 --- a/app/src/main/java/exh/util/ViewUtil.kt +++ b/app/src/main/java/exh/util/ViewUtil.kt @@ -2,7 +2,10 @@ package exh.util import android.content.Context -fun dpToPx(context: Context, dp: Int): Int { +fun dpToPx( + context: Context, + dp: Int +): Int { val scale = context.resources.displayMetrics.density return (dp * scale + 0.5f).toInt() } diff --git a/app/src/main/java/org/vepta/vdm/ByteCursor.kt b/app/src/main/java/org/vepta/vdm/ByteCursor.kt index bbcbf2b0e17f..38dca700ea3e 100644 --- a/app/src/main/java/org/vepta/vdm/ByteCursor.kt +++ b/app/src/main/java/org/vepta/vdm/ByteCursor.kt @@ -39,14 +39,18 @@ class ByteCursor(val content: ByteArray) { } // Epic hack to get an unsigned short properly... - fun fakeNextShortInt(): Int = ByteBuffer - .wrap(arrayOf(0x00, 0x00, *next(2).toTypedArray()).toByteArray()) - .getInt(0) + fun fakeNextShortInt(): Int = + ByteBuffer + .wrap(arrayOf(0x00, 0x00, *next(2).toTypedArray()).toByteArray()) + .getInt(0) // fun nextShort(): Short = byteBuffer(2).getShort(0) fun nextInt(): Int = byteBuffer(4).getInt(0) + fun nextLong(): Long = byteBuffer(8).getLong(0) + fun nextFloat(): Float = byteBuffer(4).getFloat(0) + fun nextDouble(): Double = byteBuffer(8).getDouble(0) fun skip(count: Int) { diff --git a/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializer.kt b/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializer.kt index a760e467a9dd..373686f8401f 100755 --- a/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializer.kt +++ b/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializer.kt @@ -2,8 +2,6 @@ package xyz.nulldev.ts.api.http.serializer import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList -import kotlin.reflect.KMutableProperty1 -import kotlin.reflect.full.isSubclassOf import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.boolean @@ -17,27 +15,31 @@ import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.long import kotlinx.serialization.json.put import kotlinx.serialization.json.putJsonObject +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.full.isSubclassOf class FilterSerializer { - val serializers = listOf>( - // EXH --> - HelpDialogSerializer(this), - // EXH <-- - HeaderSerializer(this), - SeparatorSerializer(this), - SelectSerializer(this), - TextSerializer(this), - CheckboxSerializer(this), - TriStateSerializer(this), - GroupSerializer(this), - SortSerializer(this) - ) + val serializers = + listOf>( + // EXH --> + HelpDialogSerializer(this), + // EXH <-- + HeaderSerializer(this), + SeparatorSerializer(this), + SelectSerializer(this), + TextSerializer(this), + CheckboxSerializer(this), + TriStateSerializer(this), + GroupSerializer(this), + SortSerializer(this) + ) - fun serialize(filters: FilterList) = buildJsonArray { - filters.filterIsInstance>().forEach { - add(serialize(it)) + fun serialize(filters: FilterList) = + buildJsonArray { + filters.filterIsInstance>().forEach { + add(serialize(it)) + } } - } fun serialize(filter: Filter): JsonObject { return serializers @@ -45,59 +47,67 @@ class FilterSerializer { .firstOrNull { filter::class.isSubclassOf(it.clazz) }?.let { serializer -> - buildJsonObject { - with(serializer) { serialize(filter) } + buildJsonObject { + with(serializer) { serialize(filter) } - val classMappings = mutableListOf>() + val classMappings = mutableListOf>() - serializer.mappings().forEach { - val res = it.second.get(filter) - put(it.first, res.toString()) - classMappings += it.first to (res?.javaClass?.name ?: "null") - } + serializer.mappings().forEach { + val res = it.second.get(filter) + put(it.first, res.toString()) + classMappings += it.first to (res?.javaClass?.name ?: "null") + } - putJsonObject(CLASS_MAPPINGS) { - classMappings.forEach { (t, u) -> - put(t, u.toString()) + putJsonObject(CLASS_MAPPINGS) { + classMappings.forEach { (t, u) -> + put(t, u.toString()) + } } - } - put(TYPE, serializer.type) - } - } ?: throw IllegalArgumentException("Cannot serialize this Filter object!") + put(TYPE, serializer.type) + } + } ?: throw IllegalArgumentException("Cannot serialize this Filter object!") } - fun deserialize(filters: FilterList, json: JsonArray) { + fun deserialize( + filters: FilterList, + json: JsonArray + ) { filters.filterIsInstance>().zip(json).forEach { (filter, obj) -> deserialize(filter, obj.jsonObject) } } - fun deserialize(filter: Filter, json: JsonObject) { - val serializer = serializers - .filterIsInstance>>() - .firstOrNull { - it.type == json[TYPE]!!.jsonPrimitive.content - } ?: throw IllegalArgumentException("Cannot deserialize this type!") + fun deserialize( + filter: Filter, + json: JsonObject + ) { + val serializer = + serializers + .filterIsInstance>>() + .firstOrNull { + it.type == json[TYPE]!!.jsonPrimitive.content + } ?: throw IllegalArgumentException("Cannot deserialize this type!") serializer.deserialize(json, filter) serializer.mappings().forEach { if (it.second is KMutableProperty1) { val obj = json[it.first]!!.jsonPrimitive - val res: Any? = when (json[CLASS_MAPPINGS]!!.jsonObject[it.first]!!.jsonPrimitive.content) { - java.lang.Integer::class.java.name -> obj.int - java.lang.Long::class.java.name -> obj.long - java.lang.Float::class.java.name -> obj.float - java.lang.Double::class.java.name -> obj.double - java.lang.String::class.java.name -> obj.content - java.lang.Boolean::class.java.name -> obj.boolean - java.lang.Byte::class.java.name -> obj.content.toByte() - java.lang.Short::class.java.name -> obj.content.toShort() - java.lang.Character::class.java.name -> obj.content[0] - "null" -> null - else -> throw IllegalArgumentException("Cannot deserialize this type!") - } + val res: Any? = + when (json[CLASS_MAPPINGS]!!.jsonObject[it.first]!!.jsonPrimitive.content) { + java.lang.Integer::class.java.name -> obj.int + java.lang.Long::class.java.name -> obj.long + java.lang.Float::class.java.name -> obj.float + java.lang.Double::class.java.name -> obj.double + java.lang.String::class.java.name -> obj.content + java.lang.Boolean::class.java.name -> obj.boolean + java.lang.Byte::class.java.name -> obj.content.toByte() + java.lang.Short::class.java.name -> obj.content.toShort() + java.lang.Character::class.java.name -> obj.content[0] + "null" -> null + else -> throw IllegalArgumentException("Cannot deserialize this type!") + } @Suppress("UNCHECKED_CAST") (it.second as KMutableProperty1, in Any?>).set(filter, res) } diff --git a/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializerModels.kt b/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializerModels.kt index 34336e3981c3..a48ed9c022c5 100755 --- a/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializerModels.kt +++ b/app/src/main/java/xyz/nulldev/ts/api/http/serializer/FilterSerializerModels.kt @@ -1,8 +1,6 @@ package xyz.nulldev.ts.api.http.serializer import eu.kanade.tachiyomi.source.model.Filter -import kotlin.reflect.KClass -import kotlin.reflect.KProperty1 import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder @@ -15,10 +13,16 @@ import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlinx.serialization.json.putJsonArray +import kotlin.reflect.KClass +import kotlin.reflect.KProperty1 interface Serializer> { fun JsonObjectBuilder.serialize(filter: T) {} - fun deserialize(json: JsonObject, filter: T) {} + + fun deserialize( + json: JsonObject, + filter: T + ) {} /** * Automatic two-way mappings between fields and JSON @@ -35,11 +39,12 @@ class HelpDialogSerializer(override val serializer: FilterSerializer) : Serializ override val type = "HELP_DIALOG" override val clazz = Filter.HelpDialog::class - override fun mappings() = listOf( - Pair(NAME, Filter.HelpDialog::name), - Pair(DIALOG_TITLE, Filter.HelpDialog::dialogTitle), - Pair(MARKDOWN, Filter.HelpDialog::markdown) - ) + override fun mappings() = + listOf( + Pair(NAME, Filter.HelpDialog::name), + Pair(DIALOG_TITLE, Filter.HelpDialog::dialogTitle), + Pair(MARKDOWN, Filter.HelpDialog::markdown) + ) companion object { const val NAME = "name" @@ -53,9 +58,10 @@ class HeaderSerializer(override val serializer: FilterSerializer) : Serializer::name), - Pair(STATE, Filter.Select::state) - ) + override fun mappings() = + listOf( + Pair(NAME, Filter.Select::name), + Pair(STATE, Filter.Select::state) + ) companion object { const val NAME = "name" @@ -104,10 +112,11 @@ class TextSerializer(override val serializer: FilterSerializer) : Serializer) { + override fun deserialize( + json: JsonObject, + filter: Filter.Group + ) { json[STATE]!!.jsonArray.forEachIndexed { index, jsonElement -> if (jsonElement !is JsonNull) { @Suppress("UNCHECKED_CAST") @@ -173,9 +187,10 @@ class GroupSerializer(override val serializer: FilterSerializer) : Serializer::name) - ) + override fun mappings() = + listOf( + Pair(NAME, Filter.Group::name) + ) companion object { const val NAME = "name" @@ -204,19 +219,24 @@ class SortSerializer(override val serializer: FilterSerializer) : Serializer) : RobolectricTestRunner(klass) { - -// override fun getAppManifest(config: Config): AndroidManifest { -// return super.getAppManifest(config).apply { packageName = "eu.kanade.tachiyomi" } -// } -// } +class CustomRobolectricGradleTestRunner(klass: Class<*>) : RobolectricTestRunner(klass) { + override fun getAppManifest(config: Config): AndroidManifest { + return super.getAppManifest(config).apply { packageName = "eu.kanade.tachiyomi" } + } +} diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt index d55071816953..e27da70f4fba 100644 --- a/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt +++ b/app/src/test/java/eu/kanade/tachiyomi/data/backup/BackupTest.kt @@ -25,9 +25,9 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.RETURNS_DEEP_STUBS -import org.mockito.Mockito.`when` import org.mockito.Mockito.anyLong import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config import rx.Observable @@ -72,11 +72,12 @@ class BackupTest { db = legacyBackupManager.databaseHelper // Mock the source manager - val module = object : InjektModule { - override fun InjektRegistrar.registerInjectables() { - addSingleton(mock(SourceManager::class.java, RETURNS_DEEP_STUBS)) + val module = + object : InjektModule { + override fun InjektRegistrar.registerInjectables() { + addSingleton(mock(SourceManager::class.java, RETURNS_DEEP_STUBS)) + } } - } Injekt.importModule(module) source = mock(HttpSource::class.java) diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt index 8e9c9e5fa477..68bb4cfb4f7a 100644 --- a/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt +++ b/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt @@ -17,8 +17,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Matchers.anyLong import org.mockito.Mockito.RETURNS_DEEP_STUBS -import org.mockito.Mockito.`when` import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` import org.robolectric.Robolectric import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config @@ -31,7 +31,6 @@ import uy.kohesive.injekt.api.addSingleton @Config(constants = BuildConfig::class, sdk = [Build.VERSION_CODES.LOLLIPOP]) @RunWith(CustomRobolectricGradleTestRunner::class) class LibraryUpdateServiceTest { - lateinit var app: Application lateinit var context: Context lateinit var service: LibraryUpdateService @@ -43,11 +42,12 @@ class LibraryUpdateServiceTest { context = app.applicationContext // Mock the source manager - val module = object : InjektModule { - override fun InjektRegistrar.registerInjectables() { - addSingleton(mock(SourceManager::class.java, RETURNS_DEEP_STUBS)) + val module = + object : InjektModule { + override fun InjektRegistrar.registerInjectables() { + addSingleton(mock(SourceManager::class.java, RETURNS_DEEP_STUBS)) + } } - } Injekt.importModule(module) service = Robolectric.setupService(LibraryUpdateService::class.java) diff --git a/build.gradle.kts b/build.gradle.kts index 98d96b9686e4..2e208ed2c6a3 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,6 +32,9 @@ subprojects { outputToConsole.set(true) ignoreFailures.set(false) enableExperimentalRules.set(true) + + disabledRules.set(setOf("standard:comment-wrapping", "experimental:comment-wrapping", "standard:enum-entry-name-case")) + filter { exclude("**/generated/**") include("**/kotlin/**") diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 654601e3b7e0..576bdd7e3929 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -1,10 +1,10 @@ object Versions { - const val KTLINT = "0.36.0" + const val KTLINT = "0.47.1" } object BuildPluginsVersion { const val AGP = "7.4.2" const val KOTLIN = "1.9.22" - const val KTLINT = "9.2.1" + const val KTLINT = "12.1.0" const val VERSIONS_PLUGIN = "0.28.0" }