mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-22 17:51:50 +01:00
Merge branch 'dev' into dev-settings-search
This commit is contained in:
commit
528c1b90c2
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@ -2,7 +2,7 @@
|
||||
|
||||
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
|
||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
||||
|
||||
|
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -9,7 +9,7 @@ labels: "bug"
|
||||
|
||||
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
|
||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
||||
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -9,7 +9,7 @@ labels: "feature"
|
||||
|
||||
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
|
||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
| 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
|
||||
@ -23,7 +23,7 @@ Features include:
|
||||
## Download
|
||||
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
|
||||
|
||||
|
@ -40,8 +40,8 @@ android {
|
||||
minSdkVersion AndroidConfig.minSdk
|
||||
targetSdkVersion AndroidConfig.targetSdk
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
versionCode 50
|
||||
versionName "0.10.4"
|
||||
versionCode 51
|
||||
versionName "0.10.5"
|
||||
|
||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||
@ -133,7 +133,7 @@ dependencies {
|
||||
implementation 'androidx.biometric:biometric:1.1.0-alpha02'
|
||||
implementation 'androidx.browser:browser:1.2.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.core:core-ktx:1.4.0-alpha01'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
@ -163,14 +163,14 @@ dependencies {
|
||||
implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
|
||||
|
||||
// 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:logging-interceptor:$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
|
||||
implementation 'org.conscrypt:conscrypt-android:2.4.0'
|
||||
implementation 'org.conscrypt:conscrypt-android:2.5.1'
|
||||
|
||||
// REST
|
||||
final retrofit_version = '2.9.0'
|
||||
@ -281,10 +281,6 @@ dependencies {
|
||||
|
||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||
// 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 {
|
||||
@ -312,7 +308,7 @@ task copyResources(type: Copy) {
|
||||
include '**/*'
|
||||
}
|
||||
|
||||
preBuild.dependsOn(ktlintFormat, copyResources)
|
||||
preBuild.dependsOn(formatKotlin, copyResources)
|
||||
|
||||
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import java.security.Security
|
||||
import org.acra.ACRA
|
||||
import org.acra.annotation.AcraCore
|
||||
import org.acra.annotation.AcraHttpSender
|
||||
@ -24,6 +23,7 @@ import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.InjektScope
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
||||
import java.security.Security
|
||||
|
||||
@AcraCore(
|
||||
buildConfigClass = BuildConfig::class,
|
||||
|
@ -1,11 +1,15 @@
|
||||
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.library.LibraryUpdateJob
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.updater.UpdaterJob
|
||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
||||
import eu.kanade.tachiyomi.ui.library.LibrarySort
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||
import java.io.File
|
||||
|
||||
object Migrations {
|
||||
@ -89,6 +93,25 @@ object Migrations {
|
||||
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 false
|
||||
|
@ -8,9 +8,9 @@ import androidx.work.WorkManager
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import java.util.concurrent.TimeUnit
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
Worker(context, workerParams) {
|
||||
@ -36,8 +36,10 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
|
||||
val interval = prefInterval ?: preferences.backupInterval().get()
|
||||
if (interval > 0) {
|
||||
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
|
||||
interval.toLong(), TimeUnit.HOURS,
|
||||
10, TimeUnit.MINUTES
|
||||
interval.toLong(),
|
||||
TimeUnit.HOURS,
|
||||
10,
|
||||
TimeUnit.MINUTES
|
||||
)
|
||||
.addTag(TAG)
|
||||
.build()
|
||||
|
@ -50,10 +50,10 @@ import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||
import kotlin.math.max
|
||||
import rx.Observable
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlin.math.max
|
||||
|
||||
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
|
||||
|
@ -11,9 +11,9 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
internal class BackupNotifier(private val context: Context) {
|
||||
|
||||
|
@ -32,12 +32,9 @@ import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
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.isServiceRunning
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
@ -45,6 +42,10 @@ import kotlinx.coroutines.launch
|
||||
import rx.Observable
|
||||
import timber.log.Timber
|
||||
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.
|
||||
@ -398,7 +399,12 @@ class BackupRestoreService : Service() {
|
||||
return backupManager.restoreChapterFetchObservable(source, manga, chapters)
|
||||
// If there's any error, return empty update and continue.
|
||||
.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())
|
||||
}
|
||||
}
|
||||
|
@ -9,13 +9,13 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import okhttp3.Response
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Class used to create chapter cache
|
||||
|
@ -83,6 +83,11 @@ interface MangaQueries : DbProvider {
|
||||
.withPutResolver(MangaFlagsPutResolver())
|
||||
.prepare()
|
||||
|
||||
fun updateFlags(mangas: List<Manga>) = db.put()
|
||||
.objects(mangas)
|
||||
.withPutResolver(MangaFlagsPutResolver(true))
|
||||
.prepare()
|
||||
|
||||
fun updateLastUpdated(manga: Manga) = db.put()
|
||||
.`object`(manga)
|
||||
.withPutResolver(MangaLastUpdatedPutResolver())
|
||||
|
@ -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.tables.MangaTable
|
||||
|
||||
class MangaFlagsPutResolver : PutResolver<Manga>() {
|
||||
class MangaFlagsPutResolver(private val updateAll: Boolean = false) : PutResolver<Manga>() {
|
||||
|
||||
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
|
||||
val updateQuery = mapToUpdateQuery(manga)
|
||||
@ -19,11 +19,21 @@ class MangaFlagsPutResolver : PutResolver<Manga>() {
|
||||
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||
}
|
||||
|
||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
fun mapToUpdateQuery(manga: Manga): UpdateQuery {
|
||||
val builder = UpdateQuery.builder()
|
||||
|
||||
return if (updateAll) {
|
||||
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 {
|
||||
put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags)
|
||||
|
@ -7,10 +7,10 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Cache where we dump the downloads directory from the filesystem. This class is needed because
|
||||
|
@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.lang.chop
|
||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import java.util.regex.Pattern
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* DownloadNotifier is used to show notifications when downloading one or multiple chapters.
|
||||
@ -107,7 +107,9 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
}
|
||||
|
||||
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()) {
|
||||
|
@ -22,7 +22,6 @@ import eu.kanade.tachiyomi.util.lang.plusAssign
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.async
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
@ -31,6 +30,7 @@ import rx.schedulers.Schedulers
|
||||
import rx.subscriptions.CompositeSubscription
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* This class is the one in charge of downloading chapters.
|
||||
|
@ -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.download.DownloadStore
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
class DownloadQueue(
|
||||
private val store: DownloadStore,
|
||||
|
@ -5,12 +5,12 @@ import android.util.Log
|
||||
import com.bumptech.glide.Priority
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.data.DataFetcher
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import timber.log.Timber
|
||||
|
||||
open class FileFetcher(private val filePath: String = "") : DataFetcher<InputStream> {
|
||||
|
||||
|
@ -14,10 +14,10 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.util.isLocal
|
||||
import java.io.InputStream
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* A class for loading a cover associated with a [Manga] that can be present in our own cache.
|
||||
|
@ -14,9 +14,9 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import java.io.InputStream
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Class used to update Glide module settings
|
||||
|
@ -9,9 +9,9 @@ import androidx.work.WorkManager
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import java.util.concurrent.TimeUnit
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
Worker(context, workerParams) {
|
||||
@ -45,8 +45,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
.build()
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
|
||||
interval.toLong(), TimeUnit.HOURS,
|
||||
10, TimeUnit.MINUTES
|
||||
interval.toLong(),
|
||||
TimeUnit.HOURS,
|
||||
10,
|
||||
TimeUnit.MINUTES
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
|
@ -22,9 +22,9 @@ import eu.kanade.tachiyomi.util.lang.chop
|
||||
import eu.kanade.tachiyomi.util.system.notification
|
||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class LibraryUpdateNotifier(private val context: Context) {
|
||||
|
||||
@ -198,18 +198,23 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
|
||||
// Mark chapters as read action
|
||||
addAction(
|
||||
R.drawable.ic_glasses_black_24dp, context.getString(R.string.action_mark_as_read),
|
||||
R.drawable.ic_glasses_black_24dp,
|
||||
context.getString(R.string.action_mark_as_read),
|
||||
NotificationReceiver.markAsReadPendingBroadcast(
|
||||
context,
|
||||
manga, chapters, Notifications.ID_NEW_CHAPTERS
|
||||
manga,
|
||||
chapters,
|
||||
Notifications.ID_NEW_CHAPTERS
|
||||
)
|
||||
)
|
||||
// View chapters action
|
||||
addAction(
|
||||
R.drawable.ic_book_24dp, context.getString(R.string.action_view_chapters),
|
||||
R.drawable.ic_book_24dp,
|
||||
context.getString(R.string.action_view_chapters),
|
||||
NotificationReceiver.openChapterPendingActivity(
|
||||
context,
|
||||
manga, Notifications.ID_NEW_CHAPTERS
|
||||
manga,
|
||||
Notifications.ID_NEW_CHAPTERS
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
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.source.SourceManager
|
||||
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.prepUpdateCover
|
||||
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||
import java.io.File
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* 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)
|
||||
// If there's any error, return empty update and continue.
|
||||
.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())
|
||||
}
|
||||
// Filter out mangas without new chapters (or failed).
|
||||
|
@ -7,7 +7,6 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.BackupRestoreService
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
@ -26,10 +25,11 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import java.io.File
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||
|
||||
/**
|
||||
* Global [BroadcastReceiver] that runs on UI thread
|
||||
@ -56,19 +56,22 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
// Launch share activity and dismiss notification
|
||||
ACTION_SHARE_IMAGE ->
|
||||
shareImage(
|
||||
context, intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
context,
|
||||
intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
// Delete image from path and dismiss notification
|
||||
ACTION_DELETE_IMAGE ->
|
||||
deleteImage(
|
||||
context, intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
context,
|
||||
intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
// Share backup file
|
||||
ACTION_SHARE_BACKUP ->
|
||||
shareBackup(
|
||||
context, intent.getParcelableExtra(EXTRA_URI),
|
||||
context,
|
||||
intent.getParcelableExtra(EXTRA_URI),
|
||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||
)
|
||||
ACTION_CANCEL_RESTORE -> cancelRestore(
|
||||
@ -80,7 +83,8 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
// Open reader activity
|
||||
ACTION_OPEN_CHAPTER -> {
|
||||
openChapter(
|
||||
context, intent.getLongExtra(EXTRA_MANGA_ID, -1),
|
||||
context,
|
||||
intent.getLongExtra(EXTRA_MANGA_ID, -1),
|
||||
intent.getLongExtra(EXTRA_CHAPTER_ID, -1)
|
||||
)
|
||||
}
|
||||
|
@ -82,53 +82,62 @@ object Notifications {
|
||||
|
||||
listOf(
|
||||
NotificationChannel(
|
||||
CHANNEL_COMMON, context.getString(R.string.channel_common),
|
||||
CHANNEL_COMMON,
|
||||
context.getString(R.string.channel_common),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
),
|
||||
NotificationChannel(
|
||||
CHANNEL_LIBRARY, context.getString(R.string.channel_library),
|
||||
CHANNEL_LIBRARY,
|
||||
context.getString(R.string.channel_library),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER_PROGRESS, context.getString(R.string.channel_progress),
|
||||
CHANNEL_DOWNLOADER_PROGRESS,
|
||||
context.getString(R.string.channel_progress),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_DOWNLOADER
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER_COMPLETE, context.getString(R.string.channel_complete),
|
||||
CHANNEL_DOWNLOADER_COMPLETE,
|
||||
context.getString(R.string.channel_complete),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_DOWNLOADER
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_DOWNLOADER_ERROR, context.getString(R.string.channel_errors),
|
||||
CHANNEL_DOWNLOADER_ERROR,
|
||||
context.getString(R.string.channel_errors),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_DOWNLOADER
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_NEW_CHAPTERS, context.getString(R.string.channel_new_chapters),
|
||||
CHANNEL_NEW_CHAPTERS,
|
||||
context.getString(R.string.channel_new_chapters),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
),
|
||||
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
|
||||
),
|
||||
NotificationChannel(
|
||||
CHANNEL_BACKUP_RESTORE_PROGRESS, context.getString(R.string.channel_progress),
|
||||
CHANNEL_BACKUP_RESTORE_PROGRESS,
|
||||
context.getString(R.string.channel_progress),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_BACKUP_RESTORE
|
||||
setShowBadge(false)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_BACKUP_RESTORE_COMPLETE, context.getString(R.string.channel_complete),
|
||||
CHANNEL_BACKUP_RESTORE_COMPLETE,
|
||||
context.getString(R.string.channel_complete),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
group = GROUP_BACKUP_RESTORE
|
||||
|
@ -109,11 +109,11 @@ object PreferenceKeys {
|
||||
|
||||
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"
|
||||
|
||||
@ -165,6 +165,18 @@ object PreferenceKeys {
|
||||
|
||||
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 trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"
|
||||
|
@ -8,19 +8,21 @@ import androidx.preference.PreferenceManager
|
||||
import com.tfcporciuncula.flow.FlowSharedPreferences
|
||||
import com.tfcporciuncula.flow.Preference
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.NsfwAllowance
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import java.io.File
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
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 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)
|
||||
|
||||
@ -254,4 +256,27 @@ class PreferencesHelper(val context: Context) {
|
||||
fun lastSearchQuerySearchSettings() = prefs.getString("last_search_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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,12 +13,12 @@ import com.google.gson.JsonParser
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import java.util.Calendar
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import rx.Observable
|
||||
import java.util.Calendar
|
||||
|
||||
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
|
||||
@ -271,9 +271,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
}
|
||||
|
||||
return ALManga(
|
||||
struct["id"].asInt, struct["title"]["romaji"].asString, struct["coverImage"]["large"].asString,
|
||||
struct["description"].nullString.orEmpty(), struct["type"].asString, struct["status"].nullString.orEmpty(),
|
||||
date, struct["chapters"].nullInt ?: 0
|
||||
struct["id"].asInt,
|
||||
struct["title"]["romaji"].asString,
|
||||
struct["coverImage"]["large"].asString,
|
||||
struct["description"].nullString.orEmpty(),
|
||||
struct["type"].asString,
|
||||
struct["status"].nullString.orEmpty(),
|
||||
date,
|
||||
struct["chapters"].nullInt ?: 0
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
data class ALManga(
|
||||
val media_id: Int,
|
||||
|
@ -12,13 +12,13 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import java.net.URLEncoder
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.net.URLEncoder
|
||||
|
||||
class BangumiApi(private val client: OkHttpClient, interceptor: BangumiInterceptor) {
|
||||
|
||||
|
@ -7,10 +7,10 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import java.text.DecimalFormat
|
||||
import rx.Completable
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.DecimalFormat
|
||||
|
||||
class Kitsu(private val context: Context, id: Int) : TrackService(id) {
|
||||
|
||||
|
@ -11,13 +11,6 @@ import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.util.lang.toCalendar
|
||||
import eu.kanade.tachiyomi.util.selectInt
|
||||
import eu.kanade.tachiyomi.util.selectText
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.GregorianCalendar
|
||||
import java.util.Locale
|
||||
import java.util.zip.GZIPInputStream
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
@ -30,6 +23,13 @@ import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.parser.Parser
|
||||
import rx.Observable
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.GregorianCalendar
|
||||
import java.util.Locale
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -13,9 +13,10 @@ import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
Worker(context, workerParams) {
|
||||
@ -23,7 +24,7 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
override fun doWork(): Result {
|
||||
return runBlocking {
|
||||
try {
|
||||
val result = UpdateChecker.getUpdateChecker().checkForUpdate()
|
||||
val result = GithubUpdateChecker().checkForUpdate()
|
||||
|
||||
if (result is UpdateResult.NewUpdate<*>) {
|
||||
val url = result.release.downloadLink
|
||||
@ -65,8 +66,10 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
.build()
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<UpdaterJob>(
|
||||
3, TimeUnit.DAYS,
|
||||
3, TimeUnit.HOURS
|
||||
3,
|
||||
TimeUnit.DAYS,
|
||||
3,
|
||||
TimeUnit.HOURS
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
|
@ -20,9 +20,9 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||
import java.io.File
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
|
||||
class UpdaterService : Service() {
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -28,5 +28,5 @@ class GithubRelease(
|
||||
* Assets class containing 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)
|
||||
}
|
||||
|
@ -4,11 +4,12 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import uy.kohesive.injekt.Injekt
|
||||
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 {
|
||||
|
||||
@ -24,6 +25,6 @@ interface GithubService {
|
||||
}
|
||||
}
|
||||
|
||||
@GET("/repos/inorichi/tachiyomi/releases/latest")
|
||||
suspend fun getLatestVersion(): GithubRelease
|
||||
@GET("/repos/{repo}/releases/latest")
|
||||
suspend fun getLatestVersion(@Path("repo", encoded = true) repo: String): GithubRelease
|
||||
}
|
||||
|
@ -1,23 +1,43 @@
|
||||
package eu.kanade.tachiyomi.data.updater.github
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.updater.UpdateChecker
|
||||
import eu.kanade.tachiyomi.data.updater.UpdateResult
|
||||
|
||||
class GithubUpdateChecker : UpdateChecker() {
|
||||
class GithubUpdateChecker {
|
||||
|
||||
private val service: GithubService = GithubService.create()
|
||||
|
||||
override suspend fun checkForUpdate(): UpdateResult {
|
||||
val release = service.getLatestVersion()
|
||||
private val repo: String by lazy {
|
||||
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
|
||||
return if (newVersion != BuildConfig.VERSION_NAME) {
|
||||
return if (isNewVersion(release.version)) {
|
||||
GithubUpdateResult.NewUpdate(release)
|
||||
} else {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,10 @@ import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||
import eu.kanade.tachiyomi.util.system.notification
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
@ -73,8 +73,10 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
|
||||
.build()
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>(
|
||||
12, TimeUnit.HOURS,
|
||||
1, TimeUnit.HOURS
|
||||
12,
|
||||
TimeUnit.HOURS,
|
||||
1,
|
||||
TimeUnit.HOURS
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
|
@ -9,10 +9,10 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||
import java.util.Date
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Date
|
||||
|
||||
internal class ExtensionGithubApi {
|
||||
|
||||
|
@ -13,11 +13,11 @@ import com.jakewharton.rxrelay.PublishRelay
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* The installer which installs, updates and uninstalls the extensions.
|
||||
|
@ -178,7 +178,13 @@ internal object ExtensionLoader {
|
||||
}
|
||||
|
||||
val extension = Extension.Installed(
|
||||
extName, pkgName, versionName, versionCode, lang, isNsfw, sources,
|
||||
extName,
|
||||
pkgName,
|
||||
versionName,
|
||||
versionCode,
|
||||
lang,
|
||||
isNsfw,
|
||||
sources,
|
||||
isUnofficial = signatureHash != officialSignature
|
||||
)
|
||||
return LoadResult.Success(extension)
|
||||
|
@ -16,15 +16,15 @@ import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||
import eu.kanade.tachiyomi.util.system.isOutdated
|
||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class CloudflareInterceptor(private val context: Context) : Interceptor {
|
||||
|
||||
|
@ -3,15 +3,15 @@ package eu.kanade.tachiyomi.network
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
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.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.dnsoverhttps.DnsOverHttps
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.net.InetAddress
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class NetworkHelper(context: Context) {
|
||||
|
||||
|
@ -1,9 +1,5 @@
|
||||
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 okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
@ -13,6 +9,10 @@ import okhttp3.Response
|
||||
import rx.Observable
|
||||
import rx.Producer
|
||||
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> {
|
||||
return Observable.unsafeCreate { subscriber ->
|
||||
@ -54,22 +54,24 @@ fun Call.asObservable(): Observable<Response> {
|
||||
// Based on https://github.com/gildor/kotlin-coroutines-okhttp
|
||||
suspend fun Call.await(assertSuccess: Boolean = false): Response {
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
enqueue(object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
if (assertSuccess && !response.isSuccessful) {
|
||||
continuation.resumeWithException(Exception("HTTP error ${response.code}"))
|
||||
return
|
||||
enqueue(
|
||||
object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
if (assertSuccess && !response.isSuccessful) {
|
||||
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 {
|
||||
try {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package eu.kanade.tachiyomi.network
|
||||
|
||||
import java.io.IOException
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.ResponseBody
|
||||
import okio.Buffer
|
||||
@ -8,6 +7,7 @@ import okio.BufferedSource
|
||||
import okio.ForwardingSource
|
||||
import okio.Source
|
||||
import okio.buffer
|
||||
import java.io.IOException
|
||||
|
||||
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
package eu.kanade.tachiyomi.network
|
||||
|
||||
import java.util.concurrent.TimeUnit.MINUTES
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import java.util.concurrent.TimeUnit.MINUTES
|
||||
|
||||
private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build()
|
||||
private val DEFAULT_HEADERS = Headers.Builder().build()
|
||||
|
@ -14,6 +14,10 @@ import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.EpubFile
|
||||
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.FileInputStream
|
||||
import java.io.InputStream
|
||||
@ -21,10 +25,6 @@ import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.zip.ZipEntry
|
||||
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 {
|
||||
companion object {
|
||||
|
@ -10,15 +10,15 @@ import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import java.security.MessageDigest
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
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.
|
||||
|
@ -7,11 +7,11 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
||||
|
||||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
|
@ -22,27 +22,29 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
||||
lateinit var binding: VB
|
||||
|
||||
init {
|
||||
addLifecycleListener(object : LifecycleListener() {
|
||||
override fun postCreateView(controller: Controller, view: View) {
|
||||
onViewCreated(view)
|
||||
}
|
||||
addLifecycleListener(
|
||||
object : LifecycleListener() {
|
||||
override fun postCreateView(controller: Controller, view: View) {
|
||||
onViewCreated(view)
|
||||
}
|
||||
|
||||
override fun preCreateView(controller: Controller) {
|
||||
Timber.d("Create view for ${controller.instance()}")
|
||||
}
|
||||
override fun preCreateView(controller: Controller) {
|
||||
Timber.d("Create view for ${controller.instance()}")
|
||||
}
|
||||
|
||||
override fun preAttach(controller: Controller, view: View) {
|
||||
Timber.d("Attach view for ${controller.instance()}")
|
||||
}
|
||||
override fun preAttach(controller: Controller, view: View) {
|
||||
Timber.d("Attach view for ${controller.instance()}")
|
||||
}
|
||||
|
||||
override fun preDetach(controller: Controller, view: View) {
|
||||
Timber.d("Detach view for ${controller.instance()}")
|
||||
}
|
||||
override fun preDetach(controller: Controller, view: View) {
|
||||
Timber.d("Detach view for ${controller.instance()}")
|
||||
}
|
||||
|
||||
override fun preDestroyView(controller: Controller, view: View) {
|
||||
Timber.d("Destroy view for ${controller.instance()}")
|
||||
override fun preDestroyView(controller: Controller, view: View) {
|
||||
Timber.d("Destroy view for ${controller.instance()}")
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
override val containerView: View?
|
||||
@ -98,17 +100,19 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
||||
var expandActionViewFromInteraction = false
|
||||
|
||||
fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) {
|
||||
setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
return onExpand?.invoke(item) ?: true
|
||||
}
|
||||
setOnActionExpandListener(
|
||||
object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||
return onExpand?.invoke(item) ?: true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
activity?.invalidateOptionsMenu()
|
||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||
activity?.invalidateOptionsMenu()
|
||||
|
||||
return onCollapse?.invoke(item) ?: true
|
||||
return onCollapse?.invoke(item) ?: true
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if (expandActionViewFromInteraction) {
|
||||
expandActionViewFromInteraction = false
|
||||
|
@ -10,12 +10,12 @@ import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import java.util.concurrent.TimeUnit
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private typealias ExtensionTuple =
|
||||
Triple<List<Extension.Installed>, List<Extension.Untrusted>, List<Extension.Available>>
|
||||
|
@ -20,8 +20,6 @@ import androidx.preference.PreferenceManager
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
|
||||
@ -92,7 +90,6 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
||||
ExtensionDetailsHeaderAdapter(presenter),
|
||||
initPreferencesAdapter(context, extension)
|
||||
)
|
||||
binding.extensionPrefsRecycler.addItemDecoration(DividerItemDecoration(context, VERTICAL))
|
||||
}
|
||||
|
||||
private fun initPreferencesAdapter(context: Context, extension: Extension.Installed): PreferenceGroupAdapter {
|
||||
@ -112,7 +109,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
||||
.forEach {
|
||||
val preferenceBlock = {
|
||||
it.value
|
||||
.sortedWith(compareBy({ !it.isEnabled() }, { it.name }))
|
||||
.sortedWith(compareBy({ !it.isEnabled() }, { it.name.toLowerCase() }))
|
||||
.forEach { source ->
|
||||
val sourcePrefs = mutableListOf<Preference>()
|
||||
|
||||
|
@ -19,8 +19,6 @@ import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceGroupAdapter
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
|
||||
@ -86,7 +84,6 @@ class SourcePreferencesController(bundle: Bundle? = null) :
|
||||
|
||||
binding.recycler.layoutManager = LinearLayoutManager(context)
|
||||
binding.recycler.adapter = PreferenceGroupAdapter(screen)
|
||||
binding.recycler.addItemDecoration(DividerItemDecoration(context, VERTICAL))
|
||||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
|
@ -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.GlobalSearchPresenter
|
||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||
import java.util.Date
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import java.util.Date
|
||||
|
||||
class SearchPresenter(
|
||||
initialQuery: String? = "",
|
||||
|
@ -29,7 +29,7 @@ class MigrationSourcesPresenter(
|
||||
val header = SelectionHeader()
|
||||
return library.map { it.source }.toSet()
|
||||
.mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null }
|
||||
.sortedBy { it.name }
|
||||
.sortedBy { it.name.toLowerCase() }
|
||||
.map { SourceItem(it, header) }
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,9 @@ import eu.kanade.tachiyomi.util.preference.onChange
|
||||
import eu.kanade.tachiyomi.util.preference.switchPreferenceCategory
|
||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import java.util.TreeMap
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.TreeMap
|
||||
|
||||
class SourceFilterController : SettingsController() {
|
||||
|
||||
@ -42,7 +42,7 @@ class SourceFilterController : SettingsController() {
|
||||
)
|
||||
|
||||
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
|
||||
switchPreferenceCategory {
|
||||
|
@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import java.util.TreeMap
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@ -21,6 +20,7 @@ import rx.Observable
|
||||
import rx.Subscription
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.TreeMap
|
||||
|
||||
/**
|
||||
* Presenter of [SourceController]
|
||||
@ -128,7 +128,7 @@ class SourcePresenter(
|
||||
return sourceManager.getCatalogueSources()
|
||||
.filter { it.lang in languages }
|
||||
.filterNot { it.id.toString() in disabledSourceIds }
|
||||
.sortedBy { "(${it.lang}) ${it.name}" } +
|
||||
.sortedBy { "(${it.lang}) ${it.name.toLowerCase()}" } +
|
||||
sourceManager.get(LocalSource.ID) as LocalSource
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,6 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@ -216,7 +215,6 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
id = R.id.recycler
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
|
||||
}
|
||||
} else {
|
||||
(binding.catalogueView.inflate(R.layout.source_recycler_autofit) as AutofitRecyclerView).apply {
|
||||
|
@ -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.TriStateItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem
|
||||
import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
import java.util.Date
|
||||
import kotlinx.coroutines.flow.subscribe
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
@ -39,6 +38,7 @@ import rx.subjects.PublishSubject
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* Presenter of [BrowseSourceController].
|
||||
@ -268,6 +268,8 @@ open class BrowseSourcePresenter(
|
||||
|
||||
if (!manga.favorite) {
|
||||
manga.removeCovers(coverCache)
|
||||
} else {
|
||||
ChapterSettingsHelper.applySettingDefaults(manga)
|
||||
}
|
||||
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
|
@ -38,10 +38,13 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
|
||||
val coverHeight = parent.itemWidth / 3 * 4
|
||||
view.apply {
|
||||
card.layoutParams = FrameLayout.LayoutParams(
|
||||
MATCH_PARENT, coverHeight
|
||||
MATCH_PARENT,
|
||||
coverHeight
|
||||
)
|
||||
gradient.layoutParams = FrameLayout.LayoutParams(
|
||||
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM
|
||||
MATCH_PARENT,
|
||||
coverHeight / 2,
|
||||
Gravity.BOTTOM
|
||||
)
|
||||
}
|
||||
SourceGridHolder(view, adapter)
|
||||
@ -51,7 +54,8 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
|
||||
val coverHeight = parent.itemWidth / 3 * 4
|
||||
view.apply {
|
||||
card.layoutParams = ConstraintLayout.LayoutParams(
|
||||
MATCH_PARENT, coverHeight
|
||||
MATCH_PARENT,
|
||||
coverHeight
|
||||
)
|
||||
}
|
||||
SourceComfortableGridHolder(view, adapter)
|
||||
|
@ -30,7 +30,8 @@ open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<Selec
|
||||
spinner.prompt = filter.name
|
||||
spinner.adapter = ArrayAdapter<Any>(
|
||||
holder.itemView.context,
|
||||
android.R.layout.simple_spinner_item, filter.values
|
||||
android.R.layout.simple_spinner_item,
|
||||
filter.values
|
||||
).apply {
|
||||
setDropDownViewResource(R.layout.common_spinner_item)
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.davidea.viewholders.FlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.R as TR
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.R as TR
|
||||
|
||||
open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem<TriStateItem.Holder>() {
|
||||
|
||||
|
@ -108,17 +108,19 @@ open class GlobalSearchController(
|
||||
val searchView = searchItem.actionView as SearchView
|
||||
searchView.maxWidth = Int.MAX_VALUE
|
||||
|
||||
searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
|
||||
searchView.onActionViewExpanded() // Required to show the query in the view
|
||||
searchView.setQuery(presenter.query, false)
|
||||
return true
|
||||
}
|
||||
searchItem.setOnActionExpandListener(
|
||||
object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
|
||||
searchView.onActionViewExpanded() // Required to show the query in the view
|
||||
searchView.setQuery(presenter.query, false)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
|
||||
return true
|
||||
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
searchView.queryTextEvents()
|
||||
.filterIsInstance<QueryTextEvent.QuerySubmitted>()
|
||||
|
@ -108,7 +108,7 @@ open class GlobalSearchPresenter(
|
||||
return sourceManager.getCatalogueSources()
|
||||
.filter { it.lang in languages }
|
||||
.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> {
|
||||
@ -188,7 +188,7 @@ open class GlobalSearchPresenter(
|
||||
{ it.results.isNullOrEmpty() },
|
||||
// Same as initial sort, i.e. pinned first then alphabetically
|
||||
{ it.source.id.toString() !in pinnedSourceIds },
|
||||
{ "${it.source.name} (${it.source.lang})" }
|
||||
{ "${it.source.name.toLowerCase()} (${it.source.lang})" }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -198,8 +198,11 @@ class CategoryController :
|
||||
R.id.action_delete -> {
|
||||
undoHelper = UndoHelper(adapter, this)
|
||||
undoHelper?.start(
|
||||
adapter.selectedPositions, activity!!.root_coordinator,
|
||||
R.string.snack_categories_deleted, R.string.action_undo, 3000
|
||||
adapter.selectedPositions,
|
||||
activity!!.root_coordinator,
|
||||
R.string.snack_categories_deleted,
|
||||
R.string.action_undo,
|
||||
3000
|
||||
)
|
||||
|
||||
mode.finish()
|
||||
|
@ -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.NucleusController
|
||||
import eu.kanade.tachiyomi.util.view.shrinkOnScroll
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Controller that shows the currently active downloads.
|
||||
|
@ -183,7 +183,7 @@ class LibraryController(
|
||||
createActionModeIfNeeded()
|
||||
}
|
||||
|
||||
settingsSheet = LibrarySettingsSheet(activity!!) { group ->
|
||||
settingsSheet = LibrarySettingsSheet(router) { group ->
|
||||
when (group) {
|
||||
is LibrarySettingsSheet.Filter.FilterGroup -> onFilterChanged()
|
||||
is LibrarySettingsSheet.Sort.SortGroup -> onSortChanged()
|
||||
|
@ -45,7 +45,9 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe
|
||||
view.apply {
|
||||
card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight)
|
||||
gradient.layoutParams = FrameLayout.LayoutParams(
|
||||
MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM
|
||||
MATCH_PARENT,
|
||||
coverHeight / 2,
|
||||
Gravity.BOTTOM
|
||||
)
|
||||
}
|
||||
LibraryCompactGridHolder(view, adapter)
|
||||
@ -55,7 +57,8 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe
|
||||
val coverHeight = parent.itemWidth / 3 * 4
|
||||
view.apply {
|
||||
card.layoutParams = ConstraintLayout.LayoutParams(
|
||||
MATCH_PARENT, coverHeight
|
||||
MATCH_PARENT,
|
||||
coverHeight
|
||||
)
|
||||
}
|
||||
LibraryComfortableGridHolder(view, adapter)
|
||||
|
@ -19,14 +19,16 @@ import eu.kanade.tachiyomi.util.lang.combineLatest
|
||||
import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
import java.util.Collections
|
||||
import java.util.Comparator
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_IGNORE
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_INCLUDE
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Collections
|
||||
import java.util.Comparator
|
||||
|
||||
/**
|
||||
* Class containing library information.
|
||||
@ -110,34 +112,45 @@ class LibraryPresenter(
|
||||
* @param map the map to filter.
|
||||
*/
|
||||
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 filterCompleted = preferences.filterCompleted().get()
|
||||
|
||||
val filterFn: (LibraryItem) -> Boolean = f@{ item ->
|
||||
// Filter when there isn't unread chapters.
|
||||
if (filterUnread && item.manga.unread == 0) {
|
||||
return@f false
|
||||
val filterFnUnread: (LibraryItem) -> Boolean = unread@{ item ->
|
||||
if (filterUnread == STATE_IGNORE) return@unread true
|
||||
val isUnread = item.manga.unread != 0
|
||||
|
||||
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@f false
|
||||
}
|
||||
return@downloaded if (downloadedOnly || filterDownloaded == STATE_INCLUDE) isDownloaded
|
||||
else !isDownloaded
|
||||
}
|
||||
|
||||
// Filter when there are no downloads.
|
||||
if (filterDownloaded) {
|
||||
// Local manga are always downloaded
|
||||
if (item.manga.isLocal()) {
|
||||
return@f true
|
||||
}
|
||||
// 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
|
||||
val filterFn: (LibraryItem) -> Boolean = filter@{ item ->
|
||||
return@filter !(
|
||||
!filterFnUnread(item) ||
|
||||
!filterFnCompleted(item) ||
|
||||
!filterFnDownloaded(item)
|
||||
)
|
||||
}
|
||||
|
||||
return map.mapValues { entry -> entry.value.filter(filterFn) }
|
||||
|
@ -1,33 +1,36 @@
|
||||
package eu.kanade.tachiyomi.ui.library
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
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 uy.kohesive.injekt.injectLazy
|
||||
|
||||
class LibrarySettingsSheet(
|
||||
activity: Activity,
|
||||
router: Router,
|
||||
onGroupClickListener: (ExtendedNavigationView.Group) -> Unit
|
||||
) : TabbedBottomSheetDialog(activity) {
|
||||
) : TabbedBottomSheetDialog(router) {
|
||||
|
||||
val filters: Filter
|
||||
private val sort: Sort
|
||||
private val display: Display
|
||||
|
||||
init {
|
||||
filters = Filter(activity)
|
||||
filters = Filter(router.activity!!)
|
||||
filters.onGroupClicked = onGroupClickListener
|
||||
|
||||
sort = Sort(activity)
|
||||
sort = Sort(router.activity!!)
|
||||
sort.onGroupClicked = onGroupClickListener
|
||||
|
||||
display = Display(activity)
|
||||
display = Display(router.activity!!)
|
||||
display.onGroupClicked = onGroupClickListener
|
||||
}
|
||||
|
||||
@ -59,33 +62,43 @@ class LibrarySettingsSheet(
|
||||
* Returns true if there's at least one filter from [FilterGroup] active.
|
||||
*/
|
||||
fun hasActiveFilters(): Boolean {
|
||||
return filterGroup.items.any { it.checked }
|
||||
return filterGroup.items.any { it.state != Item.TriStateGroup.STATE_IGNORE }
|
||||
}
|
||||
|
||||
inner class FilterGroup : Group {
|
||||
|
||||
private val downloaded = Item.CheckboxGroup(R.string.action_filter_downloaded, this)
|
||||
private val unread = Item.CheckboxGroup(R.string.action_filter_unread, this)
|
||||
private val completed = Item.CheckboxGroup(R.string.completed, this)
|
||||
private val downloaded = Item.TriStateGroup(R.string.action_filter_downloaded, this)
|
||||
private val unread = Item.TriStateGroup(R.string.action_filter_unread, this)
|
||||
private val completed = Item.TriStateGroup(R.string.completed, this)
|
||||
|
||||
override val header = null
|
||||
override val items = listOf(downloaded, unread, completed)
|
||||
override val footer = null
|
||||
|
||||
override fun initModels() {
|
||||
downloaded.checked = preferences.downloadedOnly().get() || preferences.filterDownloaded().get()
|
||||
downloaded.enabled = !preferences.downloadedOnly().get()
|
||||
unread.checked = preferences.filterUnread().get()
|
||||
completed.checked = preferences.filterCompleted().get()
|
||||
if (preferences.downloadedOnly().get()) {
|
||||
downloaded.state = STATE_INCLUDE
|
||||
downloaded.enabled = false
|
||||
} else {
|
||||
downloaded.state = preferences.filterDownloaded().get()
|
||||
}
|
||||
unread.state = preferences.filterUnread().get()
|
||||
completed.state = preferences.filterCompleted().get()
|
||||
}
|
||||
|
||||
override fun onItemClicked(item: Item) {
|
||||
item as Item.CheckboxGroup
|
||||
item.checked = !item.checked
|
||||
item as Item.TriStateGroup
|
||||
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) {
|
||||
downloaded -> preferences.filterDownloaded().set(item.checked)
|
||||
unread -> preferences.filterUnread().set(item.checked)
|
||||
completed -> preferences.filterCompleted().set(item.checked)
|
||||
downloaded -> preferences.filterDownloaded().set(newState)
|
||||
unread -> preferences.filterUnread().set(newState)
|
||||
completed -> preferences.filterCompleted().set(newState)
|
||||
}
|
||||
|
||||
adapter.notifyItemChanged(item)
|
||||
|
@ -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.launchUI
|
||||
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.tabs
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import timber.log.Timber
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class MainActivity : BaseActivity<MainActivityBinding>() {
|
||||
|
||||
@ -126,26 +126,28 @@ class MainActivity : BaseActivity<MainActivityBinding>() {
|
||||
onBackPressed()
|
||||
}
|
||||
|
||||
router.addChangeListener(object : ControllerChangeHandler.ControllerChangeListener {
|
||||
override fun onChangeStarted(
|
||||
to: Controller?,
|
||||
from: Controller?,
|
||||
isPush: Boolean,
|
||||
container: ViewGroup,
|
||||
handler: ControllerChangeHandler
|
||||
) {
|
||||
syncActivityViewWithController(to, from)
|
||||
}
|
||||
router.addChangeListener(
|
||||
object : ControllerChangeHandler.ControllerChangeListener {
|
||||
override fun onChangeStarted(
|
||||
to: Controller?,
|
||||
from: Controller?,
|
||||
isPush: Boolean,
|
||||
container: ViewGroup,
|
||||
handler: ControllerChangeHandler
|
||||
) {
|
||||
syncActivityViewWithController(to, from)
|
||||
}
|
||||
|
||||
override fun onChangeCompleted(
|
||||
to: Controller?,
|
||||
from: Controller?,
|
||||
isPush: Boolean,
|
||||
container: ViewGroup,
|
||||
handler: ControllerChangeHandler
|
||||
) {
|
||||
override fun onChangeCompleted(
|
||||
to: Controller?,
|
||||
from: Controller?,
|
||||
isPush: Boolean,
|
||||
container: ViewGroup,
|
||||
handler: ControllerChangeHandler
|
||||
) {
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
syncActivityViewWithController(router.backstack.lastOrNull()?.controller())
|
||||
|
||||
|
@ -52,7 +52,6 @@ import eu.kanade.tachiyomi.ui.library.ChangeMangaCoverDialog
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
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.ChaptersAdapter
|
||||
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.updates.UpdatesController
|
||||
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.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.getCoordinates
|
||||
import eu.kanade.tachiyomi.util.view.shrinkOnScroll
|
||||
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.toolbar
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@ -83,6 +82,7 @@ import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlin.math.min
|
||||
|
||||
class MangaController :
|
||||
NucleusController<MangaControllerBinding, MangaPresenter>,
|
||||
@ -207,7 +207,6 @@ class MangaController :
|
||||
|
||||
binding.recycler.adapter = ConcatAdapter(mangaInfoAdapter, chaptersHeaderAdapter, chaptersAdapter)
|
||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||
binding.recycler.addItemDecoration(ChapterDividerItemDecoration(view.context))
|
||||
binding.recycler.setHasFixedSize(true)
|
||||
chaptersAdapter?.fastScroller = binding.fastScroller
|
||||
|
||||
@ -238,7 +237,7 @@ class MangaController :
|
||||
|
||||
binding.actionToolbar.offsetAppbarHeight(activity!!)
|
||||
|
||||
settingsSheet = ChaptersSettingsSheet(activity!!, presenter) { group ->
|
||||
settingsSheet = ChaptersSettingsSheet(router, presenter) { group ->
|
||||
if (group is ChaptersSettingsSheet.Filter.FilterGroup) {
|
||||
updateFilterIconState()
|
||||
chaptersAdapter?.notifyDataSetChanged()
|
||||
@ -292,10 +291,10 @@ class MangaController :
|
||||
// Get coordinates and start animation
|
||||
actionFab?.getCoordinates()?.let { coordinates ->
|
||||
if (!binding.revealView.showRevealEffect(
|
||||
coordinates.x,
|
||||
coordinates.y,
|
||||
revealAnimationListener
|
||||
)
|
||||
coordinates.x,
|
||||
coordinates.y,
|
||||
revealAnimationListener
|
||||
)
|
||||
) {
|
||||
openChapter(item.chapter)
|
||||
}
|
||||
@ -343,7 +342,8 @@ class MangaController :
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// Hide options for non-library manga
|
||||
@ -354,6 +354,7 @@ class MangaController :
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
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_custom, R.id.download_unread, R.id.download_all
|
||||
-> downloadChapters(item.itemId)
|
||||
@ -694,7 +695,11 @@ class MangaController :
|
||||
fun onFetchChaptersError(error: Throwable) {
|
||||
isRefreshingChapters = false
|
||||
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) {
|
||||
|
@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
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.isLocal
|
||||
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.shouldDownloadNewChapters
|
||||
import eu.kanade.tachiyomi.util.updateCoverLastModified
|
||||
import java.util.Date
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
@ -34,6 +34,7 @@ import rx.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Date
|
||||
|
||||
class MangaPresenter(
|
||||
val manga: Manga,
|
||||
@ -82,6 +83,10 @@ class MangaPresenter(
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
|
||||
if (!manga.favorite) {
|
||||
ChapterSettingsHelper.applySettingDefaults(manga)
|
||||
}
|
||||
|
||||
// Manga info - start
|
||||
|
||||
getMangaObservable()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -9,11 +9,11 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
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.chapter_description
|
||||
import kotlinx.android.synthetic.main.chapters_item.chapter_title
|
||||
import kotlinx.android.synthetic.main.chapters_item.download_text
|
||||
import java.util.Date
|
||||
|
||||
class ChapterHolder(
|
||||
view: View,
|
||||
|
@ -6,10 +6,10 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.DateFormat
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class ChaptersAdapter(
|
||||
controller: MangaController,
|
||||
|
@ -1,34 +1,39 @@
|
||||
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
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.TabbedBottomSheetDialog
|
||||
|
||||
class ChaptersSettingsSheet(
|
||||
activity: Activity,
|
||||
private val router: Router,
|
||||
private val presenter: MangaPresenter,
|
||||
onGroupClickListener: (ExtendedNavigationView.Group) -> Unit
|
||||
) : TabbedBottomSheetDialog(activity) {
|
||||
) : TabbedBottomSheetDialog(router) {
|
||||
|
||||
val filters: Filter
|
||||
private val sort: Sort
|
||||
private val display: Display
|
||||
|
||||
init {
|
||||
filters = Filter(activity)
|
||||
filters = Filter(router.activity!!)
|
||||
filters.onGroupClicked = onGroupClickListener
|
||||
|
||||
sort = Sort(activity)
|
||||
sort = Sort(router.activity!!)
|
||||
sort.onGroupClicked = onGroupClickListener
|
||||
|
||||
display = Display(activity)
|
||||
display = Display(router.activity!!)
|
||||
display.onGroupClicked = onGroupClickListener
|
||||
|
||||
binding.menu.isVisible = true
|
||||
binding.menu.setOnClickListener { it.post { showPopupMenu(it) } }
|
||||
}
|
||||
|
||||
override fun getTabViews(): List<View> = listOf(
|
||||
@ -43,6 +48,23 @@ class ChaptersSettingsSheet(
|
||||
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, ...).
|
||||
*/
|
||||
|
@ -66,7 +66,7 @@ class MangaChaptersHeaderAdapter(
|
||||
} else {
|
||||
view.context.getResourceColor(R.attr.colorOnPrimary)
|
||||
}
|
||||
DrawableCompat.setTint(binding.btnChaptersFilter.icon, filterColor)
|
||||
DrawableCompat.setTint(binding.btnChaptersFilter.drawable, filterColor)
|
||||
|
||||
merge(view.clicks(), binding.btnChaptersFilter.clicks())
|
||||
.onEach { controller.showSettingsSheet() }
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -105,13 +105,27 @@ class MangaInfoHeaderAdapter(
|
||||
isVisible = true
|
||||
|
||||
if (trackCount > 0) {
|
||||
setIconResource(R.drawable.ic_done_24dp)
|
||||
text = view.context.resources.getQuantityString(R.plurals.num_trackers, trackCount, trackCount)
|
||||
isChecked = true
|
||||
setCompoundDrawablesWithIntrinsicBounds(
|
||||
null,
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_done_24dp),
|
||||
null,
|
||||
null
|
||||
)
|
||||
text = view.context.resources.getQuantityString(
|
||||
R.plurals.num_trackers,
|
||||
trackCount,
|
||||
trackCount
|
||||
)
|
||||
isSelected = true
|
||||
} 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)
|
||||
isChecked = false
|
||||
isSelected = false
|
||||
}
|
||||
|
||||
clicks()
|
||||
@ -128,12 +142,6 @@ class MangaInfoHeaderAdapter(
|
||||
.onEach { controller.openMangaInWebView() }
|
||||
.launchIn(scope)
|
||||
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()
|
||||
@ -285,14 +293,24 @@ class MangaInfoHeaderAdapter(
|
||||
|
||||
// Update genres list
|
||||
if (!manga.genre.isNullOrBlank()) {
|
||||
binding.mangaGenresTagsCompactChips.setChips(manga.getGenres(), controller::performSearch)
|
||||
binding.mangaGenresTagsFullChips.setChips(manga.getGenres(), controller::performSearch)
|
||||
binding.mangaGenresTagsCompactChips.setChips(
|
||||
manga.getGenres(),
|
||||
controller::performSearch
|
||||
)
|
||||
binding.mangaGenresTagsFullChips.setChips(
|
||||
manga.getGenres(),
|
||||
controller::performSearch
|
||||
)
|
||||
} else {
|
||||
binding.mangaGenresTagsWrapper.isVisible = false
|
||||
}
|
||||
|
||||
// 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) }
|
||||
.launchIn(scope)
|
||||
|
||||
@ -310,20 +328,22 @@ class MangaInfoHeaderAdapter(
|
||||
|
||||
private fun toggleMangaInfo(context: Context) {
|
||||
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) {
|
||||
text = if (isExpanded) {
|
||||
contentDescription = if (isExpanded) {
|
||||
context.getString(R.string.manga_info_expand)
|
||||
} else {
|
||||
context.getString(R.string.manga_info_collapse)
|
||||
}
|
||||
|
||||
icon = if (isExpanded) {
|
||||
context.getDrawable(R.drawable.ic_baseline_expand_more_24dp)
|
||||
} else {
|
||||
context.getDrawable(R.drawable.ic_baseline_expand_less_24dp)
|
||||
}
|
||||
setImageDrawable(
|
||||
if (isExpanded) {
|
||||
context.getDrawable(R.drawable.ic_baseline_expand_more_24dp)
|
||||
} else {
|
||||
context.getDrawable(R.drawable.ic_baseline_expand_less_24dp)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
with(binding.mangaSummaryText) {
|
||||
@ -355,13 +375,18 @@ class MangaInfoHeaderAdapter(
|
||||
// Set the Favorite drawable to the correct one.
|
||||
// Border drawable if false, filled drawable if true.
|
||||
binding.btnFavorite.apply {
|
||||
icon = ContextCompat.getDrawable(
|
||||
context,
|
||||
if (isFavorite) R.drawable.ic_favorite_24dp else R.drawable.ic_favorite_border_24dp
|
||||
setCompoundDrawablesWithIntrinsicBounds(
|
||||
null,
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
if (isFavorite) R.drawable.ic_favorite_24dp else R.drawable.ic_favorite_border_24dp
|
||||
),
|
||||
null,
|
||||
null
|
||||
)
|
||||
text =
|
||||
context.getString(if (isFavorite) R.string.in_library else R.string.add_to_library)
|
||||
isChecked = isFavorite
|
||||
isSelected = isFavorite
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import java.util.Calendar
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Calendar
|
||||
|
||||
class SetTrackReadingDatesDialog<T> : DialogController
|
||||
where T : Controller, T : SetTrackReadingDatesDialog.Listener {
|
||||
|
@ -5,8 +5,8 @@ import androidx.core.view.isVisible
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.databinding.TrackItemBinding
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder
|
||||
import java.text.DateFormat
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.DateFormat
|
||||
|
||||
class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter) : BaseViewHolder(binding.root) {
|
||||
|
||||
|
@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
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.track_search
|
||||
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 uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class TrackSearchDialog : DialogController {
|
||||
|
||||
|
@ -10,9 +10,9 @@ import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
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.UpdaterService
|
||||
import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.setting.SettingsController
|
||||
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.system.copyToClipboard
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import timber.log.Timber
|
||||
import java.text.DateFormat
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import timber.log.Timber
|
||||
|
||||
class AboutController : SettingsController() {
|
||||
|
||||
/**
|
||||
* Checks for new releases
|
||||
*/
|
||||
private val updateChecker by lazy { UpdateChecker.getUpdateChecker() }
|
||||
private val updateChecker by lazy { GithubUpdateChecker() }
|
||||
|
||||
private val dateFormat: DateFormat = preferences.dateFormat()
|
||||
|
||||
@ -234,7 +234,9 @@ class AboutController : SettingsController() {
|
||||
val buildTime = inputDf.parse(BuildConfig.BUILD_TIME)
|
||||
|
||||
val outputDf = DateFormat.getDateTimeInstance(
|
||||
DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault()
|
||||
DateFormat.MEDIUM,
|
||||
DateFormat.SHORT,
|
||||
Locale.getDefault()
|
||||
)
|
||||
outputDf.timeZone = TimeZone.getDefault()
|
||||
|
||||
|
@ -7,7 +7,6 @@ import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
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.RootController
|
||||
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 rx.android.schedulers.AndroidSchedulers
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||
|
||||
class MoreController :
|
||||
SettingsController(),
|
||||
|
@ -21,6 +21,7 @@ import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.widget.SeekBar
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.setPadding
|
||||
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.widget.SimpleAnimationListener
|
||||
import eu.kanade.tachiyomi.widget.SimpleSeekBarListener
|
||||
import java.io.File
|
||||
import kotlin.math.abs
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.drop
|
||||
@ -67,6 +66,8 @@ import kotlinx.coroutines.flow.sample
|
||||
import nucleus.factory.RequiresPresenter
|
||||
import timber.log.Timber
|
||||
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
|
||||
@ -290,24 +291,27 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.readerMenu) { _, insets ->
|
||||
if (!window.isDefaultBar()) {
|
||||
val systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
binding.readerMenu.setPadding(
|
||||
insets.systemWindowInsetLeft,
|
||||
insets.systemWindowInsetTop,
|
||||
insets.systemWindowInsetRight,
|
||||
insets.systemWindowInsetBottom
|
||||
systemInsets.left,
|
||||
systemInsets.top,
|
||||
systemInsets.right,
|
||||
systemInsets.bottom
|
||||
)
|
||||
}
|
||||
insets
|
||||
}
|
||||
|
||||
// Init listeners on bottom menu
|
||||
binding.pageSeekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||
if (viewer != null && fromUser) {
|
||||
moveToPageIndex(value)
|
||||
binding.pageSeekbar.setOnSeekBarChangeListener(
|
||||
object : SimpleSeekBarListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||
if (viewer != null && fromUser) {
|
||||
moveToPageIndex(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
binding.leftChapter.setOnClickListener {
|
||||
if (viewer != null) {
|
||||
if (viewer is R2LPagerViewer) {
|
||||
@ -347,12 +351,14 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
|
||||
if (animate) {
|
||||
val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top)
|
||||
toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() {
|
||||
override fun onAnimationStart(animation: Animation) {
|
||||
// Fix status bar being translucent the first time it's opened.
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
toolbarAnimation.setAnimationListener(
|
||||
object : SimpleAnimationListener() {
|
||||
override fun onAnimationStart(animation: Animation) {
|
||||
// Fix status bar being translucent the first time it's opened.
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
binding.toolbar.startAnimation(toolbarAnimation)
|
||||
|
||||
val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_bottom)
|
||||
@ -371,11 +377,13 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
|
||||
if (animate) {
|
||||
val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_top)
|
||||
toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() {
|
||||
override fun onAnimationEnd(animation: Animation) {
|
||||
binding.readerMenu.isVisible = false
|
||||
toolbarAnimation.setAnimationListener(
|
||||
object : SimpleAnimationListener() {
|
||||
override fun onAnimationEnd(animation: Animation) {
|
||||
binding.readerMenu.isVisible = false
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
binding.toolbar.startAnimation(toolbarAnimation)
|
||||
|
||||
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
|
||||
* 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 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)
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
type = "image/*"
|
||||
|
@ -74,45 +74,55 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet
|
||||
}
|
||||
binding.colorFilterMode.setSelection(preferences.colorFilterMode().get(), false)
|
||||
|
||||
binding.seekbarColorFilterAlpha.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
setColorValue(value, ALPHA_MASK, 24)
|
||||
binding.seekbarColorFilterAlpha.setOnSeekBarChangeListener(
|
||||
object : SimpleSeekBarListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
setColorValue(value, ALPHA_MASK, 24)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
binding.seekbarColorFilterRed.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
setColorValue(value, RED_MASK, 16)
|
||||
binding.seekbarColorFilterRed.setOnSeekBarChangeListener(
|
||||
object : SimpleSeekBarListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
setColorValue(value, RED_MASK, 16)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
binding.seekbarColorFilterGreen.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
setColorValue(value, GREEN_MASK, 8)
|
||||
binding.seekbarColorFilterGreen.setOnSeekBarChangeListener(
|
||||
object : SimpleSeekBarListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
setColorValue(value, GREEN_MASK, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
binding.seekbarColorFilterBlue.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
setColorValue(value, BLUE_MASK, 0)
|
||||
binding.seekbarColorFilterBlue.setOnSeekBarChangeListener(
|
||||
object : SimpleSeekBarListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
setColorValue(value, BLUE_MASK, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
binding.brightnessSeekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
preferences.customBrightnessValue().set(value)
|
||||
binding.brightnessSeekbar.setOnSeekBarChangeListener(
|
||||
object : SimpleSeekBarListener() {
|
||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
preferences.customBrightnessValue().set(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
@ -27,9 +27,6 @@ import eu.kanade.tachiyomi.util.lang.takeBytes
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||
import eu.kanade.tachiyomi.util.updateCoverLastModified
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import rx.Completable
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
@ -38,6 +35,9 @@ import rx.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Presenter used by the activity to perform background operations.
|
||||
@ -569,7 +569,7 @@ class ReaderPresenter(
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst(
|
||||
{ view, file -> view.onShareImageResult(file) },
|
||||
{ view, file -> view.onShareImageResult(file, page) },
|
||||
{ _, _ -> /* Empty */ }
|
||||
)
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||
import rx.Observable
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import rx.Observable
|
||||
|
||||
/**
|
||||
* Loader used to load a chapter from a directory given on [file].
|
||||
|
@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.ui.reader.loader
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.util.storage.EpubFile
|
||||
import java.io.File
|
||||
import rx.Observable
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Loader used to load a chapter from a .epub 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.ReaderPage
|
||||
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.Observable
|
||||
import rx.schedulers.Schedulers
|
||||
@ -18,6 +15,9 @@ import rx.subscriptions.CompositeSubscription
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
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.
|
||||
|
@ -4,14 +4,14 @@ import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||
import junrar.Archive
|
||||
import junrar.rarfile.FileHeader
|
||||
import rx.Observable
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.PipedInputStream
|
||||
import java.io.PipedOutputStream
|
||||
import java.util.concurrent.Executors
|
||||
import junrar.Archive
|
||||
import junrar.rarfile.FileHeader
|
||||
import rx.Observable
|
||||
|
||||
/**
|
||||
* Loader used to load a chapter from a .rar or .cbr file.
|
||||
|
@ -5,11 +5,11 @@ import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||
import rx.Observable
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import rx.Observable
|
||||
|
||||
/**
|
||||
* Loader used to load a chapter from a .zip or .cbz 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
|
||||
}
|
||||
}
|
@ -63,9 +63,12 @@ class ReaderProgressBar @JvmOverloads constructor(
|
||||
*/
|
||||
private val rotationAnimation by lazy {
|
||||
RotateAnimation(
|
||||
0f, 360f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f
|
||||
0f,
|
||||
360f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f
|
||||
).apply {
|
||||
interpolator = LinearInterpolator()
|
||||
repeatCount = Animation.INFINITE
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user