Compare commits

..

1 Commits

Author SHA1 Message Date
Jobobby04 cbf82a9d6a Hide dedupe by priority 2021-04-11 21:53:50 -04:00
189 changed files with 1506 additions and 1884 deletions
+1
View File
@@ -1 +1,2 @@
github: inorichi
ko_fi: inorichi
+2 -8
View File
@@ -2,15 +2,9 @@
I acknowledge that:
- I have updated:
- To the latest version of the app (stable is v1.6.2)
- All extensions
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
- I have updated to the latest version of the app (stable is v1.6.0)
- I have updated all extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open issue
- I will fill out the title and the information in this template
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
+2 -8
View File
@@ -9,15 +9,9 @@ labels: "bug"
I acknowledge that:
- I have updated:
- To the latest version of the app (stable is v1.6.2)
- All extensions
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
- I have updated to the latest version of the app (stable is v1.6.0)
- I have updated all extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open issue
- I will fill out the title and the information in this template
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
+2 -7
View File
@@ -9,14 +9,9 @@ labels: "feature"
I acknowledge that:
- I have updated:
- To the latest version of the app (stable is v1.6.2)
- All extensions
- I have updated to the latest version of the app (stable is v1.6.0)
- I have updated all extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open issue
- I will fill out the title and the information in this template
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
Binary file not shown.

Before

Width:  |  Height:  |  Size: 489 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

