mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2025-12-13 12:22:03 +01:00
Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6aff438a16 | ||
|
|
13324dd1a1 | ||
|
|
ae9bf06b46 | ||
|
|
5236834911 | ||
|
|
bf80dd622c | ||
|
|
662b71436e | ||
|
|
f608cb55eb | ||
|
|
6ba82da029 | ||
|
|
f407e30b6e | ||
|
|
4e7b8c98f9 | ||
|
|
5f9574541f | ||
|
|
08a6db7d6e | ||
|
|
b485e1d657 | ||
|
|
e8d8621f06 | ||
|
|
4cefbce7c3 | ||
|
|
fa31369f99 | ||
|
|
d0bf93ebb7 | ||
|
|
41a747c7e7 | ||
|
|
8882cd4787 | ||
|
|
6676490e09 | ||
|
|
68bea8a196 | ||
|
|
25995c09a0 | ||
|
|
0eb8d7d081 | ||
|
|
554f890ae3 | ||
|
|
dd1743698f | ||
|
|
b092e98ac9 | ||
|
|
9ee6262aed | ||
|
|
24a2d86f41 | ||
|
|
b5c5c66336 | ||
|
|
7654feb6a8 | ||
|
|
a598ac3993 | ||
|
|
cab919d74c | ||
|
|
60a929b92c | ||
|
|
356b7c346a | ||
|
|
ad57fde1c5 | ||
|
|
17f7dea21b | ||
|
|
b40af7c3c6 | ||
|
|
9065362fde | ||
|
|
d264b03ca1 | ||
|
|
ad9bad3d17 | ||
|
|
dfd858034f | ||
|
|
58ad8fa8c0 | ||
|
|
38610d8a24 | ||
|
|
27cec697bf | ||
|
|
024f9a8c76 | ||
|
|
f7cc36f2f0 | ||
|
|
ef5148ebb4 | ||
|
|
6dbc0a6fd5 | ||
|
|
fba3f9d501 | ||
|
|
d9f8137362 | ||
|
|
28416489b2 | ||
|
|
54a23ddd1f | ||
|
|
3287ca9cf2 | ||
|
|
a59e134862 | ||
|
|
1f8c5b0120 | ||
|
|
c7f839ea4a | ||
|
|
d981245723 | ||
|
|
1f729f1cb3 | ||
|
|
b4577d6676 | ||
|
|
544adb9940 | ||
|
|
1875c4a752 | ||
|
|
5f0493f1e5 | ||
|
|
c749e50bec | ||
|
|
a4e5e3ece5 | ||
|
|
2a69d1b051 | ||
|
|
126e1e2d9d | ||
|
|
0586e1d3ad | ||
|
|
07cb1c237e | ||
|
|
f4f1efe5fa | ||
|
|
37fdf4d434 | ||
|
|
99b46096a4 | ||
|
|
12e90ae35e | ||
|
|
023311a874 | ||
|
|
155a4dd463 | ||
|
|
15bed1ac4c | ||
|
|
27f55f8098 | ||
|
|
00598879e2 | ||
|
|
df274a0a78 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,2 +1 @@
|
||||
github: inorichi
|
||||
ko_fi: inorichi
|
||||
|
||||
10
.github/ISSUE_TEMPLATE.md
vendored
10
.github/ISSUE_TEMPLATE.md
vendored
@@ -2,9 +2,15 @@
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v0.10.10)
|
||||
- I have updated all extensions
|
||||
- I have updated:
|
||||
- To the latest version of the app (stable is v0.10.12)
|
||||
- All extensions
|
||||
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
||||
- 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**
|
||||
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -9,9 +9,15 @@ labels: "bug"
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v0.10.10)
|
||||
- I have updated all extensions
|
||||
- I have updated:
|
||||
- To the latest version of the app (stable is v0.10.12)
|
||||
- All extensions
|
||||
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
||||
- 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**
|
||||
|
||||
|
||||
9
.github/ISSUE_TEMPLATE/feature_request.md
vendored
9
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -9,9 +9,14 @@ labels: "feature"
|
||||
|
||||
I acknowledge that:
|
||||
|
||||
- I have updated to the latest version of the app (stable is v0.10.10)
|
||||
- I have updated all extensions
|
||||
- I have updated:
|
||||
- To the latest version of the app (stable is v0.10.12)
|
||||
- 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**
|
||||
|
||||
|
||||
BIN
.github/readme-images/screens.png
vendored
BIN
.github/readme-images/screens.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 454 KiB |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -88,7 +88,7 @@ jobs:
|
||||
MD5: ${{ env.APK_MD5 }}
|
||||
files: |
|
||||
tachiyomi-${{ env.VERSION_TAG }}.apk
|
||||
draft: ${{ github.event.inputs.dry-run != '' }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.github/workflows/issue_closer.yml
vendored
2
.github/workflows/issue_closer.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Autoclose issues
|
||||
uses: arkon/issue-closer-action@v3.0
|
||||
uses: arkon/issue-closer-action@v3.1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
rules: |
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
### r2903
|
||||
- The MyAnimeList tracker was rewritten. You will need to log out and log in again.
|
||||
|
||||
### r1810
|
||||
- Background jobs were migrated to a new system. You may need to toggle the settings to ensure they
|
||||
run properly. This includes app updates, library updates, and automatic backups.
|
||||
|
||||
### r1340
|
||||
- A new screen for managing extensions was added. If you previously installed extensions from FDroid,
|
||||
you will have to uninstall all of them first (tap on the extension then uninstall), otherwise you won't be able
|
||||
to update them due to signature mismatch. You won't lose anything in this process as the extensions themselves
|
||||
don't store anything.
|
||||
|
||||
### r959
|
||||
- The download manager has been rewritten and it's possible some of your downloads
|
||||
aren't recognized anymore. You may have to check your downloads folder and manually delete those.
|
||||
- You can now download to any folder in your SD card.
|
||||
- The download directory setting has been reset.
|
||||
|
||||
### r857
|
||||
- **Important!** Delete after read has been updated.
|
||||
This means the value has been reset set to disabled.
|
||||
This can be changed in Settings > Downloads
|
||||
|
||||
### r736
|
||||
- **Important!** Now chapters follow the order of the sources. **It's required that you update your entire library
|
||||
before reading in order for them to be synced.** Old behavior can be restored for a manga in the overflow menu of the chapters tab.
|
||||
|
||||
### r724
|
||||
- Kissmanga covers may not load anymore. The only workaround is to update the details of the manga
|
||||
from the info tab, or clearing the database (the latter won't fix covers from library manga).
|
||||
@@ -11,7 +11,7 @@ Tachiyomi is a free and open source manga reader for Android 5.0 and above.
|
||||
## Features
|
||||
|
||||
Features include:
|
||||
* Online reading from sources such as MangaDex, MangaSee, Mangakakalot, [and more](https://github.com/tachiyomiorg/tachiyomi-extensions)
|
||||
* Online reading from [a variety of sources](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
|
||||
|
||||
@@ -29,8 +29,8 @@ android {
|
||||
minSdkVersion(AndroidConfig.minSdk)
|
||||
targetSdkVersion(AndroidConfig.targetSdk)
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
versionCode = 57
|
||||
versionName = "0.10.10"
|
||||
versionCode = 59
|
||||
versionName = "0.10.12"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||
@@ -153,7 +153,7 @@ dependencies {
|
||||
implementation("com.github.pwittchen:reactivenetwork:0.13.0")
|
||||
|
||||
// Network client
|
||||
val okhttpVersion = "5.0.0-alpha.2"
|
||||
val okhttpVersion = "4.9.1"
|
||||
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
|
||||
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
|
||||
@@ -163,7 +163,7 @@ dependencies {
|
||||
implementation("org.conscrypt:conscrypt-android:2.5.1")
|
||||
|
||||
// JSON
|
||||
val kotlinSerializationVersion = "1.0.1"
|
||||
val kotlinSerializationVersion = "1.1.0"
|
||||
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")
|
||||
@@ -174,7 +174,7 @@ dependencies {
|
||||
|
||||
// Disk
|
||||
implementation("com.jakewharton:disklrucache:2.0.2")
|
||||
implementation("com.github.tachiyomiorg:unifile:e9e3a40")
|
||||
implementation("com.github.tachiyomiorg:unifile:17bec43")
|
||||
implementation("com.github.junrar:junrar:7.4.0")
|
||||
|
||||
// HTML parser
|
||||
@@ -260,12 +260,12 @@ dependencies {
|
||||
|
||||
implementation(kotlin("reflect", version = BuildPluginsVersion.KOTLIN))
|
||||
|
||||
val coroutinesVersion = "1.4.2"
|
||||
val coroutinesVersion = "1.4.3"
|
||||
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.6")
|
||||
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7")
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/Theme.Tachiyomi.Light"
|
||||
android:theme="@style/Theme.Base"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
<activity
|
||||
android:name=".ui.main.MainActivity"
|
||||
@@ -84,7 +84,7 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.security.BiometricUnlockActivity"
|
||||
android:theme="@style/Theme.Splash" />
|
||||
android:theme="@style/Theme.Base" />
|
||||
<activity
|
||||
android:name=".ui.webview.WebViewActivity"
|
||||
android:configChanges="uiMode|orientation|screenSize" />
|
||||
|
||||
@@ -52,6 +52,9 @@ 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,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi
|
||||
|
||||
import android.os.Build
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
||||
@@ -139,6 +140,15 @@ 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,17 +2,17 @@ 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.source.model.Page
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
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
|
||||
@@ -42,8 +42,7 @@ class ChapterCache(private val context: Context) {
|
||||
const val PARAMETER_CACHE_SIZE = 100L * 1024 * 1024
|
||||
}
|
||||
|
||||
/** Google Json class used for parsing JSON files. */
|
||||
private val gson: Gson by injectLazy()
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
/** Cache class used for cache management. */
|
||||
private val diskCache = DiskLruCache.open(
|
||||
@@ -56,7 +55,7 @@ class ChapterCache(private val context: Context) {
|
||||
/**
|
||||
* Returns directory of cache.
|
||||
*/
|
||||
val cacheDir: File
|
||||
private val cacheDir: File
|
||||
get() = diskCache.directory
|
||||
|
||||
/**
|
||||
@@ -71,43 +70,19 @@ class ChapterCache(private val context: Context) {
|
||||
val readableSize: String
|
||||
get() = Formatter.formatFileSize(context, realSize)
|
||||
|
||||
/**
|
||||
* 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 an observable of the list of pages.
|
||||
* @return the list of pages.
|
||||
*/
|
||||
fun getPageListFromCache(chapter: Chapter): Observable<List<Page>> {
|
||||
return Observable.fromCallable {
|
||||
// Get the key for the chapter.
|
||||
val key = DiskUtil.hashKeyForDisk(getKey(chapter))
|
||||
fun getPageListFromCache(chapter: Chapter): List<Page> {
|
||||
// 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
|
||||
diskCache.get(key).use {
|
||||
gson.fromJson<List<Page>>(it.getString(0))
|
||||
}
|
||||
// Convert JSON string to list of objects. Throws an exception if snapshot is null
|
||||
return diskCache.get(key).use {
|
||||
json.decodeFromString(it.getString(0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +94,7 @@ class ChapterCache(private val context: Context) {
|
||||
*/
|
||||
fun putPageListToCache(chapter: Chapter, pages: List<Page>) {
|
||||
// Convert list of pages to json string.
|
||||
val cachedValue = gson.toJson(pages)
|
||||
val cachedValue = json.encodeToString(pages)
|
||||
|
||||
// Initialize the editor (edits the values for an entry).
|
||||
var editor: DiskLruCache.Editor? = null
|
||||
@@ -199,6 +174,38 @@ 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}"
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ 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)
|
||||
}
|
||||
}
|
||||
@@ -84,7 +83,6 @@ 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()
|
||||
@@ -116,6 +114,7 @@ internal class DownloadNotifier(private val context: Context) {
|
||||
}
|
||||
|
||||
setProgress(download.pages!!.size, download.downloadedImages, false)
|
||||
setOngoing(true)
|
||||
|
||||
show(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
|
||||
}
|
||||
@@ -130,6 +129,7 @@ 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))
|
||||
return downloadsDir.findFile(getSourceDirName(source), true)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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))
|
||||
return sourceDir?.findFile(getMangaDirName(manga), true)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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) }
|
||||
.mapNotNull { mangaDir?.findFile(it, true) }
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ class DownloadProvider(private val context: Context) {
|
||||
* @param source the source to query.
|
||||
*/
|
||||
fun getSourceDirName(source: Source): String {
|
||||
return source.toString()
|
||||
return DiskUtil.buildValidFilename(source.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,6 +150,7 @@ 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)
|
||||
)
|
||||
|
||||
@@ -11,9 +11,6 @@ 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()
|
||||
@@ -24,21 +21,19 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
|
||||
if (oauth == null) {
|
||||
oauth = myanimelist.loadOAuth()
|
||||
}
|
||||
// Refresh access token if null or expired.
|
||||
if (oauth!!.isExpired()) {
|
||||
// Refresh access token if expired
|
||||
if (oauth != null && 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,8 +7,9 @@ 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() > expires_in
|
||||
fun isExpired() = System.currentTimeMillis() > created_at + (expires_in * 1000)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
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
|
||||
@@ -11,52 +8,26 @@ 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(): Result {
|
||||
return runBlocking {
|
||||
try {
|
||||
val result = GithubUpdateChecker().checkForUpdate()
|
||||
override fun doWork() = runBlocking {
|
||||
try {
|
||||
val result = GithubUpdateChecker().checkForUpdate()
|
||||
|
||||
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()
|
||||
if (result is UpdateResult.NewUpdate<*>) {
|
||||
UpdaterNotifier(context).promptUpdate(result.release.downloadLink)
|
||||
}
|
||||
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,6 +1,8 @@
|
||||
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
|
||||
@@ -28,6 +30,27 @@ 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.
|
||||
*
|
||||
@@ -63,19 +86,20 @@ 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)
|
||||
// Install action
|
||||
setContentIntent(NotificationHandler.installApkPendingActivity(context, uri))
|
||||
setContentIntent(installIntent)
|
||||
|
||||
clearActions()
|
||||
addAction(
|
||||
R.drawable.ic_system_update_alt_white_24dp,
|
||||
context.getString(R.string.action_install),
|
||||
NotificationHandler.installApkPendingActivity(context, uri)
|
||||
installIntent
|
||||
)
|
||||
// Cancel action
|
||||
addAction(
|
||||
R.drawable.ic_close_24dp,
|
||||
context.getString(R.string.action_cancel),
|
||||
@@ -96,13 +120,13 @@ internal class UpdaterNotifier(private val context: Context) {
|
||||
setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||
setOnlyAlertOnce(false)
|
||||
setProgress(0, 0, false)
|
||||
// Retry action
|
||||
|
||||
clearActions()
|
||||
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.w(e, "Extension load error: $extName ($it)")
|
||||
Timber.e(e, "Extension load error: $extName ($it)")
|
||||
return LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +1,39 @@
|
||||
package eu.kanade.tachiyomi.ui.base.activity
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
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
|
||||
else -> R.style.Theme_Tachiyomi_Dark
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(
|
||||
when {
|
||||
isDarkMode -> darkTheme
|
||||
else -> lightTheme
|
||||
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.amoled -> R.style.Theme_Tachiyomi_Dark_Amoled
|
||||
}
|
||||
)
|
||||
|
||||
} 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,7 +19,8 @@ import timber.log.Timber
|
||||
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
||||
RestoreViewOnCreateController(bundle) {
|
||||
|
||||
lateinit var binding: VB
|
||||
protected lateinit var binding: VB
|
||||
private set
|
||||
|
||||
lateinit var viewScope: CoroutineScope
|
||||
|
||||
@@ -51,11 +52,12 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
|
||||
return inflateView(inflater, container)
|
||||
}
|
||||
abstract fun createBinding(inflater: LayoutInflater): VB
|
||||
|
||||
abstract fun inflateView(inflater: LayoutInflater, container: ViewGroup): View
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
|
||||
binding = createBinding(inflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
open fun onViewCreated(view: View) {}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ 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,10 +49,7 @@ class BrowseController :
|
||||
return resources!!.getString(R.string.browse)
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = PagerControllerBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
override fun createBinding(inflater: LayoutInflater) = PagerControllerBinding.inflate(inflater)
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
|
||||
@@ -5,7 +5,6 @@ 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
|
||||
@@ -57,18 +56,16 @@ open class ExtensionController :
|
||||
return ExtensionPresenter()
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = ExtensionControllerBinding.inflate(inflater)
|
||||
override fun createBinding(inflater: LayoutInflater) = ExtensionControllerBinding.inflate(inflater)
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
|
||||
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.SourceMainControllerCardHeaderBinding
|
||||
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
|
||||
|
||||
class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<*>) :
|
||||
FlexibleViewHolder(view, adapter) {
|
||||
|
||||
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
|
||||
private val binding = SectionHeaderItemBinding.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.source_main_controller_card_header
|
||||
return R.layout.section_header_item
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,7 +12,6 @@ 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
|
||||
@@ -65,15 +64,9 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
override fun createBinding(inflater: LayoutInflater): ExtensionDetailControllerBinding {
|
||||
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
|
||||
binding = ExtensionDetailControllerBinding.inflate(themedInflater)
|
||||
binding.extensionPrefsRecycler.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
return binding.root
|
||||
return ExtensionDetailControllerBinding.inflate(themedInflater)
|
||||
}
|
||||
|
||||
override fun createPresenter(): ExtensionDetailsPresenter {
|
||||
@@ -88,6 +81,12 @@ 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,7 +6,6 @@ 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,10 +44,9 @@ class SourcePreferencesController(bundle: Bundle? = null) :
|
||||
bundleOf(SOURCE_ID to sourceId)
|
||||
)
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
override fun createBinding(inflater: LayoutInflater): SourcePreferencesControllerBinding {
|
||||
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
|
||||
binding = SourcePreferencesControllerBinding.inflate(themedInflater)
|
||||
return binding.root
|
||||
return SourcePreferencesControllerBinding.inflate(themedInflater)
|
||||
}
|
||||
|
||||
override fun createPresenter(): SourcePreferencesPresenter {
|
||||
|
||||
@@ -3,9 +3,9 @@ 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
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.kanade.tachiyomi.databinding.MigrationMangaControllerBinding
|
||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||
@@ -44,14 +44,17 @@ class MigrationMangaController :
|
||||
return MigrationMangaPresenter(sourceId)
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = MigrationMangaControllerBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
override fun createBinding(inflater: LayoutInflater) = MigrationMangaControllerBinding.inflate(inflater)
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
|
||||
binding.recycler.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
|
||||
adapter = MigrationMangaAdapter(this)
|
||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||
binding.recycler.adapter = adapter
|
||||
|
||||
@@ -104,24 +104,22 @@ class SearchPresenter(
|
||||
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
|
||||
val maxChapterRead = prevMangaChapters
|
||||
.filter { it.read }
|
||||
.maxByOrNull { it.chapter_number }?.chapter_number
|
||||
val bookmarkedChapters = prevMangaChapters
|
||||
.filter { it.bookmark && it.isRecognizedNumber }
|
||||
.map { it.chapter_number }
|
||||
if (maxChapterRead != null) {
|
||||
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||
for (chapter in dbChapters) {
|
||||
if (chapter.isRecognizedNumber) {
|
||||
if (chapter.chapter_number <= maxChapterRead) {
|
||||
chapter.read = true
|
||||
}
|
||||
if (chapter.chapter_number in bookmarkedChapters) {
|
||||
chapter.bookmark = true
|
||||
}
|
||||
.maxOfOrNull { it.chapter_number } ?: 0f
|
||||
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||
for (chapter in dbChapters) {
|
||||
if (chapter.isRecognizedNumber) {
|
||||
val prevChapter = prevMangaChapters
|
||||
.find { it.isRecognizedNumber && it.chapter_number == chapter.chapter_number }
|
||||
if (prevChapter != null) {
|
||||
chapter.date_fetch = prevChapter.date_fetch
|
||||
chapter.bookmark = prevChapter.bookmark
|
||||
}
|
||||
if (chapter.chapter_number <= maxChapterRead) {
|
||||
chapter.read = true
|
||||
}
|
||||
}
|
||||
db.insertChapters(dbChapters).executeAsBlocking()
|
||||
}
|
||||
db.insertChapters(dbChapters).executeAsBlocking()
|
||||
}
|
||||
|
||||
// Update categories
|
||||
|
||||
@@ -5,7 +5,6 @@ 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
|
||||
@@ -30,18 +29,16 @@ class MigrationSourcesController :
|
||||
return MigrationSourcesPresenter()
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = MigrationSourcesControllerBinding.inflate(inflater)
|
||||
override fun createBinding(inflater: LayoutInflater) = MigrationSourcesControllerBinding.inflate(inflater)
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
|
||||
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.SourceMainControllerCardHeaderBinding
|
||||
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
|
||||
|
||||
/**
|
||||
* 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.source_main_controller_card_header
|
||||
return R.layout.section_header_item
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,7 +45,7 @@ class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
|
||||
|
||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||
|
||||
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
|
||||
private val binding = SectionHeaderItemBinding.bind(view)
|
||||
|
||||
init {
|
||||
binding.title.text = view.context.getString(R.string.migration_selection_prompt)
|
||||
|
||||
@@ -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.SourceMainControllerCardHeaderBinding
|
||||
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
|
||||
class LangHolder(view: View, adapter: FlexibleAdapter<*>) :
|
||||
FlexibleViewHolder(view, adapter) {
|
||||
|
||||
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
|
||||
private val binding = SectionHeaderItemBinding.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.source_main_controller_card_header
|
||||
return R.layout.section_header_item
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,6 @@ 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 com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.list.listItems
|
||||
@@ -67,25 +66,16 @@ class SourceController :
|
||||
return SourcePresenter()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
override fun createBinding(inflater: LayoutInflater) = SourceMainControllerBinding.inflate(inflater)
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
|
||||
binding.recycler.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
|
||||
adapter = SourceAdapter(this)
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ 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) :
|
||||
@@ -46,9 +45,9 @@ class SourceHolder(private val view: View, val adapter: SourceAdapter) :
|
||||
|
||||
binding.pin.isVisible = true
|
||||
if (item.isPinned) {
|
||||
binding.pin.setVectorCompat(R.drawable.ic_push_pin_24dp, view.context.getResourceColor(R.attr.colorAccent))
|
||||
binding.pin.setVectorCompat(R.drawable.ic_push_pin_24dp, R.attr.colorAccent)
|
||||
} else {
|
||||
binding.pin.setVectorCompat(R.drawable.ic_push_pin_outline_24dp, view.context.getResourceColor(android.R.attr.textColorHint))
|
||||
binding.pin.setVectorCompat(R.drawable.ic_push_pin_outline_24dp, android.R.attr.textColorHint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,10 +124,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
return BrowseSourcePresenter(args.getLong(SOURCE_ID_KEY), args.getString(SEARCH_QUERY_KEY))
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = SourceControllerBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
override fun createBinding(inflater: LayoutInflater) = SourceControllerBinding.inflate(inflater)
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
@@ -269,6 +266,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
if (router.backstackSize >= 2 && router.backstack[router.backstackSize - 2].controller() is GlobalSearchController) {
|
||||
router.popController(this)
|
||||
} else {
|
||||
nonSubmittedQuery = ""
|
||||
searchWithQuery("")
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.databinding.SourceFilterSheetBinding
|
||||
@@ -17,10 +18,10 @@ class SourceFilterSheet(
|
||||
onResetClicked: () -> Unit
|
||||
) : BaseBottomSheetDialog(activity) {
|
||||
|
||||
private var filterNavView: FilterNavigationView
|
||||
private var filterNavView: FilterNavigationView = FilterNavigationView(activity)
|
||||
private val sheetBehavior: BottomSheetBehavior<*>
|
||||
|
||||
init {
|
||||
filterNavView = FilterNavigationView(activity)
|
||||
filterNavView.onFilterClicked = {
|
||||
onFilterClicked()
|
||||
this.dismiss()
|
||||
@@ -28,13 +29,23 @@ class SourceFilterSheet(
|
||||
filterNavView.onResetClicked = onResetClicked
|
||||
|
||||
setContentView(filterNavView)
|
||||
|
||||
sheetBehavior = BottomSheetBehavior.from(filterNavView.parent as ViewGroup)
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
sheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
|
||||
fun setFilters(items: List<IFlexible<*>>) {
|
||||
filterNavView.adapter.updateDataSet(items)
|
||||
}
|
||||
|
||||
class FilterNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
class FilterNavigationView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null
|
||||
) :
|
||||
SimpleNavigationView(context, attrs) {
|
||||
|
||||
var onFilterClicked = {}
|
||||
@@ -42,9 +53,12 @@ class SourceFilterSheet(
|
||||
|
||||
val adapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter<IFlexible<*>>(null)
|
||||
.setDisplayHeadersAtStartUp(true)
|
||||
.setStickyHeaders(true)
|
||||
|
||||
private val binding = SourceFilterSheetBinding.inflate(LayoutInflater.from(context), null, false)
|
||||
private val binding = SourceFilterSheetBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
null,
|
||||
false
|
||||
)
|
||||
|
||||
init {
|
||||
recycler.adapter = adapter
|
||||
|
||||
@@ -6,7 +6,6 @@ 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,22 +49,7 @@ open class GlobalSearchController(
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 createBinding(inflater: LayoutInflater) = GlobalSearchControllerBinding.inflate(inflater)
|
||||
|
||||
override fun getTitle(): String? {
|
||||
return presenter.query
|
||||
@@ -142,6 +126,12 @@ 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.
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
@@ -68,21 +67,7 @@ class CategoryController :
|
||||
return resources?.getString(R.string.action_edit_categories)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
override fun createBinding(inflater: LayoutInflater) = CategoriesControllerBinding.inflate(inflater)
|
||||
|
||||
/**
|
||||
* Called after view inflation. Used to initialize the view.
|
||||
@@ -92,6 +77,12 @@ 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)
|
||||
|
||||
@@ -5,7 +5,6 @@ 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
|
||||
@@ -55,15 +54,7 @@ class DownloadController :
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = DownloadControllerBinding.inflate(inflater)
|
||||
binding.recycler.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
override fun createBinding(inflater: LayoutInflater) = DownloadControllerBinding.inflate(inflater)
|
||||
|
||||
override fun createPresenter(): DownloadPresenter {
|
||||
return DownloadPresenter()
|
||||
@@ -76,6 +67,12 @@ 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,15 +81,14 @@ class DownloadHolder(private val view: View, val adapter: DownloadAdapter) :
|
||||
|
||||
private fun showPopupMenu(view: View) {
|
||||
view.popupMenu(
|
||||
R.menu.download_single,
|
||||
{
|
||||
menuRes = R.menu.download_single,
|
||||
initMenu = {
|
||||
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,7 +7,6 @@ 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.core.graphics.drawable.DrawableCompat
|
||||
@@ -18,6 +17,7 @@ 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.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
@@ -163,14 +163,17 @@ class LibraryController(
|
||||
return LibraryPresenter()
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = LibraryControllerBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
override fun createBinding(inflater: LayoutInflater) = LibraryControllerBinding.inflate(inflater)
|
||||
|
||||
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()
|
||||
|
||||
@@ -13,6 +13,7 @@ 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,6 +50,8 @@ 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 kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@@ -98,23 +101,27 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
||||
padding(left = true, top = true, right = true)
|
||||
}
|
||||
}
|
||||
binding.bottomNav.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
binding.rootFab.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
margin()
|
||||
}
|
||||
}
|
||||
binding.bottomNav.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure navigation bar is on bottom when making it transparent
|
||||
// Make sure navigation bar is on bottom before we modify it
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
|
||||
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
|
||||
// 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
|
||||
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)
|
||||
}
|
||||
}
|
||||
insets
|
||||
@@ -123,6 +130,15 @@ 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() }
|
||||
@@ -146,6 +162,9 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
||||
val controller = router.getControllerWithTag(id.toString()) as? LibraryController
|
||||
controller?.showSettingsSheet()
|
||||
}
|
||||
R.id.nav_updates -> {
|
||||
router.pushController(DownloadController().withFadeTransaction())
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
@@ -439,7 +458,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()
|
||||
view.translationY = -maxAbsOffset - verticalOffset.toFloat() + appBarLayout.marginTop
|
||||
}
|
||||
binding.appbar.addOnOffsetChangedListener(listener)
|
||||
fixedViewsToBottom[view] = listener
|
||||
|
||||
@@ -11,7 +11,6 @@ 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.core.graphics.blue
|
||||
@@ -199,18 +198,21 @@ class MangaController :
|
||||
)
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = MangaControllerBinding.inflate(inflater)
|
||||
override fun createBinding(inflater: LayoutInflater) = MangaControllerBinding.inflate(inflater)
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
|
||||
binding.recycler.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
binding.actionToolbar.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
margin(bottom = true)
|
||||
}
|
||||
}
|
||||
|
||||
if (manga == null || source == null) return
|
||||
|
||||
@@ -725,8 +727,7 @@ class MangaController :
|
||||
|
||||
fun onChapterDownloadUpdate(download: Download) {
|
||||
chaptersAdapter?.currentItems?.find { it.id == download.chapter.id }?.let {
|
||||
chaptersAdapter?.updateItem(it)
|
||||
chaptersAdapter?.notifyDataSetChanged()
|
||||
chaptersAdapter?.updateItem(it, it.status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -850,7 +851,6 @@ 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
|
||||
@@ -882,10 +882,6 @@ 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
|
||||
}
|
||||
|
||||
@@ -1007,10 +1003,17 @@ class MangaController :
|
||||
|
||||
// OVERFLOW MENU DIALOGS
|
||||
|
||||
private fun getUnreadChaptersSorted() = presenter.chapters
|
||||
.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }
|
||||
.distinctBy { it.name }
|
||||
.sortedByDescending { it.source_order }
|
||||
private fun getUnreadChaptersSorted(): List<ChapterItem> {
|
||||
val chapters = presenter.chapters
|
||||
.sortedWith(presenter.getChapterSort())
|
||||
.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }
|
||||
.distinctBy { it.name }
|
||||
return if (presenter.sortDescending()) {
|
||||
chapters.reversed()
|
||||
} else {
|
||||
chapters
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadChapters(choice: Int) {
|
||||
val chaptersToDownload = when (choice) {
|
||||
|
||||
@@ -429,7 +429,11 @@ class MangaPresenter(
|
||||
observable = observable.filter { !it.bookmark }
|
||||
}
|
||||
|
||||
val sortFunction: (Chapter, Chapter) -> Int = when (manga.sorting) {
|
||||
return observable.toSortedList(getChapterSort())
|
||||
}
|
||||
|
||||
fun getChapterSort(): (Chapter, Chapter) -> Int {
|
||||
return 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) }
|
||||
@@ -444,8 +448,6 @@ class MangaPresenter(
|
||||
}
|
||||
else -> throw NotImplementedError("Unimplemented sorting method")
|
||||
}
|
||||
|
||||
return observable.toSortedList(sortFunction)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -472,7 +474,12 @@ class MangaPresenter(
|
||||
* Returns the next unread chapter or null if everything is read.
|
||||
*/
|
||||
fun getNextUnreadChapter(): ChapterItem? {
|
||||
return chapters.sortedByDescending { it.source_order }.find { !it.read }
|
||||
val chapters = chapters.sortedWith(getChapterSort())
|
||||
return if (sortDescending()) {
|
||||
return chapters.findLast { !it.read }
|
||||
} else {
|
||||
chapters.find { !it.read }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,42 +6,38 @@ 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
|
||||
private val binding: ChapterDownloadViewBinding =
|
||||
ChapterDownloadViewBinding.inflate(LayoutInflater.from(context), this, false)
|
||||
|
||||
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()
|
||||
updateLayout(state, progress)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
downloadIconAnimator =
|
||||
ObjectAnimator.ofFloat(binding.downloadIcon, "alpha", 1f, 0f).apply {
|
||||
duration = 1000
|
||||
@@ -49,22 +45,36 @@ class ChapterDownloadView @JvmOverloads constructor(context: Context, attrs: Att
|
||||
repeatMode = ObjectAnimator.REVERSE
|
||||
}
|
||||
downloadIconAnimator?.start()
|
||||
isAnimating = true
|
||||
}
|
||||
} else {
|
||||
downloadIconAnimator?.currentPlayTime = System.currentTimeMillis() % 2000
|
||||
} else if (downloadIconAnimator != null) {
|
||||
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.QUEUE && progress > 0)
|
||||
binding.downloadProgress.progress = progress
|
||||
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)
|
||||
}
|
||||
|
||||
binding.downloadedIcon.isVisible = state == Download.State.DOWNLOADED
|
||||
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.errorIcon.isVisible = state == Download.State.ERROR
|
||||
this.state = state
|
||||
this.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -59,8 +59,10 @@ class ChapterHolder(
|
||||
descriptions.add(adapter.dateFormat.format(Date(chapter.date_upload)))
|
||||
}
|
||||
if (!chapter.read && chapter.last_page_read > 0) {
|
||||
val lastPageRead = SpannableString(itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1)).apply {
|
||||
setSpan(ForegroundColorSpan(adapter.readColor), 0, length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
val lastPageRead = buildSpannedString {
|
||||
color(adapter.readColor) {
|
||||
append(itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1))
|
||||
}
|
||||
}
|
||||
descriptions.add(lastPageRead)
|
||||
}
|
||||
|
||||
@@ -51,16 +51,12 @@ class ChaptersSettingsSheet(
|
||||
|
||||
private fun showPopupMenu(view: View) {
|
||||
view.popupMenu(
|
||||
R.menu.default_chapter_filter,
|
||||
{
|
||||
},
|
||||
{
|
||||
when (this.itemId) {
|
||||
menuRes = R.menu.default_chapter_filter,
|
||||
onMenuItemClick = {
|
||||
when (itemId) {
|
||||
R.id.set_as_default -> {
|
||||
SetChapterSettingsDialog(presenter.manga).showDialog(router)
|
||||
true
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -29,7 +29,6 @@ open class BaseChapterHolder(
|
||||
},
|
||||
onMenuItemClick = {
|
||||
adapter.clickListener.deleteChapter(position)
|
||||
true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
|
||||
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
|
||||
@@ -78,15 +79,6 @@ class AboutController : SettingsController() {
|
||||
openInBrowser(url)
|
||||
}
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
preference {
|
||||
key = "pref_about_notices"
|
||||
titleRes = R.string.notices
|
||||
onClick {
|
||||
openInBrowser("https://github.com/tachiyomiorg/tachiyomi/blob/master/PREVIEW_RELEASE_NOTES.md")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preferenceCategory {
|
||||
preference {
|
||||
@@ -97,6 +89,14 @@ 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"
|
||||
@@ -116,15 +116,7 @@ class AboutController : SettingsController() {
|
||||
preference {
|
||||
key = "pref_about_github"
|
||||
title = "GitHub"
|
||||
"https://github.com/tachiyomiorg/tachiyomi".also {
|
||||
summary = it
|
||||
onClick { openInBrowser(it) }
|
||||
}
|
||||
}
|
||||
preference {
|
||||
key = "pref_about_label_extensions"
|
||||
titleRes = R.string.label_extensions
|
||||
"https://github.com/tachiyomiorg/tachiyomi-extensions".also {
|
||||
"https://github.com/tachiyomiorg".also {
|
||||
summary = it
|
||||
onClick { openInBrowser(it) }
|
||||
}
|
||||
@@ -138,6 +130,7 @@ class AboutController : SettingsController() {
|
||||
.withAboutIconShown(false)
|
||||
.withAboutVersionShown(false)
|
||||
.withLicenseShown(true)
|
||||
.withEdgeToEdge(true)
|
||||
.start(activity!!)
|
||||
}
|
||||
}
|
||||
@@ -150,6 +143,11 @@ 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 {
|
||||
@@ -201,19 +199,10 @@ class AboutController : SettingsController() {
|
||||
}
|
||||
|
||||
private fun copyDebugInfo() {
|
||||
val deviceInfo =
|
||||
"""
|
||||
App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE})
|
||||
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)
|
||||
activity?.let {
|
||||
val deviceInfo = CrashLogUtil(it).getDebugInfo()
|
||||
activity?.copyToClipboard("Debug information", deviceInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFormattedBuildTime(): String {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
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
|
||||
@@ -26,7 +30,10 @@ import eu.kanade.tachiyomi.util.preference.switchPreference
|
||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
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
|
||||
|
||||
@@ -39,6 +46,9 @@ 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
|
||||
|
||||
@@ -115,6 +125,19 @@ 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
|
||||
@@ -141,6 +164,10 @@ 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) {
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ 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.MenuItem
|
||||
@@ -59,6 +58,7 @@ 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
|
||||
@@ -357,13 +357,18 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
setTooltip(R.string.viewer)
|
||||
|
||||
setOnClickListener {
|
||||
val newReadingMode =
|
||||
ReadingModeType.getNextReadingMode(presenter.getMangaViewer(resolveDefault = false))
|
||||
presenter.setMangaViewer(newReadingMode.prefValue)
|
||||
popupMenu(
|
||||
items = ReadingModeType.values().map { it.prefValue to it.stringRes },
|
||||
selectedItemId = presenter.getMangaViewer(resolveDefault = false),
|
||||
) {
|
||||
val newReadingMode = ReadingModeType.fromPreference(itemId)
|
||||
|
||||
menuToggleToast?.cancel()
|
||||
if (!preferences.showReadingMode()) {
|
||||
menuToggleToast = toast(newReadingMode.stringRes)
|
||||
presenter.setMangaViewer(newReadingMode.prefValue)
|
||||
|
||||
menuToggleToast?.cancel()
|
||||
if (!preferences.showReadingMode()) {
|
||||
menuToggleToast = toast(newReadingMode.stringRes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,14 +378,18 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
setTooltip(R.string.pref_rotation_type)
|
||||
|
||||
setOnClickListener {
|
||||
val newOrientation =
|
||||
OrientationType.getNextOrientation(preferences.rotation().get(), resources)
|
||||
popupMenu(
|
||||
items = OrientationType.values().map { it.prefValue to it.stringRes },
|
||||
selectedItemId = preferences.rotation().get(),
|
||||
) {
|
||||
val newOrientation = OrientationType.fromPreference(itemId)
|
||||
|
||||
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) }
|
||||
@@ -414,11 +423,16 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
setOnClickListener {
|
||||
ReaderSettingsSheet(this@ReaderActivity).show()
|
||||
}
|
||||
|
||||
setOnLongClickListener {
|
||||
ReaderSettingsSheet(this@ReaderActivity, showColorFilterSettings = true).show()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateRotationShortcut(preference: Int) {
|
||||
val orientation = OrientationType.fromPreference(preference, resources)
|
||||
val orientation = OrientationType.fromPreference(preference)
|
||||
binding.actionRotation.setImageResource(orientation.iconRes)
|
||||
}
|
||||
|
||||
@@ -554,10 +568,12 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
}
|
||||
|
||||
private fun showReadingModeToast(mode: Int) {
|
||||
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)
|
||||
try {
|
||||
val strings = resources.getStringArray(R.array.viewers_selector)
|
||||
readingModeToast?.cancel()
|
||||
readingModeToast = toast(strings[mode])
|
||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
||||
Timber.e("Unknown reading mode: $mode")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -762,7 +778,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
* Forces the user preferred [orientation] on the activity.
|
||||
*/
|
||||
private fun setOrientation(orientation: Int) {
|
||||
val newOrientation = OrientationType.fromPreference(orientation, resources)
|
||||
val newOrientation = OrientationType.fromPreference(orientation)
|
||||
if (newOrientation.flag != requestedOrientation) {
|
||||
requestedOrientation = newOrientation.flag
|
||||
}
|
||||
|
||||
@@ -85,8 +85,7 @@ class HttpPageLoader(
|
||||
* the local cache, otherwise fallbacks to network.
|
||||
*/
|
||||
override fun getPages(): Observable<List<ReaderPage>> {
|
||||
return chapterCache
|
||||
.getPageListFromCache(chapter.chapter)
|
||||
return Observable.fromCallable { chapterCache.getPageListFromCache(chapter.chapter) }
|
||||
.onErrorResumeNext { source.fetchPageList(chapter.chapter) }
|
||||
.map { pages ->
|
||||
pages.mapIndexed { index, page ->
|
||||
|
||||
@@ -1,43 +1,20 @@
|
||||
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),
|
||||
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);
|
||||
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),
|
||||
;
|
||||
|
||||
companion object {
|
||||
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()
|
||||
}
|
||||
fun fromPreference(preference: Int): OrientationType =
|
||||
values().find { it.prefValue == preference } ?: FREE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ 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) : TabbedBottomSheetDialog(activity) {
|
||||
class ReaderSettingsSheet(
|
||||
private val activity: ReaderActivity,
|
||||
showColorFilterSettings: Boolean = false,
|
||||
) : TabbedBottomSheetDialog(activity) {
|
||||
|
||||
private val readingModeSettings = ReaderReadingModeSettings(activity)
|
||||
private val generalSettings = ReaderGeneralSettings(activity)
|
||||
@@ -19,7 +22,7 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : TabbedBottomSh
|
||||
init {
|
||||
val sheetBehavior = BottomSheetBehavior.from(binding.root.parent as ViewGroup)
|
||||
sheetBehavior.isFitToContents = false
|
||||
sheetBehavior.halfExpandedRatio = 0.5f
|
||||
sheetBehavior.halfExpandedRatio = 0.25f
|
||||
|
||||
val filterTabIndex = getTabViews().indexOf(colorFilterSettings)
|
||||
binding.tabs.addOnTabSelectedListener(object : SimpleTabSelectedListener() {
|
||||
@@ -33,13 +36,12 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : TabbedBottomSh
|
||||
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,7 +3,6 @@ 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),
|
||||
@@ -17,11 +16,6 @@ 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
|
||||
|
||||
@@ -235,16 +235,13 @@ class PagerPageHolder(
|
||||
readImageHeaderSubscription = Observable
|
||||
.fromCallable {
|
||||
val stream = streamFn().buffered(16)
|
||||
openStream = stream
|
||||
openStream = process(stream)
|
||||
|
||||
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { isAnimated ->
|
||||
if (viewer.config.dualPageSplit) {
|
||||
openStream = processDualPageSplit(openStream!!)
|
||||
}
|
||||
if (!isAnimated) {
|
||||
initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
|
||||
} else {
|
||||
@@ -257,21 +254,31 @@ class PagerPageHolder(
|
||||
.subscribe({}, {})
|
||||
}
|
||||
|
||||
private fun processDualPageSplit(openStream: InputStream): InputStream {
|
||||
var inputStream = openStream
|
||||
val (isDoublePage, stream) = when (page) {
|
||||
is InsertPage -> Pair(true, inputStream)
|
||||
else -> ImageUtil.isDoublePage(inputStream)
|
||||
private fun process(imageStream: InputStream): InputStream {
|
||||
if (!viewer.config.dualPageSplit) {
|
||||
return imageStream
|
||||
}
|
||||
inputStream = stream
|
||||
|
||||
if (!isDoublePage) return inputStream
|
||||
if (page is InsertPage) {
|
||||
return splitInHalf(imageStream)
|
||||
}
|
||||
|
||||
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 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.LEFT
|
||||
(viewer is R2LPagerViewer || viewer is VerticalPagerViewer) && page !is InsertPage -> ImageUtil.Side.RIGHT
|
||||
viewer !is L2RPagerViewer && page !is InsertPage -> ImageUtil.Side.RIGHT
|
||||
else -> error("We should choose a side!")
|
||||
}
|
||||
|
||||
@@ -282,11 +289,7 @@ class PagerPageHolder(
|
||||
}
|
||||
}
|
||||
|
||||
if (page !is InsertPage) {
|
||||
onPageSplit()
|
||||
}
|
||||
|
||||
return ImageUtil.splitInHalf(inputStream, side)
|
||||
return ImageUtil.splitInHalf(imageStream, side)
|
||||
}
|
||||
|
||||
private fun onPageSplit() {
|
||||
|
||||
@@ -385,7 +385,10 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
||||
}
|
||||
|
||||
fun onPageSplit(currentPage: ReaderPage, newPage: InsertPage) {
|
||||
adapter.onPageSplit(currentPage, newPage, this::class.java)
|
||||
activity.runOnUiThread {
|
||||
// Need to insert on UI thread else images will go blank
|
||||
adapter.onPageSplit(currentPage, newPage, this::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanupPageSplit() {
|
||||
|
||||
@@ -281,22 +281,13 @@ class WebtoonPageHolder(
|
||||
readImageHeaderSubscription = Observable
|
||||
.fromCallable {
|
||||
val stream = streamFn().buffered(16)
|
||||
openStream = stream
|
||||
openStream = process(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
|
||||
@@ -315,6 +306,20 @@ 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,16 +79,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
|
||||
recycler.addOnScrollListener(
|
||||
object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
val position = layoutManager.findLastEndVisibleItemPosition()
|
||||
val item = adapter.items.getOrNull(position)
|
||||
val allowPreload = checkAllowPreload(item as? ReaderPage)
|
||||
if (item != null && currentPage != item) {
|
||||
currentPage = item
|
||||
when (item) {
|
||||
is ReaderPage -> onPageSelected(item, allowPreload)
|
||||
is ChapterTransition -> onTransitionSelected(item)
|
||||
}
|
||||
}
|
||||
onScrolled()
|
||||
|
||||
if (dy < 0) {
|
||||
val firstIndex = layoutManager.findFirstVisibleItemPosition()
|
||||
@@ -243,11 +234,27 @@ 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.RecentSectionItemBinding
|
||||
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
|
||||
import java.util.Date
|
||||
|
||||
class DateSectionItem(val date: Date) : AbstractHeaderItem<DateSectionItem.DateSectionItemHolder>() {
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.recent_section_item
|
||||
return R.layout.section_header_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 = RecentSectionItemBinding.bind(view)
|
||||
private val binding = SectionHeaderItemBinding.bind(view)
|
||||
|
||||
private val now = Date().time
|
||||
|
||||
fun bind(item: DateSectionItem) {
|
||||
binding.sectionText.text = DateUtils.getRelativeTimeSpanString(item.date.time, now, DateUtils.DAY_IN_MILLIS)
|
||||
binding.title.text = DateUtils.getRelativeTimeSpanString(item.date.time, now, DateUtils.DAY_IN_MILLIS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ class HistoryAdapter(controller: HistoryController) :
|
||||
|
||||
init {
|
||||
setDisplayHeadersAtStartUp(true)
|
||||
setStickyHeaders(true)
|
||||
}
|
||||
|
||||
interface OnResumeClickListener {
|
||||
|
||||
@@ -7,7 +7,6 @@ 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
|
||||
@@ -20,7 +19,6 @@ 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
|
||||
@@ -36,13 +34,10 @@ 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,
|
||||
@@ -76,18 +71,16 @@ class HistoryController :
|
||||
return HistoryPresenter()
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = HistoryControllerBinding.inflate(inflater)
|
||||
override fun createBinding(inflater: LayoutInflater) = HistoryControllerBinding.inflate(inflater)
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
|
||||
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,7 +18,6 @@ class UpdatesAdapter(
|
||||
|
||||
init {
|
||||
setDisplayHeadersAtStartUp(true)
|
||||
setStickyHeaders(true)
|
||||
}
|
||||
|
||||
interface OnCoverClickListener {
|
||||
|
||||
@@ -5,7 +5,6 @@ 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
|
||||
@@ -19,7 +18,6 @@ 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
|
||||
@@ -37,13 +35,10 @@ 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,
|
||||
@@ -75,18 +70,21 @@ class UpdatesController :
|
||||
return UpdatesPresenter()
|
||||
}
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = UpdatesControllerBinding.inflate(inflater)
|
||||
override fun createBinding(inflater: LayoutInflater) = UpdatesControllerBinding.inflate(inflater)
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
binding.recycler.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
binding.actionToolbar.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
margin(bottom = true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS)
|
||||
|
||||
// Init RecyclerView and adapter
|
||||
@@ -244,8 +242,7 @@ class UpdatesController :
|
||||
adapter?.currentItems
|
||||
?.filterIsInstance<UpdatesItem>()
|
||||
?.find { it.chapter.id == download.chapter.id }?.let {
|
||||
adapter?.updateItem(it)
|
||||
adapter?.notifyDataSetChanged()
|
||||
adapter?.updateItem(it, it.status)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
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.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.base.activity.BaseThemedActivity
|
||||
import eu.kanade.tachiyomi.util.system.BiometricUtil
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import timber.log.Timber
|
||||
import java.util.Date
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
/**
|
||||
* Blank activity with a BiometricPrompt.
|
||||
*/
|
||||
class BiometricUnlockActivity : AppCompatActivity() {
|
||||
class BiometricUnlockActivity : BaseThemedActivity() {
|
||||
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -27,6 +25,7 @@ class BiometricUnlockActivity : AppCompatActivity() {
|
||||
object : BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
Timber.e(errString.toString())
|
||||
finishAffinity()
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
|
||||
import eu.kanade.tachiyomi.network.PREF_DOH_GOOGLE
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
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.preference.defaultValue
|
||||
import eu.kanade.tachiyomi.util.preference.intListPreference
|
||||
import eu.kanade.tachiyomi.util.preference.onChange
|
||||
@@ -32,9 +34,6 @@ import eu.kanade.tachiyomi.util.preference.switchPreference
|
||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
||||
import eu.kanade.tachiyomi.util.system.powerManager
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||
|
||||
@@ -172,27 +171,18 @@ class SettingsAdvancedController : SettingsController() {
|
||||
|
||||
private fun clearChapterCache() {
|
||||
if (activity == null) return
|
||||
val files = chapterCache.cacheDir.listFiles() ?: return
|
||||
|
||||
var deletedFiles = 0
|
||||
|
||||
Observable.defer { Observable.from(files) }
|
||||
.doOnNext { file ->
|
||||
if (chapterCache.removeFileFromCache(file.name)) {
|
||||
deletedFiles++
|
||||
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)
|
||||
}
|
||||
} 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,9 +24,6 @@ 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
|
||||
|
||||
@@ -35,19 +32,14 @@ abstract class SettingsController : PreferenceController() {
|
||||
var preferenceKey: String? = null
|
||||
val preferences: PreferencesHelper = Injekt.get()
|
||||
val viewScope = MainScope()
|
||||
|
||||
var untilDestroySubscriptions = CompositeSubscription()
|
||||
private set
|
||||
private var themedContext: Context? = null
|
||||
|
||||
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) {
|
||||
view.updatePadding(bottom = view.context.resources.getDimensionPixelSize(R.dimen.action_toolbar_list_padding))
|
||||
listView.clipToPadding = false
|
||||
listView.updatePadding(bottom = view.context.resources.getDimensionPixelSize(R.dimen.action_toolbar_list_padding))
|
||||
}
|
||||
|
||||
listView.applyInsetter {
|
||||
@@ -77,25 +69,31 @@ 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)
|
||||
untilDestroySubscriptions.unsubscribe()
|
||||
themedContext = null
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
val screen = preferenceManager.createPreferenceScreen(getThemedContext())
|
||||
val tv = TypedValue()
|
||||
activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true)
|
||||
themedContext = ContextThemeWrapper(activity, tv.resourceId)
|
||||
|
||||
val screen = preferenceManager.createPreferenceScreen(themedContext)
|
||||
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))
|
||||
@@ -111,7 +109,7 @@ abstract class SettingsController : PreferenceController() {
|
||||
return preferenceScreen?.title?.toString()
|
||||
}
|
||||
|
||||
fun setTitle() {
|
||||
private fun setTitle() {
|
||||
var parentController = parentController
|
||||
while (parentController != null) {
|
||||
if (parentController is BaseController<*> && parentController.getTitle() != null) {
|
||||
@@ -122,16 +120,4 @@ 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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,11 +78,12 @@ class SettingsReaderController : SettingsController() {
|
||||
titleRes = R.string.pref_rotation_type
|
||||
entriesRes = arrayOf(
|
||||
R.string.rotation_free,
|
||||
R.string.rotation_lock,
|
||||
R.string.rotation_portrait,
|
||||
R.string.rotation_landscape,
|
||||
R.string.rotation_force_portrait,
|
||||
R.string.rotation_force_landscape
|
||||
R.string.rotation_force_landscape,
|
||||
)
|
||||
entryValues = arrayOf("1", "2", "3", "4")
|
||||
entryValues = arrayOf("1", "2", "3", "4", "5")
|
||||
defaultValue = "1"
|
||||
summary = "%s"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ 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
|
||||
@@ -33,17 +32,7 @@ class SettingsSearchController :
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 createBinding(inflater: LayoutInflater) = SettingsSearchControllerBinding.inflate(inflater)
|
||||
|
||||
override fun getTitle(): String? {
|
||||
return presenter.query
|
||||
|
||||
@@ -139,8 +139,10 @@ 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,15 +2,18 @@ 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 java.io.IOException
|
||||
|
||||
class CrashLogUtil(private val context: Context) {
|
||||
|
||||
@@ -19,31 +22,44 @@ class CrashLogUtil(private val context: Context) {
|
||||
}
|
||||
|
||||
fun dumpLogs() {
|
||||
try {
|
||||
val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt")
|
||||
Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}")
|
||||
launchIO {
|
||||
try {
|
||||
val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt")
|
||||
Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}").waitFor()
|
||||
file.appendText(getDebugInfo())
|
||||
|
||||
showNotification(file.getUriCompat(context))
|
||||
} catch (e: IOException) {
|
||||
context.toast("Failed to get logs")
|
||||
showNotification(file.getUriCompat(context))
|
||||
} catch (e: Throwable) {
|
||||
withUIContext { context.toast("Failed to get logs") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getDebugInfo(): String {
|
||||
return """
|
||||
App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE})
|
||||
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),
|
||||
|
||||
@@ -157,3 +157,5 @@ private fun shouldUpdateDbChapter(dbChapter: Chapter, sourceChapter: SChapter):
|
||||
dbChapter.date_upload != sourceChapter.date_upload ||
|
||||
dbChapter.chapter_number != sourceChapter.chapter_number
|
||||
}
|
||||
|
||||
class NoChaptersException : Exception()
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package eu.kanade.tachiyomi.util.chapter
|
||||
|
||||
class NoChaptersException : Exception()
|
||||
@@ -1,7 +0,0 @@
|
||||
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,12 +1,17 @@
|
||||
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,
|
||||
@@ -17,10 +22,22 @@ object BiometricUtil {
|
||||
}
|
||||
|
||||
fun isSupported(context: Context): Boolean {
|
||||
return getSupportedAuthenticators(context) != 0
|
||||
return isLegacySecured(context) || getSupportedAuthenticators(context) != 0
|
||||
}
|
||||
|
||||
fun isDeviceCredentialAllowed(context: Context): Boolean {
|
||||
return getSupportedAuthenticators(context) and Authenticators.DEVICE_CREDENTIAL != 0
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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
|
||||
@@ -33,6 +34,7 @@ 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
|
||||
|
||||
@@ -68,10 +70,15 @@ fun Context.toast(text: String?, duration: Int = Toast.LENGTH_SHORT, block: (Toa
|
||||
fun Context.copyToClipboard(label: String, content: String) {
|
||||
if (content.isBlank()) return
|
||||
|
||||
val clipboard = getSystemService<ClipboardManager>()!!
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText(label, content))
|
||||
try {
|
||||
val clipboard = getSystemService<ClipboardManager>()!!
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText(label, content))
|
||||
|
||||
toast(getString(R.string.copied_to_clipboard, content.truncateCenter(50)))
|
||||
toast(getString(R.string.copied_to_clipboard, content.truncateCenter(50)))
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e)
|
||||
toast(R.string.clipboard_copy_error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,24 +160,18 @@ 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.
|
||||
*/
|
||||
|
||||
@@ -77,15 +77,20 @@ object ImageUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the image is a double image (width > height), return the result and original stream
|
||||
* Check whether the image is a double-page spread
|
||||
* @return true if the width is greater than the height
|
||||
*/
|
||||
fun isDoublePage(imageStream: InputStream): Pair<Boolean, InputStream> {
|
||||
fun isDoublePage(imageStream: InputStream): Boolean {
|
||||
imageStream.mark(imageStream.available() + 1)
|
||||
|
||||
val imageBytes = imageStream.readBytes()
|
||||
|
||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
|
||||
|
||||
return Pair(options.outWidth > options.outHeight, ByteArrayInputStream(imageBytes))
|
||||
imageStream.reset()
|
||||
|
||||
return options.outWidth > options.outHeight
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
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,19 +1,21 @@
|
||||
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, tint: Int? = null) {
|
||||
fun ImageView.setVectorCompat(@DrawableRes drawable: Int, @AttrRes tint: Int? = null) {
|
||||
val vector = AppCompatResources.getDrawable(context, drawable)
|
||||
if (tint != null) {
|
||||
vector?.mutate()
|
||||
vector?.setTint(tint)
|
||||
vector?.setTint(context.getResourceColor(tint))
|
||||
}
|
||||
setImageDrawable(vector)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
package eu.kanade.tachiyomi.util.view
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Point
|
||||
import android.view.Gravity
|
||||
import android.view.Menu
|
||||
@@ -9,14 +10,18 @@ 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.
|
||||
@@ -63,7 +68,7 @@ inline fun View.setTooltip(@StringRes stringRes: Int) {
|
||||
inline fun View.popupMenu(
|
||||
@MenuRes menuRes: Int,
|
||||
noinline initMenu: (Menu.() -> Unit)? = null,
|
||||
noinline onMenuItemClick: MenuItem.() -> Boolean
|
||||
noinline onMenuItemClick: MenuItem.() -> Unit
|
||||
): PopupMenu {
|
||||
val popup = PopupMenu(context, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
|
||||
popup.menuInflater.inflate(menuRes, popup.menu)
|
||||
@@ -71,7 +76,50 @@ inline fun View.popupMenu(
|
||||
if (initMenu != null) {
|
||||
popup.menu.initMenu()
|
||||
}
|
||||
popup.setOnMenuItemClickListener { it.onMenuItemClick() }
|
||||
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.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.CommonActionToolbarBinding
|
||||
import eu.kanade.tachiyomi.databinding.ActionToolbarBinding
|
||||
|
||||
/**
|
||||
* A toolbar holding only menu items.
|
||||
@@ -20,25 +20,21 @@ import eu.kanade.tachiyomi.databinding.CommonActionToolbarBinding
|
||||
class ActionToolbar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
FrameLayout(context, attrs) {
|
||||
|
||||
private val binding: CommonActionToolbarBinding
|
||||
|
||||
init {
|
||||
binding = CommonActionToolbarBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
}
|
||||
private val binding = ActionToolbarBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
|
||||
/**
|
||||
* Remove menu items and remove listener.
|
||||
*/
|
||||
fun destroy() {
|
||||
binding.commonActionMenu.menu.clear()
|
||||
binding.commonActionMenu.setOnMenuItemClickListener(null)
|
||||
binding.menu.menu.clear()
|
||||
binding.menu.setOnMenuItemClickListener(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a menu item if found.
|
||||
*/
|
||||
fun findItem(@IdRes itemId: Int): MenuItem? {
|
||||
return binding.commonActionMenu.menu.findItem(itemId)
|
||||
return binding.menu.menu.findItem(itemId)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,14 +42,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.commonActionMenu.menu.size() == 0) {
|
||||
mode.menuInflater.inflate(menuRes, binding.commonActionMenu.menu)
|
||||
binding.commonActionMenu.setOnMenuItemClickListener { listener(it) }
|
||||
if (binding.menu.menu.size() == 0) {
|
||||
mode.menuInflater.inflate(menuRes, binding.menu.menu)
|
||||
binding.menu.setOnMenuItemClickListener { listener(it) }
|
||||
}
|
||||
|
||||
binding.commonActionToolbar.isVisible = true
|
||||
binding.actionToolbar.isVisible = true
|
||||
val bottomAnimation = AnimationUtils.loadAnimation(context, R.anim.enter_from_bottom)
|
||||
binding.commonActionToolbar.startAnimation(bottomAnimation)
|
||||
binding.actionToolbar.startAnimation(bottomAnimation)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,10 +60,10 @@ class ActionToolbar @JvmOverloads constructor(context: Context, attrs: Attribute
|
||||
bottomAnimation.setAnimationListener(
|
||||
object : SimpleAnimationListener() {
|
||||
override fun onAnimationEnd(animation: Animation) {
|
||||
binding.commonActionToolbar.isVisible = false
|
||||
binding.actionToolbar.isVisible = false
|
||||
}
|
||||
}
|
||||
)
|
||||
binding.commonActionToolbar.startAnimation(bottomAnimation)
|
||||
binding.actionToolbar.startAnimation(bottomAnimation)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.setting
|
||||
package eu.kanade.tachiyomi.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
@@ -7,15 +8,21 @@ 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 SpinnerPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
class MaterialSpinnerView @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
|
||||
@@ -30,17 +37,26 @@ class SpinnerPreference @JvmOverloads constructor(context: Context, attrs: Attri
|
||||
}
|
||||
}
|
||||
|
||||
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.SpinnerPreference)
|
||||
val attr = context.obtainStyledAttributes(attrs, R.styleable.MaterialSpinnerView)
|
||||
|
||||
val title = attr.getString(R.styleable.SpinnerPreference_title).orEmpty()
|
||||
val title = attr.getString(R.styleable.MaterialSpinnerView_title).orEmpty()
|
||||
binding.title.text = title
|
||||
|
||||
val entries = (attr.getTextArray(R.styleable.SpinnerPreference_android_entries) ?: emptyArray()).map { it.toString() }
|
||||
val entries = (attr.getTextArray(R.styleable.MaterialSpinnerView_android_entries) ?: emptyArray()).map { it.toString() }
|
||||
this.entries = entries
|
||||
binding.details.text = entries.firstOrNull().orEmpty()
|
||||
|
||||
@@ -48,6 +64,14 @@ class SpinnerPreference @JvmOverloads constructor(context: Context, attrs: Attri
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -118,11 +142,19 @@ class SpinnerPreference @JvmOverloads constructor(context: Context, attrs: Attri
|
||||
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)
|
||||
@@ -1,13 +1,10 @@
|
||||
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.system.getResourceColor
|
||||
import eu.kanade.tachiyomi.util.view.setVectorCompat
|
||||
|
||||
class QuadStateCheckBox @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
AppCompatImageView(context, attrs) {
|
||||
@@ -19,19 +16,11 @@ class QuadStateCheckBox @JvmOverloads constructor(context: Context, attrs: Attri
|
||||
}
|
||||
|
||||
private fun updateDrawable() {
|
||||
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))
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.widget.ViewPagerAdapter
|
||||
|
||||
abstract class TabbedBottomSheetDialog(private val activity: Activity) : BaseBottomSheetDialog(activity) {
|
||||
|
||||
val binding: CommonTabbedSheetBinding = CommonTabbedSheetBinding.inflate(activity.layoutInflater)
|
||||
val binding = CommonTabbedSheetBinding.inflate(activity.layoutInflater)
|
||||
|
||||
init {
|
||||
val adapter = LibrarySettingsSheetAdapter()
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval"
|
||||
android:thicknessRatio="2">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<size
|
||||
android:width="25dp"
|
||||
android:height="25dp" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="?colorAccent" />
|
||||
</shape>
|
||||
7
app/src/main/res/drawable/ic_blank_24dp.xml
Normal file
7
app/src/main/res/drawable/ic_blank_24dp.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<size
|
||||
android:width="24dp"
|
||||
android:height="24dp" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
9
app/src/main/res/drawable/ic_check_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_check_24dp.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
|
||||
</vector>
|
||||
@@ -5,5 +5,5 @@
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:pathData="M22.15,13.85H1.85A1.86,1.86,0,0,1,0,12H0a1.86,1.86,0,0,1,1.85-1.85H22.15A1.86,1.86,0,0,1,24,12h0A1.86,1.86,0,0,1,22.15,13.85Z" />
|
||||
android:pathData="M1.01,7L1,17c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2H3c-1.1,0 -1.99,0.9 -1.99,2zM19,7v10H5V7h14z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:pathData="M17,1.01L7,1c-1.1,0 -1.99,0.9 -1.99,2v18c0,1.1 0.89,2 1.99,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z" />
|
||||
</vector>
|
||||
@@ -2,7 +2,7 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/common_action_toolbar"
|
||||
android:id="@+id/action_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
@@ -25,7 +25,7 @@
|
||||
app:contentInsetStart="8dp">
|
||||
|
||||
<androidx.appcompat.widget.ActionMenuView
|
||||
android:id="@+id/common_action_menu"
|
||||
android:id="@+id/menu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
@@ -7,16 +7,6 @@
|
||||
android:padding="8dp"
|
||||
android:background="?selectableItemBackgroundBorderless">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/download_icon_border"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="2dp"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/border_circle"
|
||||
app:tint="?android:attr/textColorHint"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/download_icon"
|
||||
android:layout_width="match_parent"
|
||||
@@ -32,42 +22,17 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="1dp"
|
||||
android:visibility="gone"
|
||||
app:indicatorColor="?android:attr/textColorHint"
|
||||
app:indicatorInset="0dp"
|
||||
app:indicatorSize="24dp"
|
||||
app:trackThickness="2dp" />
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/download_queued"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:indeterminate="true"
|
||||
android:padding="1dp"
|
||||
android:visibility="gone"
|
||||
android:progress="100"
|
||||
app:indicatorColor="?android:attr/textColorHint"
|
||||
app:indicatorInset="0dp"
|
||||
app:indicatorSize="24dp"
|
||||
app:trackThickness="2dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/downloaded_icon"
|
||||
android:id="@+id/download_status_icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="fitXY"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/ic_check_circle_24dp"
|
||||
app:tint="?android:attr/textColorPrimary"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/error_icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="fitXY"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/ic_error_outline_24dp"
|
||||
app:tint="?attr/colorError"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="@dimen/action_toolbar_list_padding"
|
||||
tools:listitem="@layout/source_main_controller_card_header" />
|
||||
tools:listitem="@layout/section_header_item" />
|
||||
|
||||
</eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:theme="?attr/actionBarTheme"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap" />
|
||||
app:layout_scrollFlags="scroll|enterAlways" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
|
||||
@@ -60,7 +60,8 @@
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary" />
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<eu.kanade.tachiyomi.ui.reader.setting.SpinnerPreference
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/rotation_mode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/rotation_type"
|
||||
app:title="@string/pref_rotation_type" />
|
||||
|
||||
<eu.kanade.tachiyomi.ui.reader.setting.SpinnerPreference
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/background_color"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -15,28 +15,28 @@
|
||||
android:paddingEnd="16dp"
|
||||
android:textAppearance="@style/TextAppearance.Medium.SubHeading" />
|
||||
|
||||
<eu.kanade.tachiyomi.ui.reader.setting.SpinnerPreference
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/pager_nav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/pager_nav"
|
||||
app:title="@string/pref_viewer_nav" />
|
||||
|
||||
<eu.kanade.tachiyomi.ui.reader.setting.SpinnerPreference
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/tapping_inverted"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/invert_tapping_mode"
|
||||
app:title="@string/pref_read_with_tapping_inverted" />
|
||||
|
||||
<eu.kanade.tachiyomi.ui.reader.setting.SpinnerPreference
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/scale_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/image_scale_type"
|
||||
app:title="@string/pref_image_scale_type" />
|
||||
|
||||
<eu.kanade.tachiyomi.ui.reader.setting.SpinnerPreference
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/zoom_start"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<eu.kanade.tachiyomi.ui.reader.setting.SpinnerPreference
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/viewer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user