mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2025-12-21 17:02:31 +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
|
ko_fi: inorichi
|
||||||
|
|||||||
10
.github/ISSUE_TEMPLATE.md
vendored
10
.github/ISSUE_TEMPLATE.md
vendored
@@ -2,9 +2,15 @@
|
|||||||
|
|
||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated to the latest version of the app (stable is v0.10.10)
|
- I have updated:
|
||||||
- I have updated all extensions
|
- 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
|
- 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**
|
**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 acknowledge that:
|
||||||
|
|
||||||
- I have updated to the latest version of the app (stable is v0.10.10)
|
- I have updated:
|
||||||
- I have updated all extensions
|
- 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
|
- 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**
|
**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 acknowledge that:
|
||||||
|
|
||||||
- I have updated to the latest version of the app (stable is v0.10.10)
|
- I have updated:
|
||||||
- I have updated all extensions
|
- 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
|
- 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**
|
**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 }}
|
MD5: ${{ env.APK_MD5 }}
|
||||||
files: |
|
files: |
|
||||||
tachiyomi-${{ env.VERSION_TAG }}.apk
|
tachiyomi-${{ env.VERSION_TAG }}.apk
|
||||||
draft: ${{ github.event.inputs.dry-run != '' }}
|
draft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Autoclose issues
|
- name: Autoclose issues
|
||||||
uses: arkon/issue-closer-action@v3.0
|
uses: arkon/issue-closer-action@v3.1
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
rules: |
|
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
|
||||||
|
|
||||||
Features include:
|
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
|
* Local reading of downloaded manga
|
||||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
* 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
|
* [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)
|
minSdkVersion(AndroidConfig.minSdk)
|
||||||
targetSdkVersion(AndroidConfig.targetSdk)
|
targetSdkVersion(AndroidConfig.targetSdk)
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
versionCode = 57
|
versionCode = 59
|
||||||
versionName = "0.10.10"
|
versionName = "0.10.12"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
@@ -153,7 +153,7 @@ dependencies {
|
|||||||
implementation("com.github.pwittchen:reactivenetwork:0.13.0")
|
implementation("com.github.pwittchen:reactivenetwork:0.13.0")
|
||||||
|
|
||||||
// Network client
|
// Network client
|
||||||
val okhttpVersion = "5.0.0-alpha.2"
|
val okhttpVersion = "4.9.1"
|
||||||
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
|
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
|
||||||
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
|
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
|
||||||
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
|
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
|
||||||
@@ -163,7 +163,7 @@ dependencies {
|
|||||||
implementation("org.conscrypt:conscrypt-android:2.5.1")
|
implementation("org.conscrypt:conscrypt-android:2.5.1")
|
||||||
|
|
||||||
// JSON
|
// 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-json:$kotlinSerializationVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
|
||||||
implementation("com.google.code.gson:gson:2.8.6")
|
implementation("com.google.code.gson:gson:2.8.6")
|
||||||
@@ -174,7 +174,7 @@ dependencies {
|
|||||||
|
|
||||||
// Disk
|
// Disk
|
||||||
implementation("com.jakewharton:disklrucache:2.0.2")
|
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")
|
implementation("com.github.junrar:junrar:7.4.0")
|
||||||
|
|
||||||
// HTML parser
|
// HTML parser
|
||||||
@@ -260,12 +260,12 @@ dependencies {
|
|||||||
|
|
||||||
implementation(kotlin("reflect", version = BuildPluginsVersion.KOTLIN))
|
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-core:$coroutinesVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
|
||||||
|
|
||||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||||
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.6")
|
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:theme="@style/Theme.Tachiyomi.Light"
|
android:theme="@style/Theme.Base"
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.security.BiometricUnlockActivity"
|
android:name=".ui.security.BiometricUnlockActivity"
|
||||||
android:theme="@style/Theme.Splash" />
|
android:theme="@style/Theme.Base" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.webview.WebViewActivity"
|
android:name=".ui.webview.WebViewActivity"
|
||||||
android:configChanges="uiMode|orientation|screenSize" />
|
android:configChanges="uiMode|orientation|screenSize" />
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ open class App : Application(), LifecycleObserver {
|
|||||||
LocaleHelper.updateConfiguration(this, resources.configuration)
|
LocaleHelper.updateConfiguration(this, resources.configuration)
|
||||||
|
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||||
|
|
||||||
|
// Reset Incognito Mode on relaunch
|
||||||
|
preferences.incognitoMode().set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ package eu.kanade.tachiyomi.data.cache
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
import com.github.salomonbrys.kotson.fromJson
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.jakewharton.disklrucache.DiskLruCache
|
import com.jakewharton.disklrucache.DiskLruCache
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
import rx.Observable
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@@ -42,8 +42,7 @@ class ChapterCache(private val context: Context) {
|
|||||||
const val PARAMETER_CACHE_SIZE = 100L * 1024 * 1024
|
const val PARAMETER_CACHE_SIZE = 100L * 1024 * 1024
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Google Json class used for parsing JSON files. */
|
private val json: Json by injectLazy()
|
||||||
private val gson: Gson by injectLazy()
|
|
||||||
|
|
||||||
/** Cache class used for cache management. */
|
/** Cache class used for cache management. */
|
||||||
private val diskCache = DiskLruCache.open(
|
private val diskCache = DiskLruCache.open(
|
||||||
@@ -56,7 +55,7 @@ class ChapterCache(private val context: Context) {
|
|||||||
/**
|
/**
|
||||||
* Returns directory of cache.
|
* Returns directory of cache.
|
||||||
*/
|
*/
|
||||||
val cacheDir: File
|
private val cacheDir: File
|
||||||
get() = diskCache.directory
|
get() = diskCache.directory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,43 +70,19 @@ class ChapterCache(private val context: Context) {
|
|||||||
val readableSize: String
|
val readableSize: String
|
||||||
get() = Formatter.formatFileSize(context, realSize)
|
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.
|
* Get page list from cache.
|
||||||
*
|
*
|
||||||
* @param chapter the chapter.
|
* @param chapter the chapter.
|
||||||
* @return an observable of the list of pages.
|
* @return the list of pages.
|
||||||
*/
|
*/
|
||||||
fun getPageListFromCache(chapter: Chapter): Observable<List<Page>> {
|
fun getPageListFromCache(chapter: Chapter): List<Page> {
|
||||||
return Observable.fromCallable {
|
|
||||||
// Get the key for the chapter.
|
// Get the key for the chapter.
|
||||||
val key = DiskUtil.hashKeyForDisk(getKey(chapter))
|
val key = DiskUtil.hashKeyForDisk(getKey(chapter))
|
||||||
|
|
||||||
// Convert JSON string to list of objects. Throws an exception if snapshot is null
|
// Convert JSON string to list of objects. Throws an exception if snapshot is null
|
||||||
diskCache.get(key).use {
|
return diskCache.get(key).use {
|
||||||
gson.fromJson<List<Page>>(it.getString(0))
|
json.decodeFromString(it.getString(0))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +94,7 @@ class ChapterCache(private val context: Context) {
|
|||||||
*/
|
*/
|
||||||
fun putPageListToCache(chapter: Chapter, pages: List<Page>) {
|
fun putPageListToCache(chapter: Chapter, pages: List<Page>) {
|
||||||
// Convert list of pages to json string.
|
// 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).
|
// Initialize the editor (edits the values for an entry).
|
||||||
var editor: DiskLruCache.Editor? = null
|
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 {
|
private fun getKey(chapter: Chapter): String {
|
||||||
return "${chapter.manga_id}${chapter.url}"
|
return "${chapter.manga_id}${chapter.url}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
|
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
|
||||||
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
||||||
setAutoCancel(false)
|
setAutoCancel(false)
|
||||||
setOngoing(true)
|
|
||||||
setOnlyAlertOnce(true)
|
setOnlyAlertOnce(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +83,6 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
*/
|
*/
|
||||||
fun onProgressChange(download: Download) {
|
fun onProgressChange(download: Download) {
|
||||||
with(progressNotificationBuilder) {
|
with(progressNotificationBuilder) {
|
||||||
// Check if first call.
|
|
||||||
if (!isDownloading) {
|
if (!isDownloading) {
|
||||||
setSmallIcon(android.R.drawable.stat_sys_download)
|
setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
clearActions()
|
clearActions()
|
||||||
@@ -116,6 +114,7 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setProgress(download.pages!!.size, download.downloadedImages, false)
|
setProgress(download.pages!!.size, download.downloadedImages, false)
|
||||||
|
setOngoing(true)
|
||||||
|
|
||||||
show(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
|
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))
|
setContentText(context.getString(R.string.download_notifier_download_paused))
|
||||||
setSmallIcon(R.drawable.ic_pause_24dp)
|
setSmallIcon(R.drawable.ic_pause_24dp)
|
||||||
setProgress(0, 0, false)
|
setProgress(0, 0, false)
|
||||||
|
setOngoing(false)
|
||||||
clearActions()
|
clearActions()
|
||||||
// Open download manager when clicked
|
// Open download manager when clicked
|
||||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class DownloadProvider(private val context: Context) {
|
|||||||
* @param source the source to query.
|
* @param source the source to query.
|
||||||
*/
|
*/
|
||||||
fun findSourceDir(source: Source): UniFile? {
|
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? {
|
fun findMangaDir(manga: Manga, source: Source): UniFile? {
|
||||||
val sourceDir = findSourceDir(source)
|
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? {
|
fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? {
|
||||||
val mangaDir = findMangaDir(manga, source)
|
val mangaDir = findMangaDir(manga, source)
|
||||||
return getValidChapterDirNames(chapter).asSequence()
|
return getValidChapterDirNames(chapter).asSequence()
|
||||||
.mapNotNull { mangaDir?.findFile(it) }
|
.mapNotNull { mangaDir?.findFile(it, true) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ class DownloadProvider(private val context: Context) {
|
|||||||
* @param source the source to query.
|
* @param source the source to query.
|
||||||
*/
|
*/
|
||||||
fun getSourceDirName(source: Source): String {
|
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(
|
return listOf(
|
||||||
getChapterDirName(chapter),
|
getChapterDirName(chapter),
|
||||||
|
|
||||||
|
// TODO: remove this
|
||||||
// Legacy chapter directory name used in v0.9.2 and before
|
// Legacy chapter directory name used in v0.9.2 and before
|
||||||
DiskUtil.buildValidFilename(chapter.name)
|
DiskUtil.buildValidFilename(chapter.name)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
|
|||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private var oauth: OAuth? = null
|
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 {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
@@ -24,21 +21,19 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
|
|||||||
if (oauth == null) {
|
if (oauth == null) {
|
||||||
oauth = myanimelist.loadOAuth()
|
oauth = myanimelist.loadOAuth()
|
||||||
}
|
}
|
||||||
// Refresh access token if null or expired.
|
// Refresh access token if expired
|
||||||
if (oauth!!.isExpired()) {
|
if (oauth != null && oauth!!.isExpired()) {
|
||||||
chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!.refresh_token)).use {
|
chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!.refresh_token)).use {
|
||||||
if (it.isSuccessful) {
|
if (it.isSuccessful) {
|
||||||
setAuth(json.decodeFromString(it.body!!.string()))
|
setAuth(json.decodeFromString(it.body!!.string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw on null auth.
|
|
||||||
if (oauth == null) {
|
if (oauth == null) {
|
||||||
throw Exception("No authentication token")
|
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()
|
val authRequest = originalRequest.newBuilder()
|
||||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ data class OAuth(
|
|||||||
val refresh_token: String,
|
val refresh_token: String,
|
||||||
val access_token: String,
|
val access_token: String,
|
||||||
val token_type: String,
|
val token_type: String,
|
||||||
|
val created_at: Long = System.currentTimeMillis(),
|
||||||
val expires_in: Long
|
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
|
package eu.kanade.tachiyomi.data.updater
|
||||||
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
@@ -11,51 +8,25 @@ import androidx.work.PeriodicWorkRequestBuilder
|
|||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
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.data.updater.github.GithubUpdateChecker
|
||||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
Worker(context, workerParams) {
|
Worker(context, workerParams) {
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override fun doWork() = runBlocking {
|
||||||
return runBlocking {
|
|
||||||
try {
|
try {
|
||||||
val result = GithubUpdateChecker().checkForUpdate()
|
val result = GithubUpdateChecker().checkForUpdate()
|
||||||
|
|
||||||
if (result is UpdateResult.NewUpdate<*>) {
|
if (result is UpdateResult.NewUpdate<*>) {
|
||||||
val url = result.release.downloadLink
|
UpdaterNotifier(context).promptUpdate(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()
|
Result.success()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Result.failure()
|
Result.failure()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) {
|
|
||||||
block()
|
|
||||||
context.notificationManager.notify(Notifications.ID_UPDATER, build())
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "UpdateChecker"
|
private const val TAG = "UpdateChecker"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.data.updater
|
package eu.kanade.tachiyomi.data.updater
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@@ -28,6 +30,27 @@ internal class UpdaterNotifier(private val context: Context) {
|
|||||||
context.notificationManager.notify(id, build())
|
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.
|
* Call when apk download starts.
|
||||||
*
|
*
|
||||||
@@ -63,19 +86,20 @@ internal class UpdaterNotifier(private val context: Context) {
|
|||||||
* @param uri path location of apk.
|
* @param uri path location of apk.
|
||||||
*/
|
*/
|
||||||
fun onDownloadFinished(uri: Uri) {
|
fun onDownloadFinished(uri: Uri) {
|
||||||
|
val installIntent = NotificationHandler.installApkPendingActivity(context, uri)
|
||||||
with(notificationBuilder) {
|
with(notificationBuilder) {
|
||||||
setContentText(context.getString(R.string.update_check_notification_download_complete))
|
setContentText(context.getString(R.string.update_check_notification_download_complete))
|
||||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
setOnlyAlertOnce(false)
|
setOnlyAlertOnce(false)
|
||||||
setProgress(0, 0, false)
|
setProgress(0, 0, false)
|
||||||
// Install action
|
setContentIntent(installIntent)
|
||||||
setContentIntent(NotificationHandler.installApkPendingActivity(context, uri))
|
|
||||||
|
clearActions()
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_system_update_alt_white_24dp,
|
R.drawable.ic_system_update_alt_white_24dp,
|
||||||
context.getString(R.string.action_install),
|
context.getString(R.string.action_install),
|
||||||
NotificationHandler.installApkPendingActivity(context, uri)
|
installIntent
|
||||||
)
|
)
|
||||||
// Cancel action
|
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_close_24dp,
|
R.drawable.ic_close_24dp,
|
||||||
context.getString(R.string.action_cancel),
|
context.getString(R.string.action_cancel),
|
||||||
@@ -96,13 +120,13 @@ internal class UpdaterNotifier(private val context: Context) {
|
|||||||
setSmallIcon(android.R.drawable.stat_sys_warning)
|
setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||||
setOnlyAlertOnce(false)
|
setOnlyAlertOnce(false)
|
||||||
setProgress(0, 0, false)
|
setProgress(0, 0, false)
|
||||||
// Retry action
|
|
||||||
|
clearActions()
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_refresh_24dp,
|
R.drawable.ic_refresh_24dp,
|
||||||
context.getString(R.string.action_retry),
|
context.getString(R.string.action_retry),
|
||||||
UpdaterService.downloadApkPendingService(context, url)
|
UpdaterService.downloadApkPendingService(context, url)
|
||||||
)
|
)
|
||||||
// Cancel action
|
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_close_24dp,
|
R.drawable.ic_close_24dp,
|
||||||
context.getString(R.string.action_cancel),
|
context.getString(R.string.action_cancel),
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ internal object ExtensionLoader {
|
|||||||
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
|
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Timber.w(e, "Extension load error: $extName ($it)")
|
Timber.e(e, "Extension load error: $extName ($it)")
|
||||||
return LoadResult.Error(e)
|
return LoadResult.Error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +1,39 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.activity
|
package eu.kanade.tachiyomi.ui.base.activity
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
||||||
import android.os.Build
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import eu.kanade.tachiyomi.R
|
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 eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
|
||||||
|
|
||||||
abstract class BaseThemedActivity : AppCompatActivity() {
|
abstract class BaseThemedActivity : AppCompatActivity() {
|
||||||
|
|
||||||
val preferences: PreferencesHelper by injectLazy()
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
setTheme(
|
val isDarkMode = when (preferences.themeMode().get()) {
|
||||||
when {
|
ThemeMode.light -> false
|
||||||
isDarkMode -> darkTheme
|
ThemeMode.dark -> true
|
||||||
else -> lightTheme
|
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)
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import timber.log.Timber
|
|||||||
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
||||||
RestoreViewOnCreateController(bundle) {
|
RestoreViewOnCreateController(bundle) {
|
||||||
|
|
||||||
lateinit var binding: VB
|
protected lateinit var binding: VB
|
||||||
|
private set
|
||||||
|
|
||||||
lateinit var viewScope: CoroutineScope
|
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 {
|
abstract fun createBinding(inflater: LayoutInflater): VB
|
||||||
return inflateView(inflater, container)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {}
|
open fun onViewCreated(view: View) {}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.browse
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import com.bluelinelabs.conductor.Controller
|
import com.bluelinelabs.conductor.Controller
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||||
@@ -50,10 +49,7 @@ class BrowseController :
|
|||||||
return resources!!.getString(R.string.browse)
|
return resources!!.getString(R.string.browse)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater) = PagerControllerBinding.inflate(inflater)
|
||||||
binding = PagerControllerBinding.inflate(inflater)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||||
@@ -57,18 +56,16 @@ open class ExtensionController :
|
|||||||
return ExtensionPresenter()
|
return ExtensionPresenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater) = ExtensionControllerBinding.inflate(inflater)
|
||||||
binding = ExtensionControllerBinding.inflate(inflater)
|
|
||||||
|
override fun onViewCreated(view: View) {
|
||||||
|
super.onViewCreated(view)
|
||||||
|
|
||||||
binding.recycler.applyInsetter {
|
binding.recycler.applyInsetter {
|
||||||
type(navigationBars = true) {
|
type(navigationBars = true) {
|
||||||
padding()
|
padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
|
||||||
super.onViewCreated(view)
|
|
||||||
|
|
||||||
binding.swipeRefresh.isRefreshing = true
|
binding.swipeRefresh.isRefreshing = true
|
||||||
binding.swipeRefresh.refreshes()
|
binding.swipeRefresh.refreshes()
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import android.annotation.SuppressLint
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.databinding.SourceMainControllerCardHeaderBinding
|
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
|
||||||
|
|
||||||
class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<*>) :
|
class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<*>) :
|
||||||
FlexibleViewHolder(view, adapter) {
|
FlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
|
private val binding = SectionHeaderItemBinding.bind(view)
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(item: ExtensionGroupItem) {
|
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.
|
* Returns the layout resource of this item.
|
||||||
*/
|
*/
|
||||||
override fun getLayoutRes(): Int {
|
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.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.view.ContextThemeWrapper
|
import androidx.appcompat.view.ContextThemeWrapper
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
@@ -65,15 +64,9 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
|||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater): ExtensionDetailControllerBinding {
|
||||||
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
|
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
|
||||||
binding = ExtensionDetailControllerBinding.inflate(themedInflater)
|
return ExtensionDetailControllerBinding.inflate(themedInflater)
|
||||||
binding.extensionPrefsRecycler.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return binding.root
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createPresenter(): ExtensionDetailsPresenter {
|
override fun createPresenter(): ExtensionDetailsPresenter {
|
||||||
@@ -88,6 +81,12 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
|||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.extensionPrefsRecycler.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val extension = presenter.extension ?: return
|
val extension = presenter.extension ?: return
|
||||||
val context = view.context
|
val context = view.context
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.os.Bundle
|
|||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.view.ContextThemeWrapper
|
import androidx.appcompat.view.ContextThemeWrapper
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.preference.DialogPreference
|
import androidx.preference.DialogPreference
|
||||||
@@ -45,10 +44,9 @@ class SourcePreferencesController(bundle: Bundle? = null) :
|
|||||||
bundleOf(SOURCE_ID to sourceId)
|
bundleOf(SOURCE_ID to sourceId)
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater): SourcePreferencesControllerBinding {
|
||||||
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
|
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
|
||||||
binding = SourcePreferencesControllerBinding.inflate(themedInflater)
|
return SourcePreferencesControllerBinding.inflate(themedInflater)
|
||||||
return binding.root
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createPresenter(): SourcePreferencesPresenter {
|
override fun createPresenter(): SourcePreferencesPresenter {
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.ui.browse.migration.manga
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import dev.chrisbanes.insetter.applyInsetter
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.databinding.MigrationMangaControllerBinding
|
import eu.kanade.tachiyomi.databinding.MigrationMangaControllerBinding
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
@@ -44,14 +44,17 @@ class MigrationMangaController :
|
|||||||
return MigrationMangaPresenter(sourceId)
|
return MigrationMangaPresenter(sourceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater) = MigrationMangaControllerBinding.inflate(inflater)
|
||||||
binding = MigrationMangaControllerBinding.inflate(inflater)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.recycler.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adapter = MigrationMangaAdapter(this)
|
adapter = MigrationMangaAdapter(this)
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
binding.recycler.adapter = adapter
|
binding.recycler.adapter = adapter
|
||||||
|
|||||||
@@ -104,25 +104,23 @@ class SearchPresenter(
|
|||||||
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
|
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
|
||||||
val maxChapterRead = prevMangaChapters
|
val maxChapterRead = prevMangaChapters
|
||||||
.filter { it.read }
|
.filter { it.read }
|
||||||
.maxByOrNull { it.chapter_number }?.chapter_number
|
.maxOfOrNull { it.chapter_number } ?: 0f
|
||||||
val bookmarkedChapters = prevMangaChapters
|
|
||||||
.filter { it.bookmark && it.isRecognizedNumber }
|
|
||||||
.map { it.chapter_number }
|
|
||||||
if (maxChapterRead != null) {
|
|
||||||
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||||
for (chapter in dbChapters) {
|
for (chapter in dbChapters) {
|
||||||
if (chapter.isRecognizedNumber) {
|
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) {
|
if (chapter.chapter_number <= maxChapterRead) {
|
||||||
chapter.read = true
|
chapter.read = true
|
||||||
}
|
}
|
||||||
if (chapter.chapter_number in bookmarkedChapters) {
|
|
||||||
chapter.bookmark = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.insertChapters(dbChapters).executeAsBlocking()
|
db.insertChapters(dbChapters).executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update categories
|
// Update categories
|
||||||
if (migrateCategories) {
|
if (migrateCategories) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import dev.chrisbanes.insetter.applyInsetter
|
import dev.chrisbanes.insetter.applyInsetter
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
@@ -30,18 +29,16 @@ class MigrationSourcesController :
|
|||||||
return MigrationSourcesPresenter()
|
return MigrationSourcesPresenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater) = MigrationSourcesControllerBinding.inflate(inflater)
|
||||||
binding = MigrationSourcesControllerBinding.inflate(inflater)
|
|
||||||
|
override fun onViewCreated(view: View) {
|
||||||
|
super.onViewCreated(view)
|
||||||
|
|
||||||
binding.recycler.applyInsetter {
|
binding.recycler.applyInsetter {
|
||||||
type(navigationBars = true) {
|
type(navigationBars = true) {
|
||||||
padding()
|
padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
|
||||||
super.onViewCreated(view)
|
|
||||||
|
|
||||||
adapter = SourceAdapter(this)
|
adapter = SourceAdapter(this)
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
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.flexibleadapter.items.IFlexible
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.databinding.SourceMainControllerCardHeaderBinding
|
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item that contains the selection header.
|
* Item that contains the selection header.
|
||||||
@@ -18,7 +18,7 @@ class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
|
|||||||
* Returns the layout resource of this item.
|
* Returns the layout resource of this item.
|
||||||
*/
|
*/
|
||||||
override fun getLayoutRes(): Int {
|
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) {
|
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
|
private val binding = SectionHeaderItemBinding.bind(view)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding.title.text = view.context.getString(R.string.migration_selection_prompt)
|
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 android.view.View
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.databinding.SourceMainControllerCardHeaderBinding
|
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
|
||||||
class LangHolder(view: View, adapter: FlexibleAdapter<*>) :
|
class LangHolder(view: View, adapter: FlexibleAdapter<*>) :
|
||||||
FlexibleViewHolder(view, adapter) {
|
FlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
|
private val binding = SectionHeaderItemBinding.bind(view)
|
||||||
|
|
||||||
fun bind(item: LangItem) {
|
fun bind(item: LangItem) {
|
||||||
binding.title.text = LocaleHelper.getSourceDisplayName(item.code, itemView.context)
|
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.
|
* Returns the layout resource of this item.
|
||||||
*/
|
*/
|
||||||
override fun getLayoutRes(): Int {
|
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.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.list.listItems
|
import com.afollestad.materialdialogs.list.listItems
|
||||||
@@ -67,25 +66,16 @@ class SourceController :
|
|||||||
return SourcePresenter()
|
return SourcePresenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun createBinding(inflater: LayoutInflater) = SourceMainControllerBinding.inflate(inflater)
|
||||||
* Initiate the view with [R.layout.source_main_controller].
|
|
||||||
*
|
override fun onViewCreated(view: View) {
|
||||||
* @param inflater used to load the layout xml.
|
super.onViewCreated(view)
|
||||||
* @param container containing parent views.
|
|
||||||
* @return inflated view.
|
|
||||||
*/
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
|
||||||
binding = SourceMainControllerBinding.inflate(inflater)
|
|
||||||
binding.recycler.applyInsetter {
|
binding.recycler.applyInsetter {
|
||||||
type(navigationBars = true) {
|
type(navigationBars = true) {
|
||||||
padding()
|
padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
|
||||||
super.onViewCreated(view)
|
|
||||||
|
|
||||||
adapter = SourceAdapter(this)
|
adapter = SourceAdapter(this)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.databinding.SourceMainControllerCardItemBinding
|
|||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.icon
|
import eu.kanade.tachiyomi.source.icon
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
|
||||||
import eu.kanade.tachiyomi.util.view.setVectorCompat
|
import eu.kanade.tachiyomi.util.view.setVectorCompat
|
||||||
|
|
||||||
class SourceHolder(private val view: View, val adapter: SourceAdapter) :
|
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
|
binding.pin.isVisible = true
|
||||||
if (item.isPinned) {
|
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 {
|
} 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))
|
return BrowseSourcePresenter(args.getLong(SOURCE_ID_KEY), args.getString(SEARCH_QUERY_KEY))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater) = SourceControllerBinding.inflate(inflater)
|
||||||
binding = SourceControllerBinding.inflate(inflater)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(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) {
|
if (router.backstackSize >= 2 && router.backstack[router.backstackSize - 2].controller() is GlobalSearchController) {
|
||||||
router.popController(this)
|
router.popController(this)
|
||||||
} else {
|
} else {
|
||||||
|
nonSubmittedQuery = ""
|
||||||
searchWithQuery("")
|
searchWithQuery("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.content.Context
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.databinding.SourceFilterSheetBinding
|
import eu.kanade.tachiyomi.databinding.SourceFilterSheetBinding
|
||||||
@@ -17,10 +18,10 @@ class SourceFilterSheet(
|
|||||||
onResetClicked: () -> Unit
|
onResetClicked: () -> Unit
|
||||||
) : BaseBottomSheetDialog(activity) {
|
) : BaseBottomSheetDialog(activity) {
|
||||||
|
|
||||||
private var filterNavView: FilterNavigationView
|
private var filterNavView: FilterNavigationView = FilterNavigationView(activity)
|
||||||
|
private val sheetBehavior: BottomSheetBehavior<*>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
filterNavView = FilterNavigationView(activity)
|
|
||||||
filterNavView.onFilterClicked = {
|
filterNavView.onFilterClicked = {
|
||||||
onFilterClicked()
|
onFilterClicked()
|
||||||
this.dismiss()
|
this.dismiss()
|
||||||
@@ -28,13 +29,23 @@ class SourceFilterSheet(
|
|||||||
filterNavView.onResetClicked = onResetClicked
|
filterNavView.onResetClicked = onResetClicked
|
||||||
|
|
||||||
setContentView(filterNavView)
|
setContentView(filterNavView)
|
||||||
|
|
||||||
|
sheetBehavior = BottomSheetBehavior.from(filterNavView.parent as ViewGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun show() {
|
||||||
|
super.show()
|
||||||
|
sheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFilters(items: List<IFlexible<*>>) {
|
fun setFilters(items: List<IFlexible<*>>) {
|
||||||
filterNavView.adapter.updateDataSet(items)
|
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) {
|
SimpleNavigationView(context, attrs) {
|
||||||
|
|
||||||
var onFilterClicked = {}
|
var onFilterClicked = {}
|
||||||
@@ -42,9 +53,12 @@ class SourceFilterSheet(
|
|||||||
|
|
||||||
val adapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter<IFlexible<*>>(null)
|
val adapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter<IFlexible<*>>(null)
|
||||||
.setDisplayHeadersAtStartUp(true)
|
.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 {
|
init {
|
||||||
recycler.adapter = adapter
|
recycler.adapter = adapter
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@@ -50,22 +49,7 @@ open class GlobalSearchController(
|
|||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun createBinding(inflater: LayoutInflater) = GlobalSearchControllerBinding.inflate(inflater)
|
||||||
* Initiate the view with [R.layout.global_search_controller].
|
|
||||||
*
|
|
||||||
* @param inflater used to load the layout xml.
|
|
||||||
* @param container containing parent views.
|
|
||||||
* @return inflated view
|
|
||||||
*/
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
|
||||||
binding = GlobalSearchControllerBinding.inflate(inflater)
|
|
||||||
binding.recycler.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
override fun getTitle(): String? {
|
||||||
return presenter.query
|
return presenter.query
|
||||||
@@ -142,6 +126,12 @@ open class GlobalSearchController(
|
|||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.recycler.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adapter = GlobalSearchAdapter(this)
|
adapter = GlobalSearchAdapter(this)
|
||||||
|
|
||||||
// Create recycler and set adapter.
|
// Create recycler and set adapter.
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.view.LayoutInflater
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@@ -68,21 +67,7 @@ class CategoryController :
|
|||||||
return resources?.getString(R.string.action_edit_categories)
|
return resources?.getString(R.string.action_edit_categories)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun createBinding(inflater: LayoutInflater) = CategoriesControllerBinding.inflate(inflater)
|
||||||
* Returns the view of this controller.
|
|
||||||
*
|
|
||||||
* @param inflater The layout inflater to create the view from XML.
|
|
||||||
* @param container The parent view for this one.
|
|
||||||
*/
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
|
||||||
binding = CategoriesControllerBinding.inflate(inflater)
|
|
||||||
binding.recycler.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after view inflation. Used to initialize the view.
|
* Called after view inflation. Used to initialize the view.
|
||||||
@@ -92,6 +77,12 @@ class CategoryController :
|
|||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.recycler.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adapter = CategoryAdapter(this@CategoryController)
|
adapter = CategoryAdapter(this@CategoryController)
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
binding.recycler.setHasFixedSize(true)
|
binding.recycler.setHasFixedSize(true)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -55,15 +54,7 @@ class DownloadController :
|
|||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater) = DownloadControllerBinding.inflate(inflater)
|
||||||
binding = DownloadControllerBinding.inflate(inflater)
|
|
||||||
binding.recycler.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createPresenter(): DownloadPresenter {
|
override fun createPresenter(): DownloadPresenter {
|
||||||
return DownloadPresenter()
|
return DownloadPresenter()
|
||||||
@@ -76,6 +67,12 @@ class DownloadController :
|
|||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.recycler.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if download queue is empty and update information accordingly.
|
// Check if download queue is empty and update information accordingly.
|
||||||
setInformationView()
|
setInformationView()
|
||||||
|
|
||||||
|
|||||||
@@ -81,15 +81,14 @@ class DownloadHolder(private val view: View, val adapter: DownloadAdapter) :
|
|||||||
|
|
||||||
private fun showPopupMenu(view: View) {
|
private fun showPopupMenu(view: View) {
|
||||||
view.popupMenu(
|
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_top).isVisible = bindingAdapterPosition != 0
|
||||||
findItem(R.id.move_to_bottom).isVisible =
|
findItem(R.id.move_to_bottom).isVisible =
|
||||||
bindingAdapterPosition != adapter.itemCount - 1
|
bindingAdapterPosition != adapter.itemCount - 1
|
||||||
},
|
},
|
||||||
{
|
onMenuItemClick = {
|
||||||
adapter.downloadItemListener.onMenuItemClick(bindingAdapterPosition, this)
|
adapter.downloadItemListener.onMenuItemClick(bindingAdapterPosition, this)
|
||||||
true
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.graphics.drawable.DrawableCompat
|
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.BehaviorRelay
|
||||||
import com.jakewharton.rxrelay.PublishRelay
|
import com.jakewharton.rxrelay.PublishRelay
|
||||||
import com.tfcporciuncula.flow.Preference
|
import com.tfcporciuncula.flow.Preference
|
||||||
|
import dev.chrisbanes.insetter.applyInsetter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
@@ -163,14 +163,17 @@ class LibraryController(
|
|||||||
return LibraryPresenter()
|
return LibraryPresenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater) = LibraryControllerBinding.inflate(inflater)
|
||||||
binding = LibraryControllerBinding.inflate(inflater)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.actionToolbar.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
margin(bottom = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adapter = LibraryAdapter(this)
|
adapter = LibraryAdapter(this)
|
||||||
binding.libraryPager.adapter = adapter
|
binding.libraryPager.adapter = adapter
|
||||||
binding.libraryPager.pageSelections()
|
binding.libraryPager.pageSelections()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import androidx.core.view.ViewCompat
|
|||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.marginTop
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceDialogController
|
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.ui.recent.updates.UpdatesController
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
|
import eu.kanade.tachiyomi.util.system.InternalResourceHelper
|
||||||
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
@@ -98,23 +101,27 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
|||||||
padding(left = true, top = true, right = true)
|
padding(left = true, top = true, right = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.bottomNav.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binding.rootFab.applyInsetter {
|
binding.rootFab.applyInsetter {
|
||||||
type(navigationBars = true) {
|
type(navigationBars = true) {
|
||||||
margin()
|
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 ->
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
|
||||||
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
|
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
|
||||||
// Keep scrim on light theme if windowLightNavigationBar is not available
|
window.navigationBarColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 || isDarkMode) {
|
!InternalResourceHelper.getBoolean(this, "config_navBarNeedsScrim", true)
|
||||||
window.navigationBarColor = Color.TRANSPARENT
|
) {
|
||||||
|
Color.TRANSPARENT
|
||||||
|
} else {
|
||||||
|
// Set navbar scrim 70% of navigationBarColor
|
||||||
|
getResourceColor(android.R.attr.navigationBarColor, 0.7F)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
insets
|
insets
|
||||||
@@ -123,6 +130,15 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
|||||||
tabAnimator = ViewHeightAnimator(binding.tabs, 0L)
|
tabAnimator = ViewHeightAnimator(binding.tabs, 0L)
|
||||||
bottomNavAnimator = ViewHeightAnimator(binding.bottomNav)
|
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
|
// Set behavior of bottom nav
|
||||||
preferences.hideBottomBar()
|
preferences.hideBottomBar()
|
||||||
.asImmediateFlow { setBottomNavBehaviorOnScroll() }
|
.asImmediateFlow { setBottomNavBehaviorOnScroll() }
|
||||||
@@ -146,6 +162,9 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
|||||||
val controller = router.getControllerWithTag(id.toString()) as? LibraryController
|
val controller = router.getControllerWithTag(id.toString()) as? LibraryController
|
||||||
controller?.showSettingsSheet()
|
controller?.showSettingsSheet()
|
||||||
}
|
}
|
||||||
|
R.id.nav_updates -> {
|
||||||
|
router.pushController(DownloadController().withFadeTransaction())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@@ -439,7 +458,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
|||||||
fun fixViewToBottom(view: View) {
|
fun fixViewToBottom(view: View) {
|
||||||
val listener = AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
|
val listener = AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
|
||||||
val maxAbsOffset = appBarLayout.measuredHeight - binding.tabs.measuredHeight
|
val maxAbsOffset = appBarLayout.measuredHeight - binding.tabs.measuredHeight
|
||||||
view.translationY = -maxAbsOffset - verticalOffset.toFloat()
|
view.translationY = -maxAbsOffset - verticalOffset.toFloat() + appBarLayout.marginTop
|
||||||
}
|
}
|
||||||
binding.appbar.addOnOffsetChangedListener(listener)
|
binding.appbar.addOnOffsetChangedListener(listener)
|
||||||
fixedViewsToBottom[view] = listener
|
fixedViewsToBottom[view] = listener
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.graphics.blue
|
import androidx.core.graphics.blue
|
||||||
@@ -199,18 +198,21 @@ class MangaController :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater) = MangaControllerBinding.inflate(inflater)
|
||||||
binding = MangaControllerBinding.inflate(inflater)
|
|
||||||
|
override fun onViewCreated(view: View) {
|
||||||
|
super.onViewCreated(view)
|
||||||
|
|
||||||
binding.recycler.applyInsetter {
|
binding.recycler.applyInsetter {
|
||||||
type(navigationBars = true) {
|
type(navigationBars = true) {
|
||||||
padding()
|
padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return binding.root
|
binding.actionToolbar.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
margin(bottom = true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
|
||||||
super.onViewCreated(view)
|
|
||||||
|
|
||||||
if (manga == null || source == null) return
|
if (manga == null || source == null) return
|
||||||
|
|
||||||
@@ -725,8 +727,7 @@ class MangaController :
|
|||||||
|
|
||||||
fun onChapterDownloadUpdate(download: Download) {
|
fun onChapterDownloadUpdate(download: Download) {
|
||||||
chaptersAdapter?.currentItems?.find { it.id == download.chapter.id }?.let {
|
chaptersAdapter?.currentItems?.find { it.id == download.chapter.id }?.let {
|
||||||
chaptersAdapter?.updateItem(it)
|
chaptersAdapter?.updateItem(it, it.status)
|
||||||
chaptersAdapter?.notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -850,7 +851,6 @@ class MangaController :
|
|||||||
binding.actionToolbar.findItem(R.id.action_mark_as_unread)?.isVisible = chapters.all { it.chapter.read }
|
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
|
// Hide FAB to avoid interfering with the bottom action toolbar
|
||||||
// actionFab?.hide()
|
|
||||||
actionFab?.isVisible = false
|
actionFab?.isVisible = false
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -882,10 +882,6 @@ class MangaController :
|
|||||||
chaptersAdapter?.clearSelection()
|
chaptersAdapter?.clearSelection()
|
||||||
selectedChapters.clear()
|
selectedChapters.clear()
|
||||||
actionMode = null
|
actionMode = null
|
||||||
|
|
||||||
// TODO: there seems to be a bug in MaterialComponents where the [ExtendedFloatingActionButton]
|
|
||||||
// fails to show up properly
|
|
||||||
// actionFab?.show()
|
|
||||||
actionFab?.isVisible = true
|
actionFab?.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1007,10 +1003,17 @@ class MangaController :
|
|||||||
|
|
||||||
// OVERFLOW MENU DIALOGS
|
// OVERFLOW MENU DIALOGS
|
||||||
|
|
||||||
private fun getUnreadChaptersSorted() = presenter.chapters
|
private fun getUnreadChaptersSorted(): List<ChapterItem> {
|
||||||
|
val chapters = presenter.chapters
|
||||||
|
.sortedWith(presenter.getChapterSort())
|
||||||
.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }
|
.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }
|
||||||
.distinctBy { it.name }
|
.distinctBy { it.name }
|
||||||
.sortedByDescending { it.source_order }
|
return if (presenter.sortDescending()) {
|
||||||
|
chapters.reversed()
|
||||||
|
} else {
|
||||||
|
chapters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun downloadChapters(choice: Int) {
|
private fun downloadChapters(choice: Int) {
|
||||||
val chaptersToDownload = when (choice) {
|
val chaptersToDownload = when (choice) {
|
||||||
|
|||||||
@@ -429,7 +429,11 @@ class MangaPresenter(
|
|||||||
observable = observable.filter { !it.bookmark }
|
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()) {
|
Manga.SORTING_SOURCE -> when (sortDescending()) {
|
||||||
true -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) }
|
true -> { c1, c2 -> c1.source_order.compareTo(c2.source_order) }
|
||||||
false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
|
false -> { c1, c2 -> c2.source_order.compareTo(c1.source_order) }
|
||||||
@@ -444,8 +448,6 @@ class MangaPresenter(
|
|||||||
}
|
}
|
||||||
else -> throw NotImplementedError("Unimplemented sorting method")
|
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.
|
* Returns the next unread chapter or null if everything is read.
|
||||||
*/
|
*/
|
||||||
fun getNextUnreadChapter(): ChapterItem? {
|
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.view.LayoutInflater
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.databinding.ChapterDownloadViewBinding
|
import eu.kanade.tachiyomi.databinding.ChapterDownloadViewBinding
|
||||||
|
import eu.kanade.tachiyomi.util.view.setVectorCompat
|
||||||
|
|
||||||
class ChapterDownloadView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
class ChapterDownloadView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||||
FrameLayout(context, attrs) {
|
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 state = Download.State.NOT_DOWNLOADED
|
||||||
private var progress = 0
|
private var progress = 0
|
||||||
|
|
||||||
private var downloadIconAnimator: ObjectAnimator? = null
|
private var downloadIconAnimator: ObjectAnimator? = null
|
||||||
private var isAnimating = false
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding = ChapterDownloadViewBinding.inflate(LayoutInflater.from(context), this, false)
|
|
||||||
addView(binding.root)
|
addView(binding.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setState(state: Download.State, progress: Int = 0) {
|
fun setState(state: Download.State, progress: Int = 0) {
|
||||||
val isDirty = this.state.value != state.value || this.progress != progress
|
val isDirty = this.state.value != state.value || this.progress != progress
|
||||||
|
|
||||||
this.state = state
|
|
||||||
this.progress = progress
|
|
||||||
|
|
||||||
if (isDirty) {
|
if (isDirty) {
|
||||||
updateLayout()
|
updateLayout(state, progress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateLayout() {
|
private fun updateLayout(state: Download.State, progress: Int) {
|
||||||
binding.downloadIconBorder.isVisible = state == Download.State.NOT_DOWNLOADED
|
binding.downloadIcon.isVisible = state == Download.State.NOT_DOWNLOADED ||
|
||||||
|
state == Download.State.DOWNLOADING || state == Download.State.QUEUE
|
||||||
binding.downloadIcon.isVisible = state == Download.State.NOT_DOWNLOADED || state == Download.State.DOWNLOADING
|
if (state == Download.State.DOWNLOADING || state == Download.State.QUEUE) {
|
||||||
if (state == Download.State.DOWNLOADING) {
|
if (downloadIconAnimator == null) {
|
||||||
if (!isAnimating) {
|
|
||||||
downloadIconAnimator =
|
downloadIconAnimator =
|
||||||
ObjectAnimator.ofFloat(binding.downloadIcon, "alpha", 1f, 0f).apply {
|
ObjectAnimator.ofFloat(binding.downloadIcon, "alpha", 1f, 0f).apply {
|
||||||
duration = 1000
|
duration = 1000
|
||||||
@@ -49,22 +45,36 @@ class ChapterDownloadView @JvmOverloads constructor(context: Context, attrs: Att
|
|||||||
repeatMode = ObjectAnimator.REVERSE
|
repeatMode = ObjectAnimator.REVERSE
|
||||||
}
|
}
|
||||||
downloadIconAnimator?.start()
|
downloadIconAnimator?.start()
|
||||||
isAnimating = true
|
|
||||||
}
|
}
|
||||||
} else {
|
downloadIconAnimator?.currentPlayTime = System.currentTimeMillis() % 2000
|
||||||
|
} else if (downloadIconAnimator != null) {
|
||||||
downloadIconAnimator?.cancel()
|
downloadIconAnimator?.cancel()
|
||||||
|
downloadIconAnimator = null
|
||||||
binding.downloadIcon.alpha = 1f
|
binding.downloadIcon.alpha = 1f
|
||||||
isAnimating = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.downloadQueued.isVisible = state == Download.State.QUEUE
|
|
||||||
|
|
||||||
binding.downloadProgress.isVisible = state == Download.State.DOWNLOADING ||
|
binding.downloadProgress.isVisible = state == Download.State.DOWNLOADING ||
|
||||||
(state == Download.State.QUEUE && progress > 0)
|
state == Download.State.NOT_DOWNLOADED || state == Download.State.QUEUE
|
||||||
binding.downloadProgress.progress = progress
|
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) {
|
||||||
binding.errorIcon.isVisible = 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = state
|
||||||
|
this.progress = progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.ui.manga.chapter
|
package eu.kanade.tachiyomi.ui.manga.chapter
|
||||||
|
|
||||||
import android.text.SpannableString
|
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.style.ForegroundColorSpan
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.text.buildSpannedString
|
||||||
|
import androidx.core.text.color
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
@@ -59,8 +59,10 @@ class ChapterHolder(
|
|||||||
descriptions.add(adapter.dateFormat.format(Date(chapter.date_upload)))
|
descriptions.add(adapter.dateFormat.format(Date(chapter.date_upload)))
|
||||||
}
|
}
|
||||||
if (!chapter.read && chapter.last_page_read > 0) {
|
if (!chapter.read && chapter.last_page_read > 0) {
|
||||||
val lastPageRead = SpannableString(itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1)).apply {
|
val lastPageRead = buildSpannedString {
|
||||||
setSpan(ForegroundColorSpan(adapter.readColor), 0, length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE)
|
color(adapter.readColor) {
|
||||||
|
append(itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
descriptions.add(lastPageRead)
|
descriptions.add(lastPageRead)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,16 +51,12 @@ class ChaptersSettingsSheet(
|
|||||||
|
|
||||||
private fun showPopupMenu(view: View) {
|
private fun showPopupMenu(view: View) {
|
||||||
view.popupMenu(
|
view.popupMenu(
|
||||||
R.menu.default_chapter_filter,
|
menuRes = R.menu.default_chapter_filter,
|
||||||
{
|
onMenuItemClick = {
|
||||||
},
|
when (itemId) {
|
||||||
{
|
|
||||||
when (this.itemId) {
|
|
||||||
R.id.set_as_default -> {
|
R.id.set_as_default -> {
|
||||||
SetChapterSettingsDialog(presenter.manga).showDialog(router)
|
SetChapterSettingsDialog(presenter.manga).showDialog(router)
|
||||||
true
|
|
||||||
}
|
}
|
||||||
else -> true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ open class BaseChapterHolder(
|
|||||||
},
|
},
|
||||||
onMenuItemClick = {
|
onMenuItemClick = {
|
||||||
adapter.clickListener.deleteChapter(position)
|
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.DialogController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
|
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsController
|
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.launchNow
|
||||||
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
||||||
import eu.kanade.tachiyomi.util.preference.onClick
|
import eu.kanade.tachiyomi.util.preference.onClick
|
||||||
@@ -78,15 +79,6 @@ class AboutController : SettingsController() {
|
|||||||
openInBrowser(url)
|
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 {
|
preferenceCategory {
|
||||||
preference {
|
preference {
|
||||||
@@ -97,6 +89,14 @@ class AboutController : SettingsController() {
|
|||||||
onClick { openInBrowser(it) }
|
onClick { openInBrowser(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
preference {
|
||||||
|
key = "pref_about_facebook"
|
||||||
|
title = "Facebook"
|
||||||
|
"https://facebook.com/tachiyomiorg".also {
|
||||||
|
summary = it
|
||||||
|
onClick { openInBrowser(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
preference {
|
preference {
|
||||||
key = "pref_about_twitter"
|
key = "pref_about_twitter"
|
||||||
title = "Twitter"
|
title = "Twitter"
|
||||||
@@ -116,15 +116,7 @@ class AboutController : SettingsController() {
|
|||||||
preference {
|
preference {
|
||||||
key = "pref_about_github"
|
key = "pref_about_github"
|
||||||
title = "GitHub"
|
title = "GitHub"
|
||||||
"https://github.com/tachiyomiorg/tachiyomi".also {
|
"https://github.com/tachiyomiorg".also {
|
||||||
summary = it
|
|
||||||
onClick { openInBrowser(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preference {
|
|
||||||
key = "pref_about_label_extensions"
|
|
||||||
titleRes = R.string.label_extensions
|
|
||||||
"https://github.com/tachiyomiorg/tachiyomi-extensions".also {
|
|
||||||
summary = it
|
summary = it
|
||||||
onClick { openInBrowser(it) }
|
onClick { openInBrowser(it) }
|
||||||
}
|
}
|
||||||
@@ -138,6 +130,7 @@ class AboutController : SettingsController() {
|
|||||||
.withAboutIconShown(false)
|
.withAboutIconShown(false)
|
||||||
.withAboutVersionShown(false)
|
.withAboutVersionShown(false)
|
||||||
.withLicenseShown(true)
|
.withLicenseShown(true)
|
||||||
|
.withEdgeToEdge(true)
|
||||||
.start(activity!!)
|
.start(activity!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,6 +143,11 @@ class AboutController : SettingsController() {
|
|||||||
private fun checkVersion() {
|
private fun checkVersion() {
|
||||||
if (activity == null) return
|
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)
|
activity?.toast(R.string.update_check_look_for_updates)
|
||||||
|
|
||||||
launchNow {
|
launchNow {
|
||||||
@@ -201,20 +199,11 @@ class AboutController : SettingsController() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun copyDebugInfo() {
|
private fun copyDebugInfo() {
|
||||||
val deviceInfo =
|
activity?.let {
|
||||||
"""
|
val deviceInfo = CrashLogUtil(it).getDebugInfo()
|
||||||
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?.copyToClipboard("Debug information", deviceInfo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getFormattedBuildTime(): String {
|
private fun getFormattedBuildTime(): String {
|
||||||
return try {
|
return try {
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package eu.kanade.tachiyomi.ui.more
|
package eu.kanade.tachiyomi.ui.more
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.R
|
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.preference.titleRes
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
|
import rx.Observable
|
||||||
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import rx.subscriptions.CompositeSubscription
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||||
|
|
||||||
@@ -39,6 +46,9 @@ class MoreController :
|
|||||||
private var isDownloading: Boolean = false
|
private var isDownloading: Boolean = false
|
||||||
private var downloadQueueSize: Int = 0
|
private var downloadQueueSize: Int = 0
|
||||||
|
|
||||||
|
private var untilDestroySubscriptions = CompositeSubscription()
|
||||||
|
private set
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
||||||
titleRes = R.string.label_more
|
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) {
|
private fun initDownloadQueueSummary(preference: Preference) {
|
||||||
// Handle running/paused status change
|
// Handle running/paused status change
|
||||||
DownloadService.runningRelay
|
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) :
|
private class MoreHeaderPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||||
Preference(context, attrs) {
|
Preference(context, attrs) {
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import android.graphics.Bitmap
|
|||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
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.defaultBar
|
||||||
import eu.kanade.tachiyomi.util.view.hideBar
|
import eu.kanade.tachiyomi.util.view.hideBar
|
||||||
import eu.kanade.tachiyomi.util.view.isDefaultBar
|
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.setTooltip
|
||||||
import eu.kanade.tachiyomi.util.view.showBar
|
import eu.kanade.tachiyomi.util.view.showBar
|
||||||
import eu.kanade.tachiyomi.widget.SimpleAnimationListener
|
import eu.kanade.tachiyomi.widget.SimpleAnimationListener
|
||||||
@@ -357,8 +357,12 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
|||||||
setTooltip(R.string.viewer)
|
setTooltip(R.string.viewer)
|
||||||
|
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
val newReadingMode =
|
popupMenu(
|
||||||
ReadingModeType.getNextReadingMode(presenter.getMangaViewer(resolveDefault = false))
|
items = ReadingModeType.values().map { it.prefValue to it.stringRes },
|
||||||
|
selectedItemId = presenter.getMangaViewer(resolveDefault = false),
|
||||||
|
) {
|
||||||
|
val newReadingMode = ReadingModeType.fromPreference(itemId)
|
||||||
|
|
||||||
presenter.setMangaViewer(newReadingMode.prefValue)
|
presenter.setMangaViewer(newReadingMode.prefValue)
|
||||||
|
|
||||||
menuToggleToast?.cancel()
|
menuToggleToast?.cancel()
|
||||||
@@ -367,14 +371,18 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Rotation
|
// Rotation
|
||||||
with(binding.actionRotation) {
|
with(binding.actionRotation) {
|
||||||
setTooltip(R.string.pref_rotation_type)
|
setTooltip(R.string.pref_rotation_type)
|
||||||
|
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
val newOrientation =
|
popupMenu(
|
||||||
OrientationType.getNextOrientation(preferences.rotation().get(), resources)
|
items = OrientationType.values().map { it.prefValue to it.stringRes },
|
||||||
|
selectedItemId = preferences.rotation().get(),
|
||||||
|
) {
|
||||||
|
val newOrientation = OrientationType.fromPreference(itemId)
|
||||||
|
|
||||||
preferences.rotation().set(newOrientation.prefValue)
|
preferences.rotation().set(newOrientation.prefValue)
|
||||||
setOrientation(newOrientation.flag)
|
setOrientation(newOrientation.flag)
|
||||||
@@ -383,6 +391,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
|||||||
menuToggleToast = toast(newOrientation.stringRes)
|
menuToggleToast = toast(newOrientation.stringRes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
preferences.rotation().asImmediateFlow { updateRotationShortcut(it) }
|
preferences.rotation().asImmediateFlow { updateRotationShortcut(it) }
|
||||||
.launchIn(lifecycleScope)
|
.launchIn(lifecycleScope)
|
||||||
|
|
||||||
@@ -414,11 +423,16 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
|||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
ReaderSettingsSheet(this@ReaderActivity).show()
|
ReaderSettingsSheet(this@ReaderActivity).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setOnLongClickListener {
|
||||||
|
ReaderSettingsSheet(this@ReaderActivity, showColorFilterSettings = true).show()
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateRotationShortcut(preference: Int) {
|
private fun updateRotationShortcut(preference: Int) {
|
||||||
val orientation = OrientationType.fromPreference(preference, resources)
|
val orientation = OrientationType.fromPreference(preference)
|
||||||
binding.actionRotation.setImageResource(orientation.iconRes)
|
binding.actionRotation.setImageResource(orientation.iconRes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,10 +568,12 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showReadingModeToast(mode: Int) {
|
private fun showReadingModeToast(mode: Int) {
|
||||||
|
try {
|
||||||
val strings = resources.getStringArray(R.array.viewers_selector)
|
val strings = resources.getStringArray(R.array.viewers_selector)
|
||||||
readingModeToast?.cancel()
|
readingModeToast?.cancel()
|
||||||
readingModeToast = toast(strings[mode]) {
|
readingModeToast = toast(strings[mode])
|
||||||
it.setGravity(Gravity.CENTER_VERTICAL or Gravity.CENTER_HORIZONTAL, 0, 0)
|
} 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.
|
* Forces the user preferred [orientation] on the activity.
|
||||||
*/
|
*/
|
||||||
private fun setOrientation(orientation: Int) {
|
private fun setOrientation(orientation: Int) {
|
||||||
val newOrientation = OrientationType.fromPreference(orientation, resources)
|
val newOrientation = OrientationType.fromPreference(orientation)
|
||||||
if (newOrientation.flag != requestedOrientation) {
|
if (newOrientation.flag != requestedOrientation) {
|
||||||
requestedOrientation = newOrientation.flag
|
requestedOrientation = newOrientation.flag
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,8 +85,7 @@ class HttpPageLoader(
|
|||||||
* the local cache, otherwise fallbacks to network.
|
* the local cache, otherwise fallbacks to network.
|
||||||
*/
|
*/
|
||||||
override fun getPages(): Observable<List<ReaderPage>> {
|
override fun getPages(): Observable<List<ReaderPage>> {
|
||||||
return chapterCache
|
return Observable.fromCallable { chapterCache.getPageListFromCache(chapter.chapter) }
|
||||||
.getPageListFromCache(chapter.chapter)
|
|
||||||
.onErrorResumeNext { source.fetchPageList(chapter.chapter) }
|
.onErrorResumeNext { source.fetchPageList(chapter.chapter) }
|
||||||
.map { pages ->
|
.map { pages ->
|
||||||
pages.mapIndexed { index, page ->
|
pages.mapIndexed { index, page ->
|
||||||
|
|||||||
@@ -1,43 +1,20 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.setting
|
package eu.kanade.tachiyomi.ui.reader.setting
|
||||||
|
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.content.res.Resources
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
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) {
|
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),
|
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),
|
PORTRAIT(2, ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT, R.string.rotation_portrait, R.drawable.ic_stay_current_portrait_24dp),
|
||||||
LOCKED_LANDSCAPE(2, ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, R.string.rotation_lock, R.drawable.ic_screen_lock_rotation_24dp),
|
LANDSCAPE(3, ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, R.string.rotation_landscape, R.drawable.ic_stay_current_landscape_24dp),
|
||||||
PORTRAIT(3, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, R.string.rotation_force_portrait, R.drawable.ic_screen_lock_portrait_24dp),
|
LOCKED_PORTRAIT(4, 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);
|
LOCKED_LANDSCAPE(5, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, R.string.rotation_force_landscape, R.drawable.ic_screen_lock_landscape_24dp),
|
||||||
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromPreference(preference: Int, resources: Resources): OrientationType = when (preference) {
|
fun fromPreference(preference: Int): OrientationType =
|
||||||
2 -> {
|
values().find { it.prefValue == preference } ?: FREE
|
||||||
val currentOrientation = resources.configuration.orientation
|
|
||||||
if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) {
|
|
||||||
LOCKED_PORTRAIT
|
|
||||||
} else {
|
|
||||||
LOCKED_LANDSCAPE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3 -> PORTRAIT
|
|
||||||
4 -> LANDSCAPE
|
|
||||||
else -> FREE
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getNextOrientation(preference: Int, resources: Resources): OrientationType {
|
|
||||||
val current = if (preference == 2) {
|
|
||||||
// Avoid issue due to 2 types having the same prefValue
|
|
||||||
LOCKED_LANDSCAPE
|
|
||||||
} else {
|
|
||||||
fromPreference(preference, resources)
|
|
||||||
}
|
|
||||||
return current.next()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
|||||||
import eu.kanade.tachiyomi.widget.SimpleTabSelectedListener
|
import eu.kanade.tachiyomi.widget.SimpleTabSelectedListener
|
||||||
import eu.kanade.tachiyomi.widget.sheet.TabbedBottomSheetDialog
|
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 readingModeSettings = ReaderReadingModeSettings(activity)
|
||||||
private val generalSettings = ReaderGeneralSettings(activity)
|
private val generalSettings = ReaderGeneralSettings(activity)
|
||||||
@@ -19,7 +22,7 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : TabbedBottomSh
|
|||||||
init {
|
init {
|
||||||
val sheetBehavior = BottomSheetBehavior.from(binding.root.parent as ViewGroup)
|
val sheetBehavior = BottomSheetBehavior.from(binding.root.parent as ViewGroup)
|
||||||
sheetBehavior.isFitToContents = false
|
sheetBehavior.isFitToContents = false
|
||||||
sheetBehavior.halfExpandedRatio = 0.5f
|
sheetBehavior.halfExpandedRatio = 0.25f
|
||||||
|
|
||||||
val filterTabIndex = getTabViews().indexOf(colorFilterSettings)
|
val filterTabIndex = getTabViews().indexOf(colorFilterSettings)
|
||||||
binding.tabs.addOnTabSelectedListener(object : SimpleTabSelectedListener() {
|
binding.tabs.addOnTabSelectedListener(object : SimpleTabSelectedListener() {
|
||||||
@@ -33,13 +36,12 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : TabbedBottomSh
|
|||||||
if (activity.menuVisible != !isFilterTab) {
|
if (activity.menuVisible != !isFilterTab) {
|
||||||
activity.setMenuVisibility(!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(
|
override fun getTabViews() = listOf(
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.reader.setting
|
|||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
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) {
|
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),
|
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 {
|
companion object {
|
||||||
fun fromPreference(preference: Int): ReadingModeType = values().find { it.prefValue == preference } ?: DEFAULT
|
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 {
|
fun isPagerType(preference: Int): Boolean {
|
||||||
val mode = fromPreference(preference)
|
val mode = fromPreference(preference)
|
||||||
return mode == LEFT_TO_RIGHT || mode == RIGHT_TO_LEFT || mode == VERTICAL
|
return mode == LEFT_TO_RIGHT || mode == RIGHT_TO_LEFT || mode == VERTICAL
|
||||||
|
|||||||
@@ -235,16 +235,13 @@ class PagerPageHolder(
|
|||||||
readImageHeaderSubscription = Observable
|
readImageHeaderSubscription = Observable
|
||||||
.fromCallable {
|
.fromCallable {
|
||||||
val stream = streamFn().buffered(16)
|
val stream = streamFn().buffered(16)
|
||||||
openStream = stream
|
openStream = process(stream)
|
||||||
|
|
||||||
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
|
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnNext { isAnimated ->
|
.doOnNext { isAnimated ->
|
||||||
if (viewer.config.dualPageSplit) {
|
|
||||||
openStream = processDualPageSplit(openStream!!)
|
|
||||||
}
|
|
||||||
if (!isAnimated) {
|
if (!isAnimated) {
|
||||||
initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
|
initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
|
||||||
} else {
|
} else {
|
||||||
@@ -257,21 +254,31 @@ class PagerPageHolder(
|
|||||||
.subscribe({}, {})
|
.subscribe({}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processDualPageSplit(openStream: InputStream): InputStream {
|
private fun process(imageStream: InputStream): InputStream {
|
||||||
var inputStream = openStream
|
if (!viewer.config.dualPageSplit) {
|
||||||
val (isDoublePage, stream) = when (page) {
|
return imageStream
|
||||||
is InsertPage -> Pair(true, inputStream)
|
|
||||||
else -> ImageUtil.isDoublePage(inputStream)
|
|
||||||
}
|
}
|
||||||
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 {
|
var side = when {
|
||||||
viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT
|
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 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!")
|
else -> error("We should choose a side!")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,11 +289,7 @@ class PagerPageHolder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page !is InsertPage) {
|
return ImageUtil.splitInHalf(imageStream, side)
|
||||||
onPageSplit()
|
|
||||||
}
|
|
||||||
|
|
||||||
return ImageUtil.splitInHalf(inputStream, side)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onPageSplit() {
|
private fun onPageSplit() {
|
||||||
|
|||||||
@@ -385,8 +385,11 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onPageSplit(currentPage: ReaderPage, newPage: InsertPage) {
|
fun onPageSplit(currentPage: ReaderPage, newPage: InsertPage) {
|
||||||
|
activity.runOnUiThread {
|
||||||
|
// Need to insert on UI thread else images will go blank
|
||||||
adapter.onPageSplit(currentPage, newPage, this::class.java)
|
adapter.onPageSplit(currentPage, newPage, this::class.java)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun cleanupPageSplit() {
|
private fun cleanupPageSplit() {
|
||||||
adapter.cleanupPageSplit()
|
adapter.cleanupPageSplit()
|
||||||
|
|||||||
@@ -281,22 +281,13 @@ class WebtoonPageHolder(
|
|||||||
readImageHeaderSubscription = Observable
|
readImageHeaderSubscription = Observable
|
||||||
.fromCallable {
|
.fromCallable {
|
||||||
val stream = streamFn().buffered(16)
|
val stream = streamFn().buffered(16)
|
||||||
openStream = stream
|
openStream = process(stream)
|
||||||
|
|
||||||
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
|
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnNext { isAnimated ->
|
.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) {
|
if (!isAnimated) {
|
||||||
val subsamplingView = initSubsamplingImageView()
|
val subsamplingView = initSubsamplingImageView()
|
||||||
subsamplingView.isVisible = true
|
subsamplingView.isVisible = true
|
||||||
@@ -315,6 +306,20 @@ class WebtoonPageHolder(
|
|||||||
addSubscription(readImageHeaderSubscription)
|
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.
|
* Called when the page has an error.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -79,16 +79,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
|
|||||||
recycler.addOnScrollListener(
|
recycler.addOnScrollListener(
|
||||||
object : RecyclerView.OnScrollListener() {
|
object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
val position = layoutManager.findLastEndVisibleItemPosition()
|
onScrolled()
|
||||||
val item = adapter.items.getOrNull(position)
|
|
||||||
val allowPreload = checkAllowPreload(item as? ReaderPage)
|
|
||||||
if (item != null && currentPage != item) {
|
|
||||||
currentPage = item
|
|
||||||
when (item) {
|
|
||||||
is ReaderPage -> onPageSelected(item, allowPreload)
|
|
||||||
is ChapterTransition -> onTransitionSelected(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dy < 0) {
|
if (dy < 0) {
|
||||||
val firstIndex = layoutManager.findFirstVisibleItemPosition()
|
val firstIndex = layoutManager.findFirstVisibleItemPosition()
|
||||||
@@ -243,11 +234,27 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
|
|||||||
val position = adapter.items.indexOf(page)
|
val position = adapter.items.indexOf(page)
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
recycler.scrollToPosition(position)
|
recycler.scrollToPosition(position)
|
||||||
|
if (layoutManager.findLastEndVisibleItemPosition() == -1) {
|
||||||
|
onScrolled(position)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Timber.d("Page $page not found in adapter")
|
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].
|
* Scrolls up by [scrollDistance].
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
|||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.databinding.RecentSectionItemBinding
|
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class DateSectionItem(val date: Date) : AbstractHeaderItem<DateSectionItem.DateSectionItemHolder>() {
|
class DateSectionItem(val date: Date) : AbstractHeaderItem<DateSectionItem.DateSectionItemHolder>() {
|
||||||
|
|
||||||
override fun getLayoutRes(): Int {
|
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 {
|
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) {
|
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
|
private val now = Date().time
|
||||||
|
|
||||||
fun bind(item: DateSectionItem) {
|
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 {
|
init {
|
||||||
setDisplayHeadersAtStartUp(true)
|
setDisplayHeadersAtStartUp(true)
|
||||||
setStickyHeaders(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnResumeClickListener {
|
interface OnResumeClickListener {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
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.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.databinding.HistoryControllerBinding
|
import eu.kanade.tachiyomi.databinding.HistoryControllerBinding
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
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.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
@@ -36,13 +34,10 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment that shows recently read manga.
|
* Fragment that shows recently read manga.
|
||||||
* Uses [R.layout.history_controller].
|
|
||||||
* UI related actions should be called from here.
|
|
||||||
*/
|
*/
|
||||||
class HistoryController :
|
class HistoryController :
|
||||||
NucleusController<HistoryControllerBinding, HistoryPresenter>(),
|
NucleusController<HistoryControllerBinding, HistoryPresenter>(),
|
||||||
RootController,
|
RootController,
|
||||||
NoToolbarElevationController,
|
|
||||||
FlexibleAdapter.OnUpdateListener,
|
FlexibleAdapter.OnUpdateListener,
|
||||||
FlexibleAdapter.EndlessScrollListener,
|
FlexibleAdapter.EndlessScrollListener,
|
||||||
HistoryAdapter.OnRemoveClickListener,
|
HistoryAdapter.OnRemoveClickListener,
|
||||||
@@ -76,18 +71,16 @@ class HistoryController :
|
|||||||
return HistoryPresenter()
|
return HistoryPresenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater) = HistoryControllerBinding.inflate(inflater)
|
||||||
binding = HistoryControllerBinding.inflate(inflater)
|
|
||||||
|
override fun onViewCreated(view: View) {
|
||||||
|
super.onViewCreated(view)
|
||||||
|
|
||||||
binding.recycler.applyInsetter {
|
binding.recycler.applyInsetter {
|
||||||
type(navigationBars = true) {
|
type(navigationBars = true) {
|
||||||
padding()
|
padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
|
||||||
super.onViewCreated(view)
|
|
||||||
|
|
||||||
// Initialize adapter
|
// Initialize adapter
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ class UpdatesAdapter(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
setDisplayHeadersAtStartUp(true)
|
setDisplayHeadersAtStartUp(true)
|
||||||
setStickyHeaders(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnCoverClickListener {
|
interface OnCoverClickListener {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
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.library.LibraryUpdateService
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.databinding.UpdatesControllerBinding
|
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.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
@@ -37,13 +35,10 @@ import timber.log.Timber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment that shows recent chapters.
|
* Fragment that shows recent chapters.
|
||||||
* Uses [R.layout.updates_controller].
|
|
||||||
* UI related actions should be called from here.
|
|
||||||
*/
|
*/
|
||||||
class UpdatesController :
|
class UpdatesController :
|
||||||
NucleusController<UpdatesControllerBinding, UpdatesPresenter>(),
|
NucleusController<UpdatesControllerBinding, UpdatesPresenter>(),
|
||||||
RootController,
|
RootController,
|
||||||
NoToolbarElevationController,
|
|
||||||
ActionMode.Callback,
|
ActionMode.Callback,
|
||||||
FlexibleAdapter.OnItemClickListener,
|
FlexibleAdapter.OnItemClickListener,
|
||||||
FlexibleAdapter.OnItemLongClickListener,
|
FlexibleAdapter.OnItemLongClickListener,
|
||||||
@@ -75,18 +70,21 @@ class UpdatesController :
|
|||||||
return UpdatesPresenter()
|
return UpdatesPresenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater) = UpdatesControllerBinding.inflate(inflater)
|
||||||
binding = UpdatesControllerBinding.inflate(inflater)
|
|
||||||
|
override fun onViewCreated(view: View) {
|
||||||
|
super.onViewCreated(view)
|
||||||
binding.recycler.applyInsetter {
|
binding.recycler.applyInsetter {
|
||||||
type(navigationBars = true) {
|
type(navigationBars = true) {
|
||||||
padding()
|
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)
|
view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS)
|
||||||
|
|
||||||
// Init RecyclerView and adapter
|
// Init RecyclerView and adapter
|
||||||
@@ -244,8 +242,7 @@ class UpdatesController :
|
|||||||
adapter?.currentItems
|
adapter?.currentItems
|
||||||
?.filterIsInstance<UpdatesItem>()
|
?.filterIsInstance<UpdatesItem>()
|
||||||
?.find { it.chapter.id == download.chapter.id }?.let {
|
?.find { it.chapter.id == download.chapter.id }?.let {
|
||||||
adapter?.updateItem(it)
|
adapter?.updateItem(it, it.status)
|
||||||
adapter?.notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
package eu.kanade.tachiyomi.ui.security
|
package eu.kanade.tachiyomi.ui.security
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.biometric.BiometricPrompt
|
import androidx.biometric.BiometricPrompt
|
||||||
import eu.kanade.tachiyomi.R
|
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 eu.kanade.tachiyomi.util.system.BiometricUtil
|
||||||
import uy.kohesive.injekt.injectLazy
|
import timber.log.Timber
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blank activity with a BiometricPrompt.
|
* Blank activity with a BiometricPrompt.
|
||||||
*/
|
*/
|
||||||
class BiometricUnlockActivity : AppCompatActivity() {
|
class BiometricUnlockActivity : BaseThemedActivity() {
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
|
||||||
private val executor = Executors.newSingleThreadExecutor()
|
private val executor = Executors.newSingleThreadExecutor()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -27,6 +25,7 @@ class BiometricUnlockActivity : AppCompatActivity() {
|
|||||||
object : BiometricPrompt.AuthenticationCallback() {
|
object : BiometricPrompt.AuthenticationCallback() {
|
||||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
super.onAuthenticationError(errorCode, errString)
|
super.onAuthenticationError(errorCode, errString)
|
||||||
|
Timber.e(errString.toString())
|
||||||
finishAffinity()
|
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.network.PREF_DOH_GOOGLE
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
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.defaultValue
|
||||||
import eu.kanade.tachiyomi.util.preference.intListPreference
|
import eu.kanade.tachiyomi.util.preference.intListPreference
|
||||||
import eu.kanade.tachiyomi.util.preference.onChange
|
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.preference.titleRes
|
||||||
import eu.kanade.tachiyomi.util.system.powerManager
|
import eu.kanade.tachiyomi.util.system.powerManager
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
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 uy.kohesive.injekt.injectLazy
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||||
|
|
||||||
@@ -172,27 +171,18 @@ class SettingsAdvancedController : SettingsController() {
|
|||||||
|
|
||||||
private fun clearChapterCache() {
|
private fun clearChapterCache() {
|
||||||
if (activity == null) return
|
if (activity == null) return
|
||||||
val files = chapterCache.cacheDir.listFiles() ?: return
|
launchIO {
|
||||||
|
try {
|
||||||
var deletedFiles = 0
|
val deletedFiles = chapterCache.clear()
|
||||||
|
withUIContext {
|
||||||
Observable.defer { Observable.from(files) }
|
|
||||||
.doOnNext { file ->
|
|
||||||
if (chapterCache.removeFileFromCache(file.name)) {
|
|
||||||
deletedFiles++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnError {
|
|
||||||
activity?.toast(R.string.cache_delete_error)
|
|
||||||
}
|
|
||||||
.doOnCompleted {
|
|
||||||
activity?.toast(resources?.getString(R.string.cache_deleted, deletedFiles))
|
activity?.toast(resources?.getString(R.string.cache_deleted, deletedFiles))
|
||||||
findPreference(CLEAR_CACHE_KEY)?.summary =
|
findPreference(CLEAR_CACHE_KEY)?.summary =
|
||||||
resources?.getString(R.string.used_cache, chapterCache.readableSize)
|
resources?.getString(R.string.used_cache, chapterCache.readableSize)
|
||||||
}
|
}
|
||||||
.subscribe()
|
} catch (e: Throwable) {
|
||||||
|
withUIContext { activity?.toast(R.string.cache_delete_error) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClearDatabaseDialogController : DialogController() {
|
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.ui.base.controller.RootController
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import rx.Observable
|
|
||||||
import rx.Subscription
|
|
||||||
import rx.subscriptions.CompositeSubscription
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@@ -35,19 +32,14 @@ abstract class SettingsController : PreferenceController() {
|
|||||||
var preferenceKey: String? = null
|
var preferenceKey: String? = null
|
||||||
val preferences: PreferencesHelper = Injekt.get()
|
val preferences: PreferencesHelper = Injekt.get()
|
||||||
val viewScope = MainScope()
|
val viewScope = MainScope()
|
||||||
|
private var themedContext: Context? = null
|
||||||
var untilDestroySubscriptions = CompositeSubscription()
|
|
||||||
private set
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View {
|
||||||
if (untilDestroySubscriptions.isUnsubscribed) {
|
|
||||||
untilDestroySubscriptions = CompositeSubscription()
|
|
||||||
}
|
|
||||||
|
|
||||||
val view = super.onCreateView(inflater, container, savedInstanceState)
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
|
||||||
if (this is RootController) {
|
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 {
|
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) {
|
override fun onDestroyView(view: View) {
|
||||||
super.onDestroyView(view)
|
super.onDestroyView(view)
|
||||||
untilDestroySubscriptions.unsubscribe()
|
themedContext = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
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
|
preferenceScreen = screen
|
||||||
setupPreferenceScreen(screen)
|
setupPreferenceScreen(screen)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun setupPreferenceScreen(screen: PreferenceScreen): PreferenceScreen
|
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) {
|
private fun animatePreferenceHighlight(view: View) {
|
||||||
ValueAnimator
|
ValueAnimator
|
||||||
.ofObject(ArgbEvaluator(), Color.TRANSPARENT, view.context.getResourceColor(R.attr.rippleColor))
|
.ofObject(ArgbEvaluator(), Color.TRANSPARENT, view.context.getResourceColor(R.attr.rippleColor))
|
||||||
@@ -111,7 +109,7 @@ abstract class SettingsController : PreferenceController() {
|
|||||||
return preferenceScreen?.title?.toString()
|
return preferenceScreen?.title?.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTitle() {
|
private fun setTitle() {
|
||||||
var parentController = parentController
|
var parentController = parentController
|
||||||
while (parentController != null) {
|
while (parentController != null) {
|
||||||
if (parentController is BaseController<*> && parentController.getTitle() != null) {
|
if (parentController is BaseController<*> && parentController.getTitle() != null) {
|
||||||
@@ -122,16 +120,4 @@ abstract class SettingsController : PreferenceController() {
|
|||||||
|
|
||||||
(activity as? AppCompatActivity)?.supportActionBar?.title = getTitle()
|
(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
|
titleRes = R.string.pref_rotation_type
|
||||||
entriesRes = arrayOf(
|
entriesRes = arrayOf(
|
||||||
R.string.rotation_free,
|
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_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"
|
defaultValue = "1"
|
||||||
summary = "%s"
|
summary = "%s"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@@ -33,17 +32,7 @@ class SettingsSearchController :
|
|||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun createBinding(inflater: LayoutInflater) = SettingsSearchControllerBinding.inflate(inflater)
|
||||||
* Initiate the view with [R.layout.settings_search_controller].
|
|
||||||
*
|
|
||||||
* @param inflater used to load the layout xml.
|
|
||||||
* @param container containing parent views.
|
|
||||||
* @return inflated view
|
|
||||||
*/
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
|
||||||
binding = SettingsSearchControllerBinding.inflate(inflater)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
override fun getTitle(): String? {
|
||||||
return presenter.query
|
return presenter.query
|
||||||
|
|||||||
@@ -139,8 +139,10 @@ class WebViewActivity : BaseViewBindingActivity<WebviewActivityBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
binding.webview?.destroy()
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
|
// Binding sometimes isn't actually instantiated yet somehow
|
||||||
|
binding?.webview?.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
|||||||
@@ -2,15 +2,18 @@ package eu.kanade.tachiyomi.util
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
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.storage.getUriCompat
|
||||||
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
||||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class CrashLogUtil(private val context: Context) {
|
class CrashLogUtil(private val context: Context) {
|
||||||
|
|
||||||
@@ -19,15 +22,31 @@ class CrashLogUtil(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun dumpLogs() {
|
fun dumpLogs() {
|
||||||
|
launchIO {
|
||||||
try {
|
try {
|
||||||
val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt")
|
val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt")
|
||||||
Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}")
|
Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}").waitFor()
|
||||||
|
file.appendText(getDebugInfo())
|
||||||
|
|
||||||
showNotification(file.getUriCompat(context))
|
showNotification(file.getUriCompat(context))
|
||||||
} catch (e: IOException) {
|
} catch (e: Throwable) {
|
||||||
context.toast("Failed to get logs")
|
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) {
|
private fun showNotification(uri: Uri) {
|
||||||
context.notificationManager.cancel(Notifications.ID_CRASH_LOGS)
|
context.notificationManager.cancel(Notifications.ID_CRASH_LOGS)
|
||||||
@@ -35,15 +54,12 @@ class CrashLogUtil(private val context: Context) {
|
|||||||
with(notificationBuilder) {
|
with(notificationBuilder) {
|
||||||
setContentTitle(context.getString(R.string.crash_log_saved))
|
setContentTitle(context.getString(R.string.crash_log_saved))
|
||||||
|
|
||||||
// Clear old actions if they exist
|
|
||||||
clearActions()
|
clearActions()
|
||||||
|
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_folder_24dp,
|
R.drawable.ic_folder_24dp,
|
||||||
context.getString(R.string.action_open_log),
|
context.getString(R.string.action_open_log),
|
||||||
NotificationReceiver.openErrorLogPendingActivity(context, uri)
|
NotificationReceiver.openErrorLogPendingActivity(context, uri)
|
||||||
)
|
)
|
||||||
|
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_share_24dp,
|
R.drawable.ic_share_24dp,
|
||||||
context.getString(R.string.action_share),
|
context.getString(R.string.action_share),
|
||||||
|
|||||||
@@ -157,3 +157,5 @@ private fun shouldUpdateDbChapter(dbChapter: Chapter, sourceChapter: SChapter):
|
|||||||
dbChapter.date_upload != sourceChapter.date_upload ||
|
dbChapter.date_upload != sourceChapter.date_upload ||
|
||||||
dbChapter.chapter_number != sourceChapter.chapter_number
|
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
|
package eu.kanade.tachiyomi.util.system
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
import androidx.biometric.BiometricManager
|
import androidx.biometric.BiometricManager
|
||||||
import androidx.biometric.BiometricManager.Authenticators
|
import androidx.biometric.BiometricManager.Authenticators
|
||||||
|
|
||||||
object BiometricUtil {
|
object BiometricUtil {
|
||||||
|
|
||||||
fun getSupportedAuthenticators(context: Context): Int {
|
fun getSupportedAuthenticators(context: Context): Int {
|
||||||
|
if (isLegacySecured(context)) {
|
||||||
|
return Authenticators.BIOMETRIC_WEAK or Authenticators.DEVICE_CREDENTIAL
|
||||||
|
}
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
Authenticators.BIOMETRIC_STRONG,
|
Authenticators.BIOMETRIC_STRONG,
|
||||||
Authenticators.BIOMETRIC_WEAK,
|
Authenticators.BIOMETRIC_WEAK,
|
||||||
@@ -17,10 +22,22 @@ object BiometricUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isSupported(context: Context): Boolean {
|
fun isSupported(context: Context): Boolean {
|
||||||
return getSupportedAuthenticators(context) != 0
|
return isLegacySecured(context) || getSupportedAuthenticators(context) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isDeviceCredentialAllowed(context: Context): Boolean {
|
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
|
package eu.kanade.tachiyomi.util.system
|
||||||
|
|
||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
|
import android.app.KeyguardManager
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
@@ -33,6 +34,7 @@ import androidx.core.net.toUri
|
|||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.lang.truncateCenter
|
import eu.kanade.tachiyomi.util.lang.truncateCenter
|
||||||
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.roundToInt
|
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) {
|
fun Context.copyToClipboard(label: String, content: String) {
|
||||||
if (content.isBlank()) return
|
if (content.isBlank()) return
|
||||||
|
|
||||||
|
try {
|
||||||
val clipboard = getSystemService<ClipboardManager>()!!
|
val clipboard = getSystemService<ClipboardManager>()!!
|
||||||
clipboard.setPrimaryClip(ClipData.newPlainText(label, content))
|
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
|
val Resources.isLTR
|
||||||
get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
|
get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
|
||||||
|
|
||||||
/**
|
|
||||||
* Property to get the notification manager from the context.
|
|
||||||
*/
|
|
||||||
val Context.notificationManager: NotificationManager
|
val Context.notificationManager: NotificationManager
|
||||||
get() = getSystemService()!!
|
get() = getSystemService()!!
|
||||||
|
|
||||||
/**
|
|
||||||
* Property to get the connectivity manager from the context.
|
|
||||||
*/
|
|
||||||
val Context.connectivityManager: ConnectivityManager
|
val Context.connectivityManager: ConnectivityManager
|
||||||
get() = getSystemService()!!
|
get() = getSystemService()!!
|
||||||
|
|
||||||
/**
|
|
||||||
* Property to get the power manager from the context.
|
|
||||||
*/
|
|
||||||
val Context.powerManager: PowerManager
|
val Context.powerManager: PowerManager
|
||||||
get() = getSystemService()!!
|
get() = getSystemService()!!
|
||||||
|
|
||||||
|
val Context.keyguardManager: KeyguardManager
|
||||||
|
get() = getSystemService()!!
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to acquire a partial wake lock.
|
* 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 imageBytes = imageStream.readBytes()
|
||||||
|
|
||||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||||
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
|
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
|
package eu.kanade.tachiyomi.util.view
|
||||||
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a vector on a [ImageView].
|
* Set a vector on a [ImageView].
|
||||||
*
|
*
|
||||||
* @param drawable id of drawable resource
|
* @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)
|
val vector = AppCompatResources.getDrawable(context, drawable)
|
||||||
if (tint != null) {
|
if (tint != null) {
|
||||||
vector?.mutate()
|
vector?.mutate()
|
||||||
vector?.setTint(tint)
|
vector?.setTint(context.getResourceColor(tint))
|
||||||
}
|
}
|
||||||
setImageDrawable(vector)
|
setImageDrawable(vector)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
package eu.kanade.tachiyomi.util.view
|
package eu.kanade.tachiyomi.util.view
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.graphics.Point
|
import android.graphics.Point
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
@@ -9,14 +10,18 @@ import android.view.MenuItem
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.annotation.MenuRes
|
import androidx.annotation.MenuRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.appcompat.view.menu.MenuBuilder
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.appcompat.widget.TooltipCompat
|
import androidx.appcompat.widget.TooltipCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.forEach
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.chip.ChipGroup
|
import com.google.android.material.chip.ChipGroup
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns coordinates of view.
|
* Returns coordinates of view.
|
||||||
@@ -63,7 +68,7 @@ inline fun View.setTooltip(@StringRes stringRes: Int) {
|
|||||||
inline fun View.popupMenu(
|
inline fun View.popupMenu(
|
||||||
@MenuRes menuRes: Int,
|
@MenuRes menuRes: Int,
|
||||||
noinline initMenu: (Menu.() -> Unit)? = null,
|
noinline initMenu: (Menu.() -> Unit)? = null,
|
||||||
noinline onMenuItemClick: MenuItem.() -> Boolean
|
noinline onMenuItemClick: MenuItem.() -> Unit
|
||||||
): PopupMenu {
|
): PopupMenu {
|
||||||
val popup = PopupMenu(context, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
|
val popup = PopupMenu(context, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
|
||||||
popup.menuInflater.inflate(menuRes, popup.menu)
|
popup.menuInflater.inflate(menuRes, popup.menu)
|
||||||
@@ -71,7 +76,50 @@ inline fun View.popupMenu(
|
|||||||
if (initMenu != null) {
|
if (initMenu != null) {
|
||||||
popup.menu.initMenu()
|
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()
|
popup.show()
|
||||||
return popup
|
return popup
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import androidx.annotation.MenuRes
|
|||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.databinding.CommonActionToolbarBinding
|
import eu.kanade.tachiyomi.databinding.ActionToolbarBinding
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A toolbar holding only menu items.
|
* 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) :
|
class ActionToolbar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||||
FrameLayout(context, attrs) {
|
FrameLayout(context, attrs) {
|
||||||
|
|
||||||
private val binding: CommonActionToolbarBinding
|
private val binding = ActionToolbarBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
|
|
||||||
init {
|
|
||||||
binding = CommonActionToolbarBinding.inflate(LayoutInflater.from(context), this, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove menu items and remove listener.
|
* Remove menu items and remove listener.
|
||||||
*/
|
*/
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
binding.commonActionMenu.menu.clear()
|
binding.menu.menu.clear()
|
||||||
binding.commonActionMenu.setOnMenuItemClickListener(null)
|
binding.menu.setOnMenuItemClickListener(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a menu item if found.
|
* Gets a menu item if found.
|
||||||
*/
|
*/
|
||||||
fun findItem(@IdRes itemId: Int): MenuItem? {
|
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) {
|
fun show(mode: ActionMode, @MenuRes menuRes: Int, listener: (item: MenuItem?) -> Boolean) {
|
||||||
// Avoid re-inflating the menu
|
// Avoid re-inflating the menu
|
||||||
if (binding.commonActionMenu.menu.size() == 0) {
|
if (binding.menu.menu.size() == 0) {
|
||||||
mode.menuInflater.inflate(menuRes, binding.commonActionMenu.menu)
|
mode.menuInflater.inflate(menuRes, binding.menu.menu)
|
||||||
binding.commonActionMenu.setOnMenuItemClickListener { listener(it) }
|
binding.menu.setOnMenuItemClickListener { listener(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.commonActionToolbar.isVisible = true
|
binding.actionToolbar.isVisible = true
|
||||||
val bottomAnimation = AnimationUtils.loadAnimation(context, R.anim.enter_from_bottom)
|
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(
|
bottomAnimation.setAnimationListener(
|
||||||
object : SimpleAnimationListener() {
|
object : SimpleAnimationListener() {
|
||||||
override fun onAnimationEnd(animation: Animation) {
|
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.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
@@ -7,15 +8,21 @@ import android.view.LayoutInflater
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.annotation.ArrayRes
|
import androidx.annotation.ArrayRes
|
||||||
|
import androidx.appcompat.view.menu.MenuBuilder
|
||||||
import androidx.appcompat.widget.PopupMenu
|
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 com.tfcporciuncula.flow.Preference
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.databinding.SpinnerPreferenceBinding
|
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) {
|
FrameLayout(context, attrs) {
|
||||||
|
|
||||||
private var entries = emptyList<String>()
|
private var entries = emptyList<String>()
|
||||||
|
private var selectedPosition = 0
|
||||||
private var popup: PopupMenu? = null
|
private var popup: PopupMenu? = null
|
||||||
|
|
||||||
var onItemSelectedListener: ((Int) -> Unit)? = 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)
|
private val binding = SpinnerPreferenceBinding.inflate(LayoutInflater.from(context), this, false)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
addView(binding.root)
|
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
|
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
|
this.entries = entries
|
||||||
binding.details.text = entries.firstOrNull().orEmpty()
|
binding.details.text = entries.firstOrNull().orEmpty()
|
||||||
|
|
||||||
@@ -48,6 +64,14 @@ class SpinnerPreference @JvmOverloads constructor(context: Context, attrs: Attri
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setSelection(selection: Int) {
|
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()
|
binding.details.text = entries.getOrNull(selection).orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,11 +142,19 @@ class SpinnerPreference @JvmOverloads constructor(context: Context, attrs: Attri
|
|||||||
return pos
|
return pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
fun createPopupMenu(onItemClick: (Int) -> Unit): PopupMenu {
|
fun createPopupMenu(onItemClick: (Int) -> Unit): PopupMenu {
|
||||||
val popup = PopupMenu(context, this, Gravity.END, R.attr.actionOverflowMenuStyle, 0)
|
val popup = PopupMenu(context, this, Gravity.END, R.attr.actionOverflowMenuStyle, 0)
|
||||||
entries.forEachIndexed { index, entry ->
|
entries.forEachIndexed { index, entry ->
|
||||||
popup.menu.add(0, index, 0, 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 ->
|
popup.setOnMenuItemClickListener { menuItem ->
|
||||||
val pos = menuClicked(menuItem)
|
val pos = menuClicked(menuItem)
|
||||||
onItemClick(pos)
|
onItemClick(pos)
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
package eu.kanade.tachiyomi.widget.materialdialogs
|
package eu.kanade.tachiyomi.widget.materialdialogs
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.annotation.AttrRes
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
import eu.kanade.tachiyomi.R
|
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) :
|
class QuadStateCheckBox @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||||
AppCompatImageView(context, attrs) {
|
AppCompatImageView(context, attrs) {
|
||||||
@@ -19,19 +16,11 @@ class QuadStateCheckBox @JvmOverloads constructor(context: Context, attrs: Attri
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDrawable() {
|
private fun updateDrawable() {
|
||||||
val drawable = when (state) {
|
when (state) {
|
||||||
State.UNCHECKED -> tintVector(context, R.drawable.ic_check_box_outline_blank_24dp, R.attr.colorControlNormal)
|
State.UNCHECKED -> setVectorCompat(R.drawable.ic_check_box_outline_blank_24dp, R.attr.colorControlNormal)
|
||||||
State.INDETERMINATE -> tintVector(context, R.drawable.ic_indeterminate_check_box_24dp)
|
State.INDETERMINATE -> setVectorCompat(R.drawable.ic_indeterminate_check_box_24dp, R.attr.colorAccent)
|
||||||
State.CHECKED -> tintVector(context, R.drawable.ic_check_box_24dp)
|
State.CHECKED -> setVectorCompat(R.drawable.ic_check_box_24dp, R.attr.colorAccent)
|
||||||
State.INVERSED -> tintVector(context, R.drawable.ic_check_box_x_24dp)
|
State.INVERSED -> setVectorCompat(R.drawable.ic_check_box_x_24dp, R.attr.colorAccent)
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.widget.ViewPagerAdapter
|
|||||||
|
|
||||||
abstract class TabbedBottomSheetDialog(private val activity: Activity) : BaseBottomSheetDialog(activity) {
|
abstract class TabbedBottomSheetDialog(private val activity: Activity) : BaseBottomSheetDialog(activity) {
|
||||||
|
|
||||||
val binding: CommonTabbedSheetBinding = CommonTabbedSheetBinding.inflate(activity.layoutInflater)
|
val binding = CommonTabbedSheetBinding.inflate(activity.layoutInflater)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val adapter = LibrarySettingsSheetAdapter()
|
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">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/black"
|
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>
|
</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"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
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_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
app:contentInsetStart="8dp">
|
app:contentInsetStart="8dp">
|
||||||
|
|
||||||
<androidx.appcompat.widget.ActionMenuView
|
<androidx.appcompat.widget.ActionMenuView
|
||||||
android:id="@+id/common_action_menu"
|
android:id="@+id/menu"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
@@ -7,16 +7,6 @@
|
|||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:background="?selectableItemBackgroundBorderless">
|
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
|
<ImageView
|
||||||
android:id="@+id/download_icon"
|
android:id="@+id/download_icon"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -32,42 +22,17 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="1dp"
|
android:padding="1dp"
|
||||||
android:visibility="gone"
|
android:progress="100"
|
||||||
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"
|
|
||||||
app:indicatorColor="?android:attr/textColorHint"
|
app:indicatorColor="?android:attr/textColorHint"
|
||||||
app:indicatorInset="0dp"
|
app:indicatorInset="0dp"
|
||||||
app:indicatorSize="24dp"
|
app:indicatorSize="24dp"
|
||||||
app:trackThickness="2dp" />
|
app:trackThickness="2dp" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/downloaded_icon"
|
android:id="@+id/download_status_icon"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:scaleType="fitXY"
|
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" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingTop="8dp"
|
android:paddingTop="8dp"
|
||||||
android:paddingBottom="@dimen/action_toolbar_list_padding"
|
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>
|
</eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
android:background="?attr/colorPrimary"
|
android:background="?attr/colorPrimary"
|
||||||
android:theme="?attr/actionBarTheme"
|
android:theme="?attr/actionBarTheme"
|
||||||
app:layout_scrollFlags="scroll|enterAlways|snap" />
|
app:layout_scrollFlags="scroll|enterAlways" />
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/tabs"
|
android:id="@+id/tabs"
|
||||||
|
|||||||
@@ -60,7 +60,8 @@
|
|||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="?attr/actionBarSize"
|
||||||
android:background="?attr/colorPrimary" />
|
android:background="?attr/colorPrimary" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@@ -10,14 +10,14 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.ui.reader.setting.SpinnerPreference
|
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||||
android:id="@+id/rotation_mode"
|
android:id="@+id/rotation_mode"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:entries="@array/rotation_type"
|
android:entries="@array/rotation_type"
|
||||||
app:title="@string/pref_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:id="@+id/background_color"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -15,28 +15,28 @@
|
|||||||
android:paddingEnd="16dp"
|
android:paddingEnd="16dp"
|
||||||
android:textAppearance="@style/TextAppearance.Medium.SubHeading" />
|
android:textAppearance="@style/TextAppearance.Medium.SubHeading" />
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.ui.reader.setting.SpinnerPreference
|
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||||
android:id="@+id/pager_nav"
|
android:id="@+id/pager_nav"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:entries="@array/pager_nav"
|
android:entries="@array/pager_nav"
|
||||||
app:title="@string/pref_viewer_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:id="@+id/tapping_inverted"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:entries="@array/invert_tapping_mode"
|
android:entries="@array/invert_tapping_mode"
|
||||||
app:title="@string/pref_read_with_tapping_inverted" />
|
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:id="@+id/scale_type"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:entries="@array/image_scale_type"
|
android:entries="@array/image_scale_type"
|
||||||
app:title="@string/pref_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:id="@+id/zoom_start"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.ui.reader.setting.SpinnerPreference
|
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||||
android:id="@+id/viewer"
|
android:id="@+id/viewer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
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