+1 -1
View File
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Autoclose issues
uses: arkon/issue-closer-action@v3.1
uses: arkon/issue-closer-action@v3.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
rules: |
+1 -1
View File
@@ -11,7 +11,7 @@ Tachiyomi is a free and open source manga reader for Android 5.0 and above. This
## Features
Features of Tachiyomi(original) include:
* Online reading from [a variety of sources](https://github.com/tachiyomiorg/tachiyomi-extensions)
* Online reading from sources such as MangaDex, MangaSee, Mangakakalot, [and more](https://github.com/tachiyomiorg/tachiyomi-extensions)
* Local reading of downloaded manga
* A configurable reader with multiple viewers, reading directions and other settings.
* [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
+7 -7
View File
@@ -34,8 +34,8 @@ android {
minSdkVersion(AndroidConfig.minSdk)
targetSdkVersion(AndroidConfig.targetSdk)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
versionCode = 16
versionName = "1.6.2"
versionCode = 14
versionName = "1.6.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
@@ -160,7 +160,7 @@ dependencies {
implementation("com.github.pwittchen:reactivenetwork:0.13.0")
// Network client
val okhttpVersion = "4.9.1"
val okhttpVersion = "5.0.0-alpha.2"
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
@@ -170,7 +170,7 @@ dependencies {
implementation("org.conscrypt:conscrypt-android:2.5.1")
// JSON
val kotlinSerializationVersion = "1.1.0"
val kotlinSerializationVersion = "1.0.1"
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
implementation("com.google.code.gson:gson:2.8.6")
@@ -181,7 +181,7 @@ dependencies {
// Disk
implementation("com.jakewharton:disklrucache:2.0.2")
implementation("com.github.tachiyomiorg:unifile:17bec43")
implementation("com.github.inorichi:unifile:e9ee588")
implementation("com.github.junrar:junrar:7.4.0")
// HTML parser
@@ -267,12 +267,12 @@ dependencies {
implementation(kotlin("reflect", version = BuildPluginsVersion.KOTLIN))
val coroutinesVersion = "1.4.3"
val coroutinesVersion = "1.4.2"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
// For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7")
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.6")
// SY -->
// [EXH] Android 7 SSL Workaround
+4 -4
View File
@@ -33,7 +33,7 @@
android:largeHeap="true"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.Base"
android:theme="@style/Theme.Tachiyomi.Light"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".ui.main.MainActivity"
@@ -85,7 +85,7 @@
</activity>
<activity
android:name=".ui.security.BiometricUnlockActivity"
android:theme="@style/Theme.Base" />
android:theme="@style/Theme.Splash" />
<activity
android:name=".ui.webview.WebViewActivity"
android:configChanges="uiMode|orientation|screenSize" />
@@ -200,7 +200,7 @@
<activity
android:name="exh.ui.intercept.InterceptActivity"
android:label="@string/app_name"
android:theme="@style/Theme.Base">
android:theme="@style/Theme.EHActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -374,7 +374,7 @@
</activity>
<activity
android:name="exh.ui.captcha.BrowserActionActivity"
android:theme="@style/Theme.Base" />
android:theme="@style/Theme.EHActivity" />
</application>
</manifest>
@@ -82,9 +82,6 @@ open class App : Application(), LifecycleObserver {
LocaleHelper.updateConfiguration(this, resources.configuration)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
// Reset Incognito Mode on relaunch
preferences.incognitoMode().set(false)
}
override fun attachBaseContext(base: Context) {
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi
import android.os.Build
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
@@ -142,15 +141,6 @@ object Migrations {
}
}
}
if (oldVersion < 59) {
// Reset rotation to Free after replacing Lock
preferences.rotation().set(1)
// Disable update check for Android 5.x users
if (BuildConfig.INCLUDE_UPDATER && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
UpdaterJob.cancelTask(context)
}
}
return true
}
@@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.data.cache
import android.content.Context
import android.text.format.Formatter
import com.github.salomonbrys.kotson.fromJson
import com.google.gson.Gson
import com.jakewharton.disklrucache.DiskLruCache
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -13,12 +15,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
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
@@ -48,12 +48,14 @@ class ChapterCache(private val context: Context) {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
/** Google Json class used for parsing JSON files. */
private val json: Json by injectLazy()
private val gson: Gson by injectLazy()
// --> EH
private val prefs: PreferencesHelper by injectLazy()
// <-- EH
/** Cache class used for cache management. */
// --> EH
private var diskCache = setupDiskCache(prefs.cacheSize().get().toLong())
init {
@@ -71,7 +73,7 @@ class ChapterCache(private val context: Context) {
/**
* Returns directory of cache.
*/
private val cacheDir: File
val cacheDir: File
get() = diskCache.directory
/**
@@ -98,19 +100,43 @@ class ChapterCache(private val context: Context) {
}
// <-- EH
/**
* Remove file from cache.
*
* @param file name of file "md5.0".
* @return status of deletion for the file.
*/
fun removeFileFromCache(file: String): Boolean {
// Make sure we don't delete the journal file (keeps track of cache).
if (file == "journal" || file.startsWith("journal.")) {
return false
}
return try {
// Remove the extension from the file to get the key of the cache
val key = file.substringBeforeLast(".")
// Remove file from cache.
diskCache.remove(key)
} catch (e: Exception) {
false
}
}
/**
* Get page list from cache.
*
* @param chapter the chapter.
* @return the list of pages.
* @return an observable of the list of pages.
*/
fun getPageListFromCache(chapter: Chapter): List<Page> {
// Get the key for the chapter.
val key = DiskUtil.hashKeyForDisk(getKey(chapter))
fun getPageListFromCache(chapter: Chapter): Observable<List<Page>> {
return Observable.fromCallable {
// Get the key for the chapter.
val key = DiskUtil.hashKeyForDisk(getKey(chapter))
// Convert JSON string to list of objects. Throws an exception if snapshot is null
return diskCache.get(key).use {
json.decodeFromString(it.getString(0))
// Convert JSON string to list of objects. Throws an exception if snapshot is null
diskCache.get(key).use {
gson.fromJson<List<Page>>(it.getString(0))
}
}
}
@@ -122,7 +148,7 @@ class ChapterCache(private val context: Context) {
*/
fun putPageListToCache(chapter: Chapter, pages: List<Page>) {
// Convert list of pages to json string.
val cachedValue = json.encodeToString(pages)
val cachedValue = gson.toJson(pages)
// Initialize the editor (edits the values for an entry).
var editor: DiskLruCache.Editor? = null
@@ -202,38 +228,6 @@ class ChapterCache(private val context: Context) {
}
}
fun clear(): Int {
var deletedFiles = 0
cacheDir.listFiles()?.forEach {
if (removeFileFromCache(it.name)) {
deletedFiles++
}
}
return deletedFiles
}
/**
* Remove file from cache.
*
* @param file name of file "md5.0".
* @return status of deletion for the file.
*/
private fun removeFileFromCache(file: String): Boolean {
// Make sure we don't delete the journal file (keeps track of cache).
if (file == "journal" || file.startsWith("journal.")) {
return false
}
return try {
// Remove the extension from the file to get the key of the cache
val key = file.substringBeforeLast(".")
// Remove file from cache.
diskCache.remove(key)
} catch (e: Exception) {
false
}
}
private fun getKey(chapter: Chapter): String {
return "${chapter.manga_id}${chapter.url}"
}
@@ -263,7 +263,7 @@ class DownloadManager(private val context: Context) {
if (removeNonFavorite && !manga.favorite) {
val mangaFolder = provider.getMangaDir(manga, source)
cleaned += 1 + mangaFolder.listFiles().orEmpty().size
cleaned += 1 + (mangaFolder.listFiles()?.size ?: 0)
mangaFolder.delete()
cache.removeManga(manga)
return cleaned
@@ -284,7 +284,8 @@ class DownloadManager(private val context: Context) {
if (cache.getDownloadCount(manga) == 0) {
val mangaFolder = provider.getMangaDir(manga, source)
if (!mangaFolder.listFiles().isNullOrEmpty()) {
val size = mangaFolder.listFiles()?.size ?: 0
if (size == 0) {
mangaFolder.delete()
cache.removeManga(manga)
} else {
@@ -28,6 +28,7 @@ internal class DownloadNotifier(private val context: Context) {
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
setAutoCancel(false)
setOngoing(true)
setOnlyAlertOnce(true)
}
}
@@ -83,6 +84,7 @@ internal class DownloadNotifier(private val context: Context) {
*/
fun onProgressChange(download: Download) {
with(progressNotificationBuilder) {
// Check if first call.
if (!isDownloading) {
setSmallIcon(android.R.drawable.stat_sys_download)
clearActions()
@@ -114,7 +116,6 @@ internal class DownloadNotifier(private val context: Context) {
}
setProgress(download.pages!!.size, download.downloadedImages, false)
setOngoing(true)
show(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
}
@@ -129,7 +130,6 @@ internal class DownloadNotifier(private val context: Context) {
setContentText(context.getString(R.string.download_notifier_download_paused))
setSmallIcon(R.drawable.ic_pause_24dp)
setProgress(0, 0, false)
setOngoing(false)
clearActions()
// Open download manager when clicked
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
@@ -65,7 +65,7 @@ class DownloadProvider(private val context: Context) {
* @param source the source to query.
*/
fun findSourceDir(source: Source): UniFile? {
return downloadsDir.findFile(getSourceDirName(source), true)
return downloadsDir.findFile(getSourceDirName(source))
}
/**
@@ -76,7 +76,7 @@ class DownloadProvider(private val context: Context) {
*/
fun findMangaDir(manga: Manga, source: Source): UniFile? {
val sourceDir = findSourceDir(source)
return sourceDir?.findFile(getMangaDirName(manga), true)
return sourceDir?.findFile(getMangaDirName(manga))
}
/**
@@ -89,7 +89,7 @@ class DownloadProvider(private val context: Context) {
fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? {
val mangaDir = findMangaDir(manga, source)
return getValidChapterDirNames(chapter).asSequence()
.mapNotNull { mangaDir?.findFile(it, true) ?: mangaDir?.findFile("$it.cbz", true) }
.mapNotNull { mangaDir?.findFile(it) ?: mangaDir?.findFile("$it.cbz") }
.firstOrNull()
}
@@ -123,12 +123,14 @@ class DownloadProvider(private val context: Context) {
source: Source
): List<UniFile> {
val mangaDir = findMangaDir(manga, source) ?: return emptyList()
return mangaDir.listFiles().orEmpty().asList().filter {
chapters.find { chp ->
getValidChapterDirNames(chp).any { dir ->
mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null
}
} == null || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
return mangaDir.listFiles()!!.asList().filter {
(
chapters.find { chp ->
getValidChapterDirNames(chp).any { dir ->
mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null
}
} == null
) || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
}
}
// SY <--
@@ -139,7 +141,7 @@ class DownloadProvider(private val context: Context) {
* @param source the source to query.
*/
fun getSourceDirName(source: Source): String {
return DiskUtil.buildValidFilename(source.toString())
return source.toString()
}
/**
@@ -176,7 +178,6 @@ class DownloadProvider(private val context: Context) {
return listOf(
getChapterDirName(chapter),
// TODO: remove this
// Legacy chapter directory name used in v0.9.2 and before
DiskUtil.buildValidFilename(chapter.name)
)
@@ -24,7 +24,6 @@ object PreferenceValues {
enum class DarkThemeVariant {
default,
blue,
amoledblue,
amoled,
red,
midnightdusk,
@@ -11,6 +11,9 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
private val json: Json by injectLazy()
private var oauth: OAuth? = null
set(value) {
field = value?.copy(expires_in = System.currentTimeMillis() + (value.expires_in * 1000))
}
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
@@ -21,19 +24,21 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
if (oauth == null) {
oauth = myanimelist.loadOAuth()
}
// Refresh access token if expired
if (oauth != null && oauth!!.isExpired()) {
// Refresh access token if null or expired.
if (oauth!!.isExpired()) {
chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!.refresh_token)).use {
if (it.isSuccessful) {
setAuth(json.decodeFromString(it.body!!.string()))
}
}
}
// Throw on null auth.
if (oauth == null) {
throw Exception("No authentication token")
}
// Add the authorization header to the original request
// Add the authorization header to the original request.
val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.build()
@@ -7,9 +7,8 @@ data class OAuth(
val refresh_token: String,
val access_token: String,
val token_type: String,
val created_at: Long = System.currentTimeMillis(),
val expires_in: Long
) {
fun isExpired() = System.currentTimeMillis() > created_at + (expires_in * 1000)
fun isExpired() = System.currentTimeMillis() > expires_in
}
@@ -1,6 +1,9 @@
package eu.kanade.tachiyomi.data.updater
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
@@ -8,26 +11,52 @@ import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
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 kotlinx.coroutines.runBlocking
import java.util.concurrent.TimeUnit
class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork() = runBlocking {
try {
val result = GithubUpdateChecker().checkForUpdate()
override fun doWork(): Result {
return runBlocking {
try {
val result = GithubUpdateChecker().checkForUpdate()
if (result is UpdateResult.NewUpdate<*>) {
UpdaterNotifier(context).promptUpdate(result.release.downloadLink)
if (result is UpdateResult.NewUpdate<*>) {
val url = result.release.downloadLink
val intent = Intent(context, UpdaterService::class.java).apply {
putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url)
}
NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON).update {
setContentTitle(context.getString(R.string.app_name))
setContentText(context.getString(R.string.update_check_notification_update_available))
setSmallIcon(android.R.drawable.stat_sys_download_done)
// Download action
addAction(
android.R.drawable.stat_sys_download_done,
context.getString(R.string.action_download),
PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
)
}
}
Result.success()
} catch (e: Exception) {
Result.failure()
}
Result.success()
} catch (e: Exception) {
Result.failure()
}
}
fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) {
block()
context.notificationManager.notify(Notifications.ID_UPDATER, build())
}
companion object {
private const val TAG = "UpdateChecker"
@@ -1,8 +1,6 @@
package eu.kanade.tachiyomi.data.updater
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.app.NotificationCompat
import eu.kanade.tachiyomi.R
@@ -30,27 +28,6 @@ internal class UpdaterNotifier(private val context: Context) {
context.notificationManager.notify(id, build())
}
fun promptUpdate(url: String) {
val intent = Intent(context, UpdaterService::class.java).apply {
putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url)
}
val pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
with(notificationBuilder) {
setContentTitle(context.getString(R.string.app_name))
setContentText(context.getString(R.string.update_check_notification_update_available))
setSmallIcon(android.R.drawable.stat_sys_download_done)
setContentIntent(pendingIntent)
clearActions()
addAction(
android.R.drawable.stat_sys_download_done,
context.getString(R.string.action_download),
pendingIntent
)
}
notificationBuilder.show()
}
/**
* Call when apk download starts.
*
@@ -86,20 +63,19 @@ internal class UpdaterNotifier(private val context: Context) {
* @param uri path location of apk.
*/
fun onDownloadFinished(uri: Uri) {
val installIntent = NotificationHandler.installApkPendingActivity(context, uri)
with(notificationBuilder) {
setContentText(context.getString(R.string.update_check_notification_download_complete))
setSmallIcon(android.R.drawable.stat_sys_download_done)
setOnlyAlertOnce(false)
setProgress(0, 0, false)
setContentIntent(installIntent)
clearActions()
// Install action
setContentIntent(NotificationHandler.installApkPendingActivity(context, uri))
addAction(
R.drawable.ic_system_update_alt_white_24dp,
context.getString(R.string.action_install),
installIntent
NotificationHandler.installApkPendingActivity(context, uri)
)
// Cancel action
addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel),
@@ -120,13 +96,13 @@ internal class UpdaterNotifier(private val context: Context) {
setSmallIcon(android.R.drawable.stat_sys_warning)
setOnlyAlertOnce(false)
setProgress(0, 0, false)
clearActions()
// Retry action
addAction(
R.drawable.ic_refresh_24dp,
context.getString(R.string.action_retry),
UpdaterService.downloadApkPendingService(context, url)
)
// Cancel action
addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel),
@@ -163,7 +163,7 @@ internal object ExtensionLoader {
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
}
} catch (e: Throwable) {
Timber.e(e, "Extension load error: $extName ($it)")
Timber.w(e, "Extension load error: $extName ($it)")
return LoadResult.Error(e)
}
}
@@ -1,25 +1,17 @@
package eu.kanade.tachiyomi.source.online
import android.app.Activity
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.controller.DialogController
interface LoginSource : Source {
val requiresLogin: Boolean
val twoFactorAuth: AuthSupport
val needsLogin: Boolean
fun isLogged(): Boolean
fun getUsername(): String
fun getLoginDialog(source: Source, activity: Activity): DialogController
fun getPassword(): String
suspend fun login(username: String, password: String, twoFactorCode: String?): Boolean
suspend fun login(username: String, password: String, twoFactorCode: String = ""): Boolean
suspend fun logout(): Boolean
enum class AuthSupport {
NOT_SUPPORTED,
SUPPORTED,
REQUIRED
}
}
@@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.source.online.all
import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
@@ -11,6 +12,7 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@@ -24,6 +26,7 @@ import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.RandomMangaSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.lang.withIOContext
@@ -43,6 +46,7 @@ import exh.metadata.metadata.MangaDexSearchMetadata
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.MangaDexDescriptionAdapter
import exh.util.urlImportFetchSearchManga
import exh.widget.preference.MangadexLoginDialog
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
@@ -176,27 +180,21 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return FollowsHandler(client, headers, Injekt.get(), useLowQualityThumbnail()).fetchFollows()
}
override val requiresLogin: Boolean = true
override val needsLogin: Boolean = true
override val twoFactorAuth = LoginSource.AuthSupport.SUPPORTED
override fun getLoginDialog(source: Source, activity: Activity): DialogController {
return MangadexLoginDialog(source as MangaDex)
}
override fun isLogged(): Boolean {
val httpUrl = MdUtil.baseUrl.toHttpUrl()
return trackManager.mdList.isLogged && network.cookieManager.get(httpUrl).any { it.name == REMEMBER_ME }
}
override fun getUsername(): String {
return trackManager.mdList.getUsername()
}
override fun getPassword(): String {
return trackManager.mdList.getPassword()
}
override suspend fun login(
username: String,
password: String,
twoFactorCode: String?
twoFactorCode: String
): Boolean {
return withIOContext {
val formBody = FormBody.Builder().apply {
@@ -204,7 +202,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
add("login_password", password)
add("no_js", "1")
add("remember_me", "1")
add("two_factor", twoFactorCode ?: "")
add("two_factor", twoFactorCode)
}
runCatching {
@@ -225,8 +223,6 @@ class MangaDex(delegate: HttpSource, val context: Context) :
} else {
throw Exception("Json data was null")
}
}.also {
preferences.setTrackCredentials(trackManager.mdList, username, password)
}
}
}
@@ -1,43 +1,68 @@
package eu.kanade.tachiyomi.ui.base.activity
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DarkThemeVariant
import eu.kanade.tachiyomi.data.preference.PreferenceValues.LightThemeVariant
import eu.kanade.tachiyomi.data.preference.PreferenceValues.ThemeMode
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import uy.kohesive.injekt.injectLazy
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
abstract class BaseThemedActivity : AppCompatActivity() {
val preferences: PreferencesHelper by injectLazy()
val isDarkMode: Boolean by lazy {
val themeMode = preferences.themeMode().get()
(themeMode == Values.ThemeMode.dark) ||
(
themeMode == Values.ThemeMode.system &&
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES)
)
}
private val lightTheme: Int by lazy {
when (preferences.themeLight().get()) {
Values.LightThemeVariant.blue -> R.style.Theme_Tachiyomi_LightBlue
else -> {
when {
// Light status + navigation bar
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> {
R.style.Theme_Tachiyomi_Light_Api27
}
// Light status bar + fallback gray navigation bar
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
R.style.Theme_Tachiyomi_Light_Api23
}
// Fallback gray status + navigation bar
else -> {
R.style.Theme_Tachiyomi_Light
}
}
}
}
}
private val darkTheme: Int by lazy {
when (preferences.themeDark().get()) {
Values.DarkThemeVariant.blue -> R.style.Theme_Tachiyomi_DarkBlue
Values.DarkThemeVariant.amoled -> R.style.Theme_Tachiyomi_Amoled
Values.DarkThemeVariant.red -> R.style.Theme_Tachiyomi_Red
Values.DarkThemeVariant.midnightdusk -> R.style.Theme_Tachiyomi_MidnightDusk
Values.DarkThemeVariant.hotpink -> R.style.Theme_Tachiyomi_HotPink
else -> R.style.Theme_Tachiyomi_Dark
}
}
override fun onCreate(savedInstanceState: Bundle?) {
val isDarkMode = when (preferences.themeMode().get()) {
ThemeMode.light -> false
ThemeMode.dark -> true
ThemeMode.system -> resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
}
val themeId = if (isDarkMode) {
when (preferences.themeDark().get()) {
DarkThemeVariant.default -> R.style.Theme_Tachiyomi_Dark
DarkThemeVariant.blue -> R.style.Theme_Tachiyomi_Dark_Blue
DarkThemeVariant.amoledblue -> R.style.Theme_Tachiyomi_Dark_AmoledBlue
DarkThemeVariant.amoled -> R.style.Theme_Tachiyomi_Dark_Amoled
DarkThemeVariant.red -> R.style.Theme_Tachiyomi_Dark_Red
DarkThemeVariant.midnightdusk -> R.style.Theme_Tachiyomi_Dark_MidnightDusk
DarkThemeVariant.hotpink -> R.style.Theme_Tachiyomi_Dark_HotPink
setTheme(
when {
isDarkMode -> darkTheme
else -> lightTheme
}
} else {
when (preferences.themeLight().get()) {
LightThemeVariant.default -> R.style.Theme_Tachiyomi_Light
LightThemeVariant.blue -> R.style.Theme_Tachiyomi_Light_Blue
}
}
setTheme(themeId)
)
super.onCreate(savedInstanceState)
}
}
@@ -19,8 +19,7 @@ import timber.log.Timber
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
RestoreViewOnCreateController(bundle) {
protected lateinit var binding: VB
private set
lateinit var binding: VB
lateinit var viewScope: CoroutineScope
@@ -52,13 +51,12 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
)
}
abstract fun createBinding(inflater: LayoutInflater): VB
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
binding = createBinding(inflater)
return binding.root
return inflateView(inflater, container)
}
abstract fun inflateView(inflater: LayoutInflater, container: ViewGroup): View
open fun onViewCreated(view: View) {}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.browse
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
@@ -50,7 +51,10 @@ class BrowseController :
return resources!!.getString(R.string.browse)
}
override fun createBinding(inflater: LayoutInflater) = PagerControllerBinding.inflate(inflater)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = PagerControllerBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
@@ -5,6 +5,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager
import com.bluelinelabs.conductor.ControllerChangeHandler
@@ -56,16 +57,18 @@ open class ExtensionController :
return ExtensionPresenter()
}
override fun createBinding(inflater: LayoutInflater) = ExtensionControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = ExtensionControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.swipeRefresh.isRefreshing = true
binding.swipeRefresh.refreshes()
@@ -4,12 +4,12 @@ import android.annotation.SuppressLint
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
import eu.kanade.tachiyomi.databinding.SourceMainControllerCardHeaderBinding
class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<*>) :
FlexibleViewHolder(view, adapter) {
private val binding = SectionHeaderItemBinding.bind(view)
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
@SuppressLint("SetTextI18n")
fun bind(item: ExtensionGroupItem) {
@@ -19,7 +19,7 @@ data class ExtensionGroupItem(val name: String, val size: Int, val showSize: Boo
* Returns the layout resource of this item.
*/
override fun getLayoutRes(): Int {
return R.layout.section_header_item
return R.layout.source_main_controller_card_header
}
/**
@@ -12,6 +12,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.os.bundleOf
import androidx.preference.Preference
@@ -64,9 +65,15 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
setHasOptionsMenu(true)
}
override fun createBinding(inflater: LayoutInflater): ExtensionDetailControllerBinding {
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
return ExtensionDetailControllerBinding.inflate(themedInflater)
binding = ExtensionDetailControllerBinding.inflate(themedInflater)
binding.extensionPrefsRecycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
override fun createPresenter(): ExtensionDetailsPresenter {
@@ -81,12 +88,6 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.extensionPrefsRecycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
val extension = presenter.extension ?: return
val context = view.context
@@ -6,6 +6,7 @@ import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.os.bundleOf
import androidx.preference.DialogPreference
@@ -45,9 +46,10 @@ class SourcePreferencesController(bundle: Bundle? = null) :
bundleOf(SOURCE_ID to sourceId)
)
override fun createBinding(inflater: LayoutInflater): SourcePreferencesControllerBinding {
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
return SourcePreferencesControllerBinding.inflate(themedInflater)
binding = SourcePreferencesControllerBinding.inflate(themedInflater)
return binding.root
}
override fun createPresenter(): SourcePreferencesPresenter {
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.latest
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.tachiyomi.R
@@ -31,6 +32,23 @@ open class LatestController :
*/
protected var adapter: LatestAdapter? = null
/**
* Initiate the view with [R.layout.global_search_controller].
*
* @param inflater used to load the layout xml.
* @param container containing parent views.
* @return inflated view
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = LatestControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
override fun getTitle(): String? {
return applicationContext?.getString(R.string.latest)
}
@@ -64,8 +82,6 @@ open class LatestController :
onMangaClick(manga)
}
override fun createBinding(inflater: LayoutInflater): LatestControllerBinding = LatestControllerBinding.inflate(inflater)
/**
* Called when the view is created
*
@@ -74,12 +90,6 @@ open class LatestController :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
adapter = LatestAdapter(this)
// Create recycler and set adapter.
@@ -6,6 +6,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -45,16 +46,18 @@ class PreMigrationController(bundle: Bundle? = null) :
override fun getTitle() = view?.context?.getString(R.string.select_sources)
override fun createBinding(inflater: LayoutInflater) = PreMigrationControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = PreMigrationControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
val ourAdapter = adapter ?: MigrationSourceAdapter(
getEnabledSources().map { MigrationSourceItem(it, isEnabled(it.id.toString())) },
@@ -8,6 +8,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.graphics.ColorUtils
import androidx.core.os.bundleOf
@@ -83,23 +84,24 @@ class MigrationListController(bundle: Bundle? = null) :
private val throttleManager = EHentaiThrottleManager()
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = MigrationListControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
override fun getTitle(): String {
return resources?.getString(R.string.migration) + " (${adapter?.items?.count {
it.manga.migrationStatus != MigrationStatus.RUNNING
}}/${adapter?.itemCount ?: 0})"
}
override fun createBinding(inflater: LayoutInflater) = MigrationListControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
setTitle()
val config = this.config ?: return
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.browse.migration.advanced.process
import android.view.MenuItem
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -103,30 +102,16 @@ class MigrationProcessAdapter(
// Update chapters read
if (MigrationFlags.hasChapters(flags)) {
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
val maxChapterRead =
prevMangaChapters.filter { it.read }.maxOfOrNull { it.chapter_number }
val dbChapters = db.getChapters(manga).executeAsBlocking()
val prevHistoryList = db.getHistoryByMangaId(prevManga.id!!).executeAsBlocking()
val historyList = mutableListOf<History>()
for (chapter in dbChapters) {
if (chapter.isRecognizedNumber) {
val prevChapter =
prevMangaChapters.find { it.isRecognizedNumber && it.chapter_number == chapter.chapter_number }
if (prevChapter != null) {
chapter.bookmark = prevChapter.bookmark
chapter.read = prevChapter.read
chapter.date_fetch = prevChapter.date_fetch
prevHistoryList.find { it.chapter_id == prevChapter.id }?.let { prevHistory ->
val history = History.create(chapter).apply { last_read = prevHistory.last_read }
historyList.add(history)
}
} else if (maxChapterRead != null && chapter.chapter_number <= maxChapterRead) {
val maxChapterRead = prevMangaChapters.filter { it.read }.maxByOrNull { it.chapter_number }?.chapter_number
if (maxChapterRead != null) {
val dbChapters = db.getChapters(manga).executeAsBlocking()
for (chapter in dbChapters) {
if (chapter.isRecognizedNumber && chapter.chapter_number <= maxChapterRead) {
chapter.read = true
}
}
db.insertChapters(dbChapters).executeAsBlocking()
}
db.insertChapters(dbChapters).executeAsBlocking()
db.updateHistoryLastRead(historyList).executeAsBlocking()
}
// Update categories
if (MigrationFlags.hasCategories(flags)) {
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.migration.manga
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import dev.chrisbanes.insetter.applyInsetter
@@ -51,16 +52,18 @@ class MigrationMangaController :
return MigrationMangaPresenter(sourceId)
}
override fun createBinding(inflater: LayoutInflater) = MigrationMangaControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = MigrationMangaControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
adapter = MigrationMangaAdapter(this)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
@@ -5,6 +5,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter
@@ -41,16 +42,18 @@ class MigrationSourcesController :
return MigrationSourcesPresenter()
}
override fun createBinding(inflater: LayoutInflater) = MigrationSourcesControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = MigrationSourcesControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
adapter = SourceAdapter(this)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
@@ -7,7 +7,7 @@ import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
import eu.kanade.tachiyomi.databinding.SourceMainControllerCardHeaderBinding
/**
* Item that contains the selection header.
@@ -18,7 +18,7 @@ class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
* Returns the layout resource of this item.
*/
override fun getLayoutRes(): Int {
return R.layout.section_header_item
return R.layout.source_main_controller_card_header
}
/**
@@ -46,7 +46,7 @@ class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
}
class Holder(view: View, adapter: FlexibleAdapter</* SY --> */ IFlexible<RecyclerView.ViewHolder> /* SY <-- */>) : FlexibleViewHolder(view, adapter) {
private val binding = SectionHeaderItemBinding.bind(view)
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
init {
binding.title.text = view.context.getString(/* SY --> */ R.string.select_a_source_to_migrate_from /* SY <-- */)
@@ -3,13 +3,13 @@ package eu.kanade.tachiyomi.ui.browse.source
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
import eu.kanade.tachiyomi.databinding.SourceMainControllerCardHeaderBinding
import eu.kanade.tachiyomi.util.system.LocaleHelper
class LangHolder(view: View, adapter: FlexibleAdapter<*>) :
FlexibleViewHolder(view, adapter) {
private val binding = SectionHeaderItemBinding.bind(view)
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
fun bind(item: LangItem) {
binding.title.text = LocaleHelper.getSourceDisplayName(item.code, itemView.context)
@@ -18,7 +18,7 @@ data class LangItem(val code: String) : AbstractHeaderItem<LangHolder>() {
* Returns the layout resource of this item.
*/
override fun getLayoutRes(): Int {
return R.layout.section_header_item
return R.layout.source_main_controller_card_header
}
/**
@@ -9,6 +9,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
@@ -87,16 +88,25 @@ class SourceController(bundle: Bundle? = null) :
return SourcePresenter(/* SY --> */ controllerMode = mode /* SY <-- */)
}
override fun createBinding(inflater: LayoutInflater) = SourceMainControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
/**
* Initiate the view with [R.layout.source_main_controller].
*
* @param inflater used to load the layout xml.
* @param container containing parent views.
* @return inflated view.
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = SourceMainControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
adapter = SourceAdapter(this)
@@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.databinding.SourceMainControllerCardItemBinding
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.icon
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.setVectorCompat
class SourceHolder(private val view: View, val adapter: SourceAdapter /* SY --> */, private val showLatest: Boolean, private val showPins: Boolean /* SY <-- */) :
@@ -51,9 +52,9 @@ class SourceHolder(private val view: View, val adapter: SourceAdapter /* SY -->
binding.pin.isVisible = showPins
if (item.isPinned) {
binding.pin.setVectorCompat(R.drawable.ic_push_pin_24dp, R.attr.colorAccent)
binding.pin.setVectorCompat(R.drawable.ic_push_pin_24dp, view.context.getResourceColor(R.attr.colorAccent))
} else {
binding.pin.setVectorCompat(R.drawable.ic_push_pin_outline_24dp, android.R.attr.textColorHint)
binding.pin.setVectorCompat(R.drawable.ic_push_pin_outline_24dp, view.context.getResourceColor(android.R.attr.textColorHint))
}
}
}
@@ -61,7 +61,6 @@ import exh.md.similar.ui.EnableMangaDexSimilarDialogController
import exh.savedsearches.EXHSavedSearch
import exh.source.getMainSource
import exh.source.isEhBasedSource
import exh.widget.preference.MangadexLoginDialog
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
@@ -159,7 +158,10 @@ open class BrowseSourceController(bundle: Bundle) :
// SY <--
}
override fun createBinding(inflater: LayoutInflater) = SourceControllerBinding.inflate(inflater)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = SourceControllerBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
@@ -180,8 +182,8 @@ open class BrowseSourceController(bundle: Bundle) :
preferences.shownMangaDexSimilarAskDialog().set(true)
}
if (mainSource is LoginSource && mainSource.requiresLogin && !mainSource.isLogged()) {
val dialog = MangadexLoginDialog(mainSource)
if (mainSource is LoginSource && mainSource.needsLogin && !mainSource.isLogged()) {
val dialog = mainSource.getLoginDialog(mainSource, activity!!)
dialog.showDialog(router)
}
// SY <--
@@ -409,7 +411,6 @@ open class BrowseSourceController(bundle: Bundle) :
if (router.backstackSize >= 2 && router.backstack[router.backstackSize - 2].controller() is GlobalSearchController) {
router.popController(this)
} else {
nonSubmittedQuery = ""
searchWithQuery("")
}
@@ -9,7 +9,6 @@ import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.chip.Chip
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
@@ -38,17 +37,10 @@ class SourceFilterSheet(
// EXH <--
) : BaseBottomSheetDialog(activity) {
private var filterNavView: FilterNavigationView = FilterNavigationView(
activity,
// SY -->
searches = searches,
source = source,
controller = controller
// SY <--
)
private val sheetBehavior: BottomSheetBehavior<*>
private var filterNavView: FilterNavigationView
init {
filterNavView = FilterNavigationView(activity /* SY --> */, searches = searches, source = source, controller = controller/* SY <-- */)
filterNavView.onFilterClicked = {
onFilterClicked()
this.dismiss()
@@ -64,13 +56,6 @@ class SourceFilterSheet(
// EXH <--
setContentView(filterNavView)
sheetBehavior = BottomSheetBehavior.from(filterNavView.parent as ViewGroup)
}
override fun show() {
super.show()
sheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
fun setFilters(items: List<IFlexible<*>>) {
@@ -87,15 +72,7 @@ class SourceFilterSheet(
}
// SY <--
class FilterNavigationView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
// SY -->
searches: List<EXHSavedSearch> = emptyList(),
source: CatalogueSource? = null,
controller: BaseController<*>? = null
// SY <--
) :
class FilterNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null /* SY --> */, searches: List<EXHSavedSearch> = emptyList(), source: CatalogueSource? = null, controller: BaseController<*>? = null/* SY <-- */) :
SimpleNavigationView(context, attrs) {
var onFilterClicked = {}
@@ -114,13 +91,8 @@ class SourceFilterSheet(
// SY <--
val adapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter<IFlexible<*>>(null)
.setDisplayHeadersAtStartUp(true)
private val binding = SourceFilterSheetBinding.inflate(
LayoutInflater.from(context),
null,
false
)
private val binding = SourceFilterSheetBinding.inflate(LayoutInflater.from(context), null, false)
init {
// SY -->
@@ -6,6 +6,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
@@ -50,7 +51,22 @@ open class GlobalSearchController(
setHasOptionsMenu(true)
}
override fun createBinding(inflater: LayoutInflater) = GlobalSearchControllerBinding.inflate(inflater)
/**
* Initiate the view with [R.layout.global_search_controller].
*
* @param inflater used to load the layout xml.
* @param container containing parent views.
* @return inflated view
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = GlobalSearchControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
override fun getTitle(): String? {
return presenter.query
@@ -127,12 +143,6 @@ open class GlobalSearchController(
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
adapter = GlobalSearchAdapter(this)
// Create recycler and set adapter.
@@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
@@ -73,6 +74,18 @@ open class IndexController :
setHasOptionsMenu(true)
}
/**
* Initiate the view with [R.layout.latest_controller].
*
* @param inflater used to load the layout xml.
* @param container containing parent views.
* @return inflated view
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = IndexControllerBinding.inflate(inflater)
return binding.root
}
override fun getTitle(): String? {
return source!!.name
}
@@ -126,8 +139,6 @@ open class IndexController :
}
}
override fun createBinding(inflater: LayoutInflater) = IndexControllerBinding.inflate(inflater)
/**
* Called when the view is created
*
@@ -4,6 +4,7 @@ import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.LinearLayoutManager
@@ -67,7 +68,21 @@ class CategoryController :
return resources?.getString(R.string.action_edit_categories)
}
override fun createBinding(inflater: LayoutInflater) = CategoriesControllerBinding.inflate(inflater)
/**
* Returns the view of this controller.
*
* @param inflater The layout inflater to create the view from XML.
* @param container The parent view for this one.
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = CategoriesControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
/**
* Called after view inflation. Used to initialize the view.
@@ -77,12 +92,6 @@ class CategoryController :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
adapter = CategoryAdapter(this@CategoryController)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.setHasFixedSize(true)
@@ -4,6 +4,7 @@ import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.LinearLayoutManager
@@ -67,7 +68,21 @@ class BiometricTimesController :
return resources?.getString(R.string.biometric_lock_times)
}
override fun createBinding(inflater: LayoutInflater) = CategoriesControllerBinding.inflate(inflater)
/**
* Returns the view of this controller.
*
* @param inflater The layout inflater to create the view from XML.
* @param container The parent view for this one.
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = CategoriesControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
/**
* Called after view inflation. Used to initialize the view.
@@ -77,12 +92,6 @@ class BiometricTimesController :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
adapter = BiometricTimesAdapter(this@BiometricTimesController)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.setHasFixedSize(true)
@@ -5,6 +5,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.LinearLayoutManager
@@ -73,7 +74,21 @@ class SortTagController :
setHasOptionsMenu(true)
}
override fun createBinding(inflater: LayoutInflater) = CategoriesControllerBinding.inflate(inflater)
/**
* Returns the view of this controller.
*
* @param inflater The layout inflater to create the view from XML.
* @param container The parent view for this one.
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = CategoriesControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
/**
* Called after view inflation. Used to initialize the view.
@@ -83,12 +98,6 @@ class SortTagController :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
adapter = SortTagAdapter(this@SortTagController)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.setHasFixedSize(true)
@@ -4,6 +4,7 @@ import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.LinearLayoutManager
@@ -64,7 +65,21 @@ class RepoController :
return resources?.getString(R.string.action_edit_repos)
}
override fun createBinding(inflater: LayoutInflater) = CategoriesControllerBinding.inflate(inflater)
/**
* Returns the view of this controller.
*
* @param inflater The layout inflater to create the view from XML.
* @param container The parent view for this one.
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = CategoriesControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
/**
* Called after view inflation. Used to initialize the view.
@@ -74,12 +89,6 @@ class RepoController :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
adapter = RepoAdapter(this@RepoController)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.setHasFixedSize(true)
@@ -4,6 +4,7 @@ import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.LinearLayoutManager
@@ -65,7 +66,21 @@ class SourceCategoryController :
return resources?.getString(R.string.action_edit_categories)
}
override fun createBinding(inflater: LayoutInflater) = CategoriesControllerBinding.inflate(inflater)
/**
* Returns the view of this controller.
*
* @param inflater The layout inflater to create the view from XML.
* @param container The parent view for this one.
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = CategoriesControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
/**
* Called after view inflation. Used to initialize the view.
@@ -75,12 +90,6 @@ class SourceCategoryController :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
adapter = SourceCategoryAdapter(this@SourceCategoryController)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.setHasFixedSize(true)
@@ -5,6 +5,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -54,7 +55,15 @@ class DownloadController :
setHasOptionsMenu(true)
}
override fun createBinding(inflater: LayoutInflater) = DownloadControllerBinding.inflate(inflater)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = DownloadControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
override fun createPresenter(): DownloadPresenter {
return DownloadPresenter()
@@ -67,12 +76,6 @@ class DownloadController :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
// Check if download queue is empty and update information accordingly.
setInformationView()
@@ -81,14 +81,15 @@ class DownloadHolder(private val view: View, val adapter: DownloadAdapter) :
private fun showPopupMenu(view: View) {
view.popupMenu(
menuRes = R.menu.download_single,
initMenu = {
R.menu.download_single,
{
findItem(R.id.move_to_top).isVisible = bindingAdapterPosition != 0
findItem(R.id.move_to_bottom).isVisible =
bindingAdapterPosition != adapter.itemCount - 1
},
onMenuItemClick = {
{
adapter.downloadItemListener.onMenuItemClick(bindingAdapterPosition, this)
true
}
)
}
@@ -7,6 +7,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
@@ -19,7 +20,6 @@ import com.google.android.material.tabs.TabLayout
import com.jakewharton.rxrelay.BehaviorRelay
import com.jakewharton.rxrelay.PublishRelay
import com.tfcporciuncula.flow.Preference
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.SelectableAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category
@@ -34,6 +34,7 @@ import eu.kanade.tachiyomi.ui.base.controller.SearchableNucleusController
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaController
@@ -42,7 +43,6 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.toast
import exh.favorites.FavoritesIntroDialog
import exh.favorites.FavoritesSyncStatus
import exh.source.MERGED_SOURCE_ID
import exh.source.PERV_EDEN_EN_SOURCE_ID
import exh.source.PERV_EDEN_IT_SOURCE_ID
import exh.source.isEhBasedManga
@@ -193,17 +193,14 @@ class LibraryController(
return LibraryPresenter()
}
override fun createBinding(inflater: LayoutInflater) = LibraryControllerBinding.inflate(inflater)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = LibraryControllerBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.actionToolbar.applyInsetter {
type(navigationBars = true) {
margin(bottom = true)
}
}
adapter = LibraryAdapter(this)
binding.libraryPager.adapter = adapter
binding.libraryPager.pageSelections()
@@ -476,6 +473,9 @@ class LibraryController(
}
}
// SY -->
R.id.action_source_migration -> {
router.pushController(MigrationSourcesController().withFadeTransaction())
}
R.id.action_sync_favorites -> {
if (preferences.exhShowSyncIntro().get()) {
activity?.let { FavoritesIntroDialog().show(it) }
@@ -542,13 +542,9 @@ class LibraryController(
// SY -->
R.id.action_migrate -> {
val skipPre = preferences.skipPreMigration().get()
val selectedMangaIds = selectedMangas.filterNot { it.source == MERGED_SOURCE_ID }.mapNotNull { it.id }
val selectedMangaIds = selectedMangas.mapNotNull { it.id }
destroyActionModeIfNeeded()
if (selectedMangaIds.isNotEmpty()) {
PreMigrationController.navigateToMigration(skipPre, router, selectedMangaIds)
} else {
activity?.toast(R.string.no_valid_manga)
}
PreMigrationController.navigateToMigration(skipPre, router, selectedMangaIds)
}
R.id.action_clean -> cleanTitles()
R.id.action_push_to_mdlist -> pushToMdList()
@@ -14,7 +14,6 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.marginTop
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceDialogController
@@ -49,8 +48,6 @@ import eu.kanade.tachiyomi.ui.recent.history.HistoryController
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.InternalResourceHelper
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.toast
import exh.EXHMigrations
import exh.eh.EHentaiUpdateWorker
@@ -126,27 +123,23 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
padding(left = true, top = true, right = true)
}
}
binding.rootFab.applyInsetter {
type(navigationBars = true) {
margin()
}
}
binding.bottomNav.applyInsetter {
type(navigationBars = true) {
padding()
}
}
binding.rootFab.applyInsetter {
type(navigationBars = true) {
margin()
}
}
// Make sure navigation bar is on bottom before we modify it
// Make sure navigation bar is on bottom when making it transparent
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
window.navigationBarColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
!InternalResourceHelper.getBoolean(this, "config_navBarNeedsScrim", true)
) {
Color.TRANSPARENT
} else {
// Set navbar scrim 70% of navigationBarColor
getResourceColor(android.R.attr.navigationBarColor, 0.7F)
// Keep scrim on light theme if windowLightNavigationBar is not available
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 || isDarkMode) {
window.navigationBarColor = Color.TRANSPARENT
}
}
insets
@@ -155,15 +148,6 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
tabAnimator = ViewHeightAnimator(binding.tabs, 0L)
bottomNavAnimator = ViewHeightAnimator(binding.bottomNav)
// If bottom nav is hidden, make it visible again when the app bar is expanded
binding.appbar.addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
if (verticalOffset == 0) {
showBottomNav(true)
}
}
)
// Set behavior of bottom nav
preferences.hideBottomBar()
.asImmediateFlow { setBottomNavBehaviorOnScroll() }
@@ -187,9 +171,14 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
val controller = router.getControllerWithTag(id.toString()) as? LibraryController
controller?.showSettingsSheet()
}
// SY -->
R.id.nav_updates -> {
router.pushController(DownloadController().withFadeTransaction())
if (router.backstack.lastOrNull()?.controller() !is DownloadController) {
val controller = router.getControllerWithTag(id.toString()) as? UpdatesController
controller?.router?.pushController(DownloadController().withFadeTransaction())
}
}
// SY <--
}
}
true
@@ -511,7 +500,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
fun fixViewToBottom(view: View) {
val listener = AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
val maxAbsOffset = appBarLayout.measuredHeight - binding.tabs.measuredHeight
view.translationY = -maxAbsOffset - verticalOffset.toFloat() + appBarLayout.marginTop
view.translationY = -maxAbsOffset - verticalOffset.toFloat()
}
binding.appbar.addOnOffsetChangedListener(listener)
fixedViewsToBottom[view] = listener
@@ -218,8 +218,8 @@ class EditMangaDialog : DialogController {
private fun ChipGroup.setChips(items: List<String>) {
removeAllViews()
items.asSequence().map { item ->
Chip(context).apply {
items.forEach { item ->
val chip = Chip(context).apply {
text = item
isCloseIconVisible = true
@@ -228,16 +228,15 @@ class EditMangaDialog : DialogController {
removeView(this)
}
}
}.forEach {
addView(it)
addView(chip)
}
val addTagChip = Chip(context).apply {
setText(R.string.add_tag)
chipIcon = ContextCompat.getDrawable(context, R.drawable.ic_add_24dp)?.apply {
setTint(context.getResourceColor(R.attr.colorAccent))
}
chipIcon = ContextCompat.getDrawable(context, R.drawable.ic_add_24dp)
chipIcon?.setTint(context.getResourceColor(R.attr.colorAccent))
textStartPadding = 0F
clicks().onEach {
@@ -17,6 +17,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
@@ -275,21 +276,18 @@ class MangaController :
)
}
override fun createBinding(inflater: LayoutInflater) = MangaControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = MangaControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
binding.actionToolbar.applyInsetter {
type(navigationBars = true) {
margin(bottom = true)
}
}
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
if (manga == null || source == null) return
val adapters: MutableList<RecyclerView.Adapter<out RecyclerView.ViewHolder>?> = mutableListOf()
@@ -481,7 +479,7 @@ class MangaController :
// Hide options for non-library manga
menu.findItem(R.id.action_edit_categories).isVisible = presenter.manga.favorite && presenter.getCategories().isNotEmpty()
menu.findItem(R.id.action_edit_cover).isVisible = /* SY --> */ false /* presenter.manga.favorite SY <-- */
menu.findItem(R.id.action_migrate).isVisible = presenter.manga.favorite /* SY --> */ && presenter.manga.source != MERGED_SOURCE_ID /* SY <-- */
menu.findItem(R.id.action_migrate).isVisible = presenter.manga.favorite
// SY -->
menu.findItem(R.id.action_edit).isVisible = presenter.manga.favorite || isLocalSource
@@ -1147,7 +1145,8 @@ class MangaController :
fun onChapterDownloadUpdate(download: Download) {
chaptersAdapter?.currentItems?.find { it.id == download.chapter.id }?.let {
chaptersAdapter?.updateItem(it, it.status)
chaptersAdapter?.updateItem(it)
chaptersAdapter?.notifyDataSetChanged()
}
}
@@ -1271,6 +1270,7 @@ class MangaController :
binding.actionToolbar.findItem(R.id.action_mark_as_unread)?.isVisible = chapters.all { it.chapter.read }
// Hide FAB to avoid interfering with the bottom action toolbar
// actionFab?.hide()
actionFab?.isVisible = false
}
return false
@@ -1302,6 +1302,10 @@ class MangaController :
chaptersAdapter?.clearSelection()
selectedChapters.clear()
actionMode = null
// TODO: there seems to be a bug in MaterialComponents where the [ExtendedFloatingActionButton]
// fails to show up properly
// actionFab?.show()
actionFab?.isVisible = true
}
@@ -1423,26 +1427,14 @@ class MangaController :
// OVERFLOW MENU DIALOGS
private fun getUnreadChaptersSorted(): List<ChapterItem> {
val chapters = presenter.chapters
.sortedWith(presenter.getChapterSort())
.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }
.distinctBy { it.name }
// SY -->
.let {
if (presenter.source.isEhBasedSource()) {
it.reversed()
} else {
it
}
}
// SY <--
return if (presenter.sortDescending()) {
chapters.reversed()
} else {
chapters
}
}
private fun getUnreadChaptersSorted() = /* SY --> */ if (presenter.source.isEhBasedSource()) presenter.chapters
.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }
.distinctBy { it.name }
.sortedBy { it.source_order }
else /* SY <-- */ presenter.chapters
.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }
.distinctBy { it.name }
.sortedByDescending { it.source_order }
private fun downloadChapters(choice: Int) {
val chaptersToDownload = when (choice) {
@@ -846,11 +846,7 @@ class MangaPresenter(
}
// SY <--
return observable.toSortedList(getChapterSort())
}
fun getChapterSort(): (Chapter, Chapter) -> Int {
return when (manga.sorting) {
val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
Manga.SORTING_SOURCE -> when (sortDescending()) {
true -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) }
false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
@@ -865,6 +861,8 @@ class MangaPresenter(
}
else -> throw NotImplementedError("Unimplemented sorting method")
}
return observable.toSortedList(sortFunction)
}
/**
@@ -891,19 +889,11 @@ class MangaPresenter(
* Returns the next unread chapter or null if everything is read.
*/
fun getNextUnreadChapter(): ChapterItem? {
val chapters = chapters.sortedWith(getChapterSort())
return if (source.isEhBasedSource()) {
if (sortDescending()) {
chapters.firstOrNull()?.takeUnless { it.read }
} else {
chapters.lastOrNull()?.takeUnless { it.read }
}
val chapter = chapters.sortedBy { it.source_order }.getOrNull(0)
if (chapter?.read == false) chapter else null
} else {
if (sortDescending()) {
return chapters.findLast { !it.read }
} else {
chapters.find { !it.read }
}
chapters.sortedByDescending { it.source_order }.find { !it.read }
}
}
@@ -6,38 +6,42 @@ import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.core.view.isVisible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.databinding.ChapterDownloadViewBinding
import eu.kanade.tachiyomi.util.view.setVectorCompat
class ChapterDownloadView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs) {
private val binding: ChapterDownloadViewBinding =
ChapterDownloadViewBinding.inflate(LayoutInflater.from(context), this, false)
private val binding: ChapterDownloadViewBinding
private var state = Download.State.NOT_DOWNLOADED
private var progress = 0
private var downloadIconAnimator: ObjectAnimator? = null
private var isAnimating = false
init {
binding = ChapterDownloadViewBinding.inflate(LayoutInflater.from(context), this, false)
addView(binding.root)
}
fun setState(state: Download.State, progress: Int = 0) {
val isDirty = this.state.value != state.value || this.progress != progress
this.state = state
this.progress = progress
if (isDirty) {
updateLayout(state, progress)
updateLayout()
}
}
private fun updateLayout(state: Download.State, progress: Int) {
binding.downloadIcon.isVisible = state == Download.State.NOT_DOWNLOADED ||
state == Download.State.DOWNLOADING || state == Download.State.QUEUE
if (state == Download.State.DOWNLOADING || state == Download.State.QUEUE) {
if (downloadIconAnimator == null) {
private fun updateLayout() {
binding.downloadIconBorder.isVisible = state == Download.State.NOT_DOWNLOADED
binding.downloadIcon.isVisible = state == Download.State.NOT_DOWNLOADED || state == Download.State.DOWNLOADING
if (state == Download.State.DOWNLOADING) {
if (!isAnimating) {
downloadIconAnimator =
ObjectAnimator.ofFloat(binding.downloadIcon, "alpha", 1f, 0f).apply {
duration = 1000
@@ -45,36 +49,22 @@ class ChapterDownloadView @JvmOverloads constructor(context: Context, attrs: Att
repeatMode = ObjectAnimator.REVERSE
}
downloadIconAnimator?.start()
isAnimating = true
}
downloadIconAnimator?.currentPlayTime = System.currentTimeMillis() % 2000
} else if (downloadIconAnimator != null) {
} else {
downloadIconAnimator?.cancel()
downloadIconAnimator = null
binding.downloadIcon.alpha = 1f
isAnimating = false
}
binding.downloadQueued.isVisible = state == Download.State.QUEUE
binding.downloadProgress.isVisible = state == Download.State.DOWNLOADING ||
state == Download.State.NOT_DOWNLOADED || state == Download.State.QUEUE
if (state == Download.State.DOWNLOADING) {
binding.downloadProgress.setProgressCompat(progress, true)
} else {
binding.downloadProgress.setProgressCompat(100, true)
}
(state == Download.State.QUEUE && progress > 0)
binding.downloadProgress.progress = progress
binding.downloadStatusIcon.apply {
if (state == Download.State.DOWNLOADED || state == Download.State.ERROR) {
isVisible = true
if (state == Download.State.DOWNLOADED) {
setVectorCompat(R.drawable.ic_check_circle_24dp, android.R.attr.textColorPrimary)
} else {
setVectorCompat(R.drawable.ic_error_outline_24dp, R.attr.colorError)
}
} else {
isVisible = false
}
}
binding.downloadedIcon.isVisible = state == Download.State.DOWNLOADED
this.state = state
this.progress = progress
binding.errorIcon.isVisible = state == Download.State.ERROR
}
}
@@ -1,9 +1,9 @@
package eu.kanade.tachiyomi.ui.manga.chapter
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.view.View
import androidx.core.text.buildSpannedString
import androidx.core.text.color
import androidx.core.view.isVisible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
@@ -64,10 +64,8 @@ class ChapterHolder(
} else /* SY <-- */ descriptions.add(adapter.dateFormat.format(Date(chapter.date_upload)))
}
if ((!chapter.read || (adapter.preserveReadingPosition && manga.isEhBasedManga())) && chapter.last_page_read > 0) {
val lastPageRead = buildSpannedString {
color(adapter.readColor) {
append(itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1))
}
val lastPageRead = SpannableString(itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1)).apply {
setSpan(ForegroundColorSpan(adapter.readColor), 0, length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE)
}
descriptions.add(lastPageRead)
}
@@ -61,12 +61,16 @@ class ChaptersSettingsSheet(
private fun showPopupMenu(view: View) {
view.popupMenu(
menuRes = R.menu.default_chapter_filter,
onMenuItemClick = {
when (itemId) {
R.menu.default_chapter_filter,
{
},
{
when (this.itemId) {
R.id.set_as_default -> {
SetChapterSettingsDialog(presenter.manga).showDialog(router)
true
}
else -> true
}
}
)
@@ -29,6 +29,7 @@ open class BaseChapterHolder(
},
onMenuItemClick = {
adapter.clickListener.deleteChapter(position)
true
}
)
}
@@ -85,13 +85,7 @@ class EditMergedSettingsHeaderAdapter(private val controller: EditMergedSettings
val mergedMangas = controller.mergedMangas
val mangaInfoAdapter: ArrayAdapter<String> = ArrayAdapter(
itemView.context,
android.R.layout.simple_spinner_item,
mergedMangas.map {
sourceManager.getOrStub(it.second.mangaSourceId).toString() + " " + it.first?.title
}
)
val mangaInfoAdapter: ArrayAdapter<String> = ArrayAdapter(itemView.context, android.R.layout.simple_spinner_item, mergedMangas.map { sourceManager.getOrStub(it.second.mangaSourceId).toString() + " " + it.first?.title })
mangaInfoAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.mangaInfoSpinner.adapter = mangaInfoAdapter
@@ -108,16 +102,11 @@ class EditMergedSettingsHeaderAdapter(private val controller: EditMergedSettings
position: Int,
id: Long
) {
val mergedInfoManga = controller.mergedMangas
.find { mergedManga ->
mergedManga.second.id == mergedMangas.getOrNull(position)?.second?.id
}
if (mergedInfoManga != null) {
controller.mergedMangas.find { mergedManga -> mergedManga.second.id == mergedMangas.getOrNull(position)?.second?.id }?.second?.let { newInfoManga ->
controller.mergedMangas.onEach {
it.second.isInfoManga = false
}
mergedInfoManga.second.isInfoManga = true
newInfoManga.isInfoManga = true
}
}
@@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
import eu.kanade.tachiyomi.ui.main.WhatsNewDialogController
import eu.kanade.tachiyomi.ui.setting.SettingsController
import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.lang.launchNow
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
import eu.kanade.tachiyomi.util.preference.onClick
@@ -88,14 +87,6 @@ class AboutController : SettingsController() {
onClick { openInBrowser(it) }
}
}
preference {
key = "pref_about_facebook"
title = "Facebook"
"https://facebook.com/tachiyomiorg".also {
summary = it
onClick { openInBrowser(it) }
}
}
preference {
key = "pref_about_twitter"
title = "Twitter"
@@ -126,7 +117,7 @@ class AboutController : SettingsController() {
preference {
key = "pref_about_label_original_tachiyomi_github"
title = "Original Tachiyomi GitHub "
"https://github.com/tachiyomiorg".also {
"https://github.com/tachiyomiorg/tachiyomi".also {
summary = it
onClick { openInBrowser(it) }
}
@@ -149,7 +140,6 @@ class AboutController : SettingsController() {
.withAboutIconShown(false)
.withAboutVersionShown(false)
.withLicenseShown(true)
.withEdgeToEdge(true)
.start(activity!!)
}
}
@@ -162,11 +152,6 @@ class AboutController : SettingsController() {
private fun checkVersion() {
if (activity == null) return
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
activity?.toast(R.string.update_check_eol)
return
}
activity?.toast(R.string.update_check_look_for_updates)
launchNow {
@@ -218,10 +203,20 @@ class AboutController : SettingsController() {
}
private fun copyDebugInfo() {
activity?.let {
val deviceInfo = CrashLogUtil(it).getDebugInfo()
activity?.copyToClipboard("Debug information", deviceInfo)
}
val deviceInfo =
"""
App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE})
Preview build: $syDebugVersion
Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})
Android build ID: ${Build.DISPLAY}
Device brand: ${Build.BRAND}
Device manufacturer: ${Build.MANUFACTURER}
Device name: ${Build.DEVICE}
Device model: ${Build.MODEL}
Device product name: ${Build.PRODUCT}
""".trimIndent()
activity?.copyToClipboard("Debug information", deviceInfo)
}
private fun getFormattedBuildTime(): String {
@@ -1,11 +1,7 @@
package eu.kanade.tachiyomi.ui.more
import android.content.Context
import android.os.Bundle
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.R
@@ -31,10 +27,7 @@ import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.openInBrowser
import exh.ui.batchadd.BatchAddController
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.injectLazy
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
@@ -47,9 +40,6 @@ class MoreController :
private var isDownloading: Boolean = false
private var downloadQueueSize: Int = 0
private var untilDestroySubscriptions = CompositeSubscription()
private set
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = R.string.label_more
@@ -138,19 +128,6 @@ class MoreController :
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View {
if (untilDestroySubscriptions.isUnsubscribed) {
untilDestroySubscriptions = CompositeSubscription()
}
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onDestroyView(view: View) {
super.onDestroyView(view)
untilDestroySubscriptions.unsubscribe()
}
private fun initDownloadQueueSummary(preference: Preference) {
// Handle running/paused status change
DownloadService.runningRelay
@@ -177,10 +154,6 @@ class MoreController :
}
}
private fun <T> Observable<T>.subscribeUntilDestroy(onNext: (T) -> Unit): Subscription {
return subscribe(onNext).also { untilDestroySubscriptions.add(it) }
}
private class MoreHeaderPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
Preference(context, attrs) {
@@ -11,6 +11,7 @@ import android.graphics.Bitmap
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.Gravity
import android.view.KeyEvent
import android.view.Menu
import android.view.MotionEvent
@@ -67,7 +68,6 @@ import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.defaultBar
import eu.kanade.tachiyomi.util.view.hideBar
import eu.kanade.tachiyomi.util.view.isDefaultBar
import eu.kanade.tachiyomi.util.view.popupMenu
import eu.kanade.tachiyomi.util.view.setTooltip
import eu.kanade.tachiyomi.util.view.showBar
import eu.kanade.tachiyomi.widget.SimpleAnimationListener
@@ -458,18 +458,13 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
setTooltip(R.string.viewer)
setOnClickListener {
popupMenu(
items = ReadingModeType.values().map { it.prefValue to it.stringRes },
selectedItemId = presenter.getMangaViewer(resolveDefault = false),
) {
val newReadingMode = ReadingModeType.fromPreference(itemId)
val newReadingMode =
ReadingModeType.getNextReadingMode(presenter.getMangaViewer(resolveDefault = false))
presenter.setMangaViewer(newReadingMode.prefValue)
presenter.setMangaViewer(newReadingMode.prefValue)
menuToggleToast?.cancel()
if (!preferences.showReadingMode()) {
menuToggleToast = toast(newReadingMode.stringRes)
}
menuToggleToast?.cancel()
if (!preferences.showReadingMode()) {
menuToggleToast = toast(newReadingMode.stringRes)
}
}
}
@@ -479,18 +474,14 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
setTooltip(R.string.pref_rotation_type)
setOnClickListener {
popupMenu(
items = OrientationType.values().map { it.prefValue to it.stringRes },
selectedItemId = preferences.rotation().get(),
) {
val newOrientation = OrientationType.fromPreference(itemId)
val newOrientation =
OrientationType.getNextOrientation(preferences.rotation().get(), resources)
preferences.rotation().set(newOrientation.prefValue)
setOrientation(newOrientation.flag)
preferences.rotation().set(newOrientation.prefValue)
setOrientation(newOrientation.flag)
menuToggleToast?.cancel()
menuToggleToast = toast(newOrientation.stringRes)
}
menuToggleToast?.cancel()
menuToggleToast = toast(newOrientation.stringRes)
}
}
preferences.rotation().asImmediateFlow { updateRotationShortcut(it) }
@@ -532,11 +523,6 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
setOnClickListener {
ReaderSettingsSheet(this@ReaderActivity).show()
}
setOnLongClickListener {
ReaderSettingsSheet(this@ReaderActivity, showColorFilterSettings = true).show()
true
}
}
// --> EH
@@ -732,7 +718,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
// EXH <--
/*private fun updateRotationShortcut(preference: Int) {
val orientation = OrientationType.fromPreference(preference)
val orientation = OrientationType.fromPreference(preference, resources)
binding.actionRotation.setImageResource(orientation.iconRes)
}*/
@@ -899,10 +885,12 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
binding.viewerContainer.addView(newViewer.getView())
// SY -->
val defaultReaderType = manga.defaultReaderType(manga.mangaType(sourceName = sourceManager.get(manga.source)?.name))
val defaultReaderType = manga.defaultReaderType(manga.mangaType(sourceName = sourceManager.getOrStub(manga.source).name))
if (preferences.useAutoWebtoon().get() && manga.viewer == 0 && defaultReaderType != null && defaultReaderType == ReadingModeType.WEBTOON.prefValue) {
readingModeToast?.cancel()
readingModeToast = toast(resources.getString(R.string.eh_auto_webtoon_snack))
readingModeToast = this.toast(resources.getString(R.string.eh_auto_webtoon_snack)) {
it.setGravity(Gravity.CENTER_VERTICAL or Gravity.CENTER_HORIZONTAL, 0, 0)
}
} else if (preferences.showReadingMode()) {
// SY <--
showReadingModeToast(presenter.getMangaViewer())
@@ -958,12 +946,10 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
}
private fun showReadingModeToast(mode: Int) {
try {
val strings = resources.getStringArray(R.array.viewers_selector)
readingModeToast?.cancel()
readingModeToast = toast(strings[mode])
} catch (e: ArrayIndexOutOfBoundsException) {
Timber.e("Unknown reading mode: $mode")
val strings = resources.getStringArray(R.array.viewers_selector)
readingModeToast?.cancel()
readingModeToast = toast(strings[mode]) {
it.setGravity(Gravity.CENTER_VERTICAL or Gravity.CENTER_HORIZONTAL, 0, 0)
}
}
@@ -1192,7 +1178,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
* Forces the user preferred [orientation] on the activity.
*/
private fun setOrientation(orientation: Int) {
val newOrientation = OrientationType.fromPreference(orientation)
val newOrientation = OrientationType.fromPreference(orientation, resources)
if (newOrientation.flag != requestedOrientation) {
requestedOrientation = newOrientation.flag
}
@@ -598,7 +598,7 @@ class ReaderPresenter(
val manga = manga ?: return preferences.defaultViewer()
// SY -->
return if (resolveDefault && manga.viewer == 0 && preferences.useAutoWebtoon().get()) {
manga.defaultReaderType(manga.mangaType(sourceName = sourceManager.get(manga.source)?.name)) ?: if (manga.viewer == 0) preferences.defaultViewer() else manga.viewer
manga.defaultReaderType(manga.mangaType(sourceName = sourceManager.getOrStub(manga.source).name)) ?: if (manga.viewer == 0) preferences.defaultViewer() else manga.viewer
} else if (resolveDefault && manga.viewer == 0) {
preferences.defaultViewer()
} else {
@@ -99,7 +99,8 @@ class HttpPageLoader(
* the local cache, otherwise fallbacks to network.
*/
override fun getPages(): Observable<List<ReaderPage>> {
return Observable.fromCallable { chapterCache.getPageListFromCache(chapter.chapter) }
return chapterCache
.getPageListFromCache(chapter.chapter)
.onErrorResumeNext { source.fetchPageList(chapter.chapter) }
.map { pages ->
// SY -->
@@ -1,20 +1,43 @@
package eu.kanade.tachiyomi.ui.reader.setting
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.content.res.Resources
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.lang.next
enum class OrientationType(val prefValue: Int, val flag: Int, @StringRes val stringRes: Int, @DrawableRes val iconRes: Int) {
FREE(1, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, R.string.rotation_free, R.drawable.ic_screen_rotation_24dp),
PORTRAIT(2, ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT, R.string.rotation_portrait, R.drawable.ic_stay_current_portrait_24dp),
LANDSCAPE(3, ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, R.string.rotation_landscape, R.drawable.ic_stay_current_landscape_24dp),
LOCKED_PORTRAIT(4, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, R.string.rotation_force_portrait, R.drawable.ic_screen_lock_portrait_24dp),
LOCKED_LANDSCAPE(5, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, R.string.rotation_force_landscape, R.drawable.ic_screen_lock_landscape_24dp),
;
LOCKED_PORTRAIT(2, ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT, R.string.rotation_lock, R.drawable.ic_screen_lock_rotation_24dp),
LOCKED_LANDSCAPE(2, ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, R.string.rotation_lock, R.drawable.ic_screen_lock_rotation_24dp),
PORTRAIT(3, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, R.string.rotation_force_portrait, R.drawable.ic_screen_lock_portrait_24dp),
LANDSCAPE(4, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, R.string.rotation_force_landscape, R.drawable.ic_screen_lock_landscape_24dp);
companion object {
fun fromPreference(preference: Int): OrientationType =
values().find { it.prefValue == preference } ?: FREE
fun fromPreference(preference: Int, resources: Resources): OrientationType = when (preference) {
2 -> {
val currentOrientation = resources.configuration.orientation
if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) {
LOCKED_PORTRAIT
} else {
LOCKED_LANDSCAPE
}
}
3 -> PORTRAIT
4 -> LANDSCAPE
else -> FREE
}
fun getNextOrientation(preference: Int, resources: Resources): OrientationType {
val current = if (preference == 2) {
// Avoid issue due to 2 types having the same prefValue
LOCKED_LANDSCAPE
} else {
fromPreference(preference, resources)
}
return current.next()
}
}
}
@@ -8,10 +8,7 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.widget.SimpleTabSelectedListener
import eu.kanade.tachiyomi.widget.sheet.TabbedBottomSheetDialog
class ReaderSettingsSheet(
private val activity: ReaderActivity,
showColorFilterSettings: Boolean = false,
) : TabbedBottomSheetDialog(activity) {
class ReaderSettingsSheet(private val activity: ReaderActivity) : TabbedBottomSheetDialog(activity) {
private val readingModeSettings = ReaderReadingModeSettings(activity)
private val generalSettings = ReaderGeneralSettings(activity)
@@ -22,7 +19,7 @@ class ReaderSettingsSheet(
init {
val sheetBehavior = BottomSheetBehavior.from(binding.root.parent as ViewGroup)
sheetBehavior.isFitToContents = false
sheetBehavior.halfExpandedRatio = 0.25f
sheetBehavior.halfExpandedRatio = 0.5f
val filterTabIndex = getTabViews().indexOf(colorFilterSettings)
binding.tabs.addOnTabSelectedListener(object : SimpleTabSelectedListener() {
@@ -36,12 +33,13 @@ class ReaderSettingsSheet(
if (activity.menuVisible != !isFilterTab) {
activity.setMenuVisibility(!isFilterTab)
}
// Partially collapse the sheet for better preview
if (isFilterTab) {
sheetBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
}
}
})
if (showColorFilterSettings) {
binding.tabs.getTabAt(filterTabIndex)?.select()
}
}
override fun getTabViews() = listOf(
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader.setting
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.lang.next
enum class ReadingModeType(val prefValue: Int, @StringRes val stringRes: Int, @DrawableRes val iconRes: Int) {
DEFAULT(0, R.string.default_viewer, R.drawable.ic_reader_default_24dp),
@@ -16,6 +17,11 @@ enum class ReadingModeType(val prefValue: Int, @StringRes val stringRes: Int, @D
companion object {
fun fromPreference(preference: Int): ReadingModeType = values().find { it.prefValue == preference } ?: DEFAULT
fun getNextReadingMode(preference: Int): ReadingModeType {
val current = fromPreference(preference)
return current.next()
}
fun isPagerType(preference: Int): Boolean {
val mode = fromPreference(preference)
return mode == LEFT_TO_RIGHT || mode == RIGHT_TO_LEFT || mode == VERTICAL
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.widget
package eu.kanade.tachiyomi.ui.reader.setting
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
@@ -8,21 +7,15 @@ import android.view.LayoutInflater
import android.view.MenuItem
import android.widget.FrameLayout
import androidx.annotation.ArrayRes
import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat
import androidx.core.view.forEach
import androidx.core.view.get
import com.tfcporciuncula.flow.Preference
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.SpinnerPreferenceBinding
import eu.kanade.tachiyomi.util.system.getResourceColor
class MaterialSpinnerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
class SpinnerPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs) {
private var entries = emptyList<String>()
private var selectedPosition = 0
private var popup: PopupMenu? = null
var onItemSelectedListener: ((Int) -> Unit)? = null
@@ -37,26 +30,17 @@ class MaterialSpinnerView @JvmOverloads constructor(context: Context, attrs: Att
}
}
private val emptyIcon by lazy {
ContextCompat.getDrawable(context, R.drawable.ic_blank_24dp)
}
private val checkmarkIcon by lazy {
ContextCompat.getDrawable(context, R.drawable.ic_check_24dp)?.mutate()?.apply {
setTint(context.getResourceColor(android.R.attr.textColorPrimary))
}
}
private val binding = SpinnerPreferenceBinding.inflate(LayoutInflater.from(context), this, false)
init {
addView(binding.root)
val attr = context.obtainStyledAttributes(attrs, R.styleable.MaterialSpinnerView)
val attr = context.obtainStyledAttributes(attrs, R.styleable.SpinnerPreference)
val title = attr.getString(R.styleable.MaterialSpinnerView_title).orEmpty()
val title = attr.getString(R.styleable.SpinnerPreference_title).orEmpty()
binding.title.text = title
val entries = (attr.getTextArray(R.styleable.MaterialSpinnerView_android_entries) ?: emptyArray()).map { it.toString() }
val entries = (attr.getTextArray(R.styleable.SpinnerPreference_android_entries) ?: emptyArray()).map { it.toString() }
this.entries = entries
binding.details.text = entries.firstOrNull().orEmpty()
@@ -64,14 +48,6 @@ class MaterialSpinnerView @JvmOverloads constructor(context: Context, attrs: Att
}
fun setSelection(selection: Int) {
popup?.menu?.get(selectedPosition)?.let {
it.icon = emptyIcon
it.title = entries[selectedPosition]
}
selectedPosition = selection
popup?.menu?.get(selectedPosition)?.let {
it.icon = checkmarkIcon
}
binding.details.text = entries.getOrNull(selection).orEmpty()
}
@@ -142,19 +118,11 @@ class MaterialSpinnerView @JvmOverloads constructor(context: Context, attrs: Att
return pos
}
@SuppressLint("RestrictedApi")
fun createPopupMenu(onItemClick: (Int) -> Unit): PopupMenu {
val popup = PopupMenu(context, this, Gravity.END, R.attr.actionOverflowMenuStyle, 0)
entries.forEachIndexed { index, entry ->
popup.menu.add(0, index, 0, entry)
}
(popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true)
popup.menu.forEach {
it.icon = emptyIcon
}
popup.menu.getItem(selectedPosition)?.let {
it.icon = checkmarkIcon
}
popup.setOnMenuItemClickListener { menuItem ->
val pos = menuClicked(menuItem)
onItemClick(pos)
@@ -250,13 +250,16 @@ class PagerPageHolder(
readImageHeaderSubscription = Observable
.fromCallable {
val stream = streamFn().buffered(16)
openStream = process(stream)
openStream = stream
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (viewer.config.dualPageSplit) {
openStream = processDualPageSplit(openStream!!)
}
if (!isAnimated) {
// SY -->
if (readerTheme >= 3) {
@@ -292,31 +295,21 @@ class PagerPageHolder(
.subscribe({}, {})
}
private fun process(imageStream: InputStream): InputStream {
if (!viewer.config.dualPageSplit) {
return imageStream
private fun processDualPageSplit(openStream: InputStream): InputStream {
var inputStream = openStream
val (isDoublePage, stream) = when (page) {
is InsertPage -> Pair(true, inputStream)
else -> ImageUtil.isDoublePage(inputStream)
}
inputStream = stream
if (page is InsertPage) {
return splitInHalf(imageStream)
}
if (!isDoublePage) return inputStream
val isDoublePage = ImageUtil.isDoublePage(imageStream)
if (!isDoublePage) {
return imageStream
}
onPageSplit()
return splitInHalf(imageStream)
}
private fun splitInHalf(imageStream: InputStream): InputStream {
var side = when {
viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT
viewer !is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT
(viewer is R2LPagerViewer || viewer is VerticalPagerViewer) && page is InsertPage -> ImageUtil.Side.LEFT
viewer is L2RPagerViewer && page !is InsertPage -> ImageUtil.Side.LEFT
viewer !is L2RPagerViewer && page !is InsertPage -> ImageUtil.Side.RIGHT
(viewer is R2LPagerViewer || viewer is VerticalPagerViewer) && page !is InsertPage -> ImageUtil.Side.RIGHT
else -> error("We should choose a side!")
}
@@ -327,7 +320,11 @@ class PagerPageHolder(
}
}
return ImageUtil.splitInHalf(imageStream, side)
if (page !is InsertPage) {
onPageSplit()
}
return ImageUtil.splitInHalf(inputStream, side)
}
private fun onPageSplit() {
@@ -385,10 +385,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
}
fun onPageSplit(currentPage: ReaderPage, newPage: InsertPage) {
activity.runOnUiThread {
// Need to insert on UI thread else images will go blank
adapter.onPageSplit(currentPage, newPage, this::class.java)
}
adapter.onPageSplit(currentPage, newPage, this::class.java)
}
private fun cleanupPageSplit() {
@@ -281,13 +281,22 @@ class WebtoonPageHolder(
readImageHeaderSubscription = Observable
.fromCallable {
val stream = streamFn().buffered(16)
openStream = process(stream)
openStream = stream
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (viewer.config.dualPageSplit) {
val (isDoublePage, stream) = ImageUtil.isDoublePage(openStream!!)
openStream = if (!isDoublePage) {
stream
} else {
val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT
ImageUtil.splitAndMerge(stream, upperSide)
}
}
if (!isAnimated) {
val subsamplingView = initSubsamplingImageView()
subsamplingView.isVisible = true
@@ -306,20 +315,6 @@ class WebtoonPageHolder(
addSubscription(readImageHeaderSubscription)
}
private fun process(imageStream: InputStream): InputStream {
if (!viewer.config.dualPageSplit) {
return imageStream
}
val isDoublePage = ImageUtil.isDoublePage(imageStream)
if (!isDoublePage) {
return imageStream
}
val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT
return ImageUtil.splitAndMerge(imageStream, upperSide)
}
/**
* Called when the page has an error.
*/
@@ -79,7 +79,16 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
recycler.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
onScrolled()
val position = layoutManager.findLastEndVisibleItemPosition()
val item = adapter.items.getOrNull(position)
val allowPreload = checkAllowPreload(item as? ReaderPage)
if (item != null && currentPage != item) {
currentPage = item
when (item) {
is ReaderPage -> onPageSelected(item, allowPreload)
is ChapterTransition -> onTransitionSelected(item)
}
}
if (dy < 0) {
val firstIndex = layoutManager.findFirstVisibleItemPosition()
@@ -240,27 +249,11 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
val position = adapter.items.indexOf(page)
if (position != -1) {
recycler.scrollToPosition(position)
if (layoutManager.findLastEndVisibleItemPosition() == -1) {
onScrolled(position)
}
} else {
Timber.d("Page $page not found in adapter")
}
}
fun onScrolled(pos: Int? = null) {
val position = pos ?: layoutManager.findLastEndVisibleItemPosition()
val item = adapter.items.getOrNull(position)
val allowPreload = checkAllowPreload(item as? ReaderPage)
if (item != null && currentPage != item) {
currentPage = item
when (item) {
is ReaderPage -> onPageSelected(item, allowPreload)
is ChapterTransition -> onTransitionSelected(item)
}
}
}
/**
* Scrolls up by [scrollDistance].
*/
@@ -8,13 +8,13 @@ import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
import eu.kanade.tachiyomi.databinding.RecentSectionItemBinding
import java.util.Date
class DateSectionItem(val date: Date) : AbstractHeaderItem<DateSectionItem.DateSectionItemHolder>() {
override fun getLayoutRes(): Int {
return R.layout.section_header_item
return R.layout.recent_section_item
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): DateSectionItemHolder {
@@ -39,12 +39,12 @@ class DateSectionItem(val date: Date) : AbstractHeaderItem<DateSectionItem.DateS
inner class DateSectionItemHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter, true) {
private val binding = SectionHeaderItemBinding.bind(view)
private val binding = RecentSectionItemBinding.bind(view)
private val now = Date().time
fun bind(item: DateSectionItem) {
binding.title.text = DateUtils.getRelativeTimeSpanString(item.date.time, now, DateUtils.DAY_IN_MILLIS)
binding.sectionText.text = DateUtils.getRelativeTimeSpanString(item.date.time, now, DateUtils.DAY_IN_MILLIS)
}
}
}
@@ -35,6 +35,7 @@ class HistoryAdapter(controller: HistoryController) :
init {
setDisplayHeadersAtStartUp(true)
setStickyHeaders(true)
}
interface OnResumeClickListener {
@@ -7,6 +7,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
@@ -19,6 +20,7 @@ import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.HistoryControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
@@ -34,10 +36,13 @@ import uy.kohesive.injekt.injectLazy
/**
* Fragment that shows recently read manga.
* Uses [R.layout.history_controller].
* UI related actions should be called from here.
*/
class HistoryController :
NucleusController<HistoryControllerBinding, HistoryPresenter>(),
RootController,
NoToolbarElevationController,
FlexibleAdapter.OnUpdateListener,
FlexibleAdapter.EndlessScrollListener,
HistoryAdapter.OnRemoveClickListener,
@@ -71,16 +76,18 @@ class HistoryController :
return HistoryPresenter()
}
override fun createBinding(inflater: LayoutInflater) = HistoryControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = HistoryControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
// Initialize adapter
binding.recycler.layoutManager = LinearLayoutManager(view.context)
@@ -18,6 +18,7 @@ class UpdatesAdapter(
init {
setDisplayHeadersAtStartUp(true)
setStickyHeaders(true)
}
interface OnCoverClickListener {
@@ -5,6 +5,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.LinearLayoutManager
@@ -18,6 +19,7 @@ import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.databinding.UpdatesControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
@@ -35,10 +37,13 @@ import timber.log.Timber
/**
* Fragment that shows recent chapters.
* Uses [R.layout.updates_controller].
* UI related actions should be called from here.
*/
class UpdatesController :
NucleusController<UpdatesControllerBinding, UpdatesPresenter>(),
RootController,
NoToolbarElevationController,
ActionMode.Callback,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
@@ -70,21 +75,18 @@ class UpdatesController :
return UpdatesPresenter()
}
override fun createBinding(inflater: LayoutInflater) = UpdatesControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = UpdatesControllerBinding.inflate(inflater)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
binding.actionToolbar.applyInsetter {
type(navigationBars = true) {
margin(bottom = true)
}
}
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS)
// Init RecyclerView and adapter
@@ -242,7 +244,8 @@ class UpdatesController :
adapter?.currentItems
?.filterIsInstance<UpdatesItem>()
?.find { it.chapter.id == download.chapter.id }?.let {
adapter?.updateItem(it, it.status)
adapter?.updateItem(it)
adapter?.notifyDataSetChanged()
}
}
@@ -1,19 +1,21 @@
package eu.kanade.tachiyomi.ui.security
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricPrompt
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.activity.BaseThemedActivity
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.system.BiometricUtil
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import java.util.Date
import java.util.concurrent.Executors
/**
* Blank activity with a BiometricPrompt.
*/
class BiometricUnlockActivity : BaseThemedActivity() {
class BiometricUnlockActivity : AppCompatActivity() {
private val preferences: PreferencesHelper by injectLazy()
private val executor = Executors.newSingleThreadExecutor()
override fun onCreate(savedInstanceState: Bundle?) {
@@ -25,7 +27,6 @@ class BiometricUnlockActivity : BaseThemedActivity() {
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
Timber.e(errString.toString())
finishAffinity()
}
@@ -29,8 +29,7 @@ import eu.kanade.tachiyomi.source.SourceManager.Companion.DELEGATED_SOURCES
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.preference.defaultValue
import eu.kanade.tachiyomi.util.preference.editTextPreference
import eu.kanade.tachiyomi.util.preference.intListPreference
@@ -41,7 +40,6 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory
import eu.kanade.tachiyomi.util.preference.summaryRes
import eu.kanade.tachiyomi.util.preference.switchPreference
import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.powerManager
import eu.kanade.tachiyomi.util.system.toast
import exh.debug.SettingsDebugController
@@ -49,8 +47,15 @@ import exh.log.EHLogLevel
import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
@@ -344,32 +349,31 @@ class SettingsAdvancedController : SettingsController() {
private fun cleanupDownloads(removeRead: Boolean, removeNonFavorite: Boolean) {
if (job?.isActive == true) return
activity?.toast(R.string.starting_cleanup)
job = launchIO {
job = GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) {
val mangaList = db.getMangas().executeAsBlocking()
val sourceManager: SourceManager = Injekt.get()
val downloadManager: DownloadManager = Injekt.get()
var foldersCleared = 0
Injekt.get<SourceManager>().getOnlineSources().forEach { source ->
val mangaFolders = downloadManager.getMangaFolders(source)
val sourceManga = mangaList
.asSequence()
.filter { it.source == source.id }
.map { it to DiskUtil.buildValidFilename(it.originalTitle) }
.toList()
val sources = sourceManager.getOnlineSources()
mangaFolders.forEach mangaFolder@{ mangaFolder ->
val manga = sourceManga.find { (_, folderName) -> folderName == mangaFolder.name }?.first
for (source in sources) {
val mangaFolders = downloadManager.getMangaFolders(source)
val sourceManga = mangaList.filter { it.source == source.id }
for (mangaFolder in mangaFolders) {
val manga = sourceManga.find { it.originalTitle == mangaFolder.name }
if (manga == null) {
// download is orphaned delete it
foldersCleared += 1 + (mangaFolder.listFiles().orEmpty().size)
foldersCleared += 1 + (mangaFolder.listFiles()?.size ?: 0)
mangaFolder.delete()
} else {
val chapterList = db.getChapters(manga).executeAsBlocking()
foldersCleared += downloadManager.cleanupChapters(chapterList, manga, source, removeRead, removeNonFavorite)
continue
}
val chapterList = db.getChapters(manga).executeAsBlocking()
foldersCleared += downloadManager.cleanupChapters(chapterList, manga, source, removeRead, removeNonFavorite)
}
}
withUIContext {
val activity = activity ?: return@withUIContext
launchUI {
val activity = activity ?: return@launchUI
val cleanupString =
if (foldersCleared == 0) activity.getString(R.string.no_folders_to_cleanup)
else resources!!.getQuantityString(
@@ -385,18 +389,27 @@ class SettingsAdvancedController : SettingsController() {
private fun clearChapterCache() {
if (activity == null) return
launchIO {
try {
val deletedFiles = chapterCache.clear()
withUIContext {
activity?.toast(resources?.getString(R.string.cache_deleted, deletedFiles))
findPreference(CLEAR_CACHE_KEY)?.summary =
resources?.getString(R.string.used_cache, chapterCache.readableSize)
val files = chapterCache.cacheDir.listFiles() ?: return
var deletedFiles = 0
Observable.defer { Observable.from(files) }
.doOnNext { file ->
if (chapterCache.removeFileFromCache(file.name)) {
deletedFiles++
}
} catch (e: Throwable) {
withUIContext { activity?.toast(R.string.cache_delete_error) }
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError {
activity?.toast(R.string.cache_delete_error)
}
.doOnCompleted {
activity?.toast(resources?.getString(R.string.cache_deleted, deletedFiles))
findPreference(CLEAR_CACHE_KEY)?.summary =
resources?.getString(R.string.used_cache, chapterCache.readableSize)
}
.subscribe()
}
class ClearDatabaseDialogController : DialogController() {
@@ -24,6 +24,9 @@ import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.util.system.getResourceColor
import kotlinx.coroutines.MainScope
import rx.Observable
import rx.Subscription
import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -32,14 +35,19 @@ abstract class SettingsController : PreferenceController() {
var preferenceKey: String? = null
val preferences: PreferencesHelper = Injekt.get()
val viewScope = MainScope()
private var themedContext: Context? = null
var untilDestroySubscriptions = CompositeSubscription()
private set
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View {
if (untilDestroySubscriptions.isUnsubscribed) {
untilDestroySubscriptions = CompositeSubscription()
}
val view = super.onCreateView(inflater, container, savedInstanceState)
if (this is RootController) {
listView.clipToPadding = false
listView.updatePadding(bottom = view.context.resources.getDimensionPixelSize(R.dimen.action_toolbar_list_padding))
view.updatePadding(bottom = view.context.resources.getDimensionPixelSize(R.dimen.action_toolbar_list_padding))
}
listView.applyInsetter {
@@ -69,31 +77,25 @@ abstract class SettingsController : PreferenceController() {
}
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
if (type.isEnter) {
setTitle()
}
setHasOptionsMenu(type.isEnter)
super.onChangeStarted(handler, type)
}
override fun onDestroyView(view: View) {
super.onDestroyView(view)
themedContext = null
untilDestroySubscriptions.unsubscribe()
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
val tv = TypedValue()
activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true)
themedContext = ContextThemeWrapper(activity, tv.resourceId)
val screen = preferenceManager.createPreferenceScreen(themedContext)
val screen = preferenceManager.createPreferenceScreen(getThemedContext())
preferenceScreen = screen
setupPreferenceScreen(screen)
}
abstract fun setupPreferenceScreen(screen: PreferenceScreen): PreferenceScreen
private fun getThemedContext(): Context {
val tv = TypedValue()
activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true)
return ContextThemeWrapper(activity, tv.resourceId)
}
private fun animatePreferenceHighlight(view: View) {
ValueAnimator
.ofObject(ArgbEvaluator(), Color.TRANSPARENT, view.context.getResourceColor(R.attr.rippleColor))
@@ -109,7 +111,7 @@ abstract class SettingsController : PreferenceController() {
return preferenceScreen?.title?.toString()
}
private fun setTitle() {
fun setTitle() {
var parentController = parentController
while (parentController != null) {
if (parentController is BaseController<*> && parentController.getTitle() != null) {
@@ -120,4 +122,16 @@ abstract class SettingsController : PreferenceController() {
(activity as? AppCompatActivity)?.supportActionBar?.title = getTitle()
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
if (type.isEnter) {
setTitle()
}
setHasOptionsMenu(type.isEnter)
super.onChangeStarted(handler, type)
}
fun <T> Observable<T>.subscribeUntilDestroy(onNext: (T) -> Unit): Subscription {
return subscribe(onNext).also { untilDestroySubscriptions.add(it) }
}
}
@@ -132,7 +132,6 @@ class SettingsGeneralController : SettingsController() {
entriesRes = arrayOf(
R.string.theme_dark_default,
R.string.theme_dark_blue,
R.string.theme_dark_amoledblue,
R.string.theme_dark_amoled,
R.string.theme_dark_red,
R.string.theme_dark_midnightdusk,
@@ -141,7 +140,6 @@ class SettingsGeneralController : SettingsController() {
entryValues = arrayOf(
Values.DarkThemeVariant.default.name,
Values.DarkThemeVariant.blue.name,
Values.DarkThemeVariant.amoledblue.name,
Values.DarkThemeVariant.amoled.name,
Values.DarkThemeVariant.red.name,
Values.DarkThemeVariant.midnightdusk.name,
@@ -93,12 +93,11 @@ class SettingsReaderController : SettingsController() {
titleRes = R.string.pref_rotation_type
entriesRes = arrayOf(
R.string.rotation_free,
R.string.rotation_portrait,
R.string.rotation_landscape,
R.string.rotation_lock,
R.string.rotation_force_portrait,
R.string.rotation_force_landscape,
R.string.rotation_force_landscape
)
entryValues = arrayOf("1", "2", "3", "4", "5")
entryValues = arrayOf("1", "2", "3", "4")
defaultValue = "1"
summary = "%s"
}
@@ -6,6 +6,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager
import eu.kanade.tachiyomi.R
@@ -32,7 +33,17 @@ class SettingsSearchController :
setHasOptionsMenu(true)
}
override fun createBinding(inflater: LayoutInflater) = SettingsSearchControllerBinding.inflate(inflater)
/**
* Initiate the view with [R.layout.settings_search_controller].
*
* @param inflater used to load the layout xml.
* @param container containing parent views.
* @return inflated view
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = SettingsSearchControllerBinding.inflate(inflater)
return binding.root
}
override fun getTitle(): String? {
return presenter.query
@@ -139,10 +139,8 @@ class WebViewActivity : BaseViewBindingActivity<WebviewActivityBinding>() {
}
override fun onDestroy() {
binding.webview?.destroy()
super.onDestroy()
// Binding sometimes isn't actually instantiated yet somehow
binding?.webview?.destroy()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -2,19 +2,15 @@ package eu.kanade.tachiyomi.util
import android.content.Context
import android.net.Uri
import android.os.Build
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toast
import exh.syDebugVersion
import java.io.IOException
class CrashLogUtil(private val context: Context) {
@@ -23,45 +19,31 @@ class CrashLogUtil(private val context: Context) {
}
fun dumpLogs() {
launchIO {
try {
val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt")
Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}").waitFor()
file.appendText(getDebugInfo())
try {
val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt")
Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}")
showNotification(file.getUriCompat(context))
} catch (e: Throwable) {
withUIContext { context.toast("Failed to get logs") }
}
showNotification(file.getUriCompat(context))
} catch (e: IOException) {
context.toast("Failed to get logs")
}
}
fun getDebugInfo(): String {
return """
App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE})
Preview build: $syDebugVersion
Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})
Android build ID: ${Build.DISPLAY}
Device brand: ${Build.BRAND}
Device manufacturer: ${Build.MANUFACTURER}
Device name: ${Build.DEVICE}
Device model: ${Build.MODEL}
Device product name: ${Build.PRODUCT}
""".trimIndent()
}
private fun showNotification(uri: Uri) {
context.notificationManager.cancel(Notifications.ID_CRASH_LOGS)
with(notificationBuilder) {
setContentTitle(context.getString(R.string.crash_log_saved))
// Clear old actions if they exist
clearActions()
addAction(
R.drawable.ic_folder_24dp,
context.getString(R.string.action_open_log),
NotificationReceiver.openErrorLogPendingActivity(context, uri)
)
addAction(
R.drawable.ic_share_24dp,
context.getString(R.string.action_share),
@@ -173,5 +173,3 @@ private fun shouldUpdateDbChapter(dbChapter: Chapter, sourceChapter: SChapter):
dbChapter.date_upload != sourceChapter.date_upload ||
dbChapter.chapter_number != sourceChapter.chapter_number
}
class NoChaptersException : Exception()
@@ -0,0 +1,3 @@
package eu.kanade.tachiyomi.util.chapter
class NoChaptersException : Exception()
@@ -0,0 +1,7 @@
package eu.kanade.tachiyomi.util.lang
inline fun <reified T : Enum<T>> T.next(): T {
val values = enumValues<T>()
val nextOrdinal = (ordinal + 1) % values.size
return values[nextOrdinal]
}
@@ -1,17 +1,12 @@
package eu.kanade.tachiyomi.util.system
import android.content.Context
import android.os.Build
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators
object BiometricUtil {
fun getSupportedAuthenticators(context: Context): Int {
if (isLegacySecured(context)) {
return Authenticators.BIOMETRIC_WEAK or Authenticators.DEVICE_CREDENTIAL
}
return listOf(
Authenticators.BIOMETRIC_STRONG,
Authenticators.BIOMETRIC_WEAK,
@@ -22,22 +17,10 @@ object BiometricUtil {
}
fun isSupported(context: Context): Boolean {
return isLegacySecured(context) || getSupportedAuthenticators(context) != 0
return getSupportedAuthenticators(context) != 0
}
fun isDeviceCredentialAllowed(context: Context): Boolean {
return isLegacySecured(context) || (getSupportedAuthenticators(context) and Authenticators.DEVICE_CREDENTIAL != 0)
}
/**
* Returns whether the device is secured with a PIN, pattern or password.
*/
private fun isLegacySecured(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
if (context.keyguardManager.isDeviceSecure) {
return true
}
}
return false
return getSupportedAuthenticators(context) and Authenticators.DEVICE_CREDENTIAL != 0
}
}
@@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.util.system
import android.app.ActivityManager
import android.app.KeyguardManager
import android.app.Notification
import android.app.NotificationManager
import android.content.BroadcastReceiver
@@ -34,7 +33,6 @@ import androidx.core.net.toUri
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.lang.truncateCenter
import timber.log.Timber
import java.io.File
import kotlin.math.roundToInt
@@ -70,15 +68,10 @@ fun Context.toast(text: String?, duration: Int = Toast.LENGTH_SHORT, block: (Toa
fun Context.copyToClipboard(label: String, content: String) {
if (content.isBlank()) return
try {
val clipboard = getSystemService<ClipboardManager>()!!
clipboard.setPrimaryClip(ClipData.newPlainText(label, content))
val clipboard = getSystemService<ClipboardManager>()!!
clipboard.setPrimaryClip(ClipData.newPlainText(label, content))
toast(getString(R.string.copied_to_clipboard, content.truncateCenter(50)))
} catch (e: Throwable) {
Timber.e(e)
toast(R.string.clipboard_copy_error)
}
toast(getString(R.string.copied_to_clipboard, content.truncateCenter(50)))
}
/**
@@ -160,18 +153,24 @@ val Float.dpToPxEnd: Float
val Resources.isLTR
get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
/**
* Property to get the notification manager from the context.
*/
val Context.notificationManager: NotificationManager
get() = getSystemService()!!
/**
* Property to get the connectivity manager from the context.
*/
val Context.connectivityManager: ConnectivityManager
get() = getSystemService()!!
/**
* Property to get the power manager from the context.
*/
val Context.powerManager: PowerManager
get() = getSystemService()!!
val Context.keyguardManager: KeyguardManager
get() = getSystemService()!!
/**
* Convenience method to acquire a partial wake lock.
*/
@@ -84,20 +84,15 @@ object ImageUtil {
}
/**
* Check whether the image is a double-page spread
* @return true if the width is greater than the height
* Check whether the image is a double image (width > height), return the result and original stream
*/
fun isDoublePage(imageStream: InputStream): Boolean {
imageStream.mark(imageStream.available() + 1)
fun isDoublePage(imageStream: InputStream): Pair<Boolean, InputStream> {
val imageBytes = imageStream.readBytes()
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
imageStream.reset()
return options.outWidth > options.outHeight
return Pair(options.outWidth > options.outHeight, ByteArrayInputStream(imageBytes))
}
/**
@@ -1,26 +0,0 @@
package eu.kanade.tachiyomi.util.system
import android.content.Context
import android.content.res.Resources
object InternalResourceHelper {
fun getBoolean(context: Context, resName: String, defaultValue: Boolean): Boolean {
val id = getResourceId(resName, "bool")
return if (id != 0) {
context.createPackageContext("android", 0).resources.getBoolean(id)
} else {
defaultValue
}
}
/**
* Get resource id from system resources
* @param resName resource name to get
* @param type resource type of [resName] to get
* @return 0 if not available
*/
private fun getResourceId(resName: String, type: String): Int {
return Resources.getSystem().getIdentifier(resName, type, "android")
}
}
@@ -1,21 +1,19 @@
package eu.kanade.tachiyomi.util.view
import android.widget.ImageView
import androidx.annotation.AttrRes
import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources
import eu.kanade.tachiyomi.util.system.getResourceColor
/**
* Set a vector on a [ImageView].
*
* @param drawable id of drawable resource
*/
fun ImageView.setVectorCompat(@DrawableRes drawable: Int, @AttrRes tint: Int? = null) {
fun ImageView.setVectorCompat(@DrawableRes drawable: Int, tint: Int? = null) {
val vector = AppCompatResources.getDrawable(context, drawable)
if (tint != null) {
vector?.mutate()
vector?.setTint(context.getResourceColor(tint))
vector?.setTint(tint)
}
setImageDrawable(vector)
}
@@ -2,7 +2,6 @@
package eu.kanade.tachiyomi.util.view
import android.annotation.SuppressLint
import android.graphics.Point
import android.view.Gravity
import android.view.Menu
@@ -10,18 +9,14 @@ import android.view.MenuItem
import android.view.View
import androidx.annotation.MenuRes
import androidx.annotation.StringRes
import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.TooltipCompat
import androidx.core.content.ContextCompat
import androidx.core.view.forEach
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.snackbar.Snackbar
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.getResourceColor
/**
* Returns coordinates of view.
@@ -68,7 +63,7 @@ inline fun View.setTooltip(@StringRes stringRes: Int) {
inline fun View.popupMenu(
@MenuRes menuRes: Int,
noinline initMenu: (Menu.() -> Unit)? = null,
noinline onMenuItemClick: MenuItem.() -> Unit
noinline onMenuItemClick: MenuItem.() -> Boolean
): PopupMenu {
val popup = PopupMenu(context, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
popup.menuInflater.inflate(menuRes, popup.menu)
@@ -76,50 +71,7 @@ inline fun View.popupMenu(
if (initMenu != null) {
popup.menu.initMenu()
}
popup.setOnMenuItemClickListener {
it.onMenuItemClick()
true
}
popup.show()
return popup
}
/**
* Shows a popup menu on top of this view.
*
* @param items menu item names to inflate the menu with. List of itemId to stringRes pairs.
* @param selectedItemId optionally show a checkmark beside an item with this itemId.
* @param onMenuItemClick function to execute when a menu item is clicked.
*/
@SuppressLint("RestrictedApi")
inline fun View.popupMenu(
items: List<Pair<Int, Int>>,
selectedItemId: Int? = null,
noinline onMenuItemClick: MenuItem.() -> Unit
): PopupMenu {
val popup = PopupMenu(context, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
items.forEach { (id, stringRes) ->
popup.menu.add(0, id, 0, stringRes)
}
if (selectedItemId != null) {
(popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true)
val emptyIcon = ContextCompat.getDrawable(context, R.drawable.ic_blank_24dp)
popup.menu.forEach { item ->
item.icon = when (item.itemId) {
selectedItemId -> ContextCompat.getDrawable(context, R.drawable.ic_check_24dp)?.mutate()?.apply {
setTint(context.getResourceColor(android.R.attr.textColorPrimary))
}
else -> emptyIcon
}
}
}
popup.setOnMenuItemClickListener {
it.onMenuItemClick()
true
}
popup.setOnMenuItemClickListener { it.onMenuItemClick() }
popup.show()
return popup
@@ -12,7 +12,7 @@ import androidx.annotation.MenuRes
import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.ActionToolbarBinding
import eu.kanade.tachiyomi.databinding.CommonActionToolbarBinding
/**
* A toolbar holding only menu items.
@@ -20,21 +20,25 @@ import eu.kanade.tachiyomi.databinding.ActionToolbarBinding
class ActionToolbar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs) {
private val binding = ActionToolbarBinding.inflate(LayoutInflater.from(context), this, true)
private val binding: CommonActionToolbarBinding
init {
binding = CommonActionToolbarBinding.inflate(LayoutInflater.from(context), this, true)
}
/**
* Remove menu items and remove listener.
*/
fun destroy() {
binding.menu.menu.clear()
binding.menu.setOnMenuItemClickListener(null)
binding.commonActionMenu.menu.clear()
binding.commonActionMenu.setOnMenuItemClickListener(null)
}
/**
* Gets a menu item if found.
*/
fun findItem(@IdRes itemId: Int): MenuItem? {
return binding.menu.menu.findItem(itemId)
return binding.commonActionMenu.menu.findItem(itemId)
}
/**
@@ -42,14 +46,14 @@ class ActionToolbar @JvmOverloads constructor(context: Context, attrs: Attribute
*/
fun show(mode: ActionMode, @MenuRes menuRes: Int, listener: (item: MenuItem?) -> Boolean) {
// Avoid re-inflating the menu
if (binding.menu.menu.size() == 0) {
mode.menuInflater.inflate(menuRes, binding.menu.menu)
binding.menu.setOnMenuItemClickListener { listener(it) }
if (binding.commonActionMenu.menu.size() == 0) {
mode.menuInflater.inflate(menuRes, binding.commonActionMenu.menu)
binding.commonActionMenu.setOnMenuItemClickListener { listener(it) }
}
binding.actionToolbar.isVisible = true
binding.commonActionToolbar.isVisible = true
val bottomAnimation = AnimationUtils.loadAnimation(context, R.anim.enter_from_bottom)
binding.actionToolbar.startAnimation(bottomAnimation)
binding.commonActionToolbar.startAnimation(bottomAnimation)
}
/**
@@ -60,10 +64,10 @@ class ActionToolbar @JvmOverloads constructor(context: Context, attrs: Attribute
bottomAnimation.setAnimationListener(
object : SimpleAnimationListener() {
override fun onAnimationEnd(animation: Animation) {
binding.actionToolbar.isVisible = false
binding.commonActionToolbar.isVisible = false
}
}
)
binding.actionToolbar.startAnimation(bottomAnimation)
binding.commonActionToolbar.startAnimation(bottomAnimation)
}
}
@@ -1,10 +1,13 @@
package eu.kanade.tachiyomi.widget.materialdialogs
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import androidx.annotation.AttrRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.AppCompatImageView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.view.setVectorCompat
import eu.kanade.tachiyomi.util.system.getResourceColor
class QuadStateCheckBox @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
AppCompatImageView(context, attrs) {
@@ -16,11 +19,19 @@ class QuadStateCheckBox @JvmOverloads constructor(context: Context, attrs: Attri
}
private fun updateDrawable() {
when (state) {
State.UNCHECKED -> setVectorCompat(R.drawable.ic_check_box_outline_blank_24dp, R.attr.colorControlNormal)
State.INDETERMINATE -> setVectorCompat(R.drawable.ic_indeterminate_check_box_24dp, R.attr.colorAccent)
State.CHECKED -> setVectorCompat(R.drawable.ic_check_box_24dp, R.attr.colorAccent)
State.INVERSED -> setVectorCompat(R.drawable.ic_check_box_x_24dp, R.attr.colorAccent)
val drawable = when (state) {
State.UNCHECKED -> tintVector(context, R.drawable.ic_check_box_outline_blank_24dp, R.attr.colorControlNormal)
State.INDETERMINATE -> tintVector(context, R.drawable.ic_indeterminate_check_box_24dp)
State.CHECKED -> tintVector(context, R.drawable.ic_check_box_24dp)
State.INVERSED -> tintVector(context, R.drawable.ic_check_box_x_24dp)
}
setImageDrawable(drawable)
}
private fun tintVector(context: Context, resId: Int, @AttrRes colorAttrRes: Int = R.attr.colorAccent): Drawable {
return AppCompatResources.getDrawable(context, resId)!!.apply {
setTint(context.getResourceColor(colorAttrRes))
}
}

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