Merge branch 'dev' into dev-settings-search

This commit is contained in:
mpm11011 2020-09-16 14:56:34 -04:00 committed by GitHub
commit 528c1b90c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
207 changed files with 2415 additions and 1427 deletions

View File

@ -2,7 +2,7 @@
I acknowledge that: I acknowledge that:
- I have updated to the latest version of the app (stable is v0.10.4) - I have updated to the latest version of the app (stable is v0.10.5)
- I have updated all extensions - I have updated all extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions - If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions

View File

@ -9,7 +9,7 @@ labels: "bug"
I acknowledge that: I acknowledge that:
- I have updated to the latest version of the app (stable is v0.10.4) - I have updated to the latest version of the app (stable is v0.10.5)
- I have updated all extensions - I have updated all extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions - If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions

View File

@ -9,7 +9,7 @@ labels: "feature"
I acknowledge that: I acknowledge that:
- I have updated to the latest version of the app (stable is v0.10.4) - I have updated to the latest version of the app (stable is v0.10.5)
- I have updated all extensions - I have updated all extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions - If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions

View File

@ -1,6 +1,6 @@
| Build | Stable | Weekly Preview | Contribute | Support Server | | Build | Stable | Weekly Preview | Contribute | Support Server |
|-------|----------|---------|------------|---------| |-------|----------|---------|------------|---------|
| [![Travis](https://img.shields.io/travis/inorichi/tachiyomi.svg)](https://travis-ci.org/inorichi/tachiyomi) | [![stable release](https://img.shields.io/github/release/inorichi/tachiyomi.svg?maxAge=3600&label=download)](https://github.com/inorichi/tachiyomi/releases) | [![latest weekly build](https://img.shields.io/badge/download-latest%20build-blue.svg)](http://tachiyomi.kanade.eu/latest) | [![Translation status](https://hosted.weblate.org/widgets/tachiyomi/-/svg-badge.svg)](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [![Discord](https://img.shields.io/discord/349436576037732353.svg)](https://discord.gg/tachiyomi) | | [![Travis](https://img.shields.io/travis/inorichi/tachiyomi.svg)](https://travis-ci.org/inorichi/tachiyomi) | [![stable release](https://img.shields.io/github/release/inorichi/tachiyomi.svg?maxAge=3600&label=download)](https://github.com/inorichi/tachiyomi/releases) | [![latest weekly build](https://img.shields.io/github/v/release/tachiyomiorg/android-app-preview.svg?maxAge=3600&label=download)](https://github.com/tachiyomiorg/android-app-preview/releases) | [![Translation status](https://hosted.weblate.org/widgets/tachiyomi/-/svg-badge.svg)](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [![Discord](https://img.shields.io/discord/349436576037732353.svg)](https://discord.gg/tachiyomi) |
# ![app icon](./.github/readme-images/app-icon.png)Tachiyomi # ![app icon](./.github/readme-images/app-icon.png)Tachiyomi
@ -23,7 +23,7 @@ Features include:
## Download ## Download
Get the app from our [releases page](https://github.com/inorichi/tachiyomi/releases). Get the app from our [releases page](https://github.com/inorichi/tachiyomi/releases).
If you want to try new features before they get to the stable release, you can download the preview version [here](http://tachiyomi.kanade.eu/latest). If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/tachiyomiorg/android-app-preview/releases).
## Issues, Feature Requests and Contributing ## Issues, Feature Requests and Contributing

View File

@ -40,8 +40,8 @@ android {
minSdkVersion AndroidConfig.minSdk minSdkVersion AndroidConfig.minSdk
targetSdkVersion AndroidConfig.targetSdk targetSdkVersion AndroidConfig.targetSdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 50 versionCode 51
versionName "0.10.4" versionName "0.10.5"
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\"" buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\"" buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
@ -133,7 +133,7 @@ dependencies {
implementation 'androidx.biometric:biometric:1.1.0-alpha02' implementation 'androidx.biometric:biometric:1.1.0-alpha02'
implementation 'androidx.browser:browser:1.2.0' implementation 'androidx.browser:browser:1.2.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
implementation 'androidx.core:core-ktx:1.4.0-alpha01' implementation 'androidx.core:core-ktx:1.4.0-alpha01'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
@ -163,14 +163,14 @@ dependencies {
implementation 'com.github.pwittchen:reactivenetwork:0.13.0' implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
// Network client // Network client
final okhttp_version = '4.8.1' final okhttp_version = '4.9.0'
implementation "com.squareup.okhttp3:okhttp:$okhttp_version" implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version" implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version"
implementation 'com.squareup.okio:okio:2.7.0' implementation 'com.squareup.okio:okio:2.8.0'
// TLS 1.3 support for Android < 10 // TLS 1.3 support for Android < 10
implementation 'org.conscrypt:conscrypt-android:2.4.0' implementation 'org.conscrypt:conscrypt-android:2.5.1'
// REST // REST
final retrofit_version = '2.9.0' final retrofit_version = '2.9.0'
@ -281,10 +281,6 @@ dependencies {
// For detecting memory leaks; see https://square.github.io/leakcanary/ // For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4' // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
// Debug tool; see https://fbflipper.com/
// debugImplementation 'com.facebook.flipper:flipper:0.50.0'
// debugImplementation 'com.facebook.soloader:soloader:0.9.0'
} }
buildscript { buildscript {
@ -312,7 +308,7 @@ task copyResources(type: Copy) {
include '**/*' include '**/*'
} }
preBuild.dependsOn(ktlintFormat, copyResources) preBuild.dependsOn(formatKotlin, copyResources)
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) { if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'

View File

@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import java.security.Security
import org.acra.ACRA import org.acra.ACRA
import org.acra.annotation.AcraCore import org.acra.annotation.AcraCore
import org.acra.annotation.AcraHttpSender import org.acra.annotation.AcraHttpSender
@ -24,6 +23,7 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.InjektScope import uy.kohesive.injekt.api.InjektScope
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import uy.kohesive.injekt.registry.default.DefaultRegistrar import uy.kohesive.injekt.registry.default.DefaultRegistrar
import java.security.Security
@AcraCore( @AcraCore(
buildConfigClass = BuildConfig::class, buildConfigClass = BuildConfig::class,

View File

@ -1,11 +1,15 @@
package eu.kanade.tachiyomi package eu.kanade.tachiyomi
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.updater.UpdaterJob import eu.kanade.tachiyomi.data.updater.UpdaterJob
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
import eu.kanade.tachiyomi.ui.library.LibrarySort import eu.kanade.tachiyomi.ui.library.LibrarySort
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import java.io.File import java.io.File
object Migrations { object Migrations {
@ -89,6 +93,25 @@ object Migrations {
preferences.librarySortingMode().set(LibrarySort.ALPHA) preferences.librarySortingMode().set(LibrarySort.ALPHA)
} }
} }
if (oldVersion < 52) {
// Migrate library filters to tri-state versions
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
fun convertBooleanPrefToTriState(key: String): Int {
val oldPrefValue = prefs.getBoolean(key, false)
return if (oldPrefValue) ExtendedNavigationView.Item.TriStateGroup.STATE_INCLUDE
else ExtendedNavigationView.Item.TriStateGroup.STATE_IGNORE
}
prefs.edit {
putInt(PreferenceKeys.filterDownloaded, convertBooleanPrefToTriState("pref_filter_downloaded_key"))
remove("pref_filter_downloaded_key")
putInt(PreferenceKeys.filterUnread, convertBooleanPrefToTriState("pref_filter_unread_key"))
remove("pref_filter_unread_key")
putInt(PreferenceKeys.filterCompleted, convertBooleanPrefToTriState("pref_filter_completed_key"))
remove("pref_filter_completed_key")
}
}
return true return true
} }
return false return false

View File

@ -8,9 +8,9 @@ import androidx.work.WorkManager
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import java.util.concurrent.TimeUnit
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) : class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) { Worker(context, workerParams) {
@ -36,8 +36,10 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
val interval = prefInterval ?: preferences.backupInterval().get() val interval = prefInterval ?: preferences.backupInterval().get()
if (interval > 0) { if (interval > 0) {
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>( val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
interval.toLong(), TimeUnit.HOURS, interval.toLong(),
10, TimeUnit.MINUTES TimeUnit.HOURS,
10,
TimeUnit.MINUTES
) )
.addTag(TAG) .addTag(TAG)
.build() .build()

View File

@ -50,10 +50,10 @@ import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import kotlin.math.max
import rx.Observable import rx.Observable
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import kotlin.math.max
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) { class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {

View File

@ -11,9 +11,9 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import uy.kohesive.injekt.injectLazy
internal class BackupNotifier(private val context: Context) { internal class BackupNotifier(private val context: Context) {

View File

@ -32,12 +32,9 @@ import eu.kanade.tachiyomi.data.database.models.TrackImpl
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning import eu.kanade.tachiyomi.util.system.isServiceRunning
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -45,6 +42,10 @@ import kotlinx.coroutines.launch
import rx.Observable import rx.Observable
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/** /**
* Restores backup from a JSON file. * Restores backup from a JSON file.
@ -398,7 +399,12 @@ class BackupRestoreService : Service() {
return backupManager.restoreChapterFetchObservable(source, manga, chapters) return backupManager.restoreChapterFetchObservable(source, manga, chapters)
// If there's any error, return empty update and continue. // If there's any error, return empty update and continue.
.onErrorReturn { .onErrorReturn {
errors.add(Date() to "${manga.title} - ${it.message}") val errorMessage = if (it is NoChaptersException) {
getString(R.string.no_chapters_error)
} else {
it.message
}
errors.add(Date() to "${manga.title} - $errorMessage")
Pair(emptyList(), emptyList()) Pair(emptyList(), emptyList())
} }
} }

View File

@ -9,13 +9,13 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.storage.saveTo
import java.io.File
import java.io.IOException
import okhttp3.Response import okhttp3.Response
import okio.buffer import okio.buffer
import okio.sink import okio.sink
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File
import java.io.IOException
/** /**
* Class used to create chapter cache * Class used to create chapter cache

View File

@ -83,6 +83,11 @@ interface MangaQueries : DbProvider {
.withPutResolver(MangaFlagsPutResolver()) .withPutResolver(MangaFlagsPutResolver())
.prepare() .prepare()
fun updateFlags(mangas: List<Manga>) = db.put()
.objects(mangas)
.withPutResolver(MangaFlagsPutResolver(true))
.prepare()
fun updateLastUpdated(manga: Manga) = db.put() fun updateLastUpdated(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
.withPutResolver(MangaLastUpdatedPutResolver()) .withPutResolver(MangaLastUpdatedPutResolver())

View File

@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.data.database.inTransactionReturn
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
class MangaFlagsPutResolver : PutResolver<Manga>() { class MangaFlagsPutResolver(private val updateAll: Boolean = false) : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn { override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga) val updateQuery = mapToUpdateQuery(manga)
@ -19,11 +19,21 @@ class MangaFlagsPutResolver : PutResolver<Manga>() {
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
} }
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() fun mapToUpdateQuery(manga: Manga): UpdateQuery {
.table(MangaTable.TABLE) val builder = UpdateQuery.builder()
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id) return if (updateAll) {
.build() builder
.table(MangaTable.TABLE)
.build()
} else {
builder
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
}
}
fun mapToContentValues(manga: Manga) = ContentValues(1).apply { fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags) put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags)

View File

@ -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.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get 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 * Cache where we dump the downloads directory from the filesystem. This class is needed because

View File

@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import java.util.regex.Pattern
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.regex.Pattern
/** /**
* DownloadNotifier is used to show notifications when downloading one or multiple chapters. * DownloadNotifier is used to show notifications when downloading one or multiple chapters.
@ -107,7 +107,9 @@ internal class DownloadNotifier(private val context: Context) {
} }
val downloadingProgressText = context.getString( val downloadingProgressText = context.getString(
R.string.chapter_downloading_progress, download.downloadedImages, download.pages!!.size R.string.chapter_downloading_progress,
download.downloadedImages,
download.pages!!.size
) )
if (preferences.hideNotificationContent()) { if (preferences.hideNotificationContent()) {

View File

@ -22,7 +22,6 @@ import eu.kanade.tachiyomi.util.lang.plusAssign
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.storage.saveTo
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import java.io.File
import kotlinx.coroutines.async import kotlinx.coroutines.async
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
@ -31,6 +30,7 @@ import rx.schedulers.Schedulers
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File
/** /**
* This class is the one in charge of downloading chapters. * This class is the one in charge of downloading chapters.

View File

@ -5,9 +5,9 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadStore import eu.kanade.tachiyomi.data.download.DownloadStore
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import java.util.concurrent.CopyOnWriteArrayList
import rx.Observable import rx.Observable
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.util.concurrent.CopyOnWriteArrayList
class DownloadQueue( class DownloadQueue(
private val store: DownloadStore, private val store: DownloadStore,

View File

@ -5,12 +5,12 @@ import android.util.Log
import com.bumptech.glide.Priority import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.data.DataFetcher import com.bumptech.glide.load.data.DataFetcher
import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import timber.log.Timber
open class FileFetcher(private val filePath: String = "") : DataFetcher<InputStream> { open class FileFetcher(private val filePath: String = "") : DataFetcher<InputStream> {

View File

@ -14,10 +14,10 @@ import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.isLocal
import java.io.InputStream
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy 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. * A class for loading a cover associated with a [Manga] that can be present in our own cache.

View File

@ -14,9 +14,9 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.module.AppGlideModule import com.bumptech.glide.module.AppGlideModule
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import java.io.InputStream
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.InputStream
/** /**
* Class used to update Glide module settings * Class used to update Glide module settings

View File

@ -9,9 +9,9 @@ import androidx.work.WorkManager
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import java.util.concurrent.TimeUnit
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) : class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) { Worker(context, workerParams) {
@ -45,8 +45,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
.build() .build()
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>( val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
interval.toLong(), TimeUnit.HOURS, interval.toLong(),
10, TimeUnit.MINUTES TimeUnit.HOURS,
10,
TimeUnit.MINUTES
) )
.addTag(TAG) .addTag(TAG)
.setConstraints(constraints) .setConstraints(constraints)

View File

@ -22,9 +22,9 @@ import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.notification import eu.kanade.tachiyomi.util.system.notification
import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import uy.kohesive.injekt.injectLazy
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.DecimalFormatSymbols import java.text.DecimalFormatSymbols
import uy.kohesive.injekt.injectLazy
class LibraryUpdateNotifier(private val context: Context) { class LibraryUpdateNotifier(private val context: Context) {
@ -198,18 +198,23 @@ class LibraryUpdateNotifier(private val context: Context) {
// Mark chapters as read action // Mark chapters as read action
addAction( 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( NotificationReceiver.markAsReadPendingBroadcast(
context, context,
manga, chapters, Notifications.ID_NEW_CHAPTERS manga,
chapters,
Notifications.ID_NEW_CHAPTERS
) )
) )
// View chapters action // View chapters action
addAction( 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( NotificationReceiver.openChapterPendingActivity(
context, context,
manga, Notifications.ID_NEW_CHAPTERS manga,
Notifications.ID_NEW_CHAPTERS
) )
) )
} }

View File

@ -6,6 +6,7 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
@ -21,20 +22,21 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.prepUpdateCover import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning import eu.kanade.tachiyomi.util.system.isServiceRunning
import java.io.File
import java.util.concurrent.atomic.AtomicInteger
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File
import java.util.concurrent.atomic.AtomicInteger
/** /**
* This class will take care of updating the chapters of the manga from the library. It can be * This class will take care of updating the chapters of the manga from the library. It can be
@ -268,7 +270,12 @@ class LibraryUpdateService(
updateManga(manga) updateManga(manga)
// If there's any error, return empty update and continue. // If there's any error, return empty update and continue.
.onErrorReturn { .onErrorReturn {
failedUpdates.add(Pair(manga, it.message)) val errorMessage = if (it is NoChaptersException) {
getString(R.string.no_chapters_error)
} else {
it.message
}
failedUpdates.add(Pair(manga, errorMessage))
Pair(emptyList(), emptyList()) Pair(emptyList(), emptyList())
} }
// Filter out mangas without new chapters (or failed). // Filter out mangas without new chapters (or failed).

View File

@ -7,7 +7,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupRestoreService import eu.kanade.tachiyomi.data.backup.BackupRestoreService
import eu.kanade.tachiyomi.data.database.DatabaseHelper 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.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import java.io.File
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy 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 * Global [BroadcastReceiver] that runs on UI thread
@ -56,19 +56,22 @@ class NotificationReceiver : BroadcastReceiver() {
// Launch share activity and dismiss notification // Launch share activity and dismiss notification
ACTION_SHARE_IMAGE -> ACTION_SHARE_IMAGE ->
shareImage( shareImage(
context, intent.getStringExtra(EXTRA_FILE_LOCATION), context,
intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
) )
// Delete image from path and dismiss notification // Delete image from path and dismiss notification
ACTION_DELETE_IMAGE -> ACTION_DELETE_IMAGE ->
deleteImage( deleteImage(
context, intent.getStringExtra(EXTRA_FILE_LOCATION), context,
intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
) )
// Share backup file // Share backup file
ACTION_SHARE_BACKUP -> ACTION_SHARE_BACKUP ->
shareBackup( shareBackup(
context, intent.getParcelableExtra(EXTRA_URI), context,
intent.getParcelableExtra(EXTRA_URI),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
) )
ACTION_CANCEL_RESTORE -> cancelRestore( ACTION_CANCEL_RESTORE -> cancelRestore(
@ -80,7 +83,8 @@ class NotificationReceiver : BroadcastReceiver() {
// Open reader activity // Open reader activity
ACTION_OPEN_CHAPTER -> { ACTION_OPEN_CHAPTER -> {
openChapter( openChapter(
context, intent.getLongExtra(EXTRA_MANGA_ID, -1), context,
intent.getLongExtra(EXTRA_MANGA_ID, -1),
intent.getLongExtra(EXTRA_CHAPTER_ID, -1) intent.getLongExtra(EXTRA_CHAPTER_ID, -1)
) )
} }

View File

@ -82,53 +82,62 @@ object Notifications {
listOf( listOf(
NotificationChannel( NotificationChannel(
CHANNEL_COMMON, context.getString(R.string.channel_common), CHANNEL_COMMON,
context.getString(R.string.channel_common),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
), ),
NotificationChannel( NotificationChannel(
CHANNEL_LIBRARY, context.getString(R.string.channel_library), CHANNEL_LIBRARY,
context.getString(R.string.channel_library),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
setShowBadge(false) setShowBadge(false)
}, },
NotificationChannel( NotificationChannel(
CHANNEL_DOWNLOADER_PROGRESS, context.getString(R.string.channel_progress), CHANNEL_DOWNLOADER_PROGRESS,
context.getString(R.string.channel_progress),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
group = GROUP_DOWNLOADER group = GROUP_DOWNLOADER
setShowBadge(false) setShowBadge(false)
}, },
NotificationChannel( NotificationChannel(
CHANNEL_DOWNLOADER_COMPLETE, context.getString(R.string.channel_complete), CHANNEL_DOWNLOADER_COMPLETE,
context.getString(R.string.channel_complete),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
group = GROUP_DOWNLOADER group = GROUP_DOWNLOADER
setShowBadge(false) setShowBadge(false)
}, },
NotificationChannel( NotificationChannel(
CHANNEL_DOWNLOADER_ERROR, context.getString(R.string.channel_errors), CHANNEL_DOWNLOADER_ERROR,
context.getString(R.string.channel_errors),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
group = GROUP_DOWNLOADER group = GROUP_DOWNLOADER
setShowBadge(false) setShowBadge(false)
}, },
NotificationChannel( NotificationChannel(
CHANNEL_NEW_CHAPTERS, context.getString(R.string.channel_new_chapters), CHANNEL_NEW_CHAPTERS,
context.getString(R.string.channel_new_chapters),
NotificationManager.IMPORTANCE_DEFAULT NotificationManager.IMPORTANCE_DEFAULT
), ),
NotificationChannel( NotificationChannel(
CHANNEL_UPDATES_TO_EXTS, context.getString(R.string.channel_ext_updates), CHANNEL_UPDATES_TO_EXTS,
context.getString(R.string.channel_ext_updates),
NotificationManager.IMPORTANCE_DEFAULT NotificationManager.IMPORTANCE_DEFAULT
), ),
NotificationChannel( NotificationChannel(
CHANNEL_BACKUP_RESTORE_PROGRESS, context.getString(R.string.channel_progress), CHANNEL_BACKUP_RESTORE_PROGRESS,
context.getString(R.string.channel_progress),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
group = GROUP_BACKUP_RESTORE group = GROUP_BACKUP_RESTORE
setShowBadge(false) setShowBadge(false)
}, },
NotificationChannel( NotificationChannel(
CHANNEL_BACKUP_RESTORE_COMPLETE, context.getString(R.string.channel_complete), CHANNEL_BACKUP_RESTORE_COMPLETE,
context.getString(R.string.channel_complete),
NotificationManager.IMPORTANCE_HIGH NotificationManager.IMPORTANCE_HIGH
).apply { ).apply {
group = GROUP_BACKUP_RESTORE group = GROUP_BACKUP_RESTORE

View File

@ -109,11 +109,11 @@ object PreferenceKeys {
const val downloadedOnly = "pref_downloaded_only" const val downloadedOnly = "pref_downloaded_only"
const val filterDownloaded = "pref_filter_downloaded_key" const val filterDownloaded = "pref_filter_library_downloaded"
const val filterUnread = "pref_filter_unread_key" const val filterUnread = "pref_filter_library_unread"
const val filterCompleted = "pref_filter_completed_key" const val filterCompleted = "pref_filter_library_completed"
const val librarySortingMode = "library_sorting_mode" const val librarySortingMode = "library_sorting_mode"
@ -165,6 +165,18 @@ object PreferenceKeys {
const val enableDoh = "enable_doh" const val enableDoh = "enable_doh"
const val defaultChapterFilterByRead = "default_chapter_filter_by_read"
const val defaultChapterFilterByDownloaded = "default_chapter_filter_by_downloaded"
const val defaultChapterFilterByBookmarked = "default_chapter_filter_by_bookmarked"
const val defaultChapterSortBySourceOrNumber = "default_chapter_sort_by_source_or_number" // and upload date
const val defaultChapterSortByAscendingOrDescending = "default_chapter_sort_by_ascending_or_descending"
const val defaultChapterDisplayByNameOrNumber = "default_chapter_display_by_name_or_number"
fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId" fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId" fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"

View File

@ -8,19 +8,21 @@ import androidx.preference.PreferenceManager
import com.tfcporciuncula.flow.FlowSharedPreferences import com.tfcporciuncula.flow.FlowSharedPreferences
import com.tfcporciuncula.flow.Preference import com.tfcporciuncula.flow.Preference
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
import eu.kanade.tachiyomi.data.preference.PreferenceValues.NsfwAllowance import eu.kanade.tachiyomi.data.preference.PreferenceValues.NsfwAllowance
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.anilist.Anilist import eu.kanade.tachiyomi.data.track.anilist.Anilist
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach
import java.io.File import java.io.File
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import kotlinx.coroutines.ExperimentalCoroutinesApi import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
import kotlinx.coroutines.flow.Flow import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
import kotlinx.coroutines.flow.onEach
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> { fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> {
@ -209,11 +211,11 @@ class PreferencesHelper(val context: Context) {
fun categoryTabs() = flowPrefs.getBoolean(Keys.categoryTabs, true) fun categoryTabs() = flowPrefs.getBoolean(Keys.categoryTabs, true)
fun filterDownloaded() = flowPrefs.getBoolean(Keys.filterDownloaded, false) fun filterDownloaded() = flowPrefs.getInt(Keys.filterDownloaded, ExtendedNavigationView.Item.TriStateGroup.STATE_IGNORE)
fun filterUnread() = flowPrefs.getBoolean(Keys.filterUnread, false) fun filterUnread() = flowPrefs.getInt(Keys.filterUnread, ExtendedNavigationView.Item.TriStateGroup.STATE_IGNORE)
fun filterCompleted() = flowPrefs.getBoolean(Keys.filterCompleted, false) fun filterCompleted() = flowPrefs.getInt(Keys.filterCompleted, ExtendedNavigationView.Item.TriStateGroup.STATE_IGNORE)
fun librarySortingMode() = flowPrefs.getInt(Keys.librarySortingMode, 0) fun librarySortingMode() = flowPrefs.getInt(Keys.librarySortingMode, 0)
@ -254,4 +256,27 @@ class PreferencesHelper(val context: Context) {
fun lastSearchQuerySearchSettings() = prefs.getString("last_search_query", "") fun lastSearchQuerySearchSettings() = prefs.getString("last_search_query", "")
fun lastSearchQuerySearchSettings(query: String) = prefs.edit { putString("last_search_query", query) } fun lastSearchQuerySearchSettings(query: String) = prefs.edit { putString("last_search_query", query) }
fun filterChapterByRead() = prefs.getInt(Keys.defaultChapterFilterByRead, Manga.SHOW_ALL)
fun filterChapterByDownloaded() = prefs.getInt(Keys.defaultChapterFilterByDownloaded, Manga.SHOW_ALL)
fun filterChapterByBookmarked() = prefs.getInt(Keys.defaultChapterFilterByBookmarked, Manga.SHOW_ALL)
fun sortChapterBySourceOrNumber() = prefs.getInt(Keys.defaultChapterSortBySourceOrNumber, Manga.SORTING_SOURCE)
fun displayChapterByNameOrNumber() = prefs.getInt(Keys.defaultChapterDisplayByNameOrNumber, Manga.DISPLAY_NAME)
fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.SORT_DESC)
fun setChapterSettingsDefault(manga: Manga) {
prefs.edit {
putInt(Keys.defaultChapterFilterByRead, manga.readFilter)
putInt(Keys.defaultChapterFilterByDownloaded, manga.downloadedFilter)
putInt(Keys.defaultChapterFilterByBookmarked, manga.bookmarkedFilter)
putInt(Keys.defaultChapterSortBySourceOrNumber, manga.sorting)
putInt(Keys.defaultChapterDisplayByNameOrNumber, manga.displayMode)
putInt(Keys.defaultChapterSortByAscendingOrDescending, if (manga.sortDescending()) Manga.SORT_DESC else Manga.SORT_ASC)
}
}
} }

View File

@ -13,12 +13,12 @@ import com.google.gson.JsonParser
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import java.util.Calendar
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import rx.Observable import rx.Observable
import java.util.Calendar
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
@ -271,9 +271,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
} }
return ALManga( return ALManga(
struct["id"].asInt, struct["title"]["romaji"].asString, struct["coverImage"]["large"].asString, struct["id"].asInt,
struct["description"].nullString.orEmpty(), struct["type"].asString, struct["status"].nullString.orEmpty(), struct["title"]["romaji"].asString,
date, struct["chapters"].nullInt ?: 0 struct["coverImage"]["large"].asString,
struct["description"].nullString.orEmpty(),
struct["type"].asString,
struct["status"].nullString.orEmpty(),
date,
struct["chapters"].nullInt ?: 0
) )
} }

View File

@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import uy.kohesive.injekt.injectLazy
data class ALManga( data class ALManga(
val media_id: Int, val media_id: Int,

View File

@ -12,13 +12,13 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import java.net.URLEncoder
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder
class BangumiApi(private val client: OkHttpClient, interceptor: BangumiInterceptor) { class BangumiApi(private val client: OkHttpClient, interceptor: BangumiInterceptor) {

View File

@ -7,10 +7,10 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import java.text.DecimalFormat
import rx.Completable import rx.Completable
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.DecimalFormat
class Kitsu(private val context: Context, id: Int) : TrackService(id) { class Kitsu(private val context: Context, id: Int) : TrackService(id) {

View File

@ -11,13 +11,6 @@ import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.lang.toCalendar import eu.kanade.tachiyomi.util.lang.toCalendar
import eu.kanade.tachiyomi.util.selectInt import eu.kanade.tachiyomi.util.selectInt
import eu.kanade.tachiyomi.util.selectText 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.FormBody
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -30,6 +23,13 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.jsoup.parser.Parser import org.jsoup.parser.Parser
import rx.Observable 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) { class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {

View File

@ -1,23 +0,0 @@
package eu.kanade.tachiyomi.data.updater
import eu.kanade.tachiyomi.BuildConfig
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) {
DevRepoUpdateChecker()
} else {
GithubUpdateChecker()
}
}
}
/**
* Returns observable containing release information
*/
abstract suspend fun checkForUpdate(): UpdateResult
}

View File

@ -13,9 +13,10 @@ import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.util.concurrent.TimeUnit
class UpdaterJob(private val context: Context, workerParams: WorkerParameters) : class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) { Worker(context, workerParams) {
@ -23,7 +24,7 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
override fun doWork(): Result { override fun doWork(): Result {
return runBlocking { return runBlocking {
try { try {
val result = UpdateChecker.getUpdateChecker().checkForUpdate() val result = GithubUpdateChecker().checkForUpdate()
if (result is UpdateResult.NewUpdate<*>) { if (result is UpdateResult.NewUpdate<*>) {
val url = result.release.downloadLink val url = result.release.downloadLink
@ -65,8 +66,10 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
.build() .build()
val request = PeriodicWorkRequestBuilder<UpdaterJob>( val request = PeriodicWorkRequestBuilder<UpdaterJob>(
3, TimeUnit.DAYS, 3,
3, TimeUnit.HOURS TimeUnit.DAYS,
3,
TimeUnit.HOURS
) )
.addTag(TAG) .addTag(TAG)
.setConstraints(constraints) .setConstraints(constraints)

View File

@ -20,9 +20,9 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.storage.saveTo
import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning import eu.kanade.tachiyomi.util.system.isServiceRunning
import java.io.File
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File
class UpdaterService : Service() { class UpdaterService : Service() {

View File

@ -1,13 +0,0 @@
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
companion object {
const val LATEST_URL = "https://tachiyomi.kanade.eu/latest"
}
}

View File

@ -1,41 +0,0 @@
package eu.kanade.tachiyomi.data.updater.devrepo
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.updater.UpdateChecker
import eu.kanade.tachiyomi.data.updater.UpdateResult
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.await
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class DevRepoUpdateChecker : UpdateChecker() {
private val client: OkHttpClient by lazy {
Injekt.get<NetworkHelper>().client.newBuilder()
.followRedirects(false)
.build()
}
private val versionRegex: Regex by lazy {
Regex("tachiyomi-r(\\d+).apk")
}
override suspend fun checkForUpdate(): UpdateResult {
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]
return if (latestVersionNumber.toInt() > BuildConfig.COMMIT_COUNT.toInt()) {
DevRepoUpdateResult.NewUpdate(DevRepoRelease("v$latestVersionNumber"))
} else {
DevRepoUpdateResult.NoNewUpdate()
}
}
}

View File

@ -1,9 +0,0 @@
package eu.kanade.tachiyomi.data.updater.devrepo
import eu.kanade.tachiyomi.data.updater.UpdateResult
sealed class DevRepoUpdateResult : UpdateResult() {
class NewUpdate(release: DevRepoRelease) : UpdateResult.NewUpdate<DevRepoRelease>(release)
class NoNewUpdate : UpdateResult.NoNewUpdate()
}

View File

@ -28,5 +28,5 @@ class GithubRelease(
* Assets class containing download url. * Assets class containing download url.
* @param downloadLink download url. * @param downloadLink download url.
*/ */
inner class Assets(@SerializedName("browser_download_url") val downloadLink: String) class Assets(@SerializedName("browser_download_url") val downloadLink: String)
} }

View File

@ -4,11 +4,12 @@ import eu.kanade.tachiyomi.network.NetworkHelper
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Path
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
/** /**
* Used to connect with the GitHub API. * Used to connect with the GitHub API to get the latest release version from a repo.
*/ */
interface GithubService { interface GithubService {
@ -24,6 +25,6 @@ interface GithubService {
} }
} }
@GET("/repos/inorichi/tachiyomi/releases/latest") @GET("/repos/{repo}/releases/latest")
suspend fun getLatestVersion(): GithubRelease suspend fun getLatestVersion(@Path("repo", encoded = true) repo: String): GithubRelease
} }

View File

@ -1,23 +1,43 @@
package eu.kanade.tachiyomi.data.updater.github package eu.kanade.tachiyomi.data.updater.github
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.updater.UpdateChecker
import eu.kanade.tachiyomi.data.updater.UpdateResult import eu.kanade.tachiyomi.data.updater.UpdateResult
class GithubUpdateChecker : UpdateChecker() { class GithubUpdateChecker {
private val service: GithubService = GithubService.create() private val service: GithubService = GithubService.create()
override suspend fun checkForUpdate(): UpdateResult { private val repo: String by lazy {
val release = service.getLatestVersion() if (BuildConfig.DEBUG) {
"tachiyomiorg/android-app-preview"
} else {
"inorichi/tachiyomi"
}
}
val newVersion = release.version.replace("[^\\d.]".toRegex(), "") suspend fun checkForUpdate(): UpdateResult {
val release = service.getLatestVersion(repo)
// Check if latest version is different from current version // Check if latest version is different from current version
return if (newVersion != BuildConfig.VERSION_NAME) { return if (isNewVersion(release.version)) {
GithubUpdateResult.NewUpdate(release) GithubUpdateResult.NewUpdate(release)
} else { } else {
GithubUpdateResult.NoNewUpdate() GithubUpdateResult.NoNewUpdate()
} }
} }
private fun isNewVersion(versionTag: String): Boolean {
// Removes prefixes like "r" or "v"
val newVersion = versionTag.replace("[^\\d.]".toRegex(), "")
return if (BuildConfig.DEBUG) {
// Preview builds: based on releases in "tachiyomiorg/android-app-preview" repo
// tagged as something like "r1234"
newVersion.toInt() > BuildConfig.COMMIT_COUNT.toInt()
} else {
// Release builds: based on releases in "inorichi/tachiyomi" repo
// tagged as something like "v0.1.2"
newVersion != BuildConfig.VERSION_NAME
}
}
} }

View File

@ -16,10 +16,10 @@ import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.util.system.notification import eu.kanade.tachiyomi.util.system.notification
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParameters) : class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) { CoroutineWorker(context, workerParams) {
@ -73,8 +73,10 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
.build() .build()
val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>( val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>(
12, TimeUnit.HOURS, 12,
1, TimeUnit.HOURS TimeUnit.HOURS,
1,
TimeUnit.HOURS
) )
.addTag(TAG) .addTag(TAG)
.setConstraints(constraints) .setConstraints(constraints)

View File

@ -9,10 +9,10 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.LoadResult import eu.kanade.tachiyomi.extension.model.LoadResult
import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import java.util.Date
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Date
internal class ExtensionGithubApi { internal class ExtensionGithubApi {

View File

@ -13,11 +13,11 @@ import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import java.io.File
import java.util.concurrent.TimeUnit
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import timber.log.Timber import timber.log.Timber
import java.io.File
import java.util.concurrent.TimeUnit
/** /**
* The installer which installs, updates and uninstalls the extensions. * The installer which installs, updates and uninstalls the extensions.

View File

@ -178,7 +178,13 @@ internal object ExtensionLoader {
} }
val extension = Extension.Installed( val extension = Extension.Installed(
extName, pkgName, versionName, versionCode, lang, isNsfw, sources, extName,
pkgName,
versionName,
versionCode,
lang,
isNsfw,
sources,
isUnofficial = signatureHash != officialSignature isUnofficial = signatureHash != officialSignature
) )
return LoadResult.Success(extension) return LoadResult.Success(extension)

View File

@ -16,15 +16,15 @@ import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.isOutdated import eu.kanade.tachiyomi.util.system.isOutdated
import eu.kanade.tachiyomi.util.system.setDefaultSettings import eu.kanade.tachiyomi.util.system.setDefaultSettings
import eu.kanade.tachiyomi.util.system.toast 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.Cookie
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import uy.kohesive.injekt.injectLazy 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 { class CloudflareInterceptor(private val context: Context) : Interceptor {

View File

@ -3,15 +3,15 @@ package eu.kanade.tachiyomi.network
import android.content.Context import android.content.Context
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import java.io.File
import java.net.InetAddress
import java.util.concurrent.TimeUnit
import okhttp3.Cache import okhttp3.Cache
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.dnsoverhttps.DnsOverHttps import okhttp3.dnsoverhttps.DnsOverHttps
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File
import java.net.InetAddress
import java.util.concurrent.TimeUnit
class NetworkHelper(context: Context) { class NetworkHelper(context: Context) {

View File

@ -1,9 +1,5 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import java.io.IOException
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call import okhttp3.Call
import okhttp3.Callback import okhttp3.Callback
@ -13,6 +9,10 @@ import okhttp3.Response
import rx.Observable import rx.Observable
import rx.Producer import rx.Producer
import rx.Subscription import rx.Subscription
import java.io.IOException
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
fun Call.asObservable(): Observable<Response> { fun Call.asObservable(): Observable<Response> {
return Observable.unsafeCreate { subscriber -> return Observable.unsafeCreate { subscriber ->
@ -54,22 +54,24 @@ fun Call.asObservable(): Observable<Response> {
// Based on https://github.com/gildor/kotlin-coroutines-okhttp // Based on https://github.com/gildor/kotlin-coroutines-okhttp
suspend fun Call.await(assertSuccess: Boolean = false): Response { suspend fun Call.await(assertSuccess: Boolean = false): Response {
return suspendCancellableCoroutine { continuation -> return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback { enqueue(
override fun onResponse(call: Call, response: Response) { object : Callback {
if (assertSuccess && !response.isSuccessful) { override fun onResponse(call: Call, response: Response) {
continuation.resumeWithException(Exception("HTTP error ${response.code}")) if (assertSuccess && !response.isSuccessful) {
return continuation.resumeWithException(Exception("HTTP error ${response.code}"))
return
}
continuation.resume(response)
} }
continuation.resume(response) override fun onFailure(call: Call, e: IOException) {
// Don't bother with resuming the continuation if it is already cancelled.
if (continuation.isCancelled) return
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
continuation.resumeWithException(e)
}
})
continuation.invokeOnCancellation { continuation.invokeOnCancellation {
try { try {

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import java.io.IOException
import okhttp3.MediaType import okhttp3.MediaType
import okhttp3.ResponseBody import okhttp3.ResponseBody
import okio.Buffer import okio.Buffer
@ -8,6 +7,7 @@ import okio.BufferedSource
import okio.ForwardingSource import okio.ForwardingSource
import okio.Source import okio.Source
import okio.buffer import okio.buffer
import java.io.IOException
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() { class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {

View File

@ -1,11 +1,11 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import java.util.concurrent.TimeUnit.MINUTES
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import java.util.concurrent.TimeUnit.MINUTES
private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build() private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build()
private val DEFAULT_HEADERS = Headers.Builder().build() private val DEFAULT_HEADERS = Headers.Builder().build()

View File

@ -14,6 +14,10 @@ import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.EpubFile import eu.kanade.tachiyomi.util.storage.EpubFile
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import junrar.Archive
import junrar.rarfile.FileHeader
import rx.Observable
import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.InputStream import java.io.InputStream
@ -21,10 +25,6 @@ import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipFile import java.util.zip.ZipFile
import junrar.Archive
import junrar.rarfile.FileHeader
import rx.Observable
import timber.log.Timber
class LocalSource(private val context: Context) : CatalogueSource { class LocalSource(private val context: Context) : CatalogueSource {
companion object { companion object {

View File

@ -10,15 +10,15 @@ import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import java.net.URI
import java.net.URISyntaxException
import java.security.MessageDigest
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.net.URI
import java.net.URISyntaxException
import java.security.MessageDigest
/** /**
* A simple implementation for sources from a website. * A simple implementation for sources from a website.

View File

@ -7,11 +7,11 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import eu.kanade.tachiyomi.R 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.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() { abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {

View File

@ -22,27 +22,29 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
lateinit var binding: VB lateinit var binding: VB
init { init {
addLifecycleListener(object : LifecycleListener() { addLifecycleListener(
override fun postCreateView(controller: Controller, view: View) { object : LifecycleListener() {
onViewCreated(view) override fun postCreateView(controller: Controller, view: View) {
} onViewCreated(view)
}
override fun preCreateView(controller: Controller) { override fun preCreateView(controller: Controller) {
Timber.d("Create view for ${controller.instance()}") Timber.d("Create view for ${controller.instance()}")
} }
override fun preAttach(controller: Controller, view: View) { override fun preAttach(controller: Controller, view: View) {
Timber.d("Attach view for ${controller.instance()}") Timber.d("Attach view for ${controller.instance()}")
} }
override fun preDetach(controller: Controller, view: View) { override fun preDetach(controller: Controller, view: View) {
Timber.d("Detach view for ${controller.instance()}") Timber.d("Detach view for ${controller.instance()}")
} }
override fun preDestroyView(controller: Controller, view: View) { override fun preDestroyView(controller: Controller, view: View) {
Timber.d("Destroy view for ${controller.instance()}") Timber.d("Destroy view for ${controller.instance()}")
}
} }
}) )
} }
override val containerView: View? override val containerView: View?
@ -98,17 +100,19 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
var expandActionViewFromInteraction = false var expandActionViewFromInteraction = false
fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) { fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) {
setOnActionExpandListener(object : MenuItem.OnActionExpandListener { setOnActionExpandListener(
override fun onMenuItemActionExpand(item: MenuItem): Boolean { object : MenuItem.OnActionExpandListener {
return onExpand?.invoke(item) ?: true override fun onMenuItemActionExpand(item: MenuItem): Boolean {
} return onExpand?.invoke(item) ?: true
}
override fun onMenuItemActionCollapse(item: MenuItem): Boolean { override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
activity?.invalidateOptionsMenu() activity?.invalidateOptionsMenu()
return onCollapse?.invoke(item) ?: true return onCollapse?.invoke(item) ?: true
}
} }
}) )
if (expandActionViewFromInteraction) { if (expandActionViewFromInteraction) {
expandActionViewFromInteraction = false expandActionViewFromInteraction = false

View File

@ -10,12 +10,12 @@ import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import java.util.concurrent.TimeUnit
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
private typealias ExtensionTuple = private typealias ExtensionTuple =
Triple<List<Extension.Installed>, List<Extension.Untrusted>, List<Extension.Available>> Triple<List<Extension.Installed>, List<Extension.Untrusted>, List<Extension.Available>>

View File

@ -20,8 +20,6 @@ import androidx.preference.PreferenceManager
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
@ -92,7 +90,6 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
ExtensionDetailsHeaderAdapter(presenter), ExtensionDetailsHeaderAdapter(presenter),
initPreferencesAdapter(context, extension) initPreferencesAdapter(context, extension)
) )
binding.extensionPrefsRecycler.addItemDecoration(DividerItemDecoration(context, VERTICAL))
} }
private fun initPreferencesAdapter(context: Context, extension: Extension.Installed): PreferenceGroupAdapter { private fun initPreferencesAdapter(context: Context, extension: Extension.Installed): PreferenceGroupAdapter {
@ -112,7 +109,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
.forEach { .forEach {
val preferenceBlock = { val preferenceBlock = {
it.value it.value
.sortedWith(compareBy({ !it.isEnabled() }, { it.name })) .sortedWith(compareBy({ !it.isEnabled() }, { it.name.toLowerCase() }))
.forEach { source -> .forEach { source ->
val sourcePrefs = mutableListOf<Preference>() val sourcePrefs = mutableListOf<Preference>()

View File

@ -19,8 +19,6 @@ import androidx.preference.Preference
import androidx.preference.PreferenceGroupAdapter import androidx.preference.PreferenceGroupAdapter
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
@ -86,7 +84,6 @@ class SourcePreferencesController(bundle: Bundle? = null) :
binding.recycler.layoutManager = LinearLayoutManager(context) binding.recycler.layoutManager = LinearLayoutManager(context)
binding.recycler.adapter = PreferenceGroupAdapter(screen) binding.recycler.adapter = PreferenceGroupAdapter(screen)
binding.recycler.addItemDecoration(DividerItemDecoration(context, VERTICAL))
} }
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {

View File

@ -13,10 +13,10 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchCardItem
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItem import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItem
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import java.util.Date
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import java.util.Date
class SearchPresenter( class SearchPresenter(
initialQuery: String? = "", initialQuery: String? = "",

View File

@ -29,7 +29,7 @@ class MigrationSourcesPresenter(
val header = SelectionHeader() val header = SelectionHeader()
return library.map { it.source }.toSet() return library.map { it.source }.toSet()
.mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null } .mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null }
.sortedBy { it.name } .sortedBy { it.name.toLowerCase() }
.map { SourceItem(it, header) } .map { SourceItem(it, header) }
} }
} }

View File

@ -16,9 +16,9 @@ import eu.kanade.tachiyomi.util.preference.onChange
import eu.kanade.tachiyomi.util.preference.switchPreferenceCategory import eu.kanade.tachiyomi.util.preference.switchPreferenceCategory
import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import java.util.TreeMap
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.TreeMap
class SourceFilterController : SettingsController() { class SourceFilterController : SettingsController() {
@ -42,7 +42,7 @@ class SourceFilterController : SettingsController() {
) )
orderedLangs.forEach { lang -> orderedLangs.forEach { lang ->
val sources = sourcesByLang[lang].orEmpty().sortedBy { it.name } val sources = sourcesByLang[lang].orEmpty().sortedBy { it.name.toLowerCase() }
// Create a preference group and set initial state and change listener // Create a preference group and set initial state and change listener
switchPreferenceCategory { switchPreferenceCategory {

View File

@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import java.util.TreeMap
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -21,6 +20,7 @@ import rx.Observable
import rx.Subscription import rx.Subscription
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.TreeMap
/** /**
* Presenter of [SourceController] * Presenter of [SourceController]
@ -128,7 +128,7 @@ class SourcePresenter(
return sourceManager.getCatalogueSources() return sourceManager.getCatalogueSources()
.filter { it.lang in languages } .filter { it.lang in languages }
.filterNot { it.id.toString() in disabledSourceIds } .filterNot { it.id.toString() in disabledSourceIds }
.sortedBy { "(${it.lang}) ${it.name}" } + .sortedBy { "(${it.lang}) ${it.name.toLowerCase()}" } +
sourceManager.get(LocalSource.ID) as LocalSource sourceManager.get(LocalSource.ID) as LocalSource
} }

View File

@ -10,7 +10,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -216,7 +215,6 @@ open class BrowseSourceController(bundle: Bundle) :
id = R.id.recycler id = R.id.recycler
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
} }
} else { } else {
(binding.catalogueView.inflate(R.layout.source_recycler_autofit) as AutofitRecyclerView).apply { (binding.catalogueView.inflate(R.layout.source_recycler_autofit) as AutofitRecyclerView).apply {

View File

@ -28,9 +28,8 @@ import eu.kanade.tachiyomi.ui.browse.source.filter.TextItem
import eu.kanade.tachiyomi.ui.browse.source.filter.TextSectionItem import eu.kanade.tachiyomi.ui.browse.source.filter.TextSectionItem
import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateItem import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateItem
import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem
import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
import java.util.Date
import kotlinx.coroutines.flow.subscribe
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@ -39,6 +38,7 @@ import rx.subjects.PublishSubject
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date
/** /**
* Presenter of [BrowseSourceController]. * Presenter of [BrowseSourceController].
@ -268,6 +268,8 @@ open class BrowseSourcePresenter(
if (!manga.favorite) { if (!manga.favorite) {
manga.removeCovers(coverCache) manga.removeCovers(coverCache)
} else {
ChapterSettingsHelper.applySettingDefaults(manga)
} }
db.insertManga(manga).executeAsBlocking() db.insertManga(manga).executeAsBlocking()

View File

@ -38,10 +38,13 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
val coverHeight = parent.itemWidth / 3 * 4 val coverHeight = parent.itemWidth / 3 * 4
view.apply { view.apply {
card.layoutParams = FrameLayout.LayoutParams( card.layoutParams = FrameLayout.LayoutParams(
MATCH_PARENT, coverHeight MATCH_PARENT,
coverHeight
) )
gradient.layoutParams = FrameLayout.LayoutParams( gradient.layoutParams = FrameLayout.LayoutParams(
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM MATCH_PARENT,
coverHeight / 2,
Gravity.BOTTOM
) )
} }
SourceGridHolder(view, adapter) SourceGridHolder(view, adapter)
@ -51,7 +54,8 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
val coverHeight = parent.itemWidth / 3 * 4 val coverHeight = parent.itemWidth / 3 * 4
view.apply { view.apply {
card.layoutParams = ConstraintLayout.LayoutParams( card.layoutParams = ConstraintLayout.LayoutParams(
MATCH_PARENT, coverHeight MATCH_PARENT,
coverHeight
) )
} }
SourceComfortableGridHolder(view, adapter) SourceComfortableGridHolder(view, adapter)

View File

@ -30,7 +30,8 @@ open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<Selec
spinner.prompt = filter.name spinner.prompt = filter.name
spinner.adapter = ArrayAdapter<Any>( spinner.adapter = ArrayAdapter<Any>(
holder.itemView.context, holder.itemView.context,
android.R.layout.simple_spinner_item, filter.values android.R.layout.simple_spinner_item,
filter.values
).apply { ).apply {
setDropDownViewResource(R.layout.common_spinner_item) setDropDownViewResource(R.layout.common_spinner_item)
} }

View File

@ -9,10 +9,10 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R as TR
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.R as TR
open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem<TriStateItem.Holder>() { open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem<TriStateItem.Holder>() {

View File

@ -108,17 +108,19 @@ open class GlobalSearchController(
val searchView = searchItem.actionView as SearchView val searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE searchView.maxWidth = Int.MAX_VALUE
searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { searchItem.setOnActionExpandListener(
override fun onMenuItemActionExpand(item: MenuItem?): Boolean { object : MenuItem.OnActionExpandListener {
searchView.onActionViewExpanded() // Required to show the query in the view override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
searchView.setQuery(presenter.query, false) searchView.onActionViewExpanded() // Required to show the query in the view
return true searchView.setQuery(presenter.query, false)
} return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
return true return true
}
} }
}) )
searchView.queryTextEvents() searchView.queryTextEvents()
.filterIsInstance<QueryTextEvent.QuerySubmitted>() .filterIsInstance<QueryTextEvent.QuerySubmitted>()

View File

@ -108,7 +108,7 @@ open class GlobalSearchPresenter(
return sourceManager.getCatalogueSources() return sourceManager.getCatalogueSources()
.filter { it.lang in languages } .filter { it.lang in languages }
.filterNot { it.id.toString() in disabledSourceIds } .filterNot { it.id.toString() in disabledSourceIds }
.sortedWith(compareBy({ it.id.toString() !in pinnedSourceIds }, { "${it.name} (${it.lang})" })) .sortedWith(compareBy({ it.id.toString() !in pinnedSourceIds }, { "${it.name.toLowerCase()} (${it.lang})" }))
} }
private fun getSourcesToQuery(): List<CatalogueSource> { private fun getSourcesToQuery(): List<CatalogueSource> {
@ -188,7 +188,7 @@ open class GlobalSearchPresenter(
{ it.results.isNullOrEmpty() }, { it.results.isNullOrEmpty() },
// Same as initial sort, i.e. pinned first then alphabetically // Same as initial sort, i.e. pinned first then alphabetically
{ it.source.id.toString() !in pinnedSourceIds }, { it.source.id.toString() !in pinnedSourceIds },
{ "${it.source.name} (${it.source.lang})" } { "${it.source.name.toLowerCase()} (${it.source.lang})" }
) )
) )
} }

View File

@ -198,8 +198,11 @@ class CategoryController :
R.id.action_delete -> { R.id.action_delete -> {
undoHelper = UndoHelper(adapter, this) undoHelper = UndoHelper(adapter, this)
undoHelper?.start( undoHelper?.start(
adapter.selectedPositions, activity!!.root_coordinator, adapter.selectedPositions,
R.string.snack_categories_deleted, R.string.action_undo, 3000 activity!!.root_coordinator,
R.string.snack_categories_deleted,
R.string.action_undo,
3000
) )
mode.finish() mode.finish()

View File

@ -18,13 +18,13 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.base.controller.FabController import eu.kanade.tachiyomi.ui.base.controller.FabController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.util.view.shrinkOnScroll import eu.kanade.tachiyomi.util.view.shrinkOnScroll
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks import reactivecircus.flowbinding.android.view.clicks
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
/** /**
* Controller that shows the currently active downloads. * Controller that shows the currently active downloads.

View File

@ -183,7 +183,7 @@ class LibraryController(
createActionModeIfNeeded() createActionModeIfNeeded()
} }
settingsSheet = LibrarySettingsSheet(activity!!) { group -> settingsSheet = LibrarySettingsSheet(router) { group ->
when (group) { when (group) {
is LibrarySettingsSheet.Filter.FilterGroup -> onFilterChanged() is LibrarySettingsSheet.Filter.FilterGroup -> onFilterChanged()
is LibrarySettingsSheet.Sort.SortGroup -> onSortChanged() is LibrarySettingsSheet.Sort.SortGroup -> onSortChanged()

View File

@ -45,7 +45,9 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe
view.apply { view.apply {
card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight) card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight)
gradient.layoutParams = FrameLayout.LayoutParams( gradient.layoutParams = FrameLayout.LayoutParams(
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM MATCH_PARENT,
coverHeight / 2,
Gravity.BOTTOM
) )
} }
LibraryCompactGridHolder(view, adapter) LibraryCompactGridHolder(view, adapter)
@ -55,7 +57,8 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe
val coverHeight = parent.itemWidth / 3 * 4 val coverHeight = parent.itemWidth / 3 * 4
view.apply { view.apply {
card.layoutParams = ConstraintLayout.LayoutParams( card.layoutParams = ConstraintLayout.LayoutParams(
MATCH_PARENT, coverHeight MATCH_PARENT,
coverHeight
) )
} }
LibraryComfortableGridHolder(view, adapter) LibraryComfortableGridHolder(view, adapter)

View File

@ -19,14 +19,16 @@ import eu.kanade.tachiyomi.util.lang.combineLatest
import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
import java.util.Collections import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_IGNORE
import java.util.Comparator import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_INCLUDE
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Collections
import java.util.Comparator
/** /**
* Class containing library information. * Class containing library information.
@ -110,34 +112,45 @@ class LibraryPresenter(
* @param map the map to filter. * @param map the map to filter.
*/ */
private fun applyFilters(map: LibraryMap): LibraryMap { private fun applyFilters(map: LibraryMap): LibraryMap {
val filterDownloaded = preferences.downloadedOnly().get() || preferences.filterDownloaded().get() val downloadedOnly = preferences.downloadedOnly().get()
val filterDownloaded = preferences.filterDownloaded().get()
val filterUnread = preferences.filterUnread().get() val filterUnread = preferences.filterUnread().get()
val filterCompleted = preferences.filterCompleted().get() val filterCompleted = preferences.filterCompleted().get()
val filterFn: (LibraryItem) -> Boolean = f@{ item -> val filterFnUnread: (LibraryItem) -> Boolean = unread@{ item ->
// Filter when there isn't unread chapters. if (filterUnread == STATE_IGNORE) return@unread true
if (filterUnread && item.manga.unread == 0) { val isUnread = item.manga.unread != 0
return@f false
return@unread if (filterUnread == STATE_INCLUDE) isUnread
else !isUnread
}
val filterFnCompleted: (LibraryItem) -> Boolean = completed@{ item ->
if (filterCompleted == STATE_IGNORE) return@completed true
val isCompleted = item.manga.status == SManga.COMPLETED
return@completed if (filterCompleted == STATE_INCLUDE) isCompleted
else !isCompleted
}
val filterFnDownloaded: (LibraryItem) -> Boolean = downloaded@{ item ->
if (!downloadedOnly && filterDownloaded == STATE_IGNORE) return@downloaded true
val isDownloaded = when {
item.manga.isLocal() -> true
item.downloadCount != -1 -> item.downloadCount > 0
else -> downloadManager.getDownloadCount(item.manga) > 0
} }
if (filterCompleted && item.manga.status != SManga.COMPLETED) { return@downloaded if (downloadedOnly || filterDownloaded == STATE_INCLUDE) isDownloaded
return@f false else !isDownloaded
} }
// Filter when there are no downloads. val filterFn: (LibraryItem) -> Boolean = filter@{ item ->
if (filterDownloaded) { return@filter !(
// Local manga are always downloaded !filterFnUnread(item) ||
if (item.manga.isLocal()) { !filterFnCompleted(item) ||
return@f true !filterFnDownloaded(item)
} )
// Don't bother with directory checking if download count has been set.
if (item.downloadCount != -1) {
return@f item.downloadCount > 0
}
return@f downloadManager.getDownloadCount(item.manga) > 0
}
true
} }
return map.mapValues { entry -> entry.value.filter(filterFn) } return map.mapValues { entry -> entry.value.filter(filterFn) }

View File

@ -1,33 +1,36 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import android.app.Activity
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import com.bluelinelabs.conductor.Router
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.widget.ExtendedNavigationView import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_EXCLUDE
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_IGNORE
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_INCLUDE
import eu.kanade.tachiyomi.widget.TabbedBottomSheetDialog import eu.kanade.tachiyomi.widget.TabbedBottomSheetDialog
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class LibrarySettingsSheet( class LibrarySettingsSheet(
activity: Activity, router: Router,
onGroupClickListener: (ExtendedNavigationView.Group) -> Unit onGroupClickListener: (ExtendedNavigationView.Group) -> Unit
) : TabbedBottomSheetDialog(activity) { ) : TabbedBottomSheetDialog(router) {
val filters: Filter val filters: Filter
private val sort: Sort private val sort: Sort
private val display: Display private val display: Display
init { init {
filters = Filter(activity) filters = Filter(router.activity!!)
filters.onGroupClicked = onGroupClickListener filters.onGroupClicked = onGroupClickListener
sort = Sort(activity) sort = Sort(router.activity!!)
sort.onGroupClicked = onGroupClickListener sort.onGroupClicked = onGroupClickListener
display = Display(activity) display = Display(router.activity!!)
display.onGroupClicked = onGroupClickListener display.onGroupClicked = onGroupClickListener
} }
@ -59,33 +62,43 @@ class LibrarySettingsSheet(
* Returns true if there's at least one filter from [FilterGroup] active. * Returns true if there's at least one filter from [FilterGroup] active.
*/ */
fun hasActiveFilters(): Boolean { fun hasActiveFilters(): Boolean {
return filterGroup.items.any { it.checked } return filterGroup.items.any { it.state != Item.TriStateGroup.STATE_IGNORE }
} }
inner class FilterGroup : Group { inner class FilterGroup : Group {
private val downloaded = Item.CheckboxGroup(R.string.action_filter_downloaded, this) private val downloaded = Item.TriStateGroup(R.string.action_filter_downloaded, this)
private val unread = Item.CheckboxGroup(R.string.action_filter_unread, this) private val unread = Item.TriStateGroup(R.string.action_filter_unread, this)
private val completed = Item.CheckboxGroup(R.string.completed, this) private val completed = Item.TriStateGroup(R.string.completed, this)
override val header = null override val header = null
override val items = listOf(downloaded, unread, completed) override val items = listOf(downloaded, unread, completed)
override val footer = null override val footer = null
override fun initModels() { override fun initModels() {
downloaded.checked = preferences.downloadedOnly().get() || preferences.filterDownloaded().get() if (preferences.downloadedOnly().get()) {
downloaded.enabled = !preferences.downloadedOnly().get() downloaded.state = STATE_INCLUDE
unread.checked = preferences.filterUnread().get() downloaded.enabled = false
completed.checked = preferences.filterCompleted().get() } else {
downloaded.state = preferences.filterDownloaded().get()
}
unread.state = preferences.filterUnread().get()
completed.state = preferences.filterCompleted().get()
} }
override fun onItemClicked(item: Item) { override fun onItemClicked(item: Item) {
item as Item.CheckboxGroup item as Item.TriStateGroup
item.checked = !item.checked val newState = when (item.state) {
STATE_IGNORE -> STATE_INCLUDE
STATE_INCLUDE -> STATE_EXCLUDE
STATE_EXCLUDE -> STATE_IGNORE
else -> throw Exception("Unknown State")
}
item.state = newState
when (item) { when (item) {
downloaded -> preferences.filterDownloaded().set(item.checked) downloaded -> preferences.filterDownloaded().set(newState)
unread -> preferences.filterUnread().set(item.checked) unread -> preferences.filterUnread().set(newState)
completed -> preferences.filterCompleted().set(item.checked) completed -> preferences.filterCompleted().set(newState)
} }
adapter.notifyItemChanged(item) adapter.notifyItemChanged(item)

View File

@ -44,13 +44,13 @@ import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import java.util.Date
import java.util.concurrent.TimeUnit
import kotlinx.android.synthetic.main.main_activity.appbar import kotlinx.android.synthetic.main.main_activity.appbar
import kotlinx.android.synthetic.main.main_activity.tabs import kotlinx.android.synthetic.main.main_activity.tabs
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import timber.log.Timber import timber.log.Timber
import java.util.Date
import java.util.concurrent.TimeUnit
class MainActivity : BaseActivity<MainActivityBinding>() { class MainActivity : BaseActivity<MainActivityBinding>() {
@ -126,26 +126,28 @@ class MainActivity : BaseActivity<MainActivityBinding>() {
onBackPressed() onBackPressed()
} }
router.addChangeListener(object : ControllerChangeHandler.ControllerChangeListener { router.addChangeListener(
override fun onChangeStarted( object : ControllerChangeHandler.ControllerChangeListener {
to: Controller?, override fun onChangeStarted(
from: Controller?, to: Controller?,
isPush: Boolean, from: Controller?,
container: ViewGroup, isPush: Boolean,
handler: ControllerChangeHandler container: ViewGroup,
) { handler: ControllerChangeHandler
syncActivityViewWithController(to, from) ) {
} syncActivityViewWithController(to, from)
}
override fun onChangeCompleted( override fun onChangeCompleted(
to: Controller?, to: Controller?,
from: Controller?, from: Controller?,
isPush: Boolean, isPush: Boolean,
container: ViewGroup, container: ViewGroup,
handler: ControllerChangeHandler handler: ControllerChangeHandler
) { ) {
}
} }
}) )
syncActivityViewWithController(router.backstack.lastOrNull()?.controller()) syncActivityViewWithController(router.backstack.lastOrNull()?.controller())

View File

@ -52,7 +52,6 @@ import eu.kanade.tachiyomi.ui.library.ChangeMangaCoverDialog
import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.main.offsetAppbarHeight import eu.kanade.tachiyomi.ui.main.offsetAppbarHeight
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterDividerItemDecoration
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersSettingsSheet import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersSettingsSheet
@ -65,13 +64,13 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.recent.history.HistoryController import eu.kanade.tachiyomi.ui.recent.history.HistoryController
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
import eu.kanade.tachiyomi.util.hasCustomCover import eu.kanade.tachiyomi.util.hasCustomCover
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.getCoordinates import eu.kanade.tachiyomi.util.view.getCoordinates
import eu.kanade.tachiyomi.util.view.shrinkOnScroll import eu.kanade.tachiyomi.util.view.shrinkOnScroll
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
import kotlin.math.min
import kotlinx.android.synthetic.main.main_activity.root_coordinator import kotlinx.android.synthetic.main.main_activity.root_coordinator
import kotlinx.android.synthetic.main.main_activity.toolbar import kotlinx.android.synthetic.main.main_activity.toolbar
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -83,6 +82,7 @@ import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import kotlin.math.min
class MangaController : class MangaController :
NucleusController<MangaControllerBinding, MangaPresenter>, NucleusController<MangaControllerBinding, MangaPresenter>,
@ -207,7 +207,6 @@ class MangaController :
binding.recycler.adapter = ConcatAdapter(mangaInfoAdapter, chaptersHeaderAdapter, chaptersAdapter) binding.recycler.adapter = ConcatAdapter(mangaInfoAdapter, chaptersHeaderAdapter, chaptersAdapter)
binding.recycler.layoutManager = LinearLayoutManager(view.context) binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.addItemDecoration(ChapterDividerItemDecoration(view.context))
binding.recycler.setHasFixedSize(true) binding.recycler.setHasFixedSize(true)
chaptersAdapter?.fastScroller = binding.fastScroller chaptersAdapter?.fastScroller = binding.fastScroller
@ -238,7 +237,7 @@ class MangaController :
binding.actionToolbar.offsetAppbarHeight(activity!!) binding.actionToolbar.offsetAppbarHeight(activity!!)
settingsSheet = ChaptersSettingsSheet(activity!!, presenter) { group -> settingsSheet = ChaptersSettingsSheet(router, presenter) { group ->
if (group is ChaptersSettingsSheet.Filter.FilterGroup) { if (group is ChaptersSettingsSheet.Filter.FilterGroup) {
updateFilterIconState() updateFilterIconState()
chaptersAdapter?.notifyDataSetChanged() chaptersAdapter?.notifyDataSetChanged()
@ -292,10 +291,10 @@ class MangaController :
// Get coordinates and start animation // Get coordinates and start animation
actionFab?.getCoordinates()?.let { coordinates -> actionFab?.getCoordinates()?.let { coordinates ->
if (!binding.revealView.showRevealEffect( if (!binding.revealView.showRevealEffect(
coordinates.x, coordinates.x,
coordinates.y, coordinates.y,
revealAnimationListener revealAnimationListener
) )
) { ) {
openChapter(item.chapter) openChapter(item.chapter)
} }
@ -343,7 +342,8 @@ class MangaController :
} }
override fun onPrepareOptionsMenu(menu: Menu) { override fun onPrepareOptionsMenu(menu: Menu) {
// Hide download options for local manga // Hide options for local manga
menu.findItem(R.id.action_share).isVisible = !isLocalSource
menu.findItem(R.id.download_group).isVisible = !isLocalSource menu.findItem(R.id.download_group).isVisible = !isLocalSource
// Hide options for non-library manga // Hide options for non-library manga
@ -354,6 +354,7 @@ class MangaController :
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_share -> shareManga()
R.id.download_next, R.id.download_next_5, R.id.download_next_10, R.id.download_next, R.id.download_next_5, R.id.download_next_10,
R.id.download_custom, R.id.download_unread, R.id.download_all R.id.download_custom, R.id.download_unread, R.id.download_all
-> downloadChapters(item.itemId) -> downloadChapters(item.itemId)
@ -694,7 +695,11 @@ class MangaController :
fun onFetchChaptersError(error: Throwable) { fun onFetchChaptersError(error: Throwable) {
isRefreshingChapters = false isRefreshingChapters = false
updateRefreshing() updateRefreshing()
activity?.toast(error.message) if (error is NoChaptersException) {
activity?.toast(activity?.getString(R.string.no_chapters_error))
} else {
activity?.toast(error.message)
}
} }
fun onChapterStatusChange(download: Download) { fun onChapterStatusChange(download: Download) {

View File

@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
@ -26,7 +27,6 @@ import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.updateCoverLastModified import eu.kanade.tachiyomi.util.updateCoverLastModified
import java.util.Date
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@ -34,6 +34,7 @@ import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date
class MangaPresenter( class MangaPresenter(
val manga: Manga, val manga: Manga,
@ -82,6 +83,10 @@ class MangaPresenter(
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
if (!manga.favorite) {
ChapterSettingsHelper.applySettingDefaults(manga)
}
// Manga info - start // Manga info - start
getMangaObservable() getMangaObservable()

View File

@ -1,59 +0,0 @@
package eu.kanade.tachiyomi.ui.manga.chapter
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.view.View
import androidx.core.view.forEach
import androidx.core.view.marginBottom
import androidx.recyclerview.widget.RecyclerView
/**
* Mimics a DividerItemDecoration that doesn't draw between the first two items.
*
* Used in MangaController since the manga info header and chapters header are the first two
* items in the list using a ConcatAdapter.
*/
class ChapterDividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
private val divider: Drawable
init {
val a = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider))
divider = a.getDrawable(0)!!
a.recycle()
}
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
if (parent.layoutManager == null) {
return
}
canvas.save()
parent.forEach {
val top = it.bottom + it.marginBottom
val bottom = top + divider.intrinsicHeight
val left = parent.paddingStart
val right = parent.width - parent.paddingEnd
divider.setBounds(left, top, right, bottom)
divider.draw(canvas)
}
canvas.restore()
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view)
if (position == 0) {
outRect.setEmpty()
} else {
outRect.set(0, 0, 0, divider.intrinsicHeight)
}
}
}

View File

@ -9,11 +9,11 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import java.util.Date
import kotlinx.android.synthetic.main.chapters_item.bookmark_icon import kotlinx.android.synthetic.main.chapters_item.bookmark_icon
import kotlinx.android.synthetic.main.chapters_item.chapter_description import kotlinx.android.synthetic.main.chapters_item.chapter_description
import kotlinx.android.synthetic.main.chapters_item.chapter_title import kotlinx.android.synthetic.main.chapters_item.chapter_title
import kotlinx.android.synthetic.main.chapters_item.download_text import kotlinx.android.synthetic.main.chapters_item.download_text
import java.util.Date
class ChapterHolder( class ChapterHolder(
view: View, view: View,

View File

@ -6,10 +6,10 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import uy.kohesive.injekt.injectLazy
import java.text.DateFormat import java.text.DateFormat
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.DecimalFormatSymbols import java.text.DecimalFormatSymbols
import uy.kohesive.injekt.injectLazy
class ChaptersAdapter( class ChaptersAdapter(
controller: MangaController, controller: MangaController,

View File

@ -1,34 +1,39 @@
package eu.kanade.tachiyomi.ui.manga.chapter package eu.kanade.tachiyomi.ui.manga.chapter
import android.app.Activity
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import androidx.core.view.isVisible
import com.bluelinelabs.conductor.Router
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.manga.MangaPresenter import eu.kanade.tachiyomi.ui.manga.MangaPresenter
import eu.kanade.tachiyomi.util.view.popupMenu
import eu.kanade.tachiyomi.widget.ExtendedNavigationView import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import eu.kanade.tachiyomi.widget.TabbedBottomSheetDialog import eu.kanade.tachiyomi.widget.TabbedBottomSheetDialog
class ChaptersSettingsSheet( class ChaptersSettingsSheet(
activity: Activity, private val router: Router,
private val presenter: MangaPresenter, private val presenter: MangaPresenter,
onGroupClickListener: (ExtendedNavigationView.Group) -> Unit onGroupClickListener: (ExtendedNavigationView.Group) -> Unit
) : TabbedBottomSheetDialog(activity) { ) : TabbedBottomSheetDialog(router) {
val filters: Filter val filters: Filter
private val sort: Sort private val sort: Sort
private val display: Display private val display: Display
init { init {
filters = Filter(activity) filters = Filter(router.activity!!)
filters.onGroupClicked = onGroupClickListener filters.onGroupClicked = onGroupClickListener
sort = Sort(activity) sort = Sort(router.activity!!)
sort.onGroupClicked = onGroupClickListener sort.onGroupClicked = onGroupClickListener
display = Display(activity) display = Display(router.activity!!)
display.onGroupClicked = onGroupClickListener display.onGroupClicked = onGroupClickListener
binding.menu.isVisible = true
binding.menu.setOnClickListener { it.post { showPopupMenu(it) } }
} }
override fun getTabViews(): List<View> = listOf( override fun getTabViews(): List<View> = listOf(
@ -43,6 +48,23 @@ class ChaptersSettingsSheet(
R.string.action_display R.string.action_display
) )
private fun showPopupMenu(view: View) {
view.popupMenu(
R.menu.default_chapter_filter,
{
},
{
when (this.itemId) {
R.id.set_as_default -> {
SetChapterSettingsDialog(presenter.manga).showDialog(router)
true
}
else -> true
}
}
)
}
/** /**
* Filters group (unread, downloaded, ...). * Filters group (unread, downloaded, ...).
*/ */

View File

@ -66,7 +66,7 @@ class MangaChaptersHeaderAdapter(
} else { } else {
view.context.getResourceColor(R.attr.colorOnPrimary) view.context.getResourceColor(R.attr.colorOnPrimary)
} }
DrawableCompat.setTint(binding.btnChaptersFilter.icon, filterColor) DrawableCompat.setTint(binding.btnChaptersFilter.drawable, filterColor)
merge(view.clicks(), binding.btnChaptersFilter.clicks()) merge(view.clicks(), binding.btnChaptersFilter.clicks())
.onEach { controller.showSettingsSheet() } .onEach { controller.showSettingsSheet() }

View File

@ -0,0 +1,48 @@
package eu.kanade.tachiyomi.ui.manga.chapter
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.DialogCheckboxView
class SetChapterSettingsDialog(bundle: Bundle? = null) : DialogController(bundle) {
constructor(manga: Manga) : this(
Bundle().apply {
putSerializable(MANGA_KEY, manga)
}
)
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val view = DialogCheckboxView(activity!!).apply {
setDescription(R.string.confirm_set_chapter_settings)
setOptionDescription(R.string.also_set_chapter_settings_for_library)
}
return MaterialDialog(activity!!)
.title(R.string.chapter_settings)
.customView(
view = view,
horizontalPadding = true
)
.positiveButton(android.R.string.ok) {
ChapterSettingsHelper.setGlobalSettings(args.getSerializable(MANGA_KEY)!! as Manga)
if (view.isChecked()) {
ChapterSettingsHelper.updateAllMangasWithGlobalDefaults()
}
activity?.toast(activity!!.getString(R.string.chapter_settings_updated))
}
.negativeButton(android.R.string.cancel)
}
private companion object {
const val MANGA_KEY = "manga"
}
}

View File

@ -105,13 +105,27 @@ class MangaInfoHeaderAdapter(
isVisible = true isVisible = true
if (trackCount > 0) { if (trackCount > 0) {
setIconResource(R.drawable.ic_done_24dp) setCompoundDrawablesWithIntrinsicBounds(
text = view.context.resources.getQuantityString(R.plurals.num_trackers, trackCount, trackCount) null,
isChecked = true ContextCompat.getDrawable(context, R.drawable.ic_done_24dp),
null,
null
)
text = view.context.resources.getQuantityString(
R.plurals.num_trackers,
trackCount,
trackCount
)
isSelected = true
} else { } else {
setIconResource(R.drawable.ic_sync_24dp) setCompoundDrawablesWithIntrinsicBounds(
null,
ContextCompat.getDrawable(context, R.drawable.ic_sync_24dp),
null,
null
)
text = view.context.getString(R.string.manga_tracking_tab) text = view.context.getString(R.string.manga_tracking_tab)
isChecked = false isSelected = false
} }
clicks() clicks()
@ -128,12 +142,6 @@ class MangaInfoHeaderAdapter(
.onEach { controller.openMangaInWebView() } .onEach { controller.openMangaInWebView() }
.launchIn(scope) .launchIn(scope)
binding.btnWebview.setTooltip(R.string.action_open_in_web_view) binding.btnWebview.setTooltip(R.string.action_open_in_web_view)
binding.btnShare.isVisible = true
binding.btnShare.clicks()
.onEach { controller.shareManga() }
.launchIn(scope)
binding.btnShare.setTooltip(R.string.action_share)
} }
binding.mangaFullTitle.longClicks() binding.mangaFullTitle.longClicks()
@ -285,14 +293,24 @@ class MangaInfoHeaderAdapter(
// Update genres list // Update genres list
if (!manga.genre.isNullOrBlank()) { if (!manga.genre.isNullOrBlank()) {
binding.mangaGenresTagsCompactChips.setChips(manga.getGenres(), controller::performSearch) binding.mangaGenresTagsCompactChips.setChips(
binding.mangaGenresTagsFullChips.setChips(manga.getGenres(), controller::performSearch) manga.getGenres(),
controller::performSearch
)
binding.mangaGenresTagsFullChips.setChips(
manga.getGenres(),
controller::performSearch
)
} else { } else {
binding.mangaGenresTagsWrapper.isVisible = false binding.mangaGenresTagsWrapper.isVisible = false
} }
// Handle showing more or less info // Handle showing more or less info
merge(binding.mangaSummarySection.clicks(), binding.mangaSummaryText.clicks(), binding.mangaInfoToggle.clicks()) merge(
binding.mangaSummarySection.clicks(),
binding.mangaSummaryText.clicks(),
binding.mangaInfoToggle.clicks()
)
.onEach { toggleMangaInfo(view.context) } .onEach { toggleMangaInfo(view.context) }
.launchIn(scope) .launchIn(scope)
@ -310,20 +328,22 @@ class MangaInfoHeaderAdapter(
private fun toggleMangaInfo(context: Context) { private fun toggleMangaInfo(context: Context) {
val isExpanded = val isExpanded =
binding.mangaInfoToggle.text == context.getString(R.string.manga_info_collapse) binding.mangaInfoToggle.contentDescription == context.getString(R.string.manga_info_collapse)
with(binding.mangaInfoToggle) { with(binding.mangaInfoToggle) {
text = if (isExpanded) { contentDescription = if (isExpanded) {
context.getString(R.string.manga_info_expand) context.getString(R.string.manga_info_expand)
} else { } else {
context.getString(R.string.manga_info_collapse) context.getString(R.string.manga_info_collapse)
} }
icon = if (isExpanded) { setImageDrawable(
context.getDrawable(R.drawable.ic_baseline_expand_more_24dp) if (isExpanded) {
} else { context.getDrawable(R.drawable.ic_baseline_expand_more_24dp)
context.getDrawable(R.drawable.ic_baseline_expand_less_24dp) } else {
} context.getDrawable(R.drawable.ic_baseline_expand_less_24dp)
}
)
} }
with(binding.mangaSummaryText) { with(binding.mangaSummaryText) {
@ -355,13 +375,18 @@ class MangaInfoHeaderAdapter(
// Set the Favorite drawable to the correct one. // Set the Favorite drawable to the correct one.
// Border drawable if false, filled drawable if true. // Border drawable if false, filled drawable if true.
binding.btnFavorite.apply { binding.btnFavorite.apply {
icon = ContextCompat.getDrawable( setCompoundDrawablesWithIntrinsicBounds(
context, null,
if (isFavorite) R.drawable.ic_favorite_24dp else R.drawable.ic_favorite_border_24dp ContextCompat.getDrawable(
context,
if (isFavorite) R.drawable.ic_favorite_24dp else R.drawable.ic_favorite_border_24dp
),
null,
null
) )
text = text =
context.getString(if (isFavorite) R.string.in_library else R.string.add_to_library) context.getString(if (isFavorite) R.string.in_library else R.string.add_to_library)
isChecked = isFavorite isSelected = isFavorite
} }
} }
} }

View File

@ -9,9 +9,9 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import java.util.Calendar
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Calendar
class SetTrackReadingDatesDialog<T> : DialogController class SetTrackReadingDatesDialog<T> : DialogController
where T : Controller, T : SetTrackReadingDatesDialog.Listener { where T : Controller, T : SetTrackReadingDatesDialog.Listener {

View File

@ -5,8 +5,8 @@ import androidx.core.view.isVisible
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.TrackItemBinding import eu.kanade.tachiyomi.databinding.TrackItemBinding
import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder
import java.text.DateFormat
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.DateFormat
class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter) : BaseViewHolder(binding.root) { class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter) : BaseViewHolder(binding.root) {

View File

@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import java.util.concurrent.TimeUnit
import kotlinx.android.synthetic.main.track_search_dialog.view.progress import kotlinx.android.synthetic.main.track_search_dialog.view.progress
import kotlinx.android.synthetic.main.track_search_dialog.view.track_search import kotlinx.android.synthetic.main.track_search_dialog.view.track_search
import kotlinx.android.synthetic.main.track_search_dialog.view.track_search_list import kotlinx.android.synthetic.main.track_search_dialog.view.track_search_list
@ -26,6 +25,7 @@ import reactivecircus.flowbinding.android.widget.itemClicks
import reactivecircus.flowbinding.android.widget.textChanges import reactivecircus.flowbinding.android.widget.textChanges
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class TrackSearchDialog : DialogController { class TrackSearchDialog : DialogController {

View File

@ -10,9 +10,9 @@ import com.afollestad.materialdialogs.MaterialDialog
import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.aboutlibraries.LibsBuilder
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.updater.UpdateChecker
import eu.kanade.tachiyomi.data.updater.UpdateResult import eu.kanade.tachiyomi.data.updater.UpdateResult
import eu.kanade.tachiyomi.data.updater.UpdaterService import eu.kanade.tachiyomi.data.updater.UpdaterService
import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.setting.SettingsController import eu.kanade.tachiyomi.ui.setting.SettingsController
import eu.kanade.tachiyomi.util.lang.launchNow import eu.kanade.tachiyomi.util.lang.launchNow
@ -23,19 +23,19 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory
import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import timber.log.Timber
import java.text.DateFormat import java.text.DateFormat
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
import timber.log.Timber
class AboutController : SettingsController() { class AboutController : SettingsController() {
/** /**
* Checks for new releases * Checks for new releases
*/ */
private val updateChecker by lazy { UpdateChecker.getUpdateChecker() } private val updateChecker by lazy { GithubUpdateChecker() }
private val dateFormat: DateFormat = preferences.dateFormat() private val dateFormat: DateFormat = preferences.dateFormat()
@ -234,7 +234,9 @@ class AboutController : SettingsController() {
val buildTime = inputDf.parse(BuildConfig.BUILD_TIME) val buildTime = inputDf.parse(BuildConfig.BUILD_TIME)
val outputDf = DateFormat.getDateTimeInstance( val outputDf = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault() DateFormat.MEDIUM,
DateFormat.SHORT,
Locale.getDefault()
) )
outputDf.timeZone = TimeZone.getDefault() outputDf.timeZone = TimeZone.getDefault()

View File

@ -7,7 +7,6 @@ import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
import eu.kanade.tachiyomi.ui.base.controller.RootController import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
@ -28,6 +27,7 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.openInBrowser
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
class MoreController : class MoreController :
SettingsController(), SettingsController(),

View File

@ -21,6 +21,7 @@ import android.view.animation.Animation
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.widget.SeekBar import android.widget.SeekBar
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.setPadding import androidx.core.view.setPadding
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
@ -56,8 +57,6 @@ import eu.kanade.tachiyomi.util.view.showBar
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.widget.SimpleAnimationListener import eu.kanade.tachiyomi.widget.SimpleAnimationListener
import eu.kanade.tachiyomi.widget.SimpleSeekBarListener import eu.kanade.tachiyomi.widget.SimpleSeekBarListener
import java.io.File
import kotlin.math.abs
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
@ -67,6 +66,8 @@ import kotlinx.coroutines.flow.sample
import nucleus.factory.RequiresPresenter import nucleus.factory.RequiresPresenter
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File
import kotlin.math.abs
/** /**
* Activity containing the reader of Tachiyomi. This activity is mostly a container of the * Activity containing the reader of Tachiyomi. This activity is mostly a container of the
@ -290,24 +291,27 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
ViewCompat.setOnApplyWindowInsetsListener(binding.readerMenu) { _, insets -> ViewCompat.setOnApplyWindowInsetsListener(binding.readerMenu) { _, insets ->
if (!window.isDefaultBar()) { if (!window.isDefaultBar()) {
val systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
binding.readerMenu.setPadding( binding.readerMenu.setPadding(
insets.systemWindowInsetLeft, systemInsets.left,
insets.systemWindowInsetTop, systemInsets.top,
insets.systemWindowInsetRight, systemInsets.right,
insets.systemWindowInsetBottom systemInsets.bottom
) )
} }
insets insets
} }
// Init listeners on bottom menu // Init listeners on bottom menu
binding.pageSeekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { binding.pageSeekbar.setOnSeekBarChangeListener(
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { object : SimpleSeekBarListener() {
if (viewer != null && fromUser) { override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
moveToPageIndex(value) if (viewer != null && fromUser) {
moveToPageIndex(value)
}
} }
} }
}) )
binding.leftChapter.setOnClickListener { binding.leftChapter.setOnClickListener {
if (viewer != null) { if (viewer != null) {
if (viewer is R2LPagerViewer) { if (viewer is R2LPagerViewer) {
@ -347,12 +351,14 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
if (animate) { if (animate) {
val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top) val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top)
toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() { toolbarAnimation.setAnimationListener(
override fun onAnimationStart(animation: Animation) { object : SimpleAnimationListener() {
// Fix status bar being translucent the first time it's opened. override fun onAnimationStart(animation: Animation) {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) // Fix status bar being translucent the first time it's opened.
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
}
} }
}) )
binding.toolbar.startAnimation(toolbarAnimation) binding.toolbar.startAnimation(toolbarAnimation)
val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_bottom) val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_bottom)
@ -371,11 +377,13 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
if (animate) { if (animate) {
val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_top) val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_top)
toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() { toolbarAnimation.setAnimationListener(
override fun onAnimationEnd(animation: Animation) { object : SimpleAnimationListener() {
binding.readerMenu.isVisible = false override fun onAnimationEnd(animation: Animation) {
binding.readerMenu.isVisible = false
}
} }
}) )
binding.toolbar.startAnimation(toolbarAnimation) binding.toolbar.startAnimation(toolbarAnimation)
val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_bottom) val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_bottom)
@ -572,9 +580,13 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
* Called from the presenter when a page is ready to be shared. It shows Android's default * Called from the presenter when a page is ready to be shared. It shows Android's default
* sharing tool. * sharing tool.
*/ */
fun onShareImageResult(file: File) { fun onShareImageResult(file: File, page: ReaderPage) {
val manga = presenter.manga ?: return
val chapter = page.chapter.chapter
val stream = file.getUriCompat(this) val stream = file.getUriCompat(this)
val intent = Intent(Intent.ACTION_SEND).apply { val intent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_TEXT, getString(R.string.share_page_info, manga.title, chapter.name, page.number))
putExtra(Intent.EXTRA_STREAM, stream) putExtra(Intent.EXTRA_STREAM, stream)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
type = "image/*" type = "image/*"

View File

@ -74,45 +74,55 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet
} }
binding.colorFilterMode.setSelection(preferences.colorFilterMode().get(), false) binding.colorFilterMode.setSelection(preferences.colorFilterMode().get(), false)
binding.seekbarColorFilterAlpha.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { binding.seekbarColorFilterAlpha.setOnSeekBarChangeListener(
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { object : SimpleSeekBarListener() {
if (fromUser) { override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
setColorValue(value, ALPHA_MASK, 24) if (fromUser) {
setColorValue(value, ALPHA_MASK, 24)
}
} }
} }
}) )
binding.seekbarColorFilterRed.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { binding.seekbarColorFilterRed.setOnSeekBarChangeListener(
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { object : SimpleSeekBarListener() {
if (fromUser) { override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
setColorValue(value, RED_MASK, 16) if (fromUser) {
setColorValue(value, RED_MASK, 16)
}
} }
} }
}) )
binding.seekbarColorFilterGreen.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { binding.seekbarColorFilterGreen.setOnSeekBarChangeListener(
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { object : SimpleSeekBarListener() {
if (fromUser) { override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
setColorValue(value, GREEN_MASK, 8) if (fromUser) {
setColorValue(value, GREEN_MASK, 8)
}
} }
} }
}) )
binding.seekbarColorFilterBlue.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { binding.seekbarColorFilterBlue.setOnSeekBarChangeListener(
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { object : SimpleSeekBarListener() {
if (fromUser) { override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
setColorValue(value, BLUE_MASK, 0) if (fromUser) {
setColorValue(value, BLUE_MASK, 0)
}
} }
} }
}) )
binding.brightnessSeekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() { binding.brightnessSeekbar.setOnSeekBarChangeListener(
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) { object : SimpleSeekBarListener() {
if (fromUser) { override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
preferences.customBrightnessValue().set(value) if (fromUser) {
preferences.customBrightnessValue().set(value)
}
} }
} }
}) )
} }
override fun onStart() { override fun onStart() {

View File

@ -27,9 +27,6 @@ import eu.kanade.tachiyomi.util.lang.takeBytes
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.updateCoverLastModified import eu.kanade.tachiyomi.util.updateCoverLastModified
import java.io.File
import java.util.Date
import java.util.concurrent.TimeUnit
import rx.Completable import rx.Completable
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
@ -38,6 +35,9 @@ import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get 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. * Presenter used by the activity to perform background operations.
@ -569,7 +569,7 @@ class ReaderPresenter(
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeFirst( .subscribeFirst(
{ view, file -> view.onShareImageResult(file) }, { view, file -> view.onShareImageResult(file, page) },
{ _, _ -> /* Empty */ } { _, _ -> /* Empty */ }
) )
} }

View File

@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import rx.Observable
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import rx.Observable
/** /**
* Loader used to load a chapter from a directory given on [file]. * Loader used to load a chapter from a directory given on [file].

View File

@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.ui.reader.loader
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.util.storage.EpubFile import eu.kanade.tachiyomi.util.storage.EpubFile
import java.io.File
import rx.Observable import rx.Observable
import java.io.File
/** /**
* Loader used to load a chapter from a .epub file. * Loader used to load a chapter from a .epub file.

View File

@ -6,9 +6,6 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.util.lang.plusAssign import eu.kanade.tachiyomi.util.lang.plusAssign
import java.util.concurrent.PriorityBlockingQueue
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.min
import rx.Completable import rx.Completable
import rx.Observable import rx.Observable
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
@ -18,6 +15,9 @@ import rx.subscriptions.CompositeSubscription
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.concurrent.PriorityBlockingQueue
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.min
/** /**
* Loader used to load chapters from an online source. * Loader used to load chapters from an online source.

View File

@ -4,14 +4,14 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import junrar.Archive
import junrar.rarfile.FileHeader
import rx.Observable
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.io.PipedInputStream import java.io.PipedInputStream
import java.io.PipedOutputStream import java.io.PipedOutputStream
import java.util.concurrent.Executors import java.util.concurrent.Executors
import junrar.Archive
import junrar.rarfile.FileHeader
import rx.Observable
/** /**
* Loader used to load a chapter from a .rar or .cbr file. * Loader used to load a chapter from a .rar or .cbr file.

View File

@ -5,11 +5,11 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import rx.Observable
import java.io.File import java.io.File
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipFile import java.util.zip.ZipFile
import rx.Observable
/** /**
* Loader used to load a chapter from a .zip or .cbz file. * Loader used to load a chapter from a .zip or .cbz file.

View File

@ -0,0 +1,15 @@
package eu.kanade.tachiyomi.ui.reader.viewer
import eu.kanade.tachiyomi.data.database.models.Chapter
import kotlin.math.floor
object MissingChapters {
fun hasMissingChapters(higher: Chapter, lower: Chapter): Boolean {
return hasMissingChapters(higher.chapter_number, lower.chapter_number)
}
fun hasMissingChapters(higherChapterNumber: Float, lowerChapterNumber: Float): Boolean {
return floor(higherChapterNumber) - floor(lowerChapterNumber) - 1f > 0f
}
}

View File

@ -63,9 +63,12 @@ class ReaderProgressBar @JvmOverloads constructor(
*/ */
private val rotationAnimation by lazy { private val rotationAnimation by lazy {
RotateAnimation( RotateAnimation(
0f, 360f, 0f,
Animation.RELATIVE_TO_SELF, 0.5f, 360f,
Animation.RELATIVE_TO_SELF, 0.5f Animation.RELATIVE_TO_SELF,
0.5f,
Animation.RELATIVE_TO_SELF,
0.5f
).apply { ).apply {
interpolator = LinearInterpolator() interpolator = LinearInterpolator()
repeatCount = Animation.INFINITE repeatCount = Animation.INFINITE

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