Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0aebe1da43 | |||
| f45fdca168 | |||
| fc5eb4cccc | |||
| 8ac309c4ae | |||
| f170446c5f | |||
| 643bec9bbb | |||
| 134be3893e | |||
| 5855822edd | |||
| 3343b766a2 | |||
| 329d24c7db | |||
| bdfbc641d9 | |||
| 6e570d7fad | |||
| b5d696ebe2 | |||
| 5299ae4856 | |||
| a9038831da | |||
| f1a8132307 | |||
| 76185338bf | |||
| bda4aae83d | |||
| 80bf908133 | |||
| 91b49f8a0c | |||
| 80a5a54e60 | |||
| 3104f3a8b5 | |||
| 4fa2c968a9 | |||
| be1e7f28ef | |||
| 4118b13e5b | |||
| 7e0f2950c1 | |||
| a8c4da9e2b | |||
| 72d315b6ed | |||
| b886f0a55a | |||
| 63fa1ee75e | |||
| 7d1ad7efb6 | |||
| 56400febd1 | |||
| aa56698dac | |||
| d37b24adb1 | |||
| d3778ac6e1 | |||
| e43777bba7 | |||
| 0b3209284a | |||
| a1be070e99 | |||
| eda47cd546 | |||
| 54d8748c58 | |||
| 77c17f2556 | |||
| d81e4158cb | |||
| 77061067ee | |||
| 707af702c1 | |||
| 1b0b98b140 | |||
| 2220b6a91d | |||
| 22b8f51fa3 | |||
| 08f0e515d5 | |||
| 28b4281683 | |||
| 676f716fcb | |||
| 665784a241 | |||
| 0d16609f95 | |||
| b46500c837 | |||
| fb5872ef51 | |||
| bc28e2d617 | |||
| de0c55117d | |||
| 936997b52e | |||
| 885c251fb4 | |||
| b8e907cea2 | |||
| f4c6b2e09c | |||
| 13f4bfa7bc | |||
| 780c1e68a6 | |||
| f10944521c | |||
| af5ebeca56 | |||
| 01ad3dc92b | |||
| 5c1423be86 | |||
| 382c23e0fd | |||
| 189b15fee6 | |||
| d3d937fe17 | |||
| 5af0e7e847 | |||
| 142bdd14b7 | |||
| 0483097fc3 | |||
| a3c26c63d4 | |||
| fbe10151f4 | |||
| 92fc5ea4a0 | |||
| d2b620f485 | |||
| 78aa57579d | |||
| b0a2d8908f | |||
| de36cd0626 | |||
| b322ecd34a | |||
| 540e234562 | |||
| f6be2c7a2a | |||
| c5df8725de | |||
| 738e2f7cf1 | |||
| 0ac56750c8 | |||
| 3fb1b4affa | |||
| 2602c49756 | |||
| d23b3c82ba |
@@ -1,2 +1 @@
|
|||||||
github: inorichi
|
|
||||||
ko_fi: inorichi
|
ko_fi: inorichi
|
||||||
|
|||||||
@@ -2,9 +2,15 @@
|
|||||||
|
|
||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated to the latest version of the app (stable is v1.6.0)
|
- I have updated:
|
||||||
- I have updated all extensions
|
- To the latest version of the app (stable is v1.6.2)
|
||||||
|
- All extensions
|
||||||
|
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
||||||
- 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,9 +9,15 @@ labels: "bug"
|
|||||||
|
|
||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated to the latest version of the app (stable is v1.6.0)
|
- I have updated:
|
||||||
- I have updated all extensions
|
- To the latest version of the app (stable is v1.6.2)
|
||||||
|
- All extensions
|
||||||
|
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
|
||||||
- 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,9 +9,14 @@ labels: "feature"
|
|||||||
|
|
||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated to the latest version of the app (stable is v1.6.0)
|
- I have updated:
|
||||||
- I have updated all extensions
|
- To the latest version of the app (stable is v1.6.2)
|
||||||
|
- 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**
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 489 KiB |
@@ -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: |
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Tachiyomi is a free and open source manga reader for Android 5.0 and above. This
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
Features of Tachiyomi(original) include:
|
Features of Tachiyomi(original) 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
|
||||||
|
|||||||
@@ -34,8 +34,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 = 14
|
versionCode = 16
|
||||||
versionName = "1.6.0"
|
versionName = "1.6.2"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
@@ -160,7 +160,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")
|
||||||
@@ -170,7 +170,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")
|
||||||
@@ -181,7 +181,7 @@ dependencies {
|
|||||||
|
|
||||||
// Disk
|
// Disk
|
||||||
implementation("com.jakewharton:disklrucache:2.0.2")
|
implementation("com.jakewharton:disklrucache:2.0.2")
|
||||||
implementation("com.github.inorichi:unifile:e9ee588")
|
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
|
||||||
@@ -267,12 +267,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")
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
// [EXH] Android 7 SSL Workaround
|
// [EXH] Android 7 SSL Workaround
|
||||||
|
|||||||
@@ -33,7 +33,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"
|
||||||
@@ -85,7 +85,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" />
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="exh.ui.intercept.InterceptActivity"
|
android:name="exh.ui.intercept.InterceptActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/Theme.EHActivity">
|
android:theme="@style/Theme.Base">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
@@ -374,7 +374,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="exh.ui.captcha.BrowserActionActivity"
|
android:name="exh.ui.captcha.BrowserActionActivity"
|
||||||
android:theme="@style/Theme.EHActivity" />
|
android:theme="@style/Theme.Base" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -82,6 +82,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
|
||||||
@@ -141,6 +142,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,8 +2,6 @@ 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.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
@@ -15,10 +13,12 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
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
|
||||||
@@ -48,14 +48,12 @@ class ChapterCache(private val context: Context) {
|
|||||||
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
/** Google Json class used for parsing JSON files. */
|
/** Google Json class used for parsing JSON files. */
|
||||||
private val gson: Gson by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
// --> EH
|
// --> EH
|
||||||
private val prefs: PreferencesHelper by injectLazy()
|
private val prefs: PreferencesHelper by injectLazy()
|
||||||
// <-- EH
|
|
||||||
|
|
||||||
/** Cache class used for cache management. */
|
/** Cache class used for cache management. */
|
||||||
// --> EH
|
|
||||||
private var diskCache = setupDiskCache(prefs.cacheSize().get().toLong())
|
private var diskCache = setupDiskCache(prefs.cacheSize().get().toLong())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -73,7 +71,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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,43 +98,19 @@ class ChapterCache(private val context: Context) {
|
|||||||
}
|
}
|
||||||
// <-- EH
|
// <-- EH
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove file from cache.
|
|
||||||
*
|
|
||||||
* @param file name of file "md5.0".
|
|
||||||
* @return status of deletion for the file.
|
|
||||||
*/
|
|
||||||
fun removeFileFromCache(file: String): Boolean {
|
|
||||||
// Make sure we don't delete the journal file (keeps track of cache).
|
|
||||||
if (file == "journal" || file.startsWith("journal.")) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return try {
|
|
||||||
// Remove the extension from the file to get the key of the cache
|
|
||||||
val key = file.substringBeforeLast(".")
|
|
||||||
// Remove file from cache.
|
|
||||||
diskCache.remove(key)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get page list from cache.
|
* 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))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +122,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
|
||||||
@@ -228,6 +202,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}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ class DownloadManager(private val context: Context) {
|
|||||||
|
|
||||||
if (removeNonFavorite && !manga.favorite) {
|
if (removeNonFavorite && !manga.favorite) {
|
||||||
val mangaFolder = provider.getMangaDir(manga, source)
|
val mangaFolder = provider.getMangaDir(manga, source)
|
||||||
cleaned += 1 + (mangaFolder.listFiles()?.size ?: 0)
|
cleaned += 1 + mangaFolder.listFiles().orEmpty().size
|
||||||
mangaFolder.delete()
|
mangaFolder.delete()
|
||||||
cache.removeManga(manga)
|
cache.removeManga(manga)
|
||||||
return cleaned
|
return cleaned
|
||||||
@@ -284,8 +284,7 @@ class DownloadManager(private val context: Context) {
|
|||||||
|
|
||||||
if (cache.getDownloadCount(manga) == 0) {
|
if (cache.getDownloadCount(manga) == 0) {
|
||||||
val mangaFolder = provider.getMangaDir(manga, source)
|
val mangaFolder = provider.getMangaDir(manga, source)
|
||||||
val size = mangaFolder.listFiles()?.size ?: 0
|
if (!mangaFolder.listFiles().isNullOrEmpty()) {
|
||||||
if (size == 0) {
|
|
||||||
mangaFolder.delete()
|
mangaFolder.delete()
|
||||||
cache.removeManga(manga)
|
cache.removeManga(manga)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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) ?: mangaDir?.findFile("$it.cbz") }
|
.mapNotNull { mangaDir?.findFile(it, true) ?: mangaDir?.findFile("$it.cbz", true) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,14 +123,12 @@ class DownloadProvider(private val context: Context) {
|
|||||||
source: Source
|
source: Source
|
||||||
): List<UniFile> {
|
): List<UniFile> {
|
||||||
val mangaDir = findMangaDir(manga, source) ?: return emptyList()
|
val mangaDir = findMangaDir(manga, source) ?: return emptyList()
|
||||||
return mangaDir.listFiles()!!.asList().filter {
|
return mangaDir.listFiles().orEmpty().asList().filter {
|
||||||
(
|
chapters.find { chp ->
|
||||||
chapters.find { chp ->
|
getValidChapterDirNames(chp).any { dir ->
|
||||||
getValidChapterDirNames(chp).any { dir ->
|
mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null
|
||||||
mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null
|
}
|
||||||
}
|
} == null || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
|
||||||
} == null
|
|
||||||
) || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
@@ -141,7 +139,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())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,6 +176,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)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ object PreferenceValues {
|
|||||||
enum class DarkThemeVariant {
|
enum class DarkThemeVariant {
|
||||||
default,
|
default,
|
||||||
blue,
|
blue,
|
||||||
|
amoledblue,
|
||||||
amoled,
|
amoled,
|
||||||
red,
|
red,
|
||||||
midnightdusk,
|
midnightdusk,
|
||||||
|
|||||||
+3
-8
@@ -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,52 +8,26 @@ 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()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Result.failure()
|
|
||||||
}
|
}
|
||||||
|
Result.success()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) {
|
|
||||||
block()
|
|
||||||
context.notificationManager.notify(Notifications.ID_UPDATER, build())
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
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,17 +1,25 @@
|
|||||||
package eu.kanade.tachiyomi.source.online
|
package eu.kanade.tachiyomi.source.online
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
||||||
|
|
||||||
interface LoginSource : Source {
|
interface LoginSource : Source {
|
||||||
val needsLogin: Boolean
|
val requiresLogin: Boolean
|
||||||
|
|
||||||
|
val twoFactorAuth: AuthSupport
|
||||||
|
|
||||||
fun isLogged(): Boolean
|
fun isLogged(): Boolean
|
||||||
|
|
||||||
fun getLoginDialog(source: Source, activity: Activity): DialogController
|
fun getUsername(): String
|
||||||
|
|
||||||
suspend fun login(username: String, password: String, twoFactorCode: String = ""): Boolean
|
fun getPassword(): String
|
||||||
|
|
||||||
|
suspend fun login(username: String, password: String, twoFactorCode: String?): Boolean
|
||||||
|
|
||||||
suspend fun logout(): Boolean
|
suspend fun logout(): Boolean
|
||||||
|
|
||||||
|
enum class AuthSupport {
|
||||||
|
NOT_SUPPORTED,
|
||||||
|
SUPPORTED,
|
||||||
|
REQUIRED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.source.online.all
|
package eu.kanade.tachiyomi.source.online.all
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@@ -12,7 +11,6 @@ import eu.kanade.tachiyomi.network.GET
|
|||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.network.await
|
import eu.kanade.tachiyomi.network.await
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
@@ -26,7 +24,6 @@ import eu.kanade.tachiyomi.source.online.MetadataSource
|
|||||||
import eu.kanade.tachiyomi.source.online.RandomMangaSource
|
import eu.kanade.tachiyomi.source.online.RandomMangaSource
|
||||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BaseController
|
import eu.kanade.tachiyomi.ui.base.controller.BaseController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
import eu.kanade.tachiyomi.util.lang.runAsObservable
|
import eu.kanade.tachiyomi.util.lang.runAsObservable
|
||||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||||
@@ -46,7 +43,6 @@ import exh.metadata.metadata.MangaDexSearchMetadata
|
|||||||
import exh.source.DelegatedHttpSource
|
import exh.source.DelegatedHttpSource
|
||||||
import exh.ui.metadata.adapters.MangaDexDescriptionAdapter
|
import exh.ui.metadata.adapters.MangaDexDescriptionAdapter
|
||||||
import exh.util.urlImportFetchSearchManga
|
import exh.util.urlImportFetchSearchManga
|
||||||
import exh.widget.preference.MangadexLoginDialog
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
@@ -180,21 +176,27 @@ class MangaDex(delegate: HttpSource, val context: Context) :
|
|||||||
return FollowsHandler(client, headers, Injekt.get(), useLowQualityThumbnail()).fetchFollows()
|
return FollowsHandler(client, headers, Injekt.get(), useLowQualityThumbnail()).fetchFollows()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val needsLogin: Boolean = true
|
override val requiresLogin: Boolean = true
|
||||||
|
|
||||||
override fun getLoginDialog(source: Source, activity: Activity): DialogController {
|
override val twoFactorAuth = LoginSource.AuthSupport.SUPPORTED
|
||||||
return MangadexLoginDialog(source as MangaDex)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isLogged(): Boolean {
|
override fun isLogged(): Boolean {
|
||||||
val httpUrl = MdUtil.baseUrl.toHttpUrl()
|
val httpUrl = MdUtil.baseUrl.toHttpUrl()
|
||||||
return trackManager.mdList.isLogged && network.cookieManager.get(httpUrl).any { it.name == REMEMBER_ME }
|
return trackManager.mdList.isLogged && network.cookieManager.get(httpUrl).any { it.name == REMEMBER_ME }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getUsername(): String {
|
||||||
|
return trackManager.mdList.getUsername()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPassword(): String {
|
||||||
|
return trackManager.mdList.getPassword()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun login(
|
override suspend fun login(
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
twoFactorCode: String
|
twoFactorCode: String?
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val formBody = FormBody.Builder().apply {
|
val formBody = FormBody.Builder().apply {
|
||||||
@@ -202,7 +204,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
|
|||||||
add("login_password", password)
|
add("login_password", password)
|
||||||
add("no_js", "1")
|
add("no_js", "1")
|
||||||
add("remember_me", "1")
|
add("remember_me", "1")
|
||||||
add("two_factor", twoFactorCode)
|
add("two_factor", twoFactorCode ?: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
@@ -223,6 +225,8 @@ class MangaDex(delegate: HttpSource, val context: Context) :
|
|||||||
} else {
|
} else {
|
||||||
throw Exception("Json data was null")
|
throw Exception("Json data was null")
|
||||||
}
|
}
|
||||||
|
}.also {
|
||||||
|
preferences.setTrackCredentials(trackManager.mdList, username, password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +1,43 @@
|
|||||||
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
|
|
||||||
Values.DarkThemeVariant.red -> R.style.Theme_Tachiyomi_Red
|
|
||||||
Values.DarkThemeVariant.midnightdusk -> R.style.Theme_Tachiyomi_MidnightDusk
|
|
||||||
Values.DarkThemeVariant.hotpink -> R.style.Theme_Tachiyomi_HotPink
|
|
||||||
else -> R.style.Theme_Tachiyomi_Dark
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
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.amoledblue -> R.style.Theme_Tachiyomi_Dark_AmoledBlue
|
||||||
|
DarkThemeVariant.amoled -> R.style.Theme_Tachiyomi_Dark_Amoled
|
||||||
|
DarkThemeVariant.red -> R.style.Theme_Tachiyomi_Dark_Red
|
||||||
|
DarkThemeVariant.midnightdusk -> R.style.Theme_Tachiyomi_Dark_MidnightDusk
|
||||||
|
DarkThemeVariant.hotpink -> R.style.Theme_Tachiyomi_Dark_HotPink
|
||||||
}
|
}
|
||||||
)
|
} 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
|
||||||
@@ -51,10 +50,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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+8
-9
@@ -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
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -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
|
||||||
@@ -46,10 +45,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,7 +3,6 @@ package eu.kanade.tachiyomi.ui.browse.latest
|
|||||||
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.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import dev.chrisbanes.insetter.applyInsetter
|
import dev.chrisbanes.insetter.applyInsetter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@@ -32,23 +31,6 @@ open class LatestController :
|
|||||||
*/
|
*/
|
||||||
protected var adapter: LatestAdapter? = null
|
protected var adapter: LatestAdapter? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiate the view with [R.layout.global_search_controller].
|
|
||||||
*
|
|
||||||
* @param inflater used to load the layout xml.
|
|
||||||
* @param container containing parent views.
|
|
||||||
* @return inflated view
|
|
||||||
*/
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
|
||||||
binding = LatestControllerBinding.inflate(inflater)
|
|
||||||
binding.recycler.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
override fun getTitle(): String? {
|
||||||
return applicationContext?.getString(R.string.latest)
|
return applicationContext?.getString(R.string.latest)
|
||||||
}
|
}
|
||||||
@@ -82,6 +64,8 @@ open class LatestController :
|
|||||||
onMangaClick(manga)
|
onMangaClick(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createBinding(inflater: LayoutInflater): LatestControllerBinding = LatestControllerBinding.inflate(inflater)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the view is created
|
* Called when the view is created
|
||||||
*
|
*
|
||||||
@@ -90,6 +74,12 @@ open class LatestController :
|
|||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.recycler.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adapter = LatestAdapter(this)
|
adapter = LatestAdapter(this)
|
||||||
|
|
||||||
// Create recycler and set adapter.
|
// Create recycler and set adapter.
|
||||||
|
|||||||
+5
-8
@@ -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.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -46,18 +45,16 @@ class PreMigrationController(bundle: Bundle? = null) :
|
|||||||
|
|
||||||
override fun getTitle() = view?.context?.getString(R.string.select_sources)
|
override fun getTitle() = view?.context?.getString(R.string.select_sources)
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun createBinding(inflater: LayoutInflater) = PreMigrationControllerBinding.inflate(inflater)
|
||||||
binding = PreMigrationControllerBinding.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)
|
|
||||||
|
|
||||||
val ourAdapter = adapter ?: MigrationSourceAdapter(
|
val ourAdapter = adapter ?: MigrationSourceAdapter(
|
||||||
getEnabledSources().map { MigrationSourceItem(it, isEnabled(it.id.toString())) },
|
getEnabledSources().map { MigrationSourceItem(it, isEnabled(it.id.toString())) },
|
||||||
|
|||||||
+9
-11
@@ -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 android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
@@ -84,24 +83,23 @@ class MigrationListController(bundle: Bundle? = null) :
|
|||||||
|
|
||||||
private val throttleManager = EHentaiThrottleManager()
|
private val throttleManager = EHentaiThrottleManager()
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
|
||||||
binding = MigrationListControllerBinding.inflate(inflater)
|
|
||||||
binding.recycler.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTitle(): String {
|
override fun getTitle(): String {
|
||||||
return resources?.getString(R.string.migration) + " (${adapter?.items?.count {
|
return resources?.getString(R.string.migration) + " (${adapter?.items?.count {
|
||||||
it.manga.migrationStatus != MigrationStatus.RUNNING
|
it.manga.migrationStatus != MigrationStatus.RUNNING
|
||||||
}}/${adapter?.itemCount ?: 0})"
|
}}/${adapter?.itemCount ?: 0})"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createBinding(inflater: LayoutInflater) = MigrationListControllerBinding.inflate(inflater)
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.recycler.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setTitle()
|
setTitle()
|
||||||
val config = this.config ?: return
|
val config = this.config ?: return
|
||||||
|
|
||||||
|
|||||||
+21
-6
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.migration.advanced.process
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.History
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
@@ -102,16 +103,30 @@ class MigrationProcessAdapter(
|
|||||||
// Update chapters read
|
// Update chapters read
|
||||||
if (MigrationFlags.hasChapters(flags)) {
|
if (MigrationFlags.hasChapters(flags)) {
|
||||||
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
|
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
|
||||||
val maxChapterRead = prevMangaChapters.filter { it.read }.maxByOrNull { it.chapter_number }?.chapter_number
|
val maxChapterRead =
|
||||||
if (maxChapterRead != null) {
|
prevMangaChapters.filter { it.read }.maxOfOrNull { it.chapter_number }
|
||||||
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||||
for (chapter in dbChapters) {
|
val prevHistoryList = db.getHistoryByMangaId(prevManga.id!!).executeAsBlocking()
|
||||||
if (chapter.isRecognizedNumber && chapter.chapter_number <= maxChapterRead) {
|
val historyList = mutableListOf<History>()
|
||||||
|
for (chapter in dbChapters) {
|
||||||
|
if (chapter.isRecognizedNumber) {
|
||||||
|
val prevChapter =
|
||||||
|
prevMangaChapters.find { it.isRecognizedNumber && it.chapter_number == chapter.chapter_number }
|
||||||
|
if (prevChapter != null) {
|
||||||
|
chapter.bookmark = prevChapter.bookmark
|
||||||
|
chapter.read = prevChapter.read
|
||||||
|
chapter.date_fetch = prevChapter.date_fetch
|
||||||
|
prevHistoryList.find { it.chapter_id == prevChapter.id }?.let { prevHistory ->
|
||||||
|
val history = History.create(chapter).apply { last_read = prevHistory.last_read }
|
||||||
|
historyList.add(history)
|
||||||
|
}
|
||||||
|
} else if (maxChapterRead != null && chapter.chapter_number <= maxChapterRead) {
|
||||||
chapter.read = true
|
chapter.read = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.insertChapters(dbChapters).executeAsBlocking()
|
|
||||||
}
|
}
|
||||||
|
db.insertChapters(dbChapters).executeAsBlocking()
|
||||||
|
db.updateHistoryLastRead(historyList).executeAsBlocking()
|
||||||
}
|
}
|
||||||
// Update categories
|
// Update categories
|
||||||
if (MigrationFlags.hasCategories(flags)) {
|
if (MigrationFlags.hasCategories(flags)) {
|
||||||
|
|||||||
+5
-8
@@ -3,7 +3,6 @@ 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 dev.chrisbanes.insetter.applyInsetter
|
||||||
@@ -52,18 +51,16 @@ 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)
|
|
||||||
|
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 = MigrationMangaAdapter(this)
|
adapter = MigrationMangaAdapter(this)
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
|
|||||||
+5
-8
@@ -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
|
||||||
@@ -42,18 +41,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)
|
||||||
|
|||||||
+3
-3
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,7 +46,7 @@ class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Holder(view: View, adapter: FlexibleAdapter</* SY --> */ IFlexible<RecyclerView.ViewHolder> /* SY <-- */>) : FlexibleViewHolder(view, adapter) {
|
class Holder(view: View, adapter: FlexibleAdapter</* SY --> */ IFlexible<RecyclerView.ViewHolder> /* SY <-- */>) : FlexibleViewHolder(view, adapter) {
|
||||||
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
|
private val binding = SectionHeaderItemBinding.bind(view)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding.title.text = view.context.getString(/* SY --> */ R.string.select_a_source_to_migrate_from /* SY <-- */)
|
binding.title.text = view.context.getString(/* SY --> */ R.string.select_a_source_to_migrate_from /* SY <-- */)
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ package eu.kanade.tachiyomi.ui.browse.source
|
|||||||
import android.view.View
|
import 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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,7 +9,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.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
@@ -88,25 +87,16 @@ class SourceController(bundle: Bundle? = null) :
|
|||||||
return SourcePresenter(/* SY --> */ controllerMode = mode /* SY <-- */)
|
return SourcePresenter(/* SY --> */ controllerMode = mode /* SY <-- */)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
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 /* SY --> */, private val showLatest: Boolean, private val showPins: Boolean /* SY <-- */) :
|
class SourceHolder(private val view: View, val adapter: SourceAdapter /* SY --> */, private val showLatest: Boolean, private val showPins: Boolean /* SY <-- */) :
|
||||||
@@ -52,9 +51,9 @@ class SourceHolder(private val view: View, val adapter: SourceAdapter /* SY -->
|
|||||||
|
|
||||||
binding.pin.isVisible = showPins
|
binding.pin.isVisible = showPins
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-6
@@ -61,6 +61,7 @@ import exh.md.similar.ui.EnableMangaDexSimilarDialogController
|
|||||||
import exh.savedsearches.EXHSavedSearch
|
import exh.savedsearches.EXHSavedSearch
|
||||||
import exh.source.getMainSource
|
import exh.source.getMainSource
|
||||||
import exh.source.isEhBasedSource
|
import exh.source.isEhBasedSource
|
||||||
|
import exh.widget.preference.MangadexLoginDialog
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
@@ -158,10 +159,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -182,8 +180,8 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
preferences.shownMangaDexSimilarAskDialog().set(true)
|
preferences.shownMangaDexSimilarAskDialog().set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mainSource is LoginSource && mainSource.needsLogin && !mainSource.isLogged()) {
|
if (mainSource is LoginSource && mainSource.requiresLogin && !mainSource.isLogged()) {
|
||||||
val dialog = mainSource.getLoginDialog(mainSource, activity!!)
|
val dialog = MangadexLoginDialog(mainSource)
|
||||||
dialog.showDialog(router)
|
dialog.showDialog(router)
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
@@ -411,6 +409,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("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.view.ViewGroup
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
@@ -37,10 +38,17 @@ class SourceFilterSheet(
|
|||||||
// EXH <--
|
// EXH <--
|
||||||
) : BaseBottomSheetDialog(activity) {
|
) : BaseBottomSheetDialog(activity) {
|
||||||
|
|
||||||
private var filterNavView: FilterNavigationView
|
private var filterNavView: FilterNavigationView = FilterNavigationView(
|
||||||
|
activity,
|
||||||
|
// SY -->
|
||||||
|
searches = searches,
|
||||||
|
source = source,
|
||||||
|
controller = controller
|
||||||
|
// SY <--
|
||||||
|
)
|
||||||
|
private val sheetBehavior: BottomSheetBehavior<*>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
filterNavView = FilterNavigationView(activity /* SY --> */, searches = searches, source = source, controller = controller/* SY <-- */)
|
|
||||||
filterNavView.onFilterClicked = {
|
filterNavView.onFilterClicked = {
|
||||||
onFilterClicked()
|
onFilterClicked()
|
||||||
this.dismiss()
|
this.dismiss()
|
||||||
@@ -56,6 +64,13 @@ class SourceFilterSheet(
|
|||||||
// EXH <--
|
// EXH <--
|
||||||
|
|
||||||
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<*>>) {
|
||||||
@@ -72,7 +87,15 @@ class SourceFilterSheet(
|
|||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
class FilterNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null /* SY --> */, searches: List<EXHSavedSearch> = emptyList(), source: CatalogueSource? = null, controller: BaseController<*>? = null/* SY <-- */) :
|
class FilterNavigationView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
// SY -->
|
||||||
|
searches: List<EXHSavedSearch> = emptyList(),
|
||||||
|
source: CatalogueSource? = null,
|
||||||
|
controller: BaseController<*>? = null
|
||||||
|
// SY <--
|
||||||
|
) :
|
||||||
SimpleNavigationView(context, attrs) {
|
SimpleNavigationView(context, attrs) {
|
||||||
|
|
||||||
var onFilterClicked = {}
|
var onFilterClicked = {}
|
||||||
@@ -91,8 +114,13 @@ class SourceFilterSheet(
|
|||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
val adapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter<IFlexible<*>>(null)
|
val adapter: FlexibleAdapter<IFlexible<*>> = FlexibleAdapter<IFlexible<*>>(null)
|
||||||
|
.setDisplayHeadersAtStartUp(true)
|
||||||
|
|
||||||
private val binding = SourceFilterSheetBinding.inflate(LayoutInflater.from(context), null, false)
|
private val binding = SourceFilterSheetBinding.inflate(
|
||||||
|
LayoutInflater.from(context),
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|||||||
+7
-17
@@ -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
|
||||||
@@ -51,22 +50,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
|
||||||
@@ -143,6 +127,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.
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.view.LayoutInflater
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@@ -74,18 +73,6 @@ open class IndexController :
|
|||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiate the view with [R.layout.latest_controller].
|
|
||||||
*
|
|
||||||
* @param inflater used to load the layout xml.
|
|
||||||
* @param container containing parent views.
|
|
||||||
* @return inflated view
|
|
||||||
*/
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
|
||||||
binding = IndexControllerBinding.inflate(inflater)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
override fun getTitle(): String? {
|
||||||
return source!!.name
|
return source!!.name
|
||||||
}
|
}
|
||||||
@@ -139,6 +126,8 @@ open class IndexController :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createBinding(inflater: LayoutInflater) = IndexControllerBinding.inflate(inflater)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the view is created
|
* Called when the view is created
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+7
-16
@@ -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 BiometricTimesController :
|
|||||||
return resources?.getString(R.string.biometric_lock_times)
|
return resources?.getString(R.string.biometric_lock_times)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun createBinding(inflater: LayoutInflater) = CategoriesControllerBinding.inflate(inflater)
|
||||||
* Returns the view of this controller.
|
|
||||||
*
|
|
||||||
* @param inflater The layout inflater to create the view from XML.
|
|
||||||
* @param container The parent view for this one.
|
|
||||||
*/
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
|
||||||
binding = CategoriesControllerBinding.inflate(inflater)
|
|
||||||
binding.recycler.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after view inflation. Used to initialize the view.
|
* Called after view inflation. Used to initialize the view.
|
||||||
@@ -92,6 +77,12 @@ class BiometricTimesController :
|
|||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.recycler.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adapter = BiometricTimesAdapter(this@BiometricTimesController)
|
adapter = BiometricTimesAdapter(this@BiometricTimesController)
|
||||||
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.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
|
||||||
@@ -74,21 +73,7 @@ class SortTagController :
|
|||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun createBinding(inflater: LayoutInflater) = CategoriesControllerBinding.inflate(inflater)
|
||||||
* Returns the view of this controller.
|
|
||||||
*
|
|
||||||
* @param inflater The layout inflater to create the view from XML.
|
|
||||||
* @param container The parent view for this one.
|
|
||||||
*/
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
|
||||||
binding = CategoriesControllerBinding.inflate(inflater)
|
|
||||||
binding.recycler.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after view inflation. Used to initialize the view.
|
* Called after view inflation. Used to initialize the view.
|
||||||
@@ -98,6 +83,12 @@ class SortTagController :
|
|||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.recycler.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adapter = SortTagAdapter(this@SortTagController)
|
adapter = SortTagAdapter(this@SortTagController)
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
binding.recycler.setHasFixedSize(true)
|
binding.recycler.setHasFixedSize(true)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -65,21 +64,7 @@ class RepoController :
|
|||||||
return resources?.getString(R.string.action_edit_repos)
|
return resources?.getString(R.string.action_edit_repos)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun createBinding(inflater: LayoutInflater) = CategoriesControllerBinding.inflate(inflater)
|
||||||
* Returns the view of this controller.
|
|
||||||
*
|
|
||||||
* @param inflater The layout inflater to create the view from XML.
|
|
||||||
* @param container The parent view for this one.
|
|
||||||
*/
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
|
||||||
binding = CategoriesControllerBinding.inflate(inflater)
|
|
||||||
binding.recycler.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after view inflation. Used to initialize the view.
|
* Called after view inflation. Used to initialize the view.
|
||||||
@@ -89,6 +74,12 @@ class RepoController :
|
|||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.recycler.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adapter = RepoAdapter(this@RepoController)
|
adapter = RepoAdapter(this@RepoController)
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
binding.recycler.setHasFixedSize(true)
|
binding.recycler.setHasFixedSize(true)
|
||||||
|
|||||||
+7
-16
@@ -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
|
||||||
@@ -66,21 +65,7 @@ class SourceCategoryController :
|
|||||||
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.
|
||||||
@@ -90,6 +75,12 @@ class SourceCategoryController :
|
|||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.recycler.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adapter = SourceCategoryAdapter(this@SourceCategoryController)
|
adapter = SourceCategoryAdapter(this@SourceCategoryController)
|
||||||
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 android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
@@ -20,6 +19,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.davidea.flexibleadapter.SelectableAdapter
|
import eu.davidea.flexibleadapter.SelectableAdapter
|
||||||
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
|
||||||
@@ -34,7 +34,6 @@ import eu.kanade.tachiyomi.ui.base.controller.SearchableNucleusController
|
|||||||
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
|
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
@@ -43,6 +42,7 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
|
|||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import exh.favorites.FavoritesIntroDialog
|
import exh.favorites.FavoritesIntroDialog
|
||||||
import exh.favorites.FavoritesSyncStatus
|
import exh.favorites.FavoritesSyncStatus
|
||||||
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import exh.source.PERV_EDEN_EN_SOURCE_ID
|
import exh.source.PERV_EDEN_EN_SOURCE_ID
|
||||||
import exh.source.PERV_EDEN_IT_SOURCE_ID
|
import exh.source.PERV_EDEN_IT_SOURCE_ID
|
||||||
import exh.source.isEhBasedManga
|
import exh.source.isEhBasedManga
|
||||||
@@ -193,14 +193,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()
|
||||||
@@ -473,9 +476,6 @@ class LibraryController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// SY -->
|
// SY -->
|
||||||
R.id.action_source_migration -> {
|
|
||||||
router.pushController(MigrationSourcesController().withFadeTransaction())
|
|
||||||
}
|
|
||||||
R.id.action_sync_favorites -> {
|
R.id.action_sync_favorites -> {
|
||||||
if (preferences.exhShowSyncIntro().get()) {
|
if (preferences.exhShowSyncIntro().get()) {
|
||||||
activity?.let { FavoritesIntroDialog().show(it) }
|
activity?.let { FavoritesIntroDialog().show(it) }
|
||||||
@@ -542,9 +542,13 @@ class LibraryController(
|
|||||||
// SY -->
|
// SY -->
|
||||||
R.id.action_migrate -> {
|
R.id.action_migrate -> {
|
||||||
val skipPre = preferences.skipPreMigration().get()
|
val skipPre = preferences.skipPreMigration().get()
|
||||||
val selectedMangaIds = selectedMangas.mapNotNull { it.id }
|
val selectedMangaIds = selectedMangas.filterNot { it.source == MERGED_SOURCE_ID }.mapNotNull { it.id }
|
||||||
destroyActionModeIfNeeded()
|
destroyActionModeIfNeeded()
|
||||||
PreMigrationController.navigateToMigration(skipPre, router, selectedMangaIds)
|
if (selectedMangaIds.isNotEmpty()) {
|
||||||
|
PreMigrationController.navigateToMigration(skipPre, router, selectedMangaIds)
|
||||||
|
} else {
|
||||||
|
activity?.toast(R.string.no_valid_manga)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
R.id.action_clean -> cleanTitles()
|
R.id.action_clean -> cleanTitles()
|
||||||
R.id.action_push_to_mdlist -> pushToMdList()
|
R.id.action_push_to_mdlist -> pushToMdList()
|
||||||
|
|||||||
@@ -14,6 +14,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
|
||||||
@@ -48,6 +49,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 exh.EXHMigrations
|
import exh.EXHMigrations
|
||||||
import exh.eh.EHentaiUpdateWorker
|
import exh.eh.EHentaiUpdateWorker
|
||||||
@@ -123,23 +126,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
|
||||||
@@ -148,6 +155,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() }
|
||||||
@@ -171,14 +187,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()
|
||||||
}
|
}
|
||||||
// SY -->
|
|
||||||
R.id.nav_updates -> {
|
R.id.nav_updates -> {
|
||||||
if (router.backstack.lastOrNull()?.controller() !is DownloadController) {
|
router.pushController(DownloadController().withFadeTransaction())
|
||||||
val controller = router.getControllerWithTag(id.toString()) as? UpdatesController
|
|
||||||
controller?.router?.pushController(DownloadController().withFadeTransaction())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@@ -500,7 +511,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
|
||||||
|
|||||||
@@ -218,8 +218,8 @@ class EditMangaDialog : DialogController {
|
|||||||
private fun ChipGroup.setChips(items: List<String>) {
|
private fun ChipGroup.setChips(items: List<String>) {
|
||||||
removeAllViews()
|
removeAllViews()
|
||||||
|
|
||||||
items.forEach { item ->
|
items.asSequence().map { item ->
|
||||||
val chip = Chip(context).apply {
|
Chip(context).apply {
|
||||||
text = item
|
text = item
|
||||||
|
|
||||||
isCloseIconVisible = true
|
isCloseIconVisible = true
|
||||||
@@ -228,15 +228,16 @@ class EditMangaDialog : DialogController {
|
|||||||
removeView(this)
|
removeView(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.forEach {
|
||||||
addView(chip)
|
addView(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val addTagChip = Chip(context).apply {
|
val addTagChip = Chip(context).apply {
|
||||||
setText(R.string.add_tag)
|
setText(R.string.add_tag)
|
||||||
|
|
||||||
chipIcon = ContextCompat.getDrawable(context, R.drawable.ic_add_24dp)
|
chipIcon = ContextCompat.getDrawable(context, R.drawable.ic_add_24dp)?.apply {
|
||||||
chipIcon?.setTint(context.getResourceColor(R.attr.colorAccent))
|
setTint(context.getResourceColor(R.attr.colorAccent))
|
||||||
|
}
|
||||||
textStartPadding = 0F
|
textStartPadding = 0F
|
||||||
|
|
||||||
clicks().onEach {
|
clicks().onEach {
|
||||||
|
|||||||
@@ -17,7 +17,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 android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -276,18 +275,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
|
||||||
val adapters: MutableList<RecyclerView.Adapter<out RecyclerView.ViewHolder>?> = mutableListOf()
|
val adapters: MutableList<RecyclerView.Adapter<out RecyclerView.ViewHolder>?> = mutableListOf()
|
||||||
@@ -479,7 +481,7 @@ class MangaController :
|
|||||||
// Hide options for non-library manga
|
// Hide options for non-library manga
|
||||||
menu.findItem(R.id.action_edit_categories).isVisible = presenter.manga.favorite && presenter.getCategories().isNotEmpty()
|
menu.findItem(R.id.action_edit_categories).isVisible = presenter.manga.favorite && presenter.getCategories().isNotEmpty()
|
||||||
menu.findItem(R.id.action_edit_cover).isVisible = /* SY --> */ false /* presenter.manga.favorite SY <-- */
|
menu.findItem(R.id.action_edit_cover).isVisible = /* SY --> */ false /* presenter.manga.favorite SY <-- */
|
||||||
menu.findItem(R.id.action_migrate).isVisible = presenter.manga.favorite
|
menu.findItem(R.id.action_migrate).isVisible = presenter.manga.favorite /* SY --> */ && presenter.manga.source != MERGED_SOURCE_ID /* SY <-- */
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
menu.findItem(R.id.action_edit).isVisible = presenter.manga.favorite || isLocalSource
|
menu.findItem(R.id.action_edit).isVisible = presenter.manga.favorite || isLocalSource
|
||||||
@@ -1145,8 +1147,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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1270,7 +1271,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
|
||||||
@@ -1302,10 +1302,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1427,14 +1423,26 @@ class MangaController :
|
|||||||
|
|
||||||
// OVERFLOW MENU DIALOGS
|
// OVERFLOW MENU DIALOGS
|
||||||
|
|
||||||
private fun getUnreadChaptersSorted() = /* SY --> */ if (presenter.source.isEhBasedSource()) presenter.chapters
|
private fun getUnreadChaptersSorted(): List<ChapterItem> {
|
||||||
.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }
|
val chapters = presenter.chapters
|
||||||
.distinctBy { it.name }
|
.sortedWith(presenter.getChapterSort())
|
||||||
.sortedBy { it.source_order }
|
.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }
|
||||||
else /* SY <-- */ presenter.chapters
|
.distinctBy { it.name }
|
||||||
.filter { !it.read && it.status == Download.State.NOT_DOWNLOADED }
|
// SY -->
|
||||||
.distinctBy { it.name }
|
.let {
|
||||||
.sortedByDescending { it.source_order }
|
if (presenter.source.isEhBasedSource()) {
|
||||||
|
it.reversed()
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
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) {
|
||||||
|
|||||||
@@ -846,7 +846,11 @@ class MangaPresenter(
|
|||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
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) }
|
||||||
@@ -861,8 +865,6 @@ class MangaPresenter(
|
|||||||
}
|
}
|
||||||
else -> throw NotImplementedError("Unimplemented sorting method")
|
else -> throw NotImplementedError("Unimplemented sorting method")
|
||||||
}
|
}
|
||||||
|
|
||||||
return observable.toSortedList(sortFunction)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -889,11 +891,19 @@ 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? {
|
||||||
|
val chapters = chapters.sortedWith(getChapterSort())
|
||||||
return if (source.isEhBasedSource()) {
|
return if (source.isEhBasedSource()) {
|
||||||
val chapter = chapters.sortedBy { it.source_order }.getOrNull(0)
|
if (sortDescending()) {
|
||||||
if (chapter?.read == false) chapter else null
|
chapters.firstOrNull()?.takeUnless { it.read }
|
||||||
|
} else {
|
||||||
|
chapters.lastOrNull()?.takeUnless { it.read }
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
chapters.sortedByDescending { it.source_order }.find { !it.read }
|
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) {
|
||||||
|
isVisible = true
|
||||||
|
if (state == Download.State.DOWNLOADED) {
|
||||||
|
setVectorCompat(R.drawable.ic_check_circle_24dp, android.R.attr.textColorPrimary)
|
||||||
|
} else {
|
||||||
|
setVectorCompat(R.drawable.ic_error_outline_24dp, R.attr.colorError)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.errorIcon.isVisible = state == Download.State.ERROR
|
this.state = state
|
||||||
|
this.progress = progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.ui.manga.chapter
|
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
|
||||||
@@ -64,8 +64,10 @@ class ChapterHolder(
|
|||||||
} else /* SY <-- */ descriptions.add(adapter.dateFormat.format(Date(chapter.date_upload)))
|
} else /* SY <-- */ descriptions.add(adapter.dateFormat.format(Date(chapter.date_upload)))
|
||||||
}
|
}
|
||||||
if ((!chapter.read || (adapter.preserveReadingPosition && manga.isEhBasedManga())) && chapter.last_page_read > 0) {
|
if ((!chapter.read || (adapter.preserveReadingPosition && manga.isEhBasedManga())) && 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,16 +61,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
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-10
@@ -42,7 +42,7 @@ class EditMergedSettingsHeaderAdapter(private val controller: EditMergedSettings
|
|||||||
android.R.layout.simple_spinner_item,
|
android.R.layout.simple_spinner_item,
|
||||||
listOf(
|
listOf(
|
||||||
"No dedupe",
|
"No dedupe",
|
||||||
"Dedupe by priority",
|
/*"Dedupe by priority",*/
|
||||||
"Show source with most chapters",
|
"Show source with most chapters",
|
||||||
"Show source with highest chapter number"
|
"Show source with highest chapter number"
|
||||||
)
|
)
|
||||||
@@ -53,9 +53,9 @@ class EditMergedSettingsHeaderAdapter(private val controller: EditMergedSettings
|
|||||||
binding.dedupeModeSpinner.setSelection(
|
binding.dedupeModeSpinner.setSelection(
|
||||||
when (it.chapterSortMode) {
|
when (it.chapterSortMode) {
|
||||||
MergedMangaReference.CHAPTER_SORT_NO_DEDUPE -> 0
|
MergedMangaReference.CHAPTER_SORT_NO_DEDUPE -> 0
|
||||||
MergedMangaReference.CHAPTER_SORT_PRIORITY -> 1
|
/*MergedMangaReference.CHAPTER_SORT_PRIORITY -> 1*/
|
||||||
MergedMangaReference.CHAPTER_SORT_MOST_CHAPTERS -> 2
|
MergedMangaReference.CHAPTER_SORT_MOST_CHAPTERS -> 1
|
||||||
MergedMangaReference.CHAPTER_SORT_HIGHEST_CHAPTER_NUMBER -> 3
|
MergedMangaReference.CHAPTER_SORT_HIGHEST_CHAPTER_NUMBER -> 2
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -69,9 +69,9 @@ class EditMergedSettingsHeaderAdapter(private val controller: EditMergedSettings
|
|||||||
) {
|
) {
|
||||||
controller.mergeReference?.chapterSortMode = when (position) {
|
controller.mergeReference?.chapterSortMode = when (position) {
|
||||||
0 -> MergedMangaReference.CHAPTER_SORT_NO_DEDUPE
|
0 -> MergedMangaReference.CHAPTER_SORT_NO_DEDUPE
|
||||||
1 -> MergedMangaReference.CHAPTER_SORT_PRIORITY
|
/*1 -> MergedMangaReference.CHAPTER_SORT_PRIORITY*/
|
||||||
2 -> MergedMangaReference.CHAPTER_SORT_MOST_CHAPTERS
|
1 -> MergedMangaReference.CHAPTER_SORT_MOST_CHAPTERS
|
||||||
3 -> MergedMangaReference.CHAPTER_SORT_HIGHEST_CHAPTER_NUMBER
|
2 -> MergedMangaReference.CHAPTER_SORT_HIGHEST_CHAPTER_NUMBER
|
||||||
else -> MergedMangaReference.CHAPTER_SORT_NO_DEDUPE
|
else -> MergedMangaReference.CHAPTER_SORT_NO_DEDUPE
|
||||||
}
|
}
|
||||||
xLogD(controller.mergeReference?.chapterSortMode)
|
xLogD(controller.mergeReference?.chapterSortMode)
|
||||||
@@ -85,7 +85,13 @@ class EditMergedSettingsHeaderAdapter(private val controller: EditMergedSettings
|
|||||||
|
|
||||||
val mergedMangas = controller.mergedMangas
|
val mergedMangas = controller.mergedMangas
|
||||||
|
|
||||||
val mangaInfoAdapter: ArrayAdapter<String> = ArrayAdapter(itemView.context, android.R.layout.simple_spinner_item, mergedMangas.map { sourceManager.getOrStub(it.second.mangaSourceId).toString() + " " + it.first?.title })
|
val mangaInfoAdapter: ArrayAdapter<String> = ArrayAdapter(
|
||||||
|
itemView.context,
|
||||||
|
android.R.layout.simple_spinner_item,
|
||||||
|
mergedMangas.map {
|
||||||
|
sourceManager.getOrStub(it.second.mangaSourceId).toString() + " " + it.first?.title
|
||||||
|
}
|
||||||
|
)
|
||||||
mangaInfoAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
mangaInfoAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
binding.mangaInfoSpinner.adapter = mangaInfoAdapter
|
binding.mangaInfoSpinner.adapter = mangaInfoAdapter
|
||||||
|
|
||||||
@@ -102,11 +108,16 @@ class EditMergedSettingsHeaderAdapter(private val controller: EditMergedSettings
|
|||||||
position: Int,
|
position: Int,
|
||||||
id: Long
|
id: Long
|
||||||
) {
|
) {
|
||||||
controller.mergedMangas.find { mergedManga -> mergedManga.second.id == mergedMangas.getOrNull(position)?.second?.id }?.second?.let { newInfoManga ->
|
val mergedInfoManga = controller.mergedMangas
|
||||||
|
.find { mergedManga ->
|
||||||
|
mergedManga.second.id == mergedMangas.getOrNull(position)?.second?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergedInfoManga != null) {
|
||||||
controller.mergedMangas.onEach {
|
controller.mergedMangas.onEach {
|
||||||
it.second.isInfoManga = false
|
it.second.isInfoManga = false
|
||||||
}
|
}
|
||||||
newInfoManga.isInfoManga = true
|
mergedInfoManga.second.isInfoManga = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ 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.main.WhatsNewDialogController
|
import eu.kanade.tachiyomi.ui.main.WhatsNewDialogController
|
||||||
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
|
||||||
@@ -87,6 +88,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"
|
||||||
@@ -117,7 +126,7 @@ class AboutController : SettingsController() {
|
|||||||
preference {
|
preference {
|
||||||
key = "pref_about_label_original_tachiyomi_github"
|
key = "pref_about_label_original_tachiyomi_github"
|
||||||
title = "Original Tachiyomi GitHub "
|
title = "Original Tachiyomi GitHub "
|
||||||
"https://github.com/tachiyomiorg/tachiyomi".also {
|
"https://github.com/tachiyomiorg".also {
|
||||||
summary = it
|
summary = it
|
||||||
onClick { openInBrowser(it) }
|
onClick { openInBrowser(it) }
|
||||||
}
|
}
|
||||||
@@ -140,6 +149,7 @@ class AboutController : SettingsController() {
|
|||||||
.withAboutIconShown(false)
|
.withAboutIconShown(false)
|
||||||
.withAboutVersionShown(false)
|
.withAboutVersionShown(false)
|
||||||
.withLicenseShown(true)
|
.withLicenseShown(true)
|
||||||
|
.withEdgeToEdge(true)
|
||||||
.start(activity!!)
|
.start(activity!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,6 +162,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 {
|
||||||
@@ -203,20 +218,10 @@ 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})
|
activity?.copyToClipboard("Debug information", deviceInfo)
|
||||||
Preview build: $syDebugVersion
|
}
|
||||||
Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})
|
|
||||||
Android build ID: ${Build.DISPLAY}
|
|
||||||
Device brand: ${Build.BRAND}
|
|
||||||
Device manufacturer: ${Build.MANUFACTURER}
|
|
||||||
Device name: ${Build.DEVICE}
|
|
||||||
Device model: ${Build.MODEL}
|
|
||||||
Device product name: ${Build.PRODUCT}
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
activity?.copyToClipboard("Debug information", deviceInfo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFormattedBuildTime(): String {
|
private fun getFormattedBuildTime(): String {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -27,7 +31,10 @@ 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 exh.ui.batchadd.BatchAddController
|
import exh.ui.batchadd.BatchAddController
|
||||||
|
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
|
||||||
|
|
||||||
@@ -40,6 +47,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
|
||||||
|
|
||||||
@@ -128,6 +138,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
|
||||||
@@ -154,6 +177,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) {
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,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.MotionEvent
|
import android.view.MotionEvent
|
||||||
@@ -68,6 +67,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
|
||||||
@@ -458,13 +458,18 @@ 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 },
|
||||||
presenter.setMangaViewer(newReadingMode.prefValue)
|
selectedItemId = presenter.getMangaViewer(resolveDefault = false),
|
||||||
|
) {
|
||||||
|
val newReadingMode = ReadingModeType.fromPreference(itemId)
|
||||||
|
|
||||||
menuToggleToast?.cancel()
|
presenter.setMangaViewer(newReadingMode.prefValue)
|
||||||
if (!preferences.showReadingMode()) {
|
|
||||||
menuToggleToast = toast(newReadingMode.stringRes)
|
menuToggleToast?.cancel()
|
||||||
|
if (!preferences.showReadingMode()) {
|
||||||
|
menuToggleToast = toast(newReadingMode.stringRes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -474,14 +479,18 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
|||||||
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)
|
||||||
|
|
||||||
menuToggleToast?.cancel()
|
menuToggleToast?.cancel()
|
||||||
menuToggleToast = toast(newOrientation.stringRes)
|
menuToggleToast = toast(newOrientation.stringRes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
preferences.rotation().asImmediateFlow { updateRotationShortcut(it) }
|
preferences.rotation().asImmediateFlow { updateRotationShortcut(it) }
|
||||||
@@ -523,6 +532,11 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
|||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
ReaderSettingsSheet(this@ReaderActivity).show()
|
ReaderSettingsSheet(this@ReaderActivity).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setOnLongClickListener {
|
||||||
|
ReaderSettingsSheet(this@ReaderActivity, showColorFilterSettings = true).show()
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --> EH
|
// --> EH
|
||||||
@@ -718,7 +732,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
|||||||
// EXH <--
|
// EXH <--
|
||||||
|
|
||||||
/*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)
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
@@ -885,12 +899,10 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
|||||||
binding.viewerContainer.addView(newViewer.getView())
|
binding.viewerContainer.addView(newViewer.getView())
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
val defaultReaderType = manga.defaultReaderType(manga.mangaType(sourceName = sourceManager.getOrStub(manga.source).name))
|
val defaultReaderType = manga.defaultReaderType(manga.mangaType(sourceName = sourceManager.get(manga.source)?.name))
|
||||||
if (preferences.useAutoWebtoon().get() && manga.viewer == 0 && defaultReaderType != null && defaultReaderType == ReadingModeType.WEBTOON.prefValue) {
|
if (preferences.useAutoWebtoon().get() && manga.viewer == 0 && defaultReaderType != null && defaultReaderType == ReadingModeType.WEBTOON.prefValue) {
|
||||||
readingModeToast?.cancel()
|
readingModeToast?.cancel()
|
||||||
readingModeToast = this.toast(resources.getString(R.string.eh_auto_webtoon_snack)) {
|
readingModeToast = toast(resources.getString(R.string.eh_auto_webtoon_snack))
|
||||||
it.setGravity(Gravity.CENTER_VERTICAL or Gravity.CENTER_HORIZONTAL, 0, 0)
|
|
||||||
}
|
|
||||||
} else if (preferences.showReadingMode()) {
|
} else if (preferences.showReadingMode()) {
|
||||||
// SY <--
|
// SY <--
|
||||||
showReadingModeToast(presenter.getMangaViewer())
|
showReadingModeToast(presenter.getMangaViewer())
|
||||||
@@ -946,10 +958,12 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showReadingModeToast(mode: Int) {
|
private fun showReadingModeToast(mode: Int) {
|
||||||
val strings = resources.getStringArray(R.array.viewers_selector)
|
try {
|
||||||
readingModeToast?.cancel()
|
val strings = resources.getStringArray(R.array.viewers_selector)
|
||||||
readingModeToast = toast(strings[mode]) {
|
readingModeToast?.cancel()
|
||||||
it.setGravity(Gravity.CENTER_VERTICAL or Gravity.CENTER_HORIZONTAL, 0, 0)
|
readingModeToast = toast(strings[mode])
|
||||||
|
} catch (e: ArrayIndexOutOfBoundsException) {
|
||||||
|
Timber.e("Unknown reading mode: $mode")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1178,7 +1192,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -598,7 +598,7 @@ class ReaderPresenter(
|
|||||||
val manga = manga ?: return preferences.defaultViewer()
|
val manga = manga ?: return preferences.defaultViewer()
|
||||||
// SY -->
|
// SY -->
|
||||||
return if (resolveDefault && manga.viewer == 0 && preferences.useAutoWebtoon().get()) {
|
return if (resolveDefault && manga.viewer == 0 && preferences.useAutoWebtoon().get()) {
|
||||||
manga.defaultReaderType(manga.mangaType(sourceName = sourceManager.getOrStub(manga.source).name)) ?: if (manga.viewer == 0) preferences.defaultViewer() else manga.viewer
|
manga.defaultReaderType(manga.mangaType(sourceName = sourceManager.get(manga.source)?.name)) ?: if (manga.viewer == 0) preferences.defaultViewer() else manga.viewer
|
||||||
} else if (resolveDefault && manga.viewer == 0) {
|
} else if (resolveDefault && manga.viewer == 0) {
|
||||||
preferences.defaultViewer()
|
preferences.defaultViewer()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -99,8 +99,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 ->
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -250,16 +250,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) {
|
||||||
// SY -->
|
// SY -->
|
||||||
if (readerTheme >= 3) {
|
if (readerTheme >= 3) {
|
||||||
@@ -295,21 +292,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!")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,11 +327,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,7 +385,10 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onPageSplit(currentPage: ReaderPage, newPage: InsertPage) {
|
fun onPageSplit(currentPage: ReaderPage, newPage: InsertPage) {
|
||||||
adapter.onPageSplit(currentPage, newPage, this::class.java)
|
activity.runOnUiThread {
|
||||||
|
// Need to insert on UI thread else images will go blank
|
||||||
|
adapter.onPageSplit(currentPage, newPage, this::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cleanupPageSplit() {
|
private fun cleanupPageSplit() {
|
||||||
|
|||||||
+15
-10
@@ -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()
|
||||||
@@ -249,11 +240,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import eu.kanade.tachiyomi.source.SourceManager.Companion.DELEGATED_SOURCES
|
|||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
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.editTextPreference
|
import eu.kanade.tachiyomi.util.preference.editTextPreference
|
||||||
import eu.kanade.tachiyomi.util.preference.intListPreference
|
import eu.kanade.tachiyomi.util.preference.intListPreference
|
||||||
@@ -40,6 +41,7 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
|||||||
import eu.kanade.tachiyomi.util.preference.summaryRes
|
import eu.kanade.tachiyomi.util.preference.summaryRes
|
||||||
import eu.kanade.tachiyomi.util.preference.switchPreference
|
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.storage.DiskUtil
|
||||||
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 exh.debug.SettingsDebugController
|
import exh.debug.SettingsDebugController
|
||||||
@@ -47,15 +49,8 @@ import exh.log.EHLogLevel
|
|||||||
import exh.source.BlacklistedSources
|
import exh.source.BlacklistedSources
|
||||||
import exh.source.EH_SOURCE_ID
|
import exh.source.EH_SOURCE_ID
|
||||||
import exh.source.EXH_SOURCE_ID
|
import exh.source.EXH_SOURCE_ID
|
||||||
import kotlinx.coroutines.CoroutineStart
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import rx.Observable
|
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
|
||||||
import rx.schedulers.Schedulers
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@@ -349,31 +344,32 @@ class SettingsAdvancedController : SettingsController() {
|
|||||||
private fun cleanupDownloads(removeRead: Boolean, removeNonFavorite: Boolean) {
|
private fun cleanupDownloads(removeRead: Boolean, removeNonFavorite: Boolean) {
|
||||||
if (job?.isActive == true) return
|
if (job?.isActive == true) return
|
||||||
activity?.toast(R.string.starting_cleanup)
|
activity?.toast(R.string.starting_cleanup)
|
||||||
job = GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT) {
|
job = launchIO {
|
||||||
val mangaList = db.getMangas().executeAsBlocking()
|
val mangaList = db.getMangas().executeAsBlocking()
|
||||||
val sourceManager: SourceManager = Injekt.get()
|
|
||||||
val downloadManager: DownloadManager = Injekt.get()
|
val downloadManager: DownloadManager = Injekt.get()
|
||||||
var foldersCleared = 0
|
var foldersCleared = 0
|
||||||
val sources = sourceManager.getOnlineSources()
|
Injekt.get<SourceManager>().getOnlineSources().forEach { source ->
|
||||||
|
|
||||||
for (source in sources) {
|
|
||||||
val mangaFolders = downloadManager.getMangaFolders(source)
|
val mangaFolders = downloadManager.getMangaFolders(source)
|
||||||
val sourceManga = mangaList.filter { it.source == source.id }
|
val sourceManga = mangaList
|
||||||
|
.asSequence()
|
||||||
|
.filter { it.source == source.id }
|
||||||
|
.map { it to DiskUtil.buildValidFilename(it.originalTitle) }
|
||||||
|
.toList()
|
||||||
|
|
||||||
for (mangaFolder in mangaFolders) {
|
mangaFolders.forEach mangaFolder@{ mangaFolder ->
|
||||||
val manga = sourceManga.find { it.originalTitle == mangaFolder.name }
|
val manga = sourceManga.find { (_, folderName) -> folderName == mangaFolder.name }?.first
|
||||||
if (manga == null) {
|
if (manga == null) {
|
||||||
// download is orphaned delete it
|
// download is orphaned delete it
|
||||||
foldersCleared += 1 + (mangaFolder.listFiles()?.size ?: 0)
|
foldersCleared += 1 + (mangaFolder.listFiles().orEmpty().size)
|
||||||
mangaFolder.delete()
|
mangaFolder.delete()
|
||||||
continue
|
} else {
|
||||||
|
val chapterList = db.getChapters(manga).executeAsBlocking()
|
||||||
|
foldersCleared += downloadManager.cleanupChapters(chapterList, manga, source, removeRead, removeNonFavorite)
|
||||||
}
|
}
|
||||||
val chapterList = db.getChapters(manga).executeAsBlocking()
|
|
||||||
foldersCleared += downloadManager.cleanupChapters(chapterList, manga, source, removeRead, removeNonFavorite)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
launchUI {
|
withUIContext {
|
||||||
val activity = activity ?: return@launchUI
|
val activity = activity ?: return@withUIContext
|
||||||
val cleanupString =
|
val cleanupString =
|
||||||
if (foldersCleared == 0) activity.getString(R.string.no_folders_to_cleanup)
|
if (foldersCleared == 0) activity.getString(R.string.no_folders_to_cleanup)
|
||||||
else resources!!.getQuantityString(
|
else resources!!.getQuantityString(
|
||||||
@@ -389,27 +385,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) }
|
activity?.toast(resources?.getString(R.string.cache_deleted, deletedFiles))
|
||||||
.doOnNext { file ->
|
findPreference(CLEAR_CACHE_KEY)?.summary =
|
||||||
if (chapterCache.removeFileFromCache(file.name)) {
|
resources?.getString(R.string.used_cache, chapterCache.readableSize)
|
||||||
deletedFiles++
|
|
||||||
}
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
withUIContext { activity?.toast(R.string.cache_delete_error) }
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnError {
|
|
||||||
activity?.toast(R.string.cache_delete_error)
|
|
||||||
}
|
|
||||||
.doOnCompleted {
|
|
||||||
activity?.toast(resources?.getString(R.string.cache_deleted, deletedFiles))
|
|
||||||
findPreference(CLEAR_CACHE_KEY)?.summary =
|
|
||||||
resources?.getString(R.string.used_cache, chapterCache.readableSize)
|
|
||||||
}
|
|
||||||
.subscribe()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClearDatabaseDialogController : DialogController() {
|
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) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ class SettingsGeneralController : SettingsController() {
|
|||||||
entriesRes = arrayOf(
|
entriesRes = arrayOf(
|
||||||
R.string.theme_dark_default,
|
R.string.theme_dark_default,
|
||||||
R.string.theme_dark_blue,
|
R.string.theme_dark_blue,
|
||||||
|
R.string.theme_dark_amoledblue,
|
||||||
R.string.theme_dark_amoled,
|
R.string.theme_dark_amoled,
|
||||||
R.string.theme_dark_red,
|
R.string.theme_dark_red,
|
||||||
R.string.theme_dark_midnightdusk,
|
R.string.theme_dark_midnightdusk,
|
||||||
@@ -140,6 +141,7 @@ class SettingsGeneralController : SettingsController() {
|
|||||||
entryValues = arrayOf(
|
entryValues = arrayOf(
|
||||||
Values.DarkThemeVariant.default.name,
|
Values.DarkThemeVariant.default.name,
|
||||||
Values.DarkThemeVariant.blue.name,
|
Values.DarkThemeVariant.blue.name,
|
||||||
|
Values.DarkThemeVariant.amoledblue.name,
|
||||||
Values.DarkThemeVariant.amoled.name,
|
Values.DarkThemeVariant.amoled.name,
|
||||||
Values.DarkThemeVariant.red.name,
|
Values.DarkThemeVariant.red.name,
|
||||||
Values.DarkThemeVariant.midnightdusk.name,
|
Values.DarkThemeVariant.midnightdusk.name,
|
||||||
|
|||||||
@@ -93,11 +93,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"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-12
@@ -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,19 @@ 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
|
import exh.syDebugVersion
|
||||||
|
|
||||||
class CrashLogUtil(private val context: Context) {
|
class CrashLogUtil(private val context: Context) {
|
||||||
|
|
||||||
@@ -19,31 +23,45 @@ class CrashLogUtil(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun dumpLogs() {
|
fun dumpLogs() {
|
||||||
try {
|
launchIO {
|
||||||
val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt")
|
try {
|
||||||
Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}")
|
val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt")
|
||||||
|
Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}").waitFor()
|
||||||
|
file.appendText(getDebugInfo())
|
||||||
|
|
||||||
showNotification(file.getUriCompat(context))
|
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})
|
||||||
|
Preview build: $syDebugVersion
|
||||||
|
Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})
|
||||||
|
Android build ID: ${Build.DISPLAY}
|
||||||
|
Device brand: ${Build.BRAND}
|
||||||
|
Device manufacturer: ${Build.MANUFACTURER}
|
||||||
|
Device name: ${Build.DEVICE}
|
||||||
|
Device model: ${Build.MODEL}
|
||||||
|
Device product name: ${Build.PRODUCT}
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
private fun showNotification(uri: Uri) {
|
private fun showNotification(uri: Uri) {
|
||||||
context.notificationManager.cancel(Notifications.ID_CRASH_LOGS)
|
context.notificationManager.cancel(Notifications.ID_CRASH_LOGS)
|
||||||
|
|
||||||
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),
|
||||||
|
|||||||
@@ -173,3 +173,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
|
||||||
|
|
||||||
val clipboard = getSystemService<ClipboardManager>()!!
|
try {
|
||||||
clipboard.setPrimaryClip(ClipData.newPlainText(label, content))
|
val clipboard = getSystemService<ClipboardManager>()!!
|
||||||
|
clipboard.setPrimaryClip(ClipData.newPlainText(label, content))
|
||||||
|
|
||||||
toast(getString(R.string.copied_to_clipboard, content.truncateCenter(50)))
|
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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -84,15 +84,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-5
@@ -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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user