Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cf0475066 | |||
| 0923cd6509 | |||
| 330908c49d | |||
| 644140b617 | |||
| d302a0fbc7 | |||
| ce8f7da9ca | |||
| f1a4811030 | |||
| a439ffcafc | |||
| 5eeab103c2 | |||
| 78ffd9d56e | |||
| 96213900ac | |||
| 85e30ef6ca | |||
| f38df69983 | |||
| 64e9515293 | |||
| 67e676d4ae | |||
| ef36a9c28c | |||
| 513bcbb80d | |||
| 3d5952ebbd | |||
| 1d55a1bec4 | |||
| 962344f5fc | |||
| ba6bcc82b6 | |||
| 6659935f3d | |||
| ccca9e8828 | |||
| b4cce2b3e0 | |||
| 9737d847fd | |||
| f180c6a07c | |||
| 024c2d4ce0 | |||
| 17731f3904 | |||
| e2dadd4213 | |||
| b4fedf9a87 | |||
| beaf6284fd | |||
| 3300eb0e79 | |||
| 3599526fde | |||
| 8b6a0ad891 | |||
| cf99ee73f5 | |||
| bbd3e3c29c | |||
| 972579bbec | |||
| 4044b0897e | |||
| 5e6c0bbc14 | |||
| a8c6474f5e | |||
| 820279634e | |||
| ce7577a2b4 | |||
| 31376e5a52 |
@@ -1,17 +1,17 @@
|
|||||||
1. **Before reporting a new issue, take a look at the [FAQ](https://tachiyomi.org/help/faq/), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened [issues](https://github.com/inorichi/tachiyomi/issues).**
|
1. **Before reporting a new issue, take a look at the [FAQ](https://tachiyomi.org/help/faq/), the [changelog](https://github.com/tachiyomiorg/tachiyomi/releases) and the already opened [issues](https://github.com/tachiyomiorg/tachiyomi/issues).**
|
||||||
2. If you are unsure, ask here: [](https://discord.gg/tachiyomi)
|
2. If you are unsure, ask here: [](https://discord.gg/tachiyomi)
|
||||||
3. What is your type of issue?
|
3. What is your type of issue?
|
||||||
* [Catalogue request](#catalogue-requests)
|
* [Catalogue request](#catalogue-requests)
|
||||||
* [Bugs](#bugs)
|
* [Bugs](#bugs)
|
||||||
* [Feature requests](#feature-requests)
|
* [Feature requests](#feature-requests)
|
||||||
* [Translations](https://tachiyomi.org/help/contribution/#translation)
|
* [Translations](https://tachiyomi.org/help/contribution/#translation)
|
||||||
4. After following 1. and 3. you can [open your issue](https://github.com/inorichi/tachiyomi/issues/new)
|
4. After following 1. and 3. you can [open your issue](https://github.com/tachiyomiorg/tachiyomi/issues/new)
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
# Catalogue requests
|
# Catalogue requests
|
||||||
|
|
||||||
* Catalogue requests should be created at https://github.com/inorichi/tachiyomi-extensions#readme, not here
|
* Catalogue requests should be created at https://github.com/tachiyomiorg/tachiyomi-extensions#readme, not here
|
||||||
|
|
||||||
# Bugs
|
# Bugs
|
||||||
* Include version (More > About > Version)
|
* Include version (More > About > Version)
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
* For large logs use http://pastebin.com/ (or similar)
|
* For large logs use http://pastebin.com/ (or similar)
|
||||||
* Don't group unrelated requests into one issue
|
* Don't group unrelated requests into one issue
|
||||||
|
|
||||||
DO: https://github.com/inorichi/tachiyomi/issues/24 https://github.com/inorichi/tachiyomi/issues/71
|
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71
|
||||||
|
|
||||||
DON'T: https://github.com/inorichi/tachiyomi/issues/75
|
DON'T: https://github.com/tachiyomiorg/tachiyomi/issues/75
|
||||||
|
|
||||||
# Feature requests
|
# Feature requests
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated to the latest version of the app (stable is v1.4.0)
|
- I have updated to the latest version of the app (stable is v1.4.1)
|
||||||
- I have updated all extensions
|
- I have updated all extensions
|
||||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||||
|
|
||||||
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ labels: "bug"
|
|||||||
|
|
||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated to the latest version of the app (stable is v1.4.0)
|
- I have updated to the latest version of the app (stable is v1.4.1)
|
||||||
- I have updated all extensions
|
- I have updated all extensions
|
||||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||||
|
|
||||||
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
||||||
|
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ contact_links:
|
|||||||
url: https://tachiyomi.org/help/
|
url: https://tachiyomi.org/help/
|
||||||
about: Common questions are answered here.
|
about: Common questions are answered here.
|
||||||
- name: Tachiyomi extensions GitHub repository
|
- name: Tachiyomi extensions GitHub repository
|
||||||
url: https://github.com/inorichi/tachiyomi-extensions
|
url: https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||||
about: Issues about an extension/source/catalogue should be opened here instead.
|
about: Issues about an extension/source/catalogue should be opened here instead.
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ labels: "feature"
|
|||||||
|
|
||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated to the latest version of the app (stable is v1.4.0)
|
- I have updated to the latest version of the app (stable is v1.4.1)
|
||||||
- I have updated all extensions
|
- I have updated all extensions
|
||||||
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
|
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||||
|
|
||||||
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: "Extension/source/catalogue issue"
|
name: "Extension/source/catalogue issue"
|
||||||
about: "Do not open an issue here. See https://github.com/inorichi/tachiyomi-extensions"
|
about: "Do not open an issue here. See https://github.com/tachiyomiorg/tachiyomi-extensions"
|
||||||
title: "THIS ISSUE IS IN THE WRONG REPO; SEE https://github.com/inorichi/tachiyomi-extensions"
|
title: "THIS ISSUE IS IN THE WRONG REPO; SEE https://github.com/tachiyomiorg/tachiyomi-extensions"
|
||||||
labels: "catalog, invalid"
|
labels: "catalog, invalid"
|
||||||
---
|
---
|
||||||
|
|
||||||
DO NOT OPEN AN ISSUE IN THIS REPO. SEE https://github.com/inorichi/tachiyomi-extensions
|
DO NOT OPEN AN ISSUE IN THIS REPO. SEE https://github.com/tachiyomiorg/tachiyomi-extensions
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build app release
|
name: Build app
|
||||||
needs: check_wrapper
|
needs: check_wrapper
|
||||||
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -60,21 +60,16 @@ jobs:
|
|||||||
dependencies-cache-enabled: true
|
dependencies-cache-enabled: true
|
||||||
configuration-cache-enabled: true
|
configuration-cache-enabled: true
|
||||||
|
|
||||||
- name: Sign Android Release
|
- name: Sign APK
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: r0adkll/sign-android-release@v1
|
||||||
with:
|
with:
|
||||||
# The directory to find your release to sign
|
|
||||||
releaseDirectory: app/build/outputs/apk/standard/release
|
releaseDirectory: app/build/outputs/apk/standard/release
|
||||||
# The key used to sign your release in base64 encoded format
|
|
||||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||||
# The key alias
|
|
||||||
alias: ${{ secrets.ALIAS }}
|
alias: ${{ secrets.ALIAS }}
|
||||||
# The password to the keystore
|
|
||||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||||
# The password for the key
|
|
||||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: actions/create-release@v1
|
uses: actions/create-release@v1
|
||||||
env:
|
env:
|
||||||
@@ -85,7 +80,7 @@ jobs:
|
|||||||
draft: true
|
draft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
- name: Upload Release APK
|
- name: Upload APK to release
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -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/inorichi/tachiyomi-extensions)
|
* Online reading from sources such as MangaDex, MangaSee, Mangakakalot, [and more](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
|
||||||
@@ -52,18 +52,19 @@ Features of TachiyomiSY include:
|
|||||||
* Enhanced views for internal and integrated sources
|
* Enhanced views for internal and integrated sources
|
||||||
* Enhanced usability for internal and delegated sources
|
* Enhanced usability for internal and delegated sources
|
||||||
|
|
||||||
* Custom sources:
|
Custom sources:
|
||||||
* * E-Hentai/ExHentai
|
* E-Hentai/ExHentai
|
||||||
* Additional features for some extensions, features include custom description, opening in app, batch add to library, and a bunch of other things based on the source:
|
|
||||||
* * 8Muses (EroMuse)
|
Additional features for some extensions, features include custom description, opening in app, batch add to library, and a bunch of other things based on the source:
|
||||||
* * HBrowse
|
* 8Muses (EroMuse)
|
||||||
* * HentaiCafe (inside Foolside)
|
* HBrowse
|
||||||
* * Hitomi.la
|
* HentaiCafe (inside Foolside)
|
||||||
* * Mangadex
|
* Hitomi.la
|
||||||
* * NHentai
|
* Mangadex
|
||||||
* * PervEden (EN and IT)
|
* NHentai
|
||||||
* * Puruin
|
* PervEden (EN and IT)
|
||||||
* * Tsumino
|
* Puruin
|
||||||
|
* Tsumino
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/releases/latest).
|
Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/releases/latest).
|
||||||
@@ -92,9 +93,9 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
|||||||
* For large logs use http://pastebin.com/ (or similar)
|
* For large logs use http://pastebin.com/ (or similar)
|
||||||
* Don't group unrelated requests into one issue
|
* Don't group unrelated requests into one issue
|
||||||
|
|
||||||
DO: https://github.com/inorichi/tachiyomi/issues/24 https://github.com/inorichi/tachiyomi/issues/71
|
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71
|
||||||
|
|
||||||
DON'T: https://github.com/inorichi/tachiyomi/issues/75
|
DON'T: https://github.com/tachiyomiorg/tachiyomi/issues/75
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ DON'T: https://github.com/inorichi/tachiyomi/issues/75
|
|||||||
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
|
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
|
||||||
* Include screenshot (if needed)
|
* Include screenshot (if needed)
|
||||||
|
|
||||||
Source requests should be created at https://github.com/inorichi/tachiyomi-extensions, they do not belong in this repository.
|
Source requests should be created at https://github.com/tachiyomiorg/tachiyomi-extensions, they do not belong in this repository.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|||||||
+4
-9
@@ -5,8 +5,8 @@ import java.text.SimpleDateFormat
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
|
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
apply plugin: 'kotlin-parcelize'
|
||||||
apply plugin: 'kotlinx-serialization'
|
apply plugin: 'kotlinx-serialization'
|
||||||
apply plugin: 'com.github.zellius.shortcut-helper'
|
apply plugin: 'com.github.zellius.shortcut-helper'
|
||||||
// Realm (EH)
|
// Realm (EH)
|
||||||
@@ -44,8 +44,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 10
|
versionCode 11
|
||||||
versionName "1.4.0"
|
versionName "1.4.1"
|
||||||
|
|
||||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||||
@@ -133,10 +133,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
androidExtensions {
|
|
||||||
experimental = true
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
// Source models and interfaces from Tachiyomi 1.x
|
// Source models and interfaces from Tachiyomi 1.x
|
||||||
@@ -253,7 +249,6 @@ dependencies {
|
|||||||
implementation 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
implementation 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
||||||
implementation 'eu.davidea:flexible-adapter:5.1.0'
|
implementation 'eu.davidea:flexible-adapter:5.1.0'
|
||||||
implementation 'eu.davidea:flexible-adapter-ui:1.0.0'
|
implementation 'eu.davidea:flexible-adapter-ui:1.0.0'
|
||||||
implementation 'com.nononsenseapps:filepicker:2.5.2'
|
|
||||||
implementation 'com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0'
|
implementation 'com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0'
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
implementation 'com.github.carlosesco:DirectionalViewPager:a844dbca0a'
|
implementation 'com.github.carlosesco:DirectionalViewPager:a844dbca0a'
|
||||||
@@ -294,7 +289,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$BuildPluginsVersion.KOTLIN"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$BuildPluginsVersion.KOTLIN"
|
||||||
|
|
||||||
final coroutines_version = '1.4.1'
|
final coroutines_version = '1.4.2'
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
|
|||||||
@@ -80,10 +80,6 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".ui.webview.WebViewActivity"
|
android:name=".ui.webview.WebViewActivity"
|
||||||
android:configChanges="uiMode|orientation|screenSize" />
|
android:configChanges="uiMode|orientation|screenSize" />
|
||||||
<activity
|
|
||||||
android:name=".widget.CustomLayoutPickerActivity"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:theme="@style/FilePickerTheme" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.AnilistLoginActivity"
|
android:name=".ui.setting.track.AnilistLoginActivity"
|
||||||
android:label="Anilist">
|
android:label="Anilist">
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
|||||||
import eu.kanade.tachiyomi.data.updater.UpdaterJob
|
import eu.kanade.tachiyomi.data.updater.UpdaterJob
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
||||||
import eu.kanade.tachiyomi.ui.library.LibrarySort
|
import eu.kanade.tachiyomi.ui.library.LibrarySort
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@@ -120,7 +121,10 @@ object Migrations {
|
|||||||
|
|
||||||
// Force MAL log out due to login flow change
|
// Force MAL log out due to login flow change
|
||||||
val trackManager = Injekt.get<TrackManager>()
|
val trackManager = Injekt.get<TrackManager>()
|
||||||
trackManager.myAnimeList.logout()
|
if (trackManager.myAnimeList.isLogged) {
|
||||||
|
trackManager.myAnimeList.logout()
|
||||||
|
context.toast(R.string.myanimelist_relogin)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class BackupNotifier(private val context: Context) {
|
|||||||
val uri = destFile.getUriCompat(context)
|
val uri = destFile.getUriCompat(context)
|
||||||
|
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.nnf_ic_file_folder,
|
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)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class DownloadPendingDeleter(context: Context) {
|
|||||||
val id: Long,
|
val id: Long,
|
||||||
val url: String,
|
val url: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val scanlator: String?
|
val scanlator: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class LibraryUpdateNotifier(private val context: Context) {
|
|||||||
|
|
||||||
setContentIntent(errorLogIntent)
|
setContentIntent(errorLogIntent)
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.nnf_ic_file_folder,
|
R.drawable.ic_folder_24dp,
|
||||||
context.getString(R.string.action_open_log),
|
context.getString(R.string.action_open_log),
|
||||||
errorLogIntent
|
errorLogIntent
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
|||||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES
|
import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES
|
||||||
import exh.MERGED_SOURCE_ID
|
import exh.MERGED_SOURCE_ID
|
||||||
|
import exh.mangaDexSourceIds
|
||||||
import exh.md.utils.FollowStatus
|
import exh.md.utils.FollowStatus
|
||||||
import exh.md.utils.MdUtil
|
import exh.md.utils.MdUtil
|
||||||
import exh.metadata.metadata.base.insertFlatMetadata
|
import exh.metadata.metadata.base.insertFlatMetadata
|
||||||
@@ -283,7 +284,7 @@ class LibraryUpdateService(
|
|||||||
val trackingExtra = intent.getStringExtra(KEY_GROUP_EXTRA)?.toIntOrNull() ?: -1
|
val trackingExtra = intent.getStringExtra(KEY_GROUP_EXTRA)?.toIntOrNull() ?: -1
|
||||||
libraryManga.filter {
|
libraryManga.filter {
|
||||||
val loggedServices = trackManager.services.filter { it.isLogged }
|
val loggedServices = trackManager.services.filter { it.isLogged }
|
||||||
val status: String = {
|
val status: String = run {
|
||||||
val tracks = db.getTracks(it).executeAsBlocking()
|
val tracks = db.getTracks(it).executeAsBlocking()
|
||||||
val track = tracks.find { track ->
|
val track = tracks.find { track ->
|
||||||
loggedServices.any { it.id == track?.sync_id }
|
loggedServices.any { it.id == track?.sync_id }
|
||||||
@@ -294,7 +295,7 @@ class LibraryUpdateService(
|
|||||||
} else {
|
} else {
|
||||||
"not tracked"
|
"not tracked"
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
trackManager.mapTrackingOrder(status, applicationContext) == trackingExtra
|
trackManager.mapTrackingOrder(status, applicationContext) == trackingExtra
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -537,7 +538,9 @@ class LibraryUpdateService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
// filter all follows from Mangadex and only add reading or rereading manga to library
|
/**
|
||||||
|
* filter all follows from Mangadex and only add reading or rereading manga to library
|
||||||
|
*/
|
||||||
private fun syncFollows(): Observable<LibraryManga> {
|
private fun syncFollows(): Observable<LibraryManga> {
|
||||||
val count = AtomicInteger(0)
|
val count = AtomicInteger(0)
|
||||||
val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager) ?: return Observable.empty()
|
val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager) ?: return Observable.empty()
|
||||||
@@ -582,7 +585,7 @@ class LibraryUpdateService(
|
|||||||
*/
|
*/
|
||||||
private fun pushFavorites(): Observable<LibraryManga> {
|
private fun pushFavorites(): Observable<LibraryManga> {
|
||||||
val count = AtomicInteger(0)
|
val count = AtomicInteger(0)
|
||||||
val listManga = db.getLibraryMangas().executeAsBlocking()
|
val listManga = db.getFavoriteMangas().executeAsBlocking().filter { it.source in mangaDexSourceIds }
|
||||||
|
|
||||||
// filter all follows from Mangadex and only add reading or rereading manga to library
|
// filter all follows from Mangadex and only add reading or rereading manga to library
|
||||||
return Observable.from(if (trackManager.mdList.isLogged) listManga else emptyList())
|
return Observable.from(if (trackManager.mdList.isLogged) listManga else emptyList())
|
||||||
|
|||||||
@@ -127,7 +127,9 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val automaticExtUpdates = "automatic_ext_updates"
|
const val automaticExtUpdates = "automatic_ext_updates"
|
||||||
|
|
||||||
const val allowNsfwSource = "allow_nsfw_source"
|
const val showNsfwSource = "show_nsfw_source"
|
||||||
|
const val showNsfwExtension = "show_nsfw_extension"
|
||||||
|
const val labelNsfwExtension = "label_nsfw_extension"
|
||||||
|
|
||||||
const val startScreen = "start_screen"
|
const val startScreen = "start_screen"
|
||||||
|
|
||||||
@@ -330,4 +332,8 @@ object PreferenceKeys {
|
|||||||
const val createLegacyBackup = "create_legacy_backup"
|
const val createLegacyBackup = "create_legacy_backup"
|
||||||
|
|
||||||
const val dontDeleteFromCategories = "dont_delete_from_categories"
|
const val dontDeleteFromCategories = "dont_delete_from_categories"
|
||||||
|
|
||||||
|
const val extensionRepos = "extension_repos"
|
||||||
|
|
||||||
|
const val cropBordersContinuesVertical = "crop_borders_continues_vertical"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,12 +44,6 @@ object PreferenceValues {
|
|||||||
BOTH
|
BOTH
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class NsfwAllowance {
|
|
||||||
ALLOWED,
|
|
||||||
PARTIAL,
|
|
||||||
BLOCKED
|
|
||||||
}
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
enum class GroupLibraryMode {
|
enum class GroupLibraryMode {
|
||||||
GLOBAL,
|
GLOBAL,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import com.tfcporciuncula.flow.Preference
|
|||||||
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
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
|
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.NsfwAllowance
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||||
@@ -231,7 +230,9 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun automaticExtUpdates() = flowPrefs.getBoolean(Keys.automaticExtUpdates, true)
|
fun automaticExtUpdates() = flowPrefs.getBoolean(Keys.automaticExtUpdates, true)
|
||||||
|
|
||||||
fun allowNsfwSource() = flowPrefs.getEnum(Keys.allowNsfwSource, NsfwAllowance.ALLOWED)
|
fun showNsfwSource() = flowPrefs.getBoolean(Keys.showNsfwSource, true)
|
||||||
|
fun showNsfwExtension() = flowPrefs.getBoolean(Keys.showNsfwExtension, true)
|
||||||
|
fun labelNsfwExtension() = prefs.getBoolean(Keys.labelNsfwExtension, true)
|
||||||
|
|
||||||
fun extensionUpdatesCount() = flowPrefs.getInt("ext_updates_count", 0)
|
fun extensionUpdatesCount() = flowPrefs.getInt("ext_updates_count", 0)
|
||||||
|
|
||||||
@@ -444,4 +445,8 @@ class PreferencesHelper(val context: Context) {
|
|||||||
fun sortTagsForLibrary() = flowPrefs.getStringSet(Keys.sortTagsForLibrary, mutableSetOf())
|
fun sortTagsForLibrary() = flowPrefs.getStringSet(Keys.sortTagsForLibrary, mutableSetOf())
|
||||||
|
|
||||||
fun dontDeleteFromCategories() = flowPrefs.getStringSet(Keys.dontDeleteFromCategories, emptySet())
|
fun dontDeleteFromCategories() = flowPrefs.getStringSet(Keys.dontDeleteFromCategories, emptySet())
|
||||||
|
|
||||||
|
fun extensionRepos() = flowPrefs.getStringSet(Keys.extensionRepos, emptySet())
|
||||||
|
|
||||||
|
fun cropBordersContinuesVertical() = flowPrefs.getBoolean(Keys.cropBordersContinuesVertical, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.net.Uri
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@@ -18,9 +19,8 @@ import kotlinx.serialization.json.jsonPrimitive
|
|||||||
import kotlinx.serialization.json.long
|
import kotlinx.serialization.json.long
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import kotlinx.serialization.json.putJsonObject
|
import kotlinx.serialization.json.putJsonObject
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@@ -30,7 +30,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val jsonMime = "application/json; charset=utf-8".toMediaTypeOrNull()
|
private val jsonMime = "application/json; charset=utf-8".toMediaType()
|
||||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
fun addLibManga(track: Track): Observable<Track> {
|
fun addLibManga(track: Track): Observable<Track> {
|
||||||
@@ -51,22 +51,18 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
put("status", track.toAnilistStatus())
|
put("status", track.toAnilistStatus())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val body = payload.toString().toRequestBody(jsonMime)
|
return authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime)))
|
||||||
val request = Request.Builder()
|
|
||||||
.url(apiUrl)
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
return authClient.newCall(request)
|
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { netResponse ->
|
.map { netResponse ->
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
netResponse.use {
|
||||||
netResponse.close()
|
val responseBody = it.body?.string().orEmpty()
|
||||||
if (responseBody.isEmpty()) {
|
if (responseBody.isEmpty()) {
|
||||||
throw Exception("Null Response")
|
throw Exception("Null Response")
|
||||||
|
}
|
||||||
|
val response = json.decodeFromString<JsonObject>(responseBody)
|
||||||
|
track.library_id = response["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long
|
||||||
|
track
|
||||||
}
|
}
|
||||||
val response = json.decodeFromString<JsonObject>(responseBody)
|
|
||||||
track.library_id = response["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long
|
|
||||||
track
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,12 +86,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
put("score", track.score.toInt())
|
put("score", track.score.toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val body = payload.toString().toRequestBody(jsonMime)
|
return authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime)))
|
||||||
val request = Request.Builder()
|
|
||||||
.url(apiUrl)
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
return authClient.newCall(request)
|
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map {
|
.map {
|
||||||
track
|
track
|
||||||
@@ -134,24 +125,21 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
put("query", search)
|
put("query", search)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val body = payload.toString().toRequestBody(jsonMime)
|
return authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime)))
|
||||||
val request = Request.Builder()
|
|
||||||
.url(apiUrl)
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
return authClient.newCall(request)
|
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { netResponse ->
|
.map { netResponse ->
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
netResponse.use {
|
||||||
if (responseBody.isEmpty()) {
|
val responseBody = it.body?.string().orEmpty()
|
||||||
throw Exception("Null Response")
|
if (responseBody.isEmpty()) {
|
||||||
|
throw Exception("Null Response")
|
||||||
|
}
|
||||||
|
val response = json.decodeFromString<JsonObject>(responseBody)
|
||||||
|
val data = response["data"]!!.jsonObject
|
||||||
|
val page = data["Page"]!!.jsonObject
|
||||||
|
val media = page["media"]!!.jsonArray
|
||||||
|
val entries = media.map { jsonToALManga(it.jsonObject) }
|
||||||
|
entries.map { it.toTrack() }
|
||||||
}
|
}
|
||||||
val response = json.decodeFromString<JsonObject>(responseBody)
|
|
||||||
val data = response["data"]!!.jsonObject
|
|
||||||
val page = data["Page"]!!.jsonObject
|
|
||||||
val media = page["media"]!!.jsonArray
|
|
||||||
val entries = media.map { jsonToALManga(it.jsonObject) }
|
|
||||||
entries.map { it.toTrack() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,24 +182,21 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
put("manga_id", track.media_id)
|
put("manga_id", track.media_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val body = payload.toString().toRequestBody(jsonMime)
|
return authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime)))
|
||||||
val request = Request.Builder()
|
|
||||||
.url(apiUrl)
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
return authClient.newCall(request)
|
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { netResponse ->
|
.map { netResponse ->
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
netResponse.use {
|
||||||
if (responseBody.isEmpty()) {
|
val responseBody = it.body?.string().orEmpty()
|
||||||
throw Exception("Null Response")
|
if (responseBody.isEmpty()) {
|
||||||
|
throw Exception("Null Response")
|
||||||
|
}
|
||||||
|
val response = json.decodeFromString<JsonObject>(responseBody)
|
||||||
|
val data = response["data"]!!.jsonObject
|
||||||
|
val page = data["Page"]!!.jsonObject
|
||||||
|
val media = page["mediaList"]!!.jsonArray
|
||||||
|
val entries = media.map { jsonToALUserManga(it.jsonObject) }
|
||||||
|
entries.firstOrNull()?.toTrack()
|
||||||
}
|
}
|
||||||
val response = json.decodeFromString<JsonObject>(responseBody)
|
|
||||||
val data = response["data"]!!.jsonObject
|
|
||||||
val page = data["Page"]!!.jsonObject
|
|
||||||
val media = page["mediaList"]!!.jsonArray
|
|
||||||
val entries = media.map { jsonToALUserManga(it.jsonObject) }
|
|
||||||
entries.firstOrNull()?.toTrack()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,22 +224,22 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
val payload = buildJsonObject {
|
val payload = buildJsonObject {
|
||||||
put("query", query)
|
put("query", query)
|
||||||
}
|
}
|
||||||
val body = payload.toString().toRequestBody(jsonMime)
|
return authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime)))
|
||||||
val request = Request.Builder()
|
|
||||||
.url(apiUrl)
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
return authClient.newCall(request)
|
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { netResponse ->
|
.map { netResponse ->
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
netResponse.use {
|
||||||
if (responseBody.isEmpty()) {
|
val responseBody = it.body?.string().orEmpty()
|
||||||
throw Exception("Null Response")
|
if (responseBody.isEmpty()) {
|
||||||
|
throw Exception("Null Response")
|
||||||
|
}
|
||||||
|
val response = json.decodeFromString<JsonObject>(responseBody)
|
||||||
|
val data = response["data"]!!.jsonObject
|
||||||
|
val viewer = data["Viewer"]!!.jsonObject
|
||||||
|
Pair(
|
||||||
|
viewer["id"]!!.jsonPrimitive.int,
|
||||||
|
viewer["mediaListOptions"]!!.jsonObject["scoreFormat"]!!.jsonPrimitive.content
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val response = json.decodeFromString<JsonObject>(responseBody)
|
|
||||||
val data = response["data"]!!.jsonObject
|
|
||||||
val viewer = data["Viewer"]!!.jsonObject
|
|
||||||
Pair(viewer["id"]!!.jsonPrimitive.int, viewer["mediaListOptions"]!!.jsonObject["scoreFormat"]!!.jsonPrimitive.content)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +283,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val clientId = "385"
|
private const val clientId = "385"
|
||||||
private const val clientUrl = "tachiyomi://anilist-auth"
|
|
||||||
private const val apiUrl = "https://graphql.anilist.co/"
|
private const val apiUrl = "https://graphql.anilist.co/"
|
||||||
private const val baseUrl = "https://anilist.co/api/v2/"
|
private const val baseUrl = "https://anilist.co/api/v2/"
|
||||||
private const val baseMangaUrl = "https://anilist.co/manga/"
|
private const val baseMangaUrl = "https://anilist.co/manga/"
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ data class ALUserManga(
|
|||||||
"DROPPED" -> Anilist.DROPPED
|
"DROPPED" -> Anilist.DROPPED
|
||||||
"PLANNING" -> Anilist.PLANNING
|
"PLANNING" -> Anilist.PLANNING
|
||||||
"REPEATING" -> Anilist.REPEATING
|
"REPEATING" -> Anilist.REPEATING
|
||||||
else -> throw NotImplementedError("Unknown status")
|
else -> throw NotImplementedError("Unknown status: $list_status")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ fun Track.toAnilistStatus() = when (status) {
|
|||||||
Anilist.DROPPED -> "DROPPED"
|
Anilist.DROPPED -> "DROPPED"
|
||||||
Anilist.PLANNING -> "PLANNING"
|
Anilist.PLANNING -> "PLANNING"
|
||||||
Anilist.REPEATING -> "REPEATING"
|
Anilist.REPEATING -> "REPEATING"
|
||||||
else -> throw NotImplementedError("Unknown status")
|
else -> throw NotImplementedError("Unknown status: $status")
|
||||||
}
|
}
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
@@ -102,5 +102,5 @@ fun Track.toAnilistScore(): String = when (preferences.anilistScoreType().get())
|
|||||||
}
|
}
|
||||||
// 10 point decimal
|
// 10 point decimal
|
||||||
"POINT_10_DECIMAL" -> (score / 10).toString()
|
"POINT_10_DECIMAL" -> (score / 10).toString()
|
||||||
else -> throw Exception("Unknown score type")
|
else -> throw NotImplementedError("Unknown score type")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.core.net.toUri
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
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 kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
@@ -34,11 +35,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
|
|||||||
.add("rating", track.score.toInt().toString())
|
.add("rating", track.score.toInt().toString())
|
||||||
.add("status", track.toBangumiStatus())
|
.add("status", track.toBangumiStatus())
|
||||||
.build()
|
.build()
|
||||||
val request = Request.Builder()
|
return authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = body))
|
||||||
.url("$apiUrl/collection/${track.media_id}/update")
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
return authClient.newCall(request)
|
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map {
|
.map {
|
||||||
track
|
track
|
||||||
@@ -46,29 +43,20 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateLibManga(track: Track): Observable<Track> {
|
fun updateLibManga(track: Track): Observable<Track> {
|
||||||
// chapter update
|
|
||||||
val body = FormBody.Builder()
|
|
||||||
.add("watched_eps", track.last_chapter_read.toString())
|
|
||||||
.build()
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url("$apiUrl/subject/${track.media_id}/update/watched_eps")
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
// read status update
|
// read status update
|
||||||
val sbody = FormBody.Builder()
|
val sbody = FormBody.Builder()
|
||||||
.add("status", track.toBangumiStatus())
|
.add("status", track.toBangumiStatus())
|
||||||
.build()
|
.build()
|
||||||
val srequest = Request.Builder()
|
return authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = sbody))
|
||||||
.url("$apiUrl/collection/${track.media_id}/update")
|
|
||||||
.post(sbody)
|
|
||||||
.build()
|
|
||||||
return authClient.newCall(srequest)
|
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map {
|
.map {
|
||||||
track
|
track
|
||||||
}.flatMap {
|
}.flatMap {
|
||||||
authClient.newCall(request)
|
// chapter update
|
||||||
|
val body = FormBody.Builder()
|
||||||
|
.add("watched_eps", track.last_chapter_read.toString())
|
||||||
|
.build()
|
||||||
|
authClient.newCall(POST("$apiUrl/subject/${track.media_id}/update/watched_eps", body = body))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map {
|
.map {
|
||||||
track
|
track
|
||||||
@@ -82,11 +70,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
|
|||||||
.buildUpon()
|
.buildUpon()
|
||||||
.appendQueryParameter("max_results", "20")
|
.appendQueryParameter("max_results", "20")
|
||||||
.build()
|
.build()
|
||||||
val request = Request.Builder()
|
return authClient.newCall(GET(url.toString()))
|
||||||
.url(url.toString())
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
return authClient.newCall(request)
|
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { netResponse ->
|
.map { netResponse ->
|
||||||
var responseBody = netResponse.body?.string().orEmpty()
|
var responseBody = netResponse.body?.string().orEmpty()
|
||||||
@@ -126,13 +110,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun findLibManga(track: Track): Observable<Track?> {
|
fun findLibManga(track: Track): Observable<Track?> {
|
||||||
val urlMangas = "$apiUrl/subject/${track.media_id}"
|
return authClient.newCall(GET("$apiUrl/subject/${track.media_id}"))
|
||||||
val requestMangas = Request.Builder()
|
|
||||||
.url(urlMangas)
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return authClient.newCall(requestMangas)
|
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { netResponse ->
|
.map { netResponse ->
|
||||||
// get comic info
|
// get comic info
|
||||||
@@ -162,13 +140,17 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun accessToken(code: String): Observable<OAuth> {
|
fun accessToken(code: String): Observable<OAuth> {
|
||||||
return client.newCall(accessTokenRequest(code)).asObservableSuccess().map { netResponse ->
|
return client.newCall(accessTokenRequest(code))
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
.asObservableSuccess()
|
||||||
if (responseBody.isEmpty()) {
|
.map { netResponse ->
|
||||||
throw Exception("Null Response")
|
netResponse.use {
|
||||||
|
val responseBody = it.body?.string().orEmpty()
|
||||||
|
if (responseBody.isEmpty()) {
|
||||||
|
throw Exception("Null Response")
|
||||||
|
}
|
||||||
|
json.decodeFromString<OAuth>(responseBody)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
json.decodeFromString<OAuth>(responseBody)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun accessTokenRequest(code: String) = POST(
|
private fun accessTokenRequest(code: String) = POST(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ fun Track.toBangumiStatus() = when (status) {
|
|||||||
Bangumi.ON_HOLD -> "on_hold"
|
Bangumi.ON_HOLD -> "on_hold"
|
||||||
Bangumi.DROPPED -> "dropped"
|
Bangumi.DROPPED -> "dropped"
|
||||||
Bangumi.PLANNING -> "wish"
|
Bangumi.PLANNING -> "wish"
|
||||||
else -> throw NotImplementedError("Unknown status")
|
else -> throw NotImplementedError("Unknown status: $status")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toTrackStatus(status: String) = when (status) {
|
fun toTrackStatus(status: String) = when (status) {
|
||||||
@@ -17,6 +17,5 @@ fun toTrackStatus(status: String) = when (status) {
|
|||||||
"on_hold" -> Bangumi.ON_HOLD
|
"on_hold" -> Bangumi.ON_HOLD
|
||||||
"dropped" -> Bangumi.DROPPED
|
"dropped" -> Bangumi.DROPPED
|
||||||
"wish" -> Bangumi.PLANNING
|
"wish" -> Bangumi.PLANNING
|
||||||
|
else -> throw NotImplementedError("Unknown status: $status")
|
||||||
else -> throw Exception("Unknown status")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
|
|
||||||
fun ensureLoggedIn() {
|
fun ensureLoggedIn() {
|
||||||
if (isAuthorized) return
|
if (isAuthorized) return
|
||||||
if (!isLogged) throw Exception("MAL login credentials not found")
|
if (!isLogged) throw Exception(context.getString(R.string.myanimelist_creds_missing))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logout() {
|
override fun logout() {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import eu.kanade.tachiyomi.util.lang.toCalendar
|
|||||||
import eu.kanade.tachiyomi.util.selectInt
|
import eu.kanade.tachiyomi.util.selectInt
|
||||||
import eu.kanade.tachiyomi.util.selectText
|
import eu.kanade.tachiyomi.util.selectText
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
@@ -282,7 +282,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
.put("score", track.score)
|
.put("score", track.score)
|
||||||
.put("num_read_chapters", track.last_chapter_read)
|
.put("num_read_chapters", track.last_chapter_read)
|
||||||
|
|
||||||
return body.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
|
return body.toString().toRequestBody("application/json; charset=utf-8".toMediaType())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mangaEditPostBody(track: MyAnimeListEditData): RequestBody {
|
private fun mangaEditPostBody(track: MyAnimeListEditData): RequestBody {
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ import kotlinx.serialization.json.jsonPrimitive
|
|||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import kotlinx.serialization.json.putJsonObject
|
import kotlinx.serialization.json.putJsonObject
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@@ -30,7 +29,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
|||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val jsonime = "application/json; charset=utf-8".toMediaTypeOrNull()
|
private val jsonMime = "application/json; charset=utf-8".toMediaType()
|
||||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
fun addLibManga(track: Track, user_id: String): Observable<Track> {
|
fun addLibManga(track: Track, user_id: String): Observable<Track> {
|
||||||
@@ -44,12 +43,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
|||||||
put("status", track.toShikimoriStatus())
|
put("status", track.toShikimoriStatus())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val body = payload.toString().toRequestBody(jsonime)
|
return authClient.newCall(POST("$apiUrl/v2/user_rates", body = payload.toString().toRequestBody(jsonMime)))
|
||||||
val request = Request.Builder()
|
|
||||||
.url("$apiUrl/v2/user_rates")
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
return authClient.newCall(request)
|
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map {
|
.map {
|
||||||
track
|
track
|
||||||
@@ -64,11 +58,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
|||||||
.appendQueryParameter("search", search)
|
.appendQueryParameter("search", search)
|
||||||
.appendQueryParameter("limit", "20")
|
.appendQueryParameter("limit", "20")
|
||||||
.build()
|
.build()
|
||||||
val request = Request.Builder()
|
return authClient.newCall(GET(url.toString()))
|
||||||
.url(url.toString())
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
return authClient.newCall(request)
|
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { netResponse ->
|
.map { netResponse ->
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
val responseBody = netResponse.body?.string().orEmpty()
|
||||||
@@ -107,30 +97,21 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun findLibManga(track: Track, user_id: String): Observable<Track?> {
|
fun findLibManga(track: Track, user_id: String): Observable<Track?> {
|
||||||
val url = "$apiUrl/v2/user_rates".toUri().buildUpon()
|
|
||||||
.appendQueryParameter("user_id", user_id)
|
|
||||||
.appendQueryParameter("target_id", track.media_id.toString())
|
|
||||||
.appendQueryParameter("target_type", "Manga")
|
|
||||||
.build()
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(url.toString())
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val urlMangas = "$apiUrl/mangas".toUri().buildUpon()
|
val urlMangas = "$apiUrl/mangas".toUri().buildUpon()
|
||||||
.appendPath(track.media_id.toString())
|
.appendPath(track.media_id.toString())
|
||||||
.build()
|
.build()
|
||||||
val requestMangas = Request.Builder()
|
return authClient.newCall(GET(urlMangas.toString()))
|
||||||
.url(urlMangas.toString())
|
|
||||||
.get()
|
|
||||||
.build()
|
|
||||||
return authClient.newCall(requestMangas)
|
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { netResponse ->
|
.map { netResponse ->
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
val responseBody = netResponse.body?.string().orEmpty()
|
||||||
json.decodeFromString<JsonObject>(responseBody)
|
json.decodeFromString<JsonObject>(responseBody)
|
||||||
}.flatMap { mangas ->
|
}.flatMap { mangas ->
|
||||||
authClient.newCall(request)
|
val url = "$apiUrl/v2/user_rates".toUri().buildUpon()
|
||||||
|
.appendQueryParameter("user_id", user_id)
|
||||||
|
.appendQueryParameter("target_id", track.media_id.toString())
|
||||||
|
.appendQueryParameter("target_type", "Manga")
|
||||||
|
.build()
|
||||||
|
authClient.newCall(GET(url.toString()))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { netResponse ->
|
.map { netResponse ->
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
val responseBody = netResponse.body?.string().orEmpty()
|
||||||
@@ -155,13 +136,17 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun accessToken(code: String): Observable<OAuth> {
|
fun accessToken(code: String): Observable<OAuth> {
|
||||||
return client.newCall(accessTokenRequest(code)).asObservableSuccess().map { netResponse ->
|
return client.newCall(accessTokenRequest(code))
|
||||||
val responseBody = netResponse.body?.string().orEmpty()
|
.asObservableSuccess()
|
||||||
if (responseBody.isEmpty()) {
|
.map { netResponse ->
|
||||||
throw Exception("Null Response")
|
netResponse.use {
|
||||||
|
val responseBody = it.body?.string().orEmpty()
|
||||||
|
if (responseBody.isEmpty()) {
|
||||||
|
throw Exception("Null Response")
|
||||||
|
}
|
||||||
|
json.decodeFromString<OAuth>(responseBody)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
json.decodeFromString<OAuth>(responseBody)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun accessTokenRequest(code: String) = POST(
|
private fun accessTokenRequest(code: String) = POST(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ fun Track.toShikimoriStatus() = when (status) {
|
|||||||
Shikimori.DROPPED -> "dropped"
|
Shikimori.DROPPED -> "dropped"
|
||||||
Shikimori.PLANNING -> "planned"
|
Shikimori.PLANNING -> "planned"
|
||||||
Shikimori.REPEATING -> "rewatching"
|
Shikimori.REPEATING -> "rewatching"
|
||||||
else -> throw NotImplementedError("Unknown status")
|
else -> throw NotImplementedError("Unknown status: $status")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toTrackStatus(status: String) = when (status) {
|
fun toTrackStatus(status: String) = when (status) {
|
||||||
@@ -19,6 +19,5 @@ fun toTrackStatus(status: String) = when (status) {
|
|||||||
"dropped" -> Shikimori.DROPPED
|
"dropped" -> Shikimori.DROPPED
|
||||||
"planned" -> Shikimori.PLANNING
|
"planned" -> Shikimori.PLANNING
|
||||||
"rewatching" -> Shikimori.REPEATING
|
"rewatching" -> Shikimori.REPEATING
|
||||||
|
else -> throw NotImplementedError("Unknown status: $status")
|
||||||
else -> throw Exception("Unknown status")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ class GithubUpdateChecker {
|
|||||||
val newVersion = versionTag.replace("[^\\d.]".toRegex(), "")
|
val newVersion = versionTag.replace("[^\\d.]".toRegex(), "")
|
||||||
|
|
||||||
return if (BuildConfig.DEBUG) {
|
return if (BuildConfig.DEBUG) {
|
||||||
// Preview builds: based on releases in "tachiyomiorg/android-app-preview" repo
|
// Preview builds: based on releases in "tachiyomiorg/tachiyomi-preview" repo
|
||||||
// tagged as something like "r1234"
|
// tagged as something like "r1234"
|
||||||
newVersion.toInt() > BuildConfig.COMMIT_COUNT.toInt()
|
newVersion.toInt() > BuildConfig.COMMIT_COUNT.toInt()
|
||||||
} else {
|
} else {
|
||||||
// Release builds: based on releases in "inorichi/tachiyomi" repo
|
// Release builds: based on releases in "tachiyomiorg/tachiyomi" repo
|
||||||
// tagged as something like "v0.1.2"
|
// tagged as something like "v0.1.2"
|
||||||
newVersion != BuildConfig.VERSION_NAME
|
newVersion != BuildConfig.VERSION_NAME
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.extension
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.elvishew.xlog.XLog
|
import com.elvishew.xlog.XLog
|
||||||
import com.jakewharton.rxrelay.BehaviorRelay
|
import com.jakewharton.rxrelay.BehaviorRelay
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@@ -76,9 +77,9 @@ class ExtensionManager(
|
|||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
return when (source.id) {
|
return when (source.id) {
|
||||||
EH_SOURCE_ID -> context.getDrawable(R.mipmap.ic_ehentai_source)
|
EH_SOURCE_ID -> ContextCompat.getDrawable(context, R.mipmap.ic_ehentai_source)
|
||||||
EXH_SOURCE_ID -> context.getDrawable(R.mipmap.ic_ehentai_source)
|
EXH_SOURCE_ID -> ContextCompat.getDrawable(context, R.mipmap.ic_ehentai_source)
|
||||||
MERGED_SOURCE_ID -> context.getDrawable(R.mipmap.ic_merged_source)
|
MERGED_SOURCE_ID -> ContextCompat.getDrawable(context, R.mipmap.ic_merged_source)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
@@ -142,8 +143,7 @@ class ExtensionManager(
|
|||||||
.map { it.extension }
|
.map { it.extension }
|
||||||
installedExtensions
|
installedExtensions
|
||||||
.flatMap { it.sources }
|
.flatMap { it.sources }
|
||||||
// overwrite is needed until the bundled sources are removed
|
.forEach { sourceManager.registerSource(it) }
|
||||||
.forEach { sourceManager.registerSource(it, true) }
|
|
||||||
|
|
||||||
untrustedExtensions = extensions
|
untrustedExtensions = extensions
|
||||||
.filterIsInstance<LoadResult.Untrusted>()
|
.filterIsInstance<LoadResult.Untrusted>()
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ internal class ExtensionGithubApi {
|
|||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
val response = service.getRepo()
|
val response = service.getRepo()
|
||||||
parseResponse(response)
|
parseResponse(response)
|
||||||
|
} /* SY --> */ + preferences.extensionRepos().get().flatMap {
|
||||||
|
val url = "$BASE_URL$it/repo/"
|
||||||
|
val response = service.getRepo("${url}index.min.json")
|
||||||
|
parseResponse(response, url)
|
||||||
}
|
}
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun checkForUpdates(context: Context): List<Extension.Installed> {
|
suspend fun checkForUpdates(context: Context): List<Extension.Installed> {
|
||||||
@@ -58,7 +63,7 @@ internal class ExtensionGithubApi {
|
|||||||
return extensionsWithUpdate
|
return extensionsWithUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseResponse(json: JsonArray): List<Extension.Available> {
|
private fun parseResponse(json: JsonArray /* SY --> */, repoUrl: String = REPO_URL_PREFIX /* SY <-- */): List<Extension.Available> {
|
||||||
return json
|
return json
|
||||||
.filter { element ->
|
.filter { element ->
|
||||||
val versionName = element.jsonObject["version"]!!.jsonPrimitive.content
|
val versionName = element.jsonObject["version"]!!.jsonPrimitive.content
|
||||||
@@ -73,20 +78,21 @@ internal class ExtensionGithubApi {
|
|||||||
val versionCode = element.jsonObject["code"]!!.jsonPrimitive.int
|
val versionCode = element.jsonObject["code"]!!.jsonPrimitive.int
|
||||||
val lang = element.jsonObject["lang"]!!.jsonPrimitive.content
|
val lang = element.jsonObject["lang"]!!.jsonPrimitive.content
|
||||||
val nsfw = element.jsonObject["nsfw"]!!.jsonPrimitive.int == 1
|
val nsfw = element.jsonObject["nsfw"]!!.jsonPrimitive.int == 1
|
||||||
val icon = "$REPO_URL_PREFIX/icon/${apkName.replace(".apk", ".png")}"
|
// SY -->
|
||||||
|
val icon = "$repoUrl/icon/${apkName.replace(".apk", ".png")}"
|
||||||
|
// SY <--
|
||||||
|
|
||||||
Extension.Available(name, pkgName, versionName, versionCode, lang, nsfw, apkName, icon)
|
Extension.Available(name, pkgName, versionName, versionCode, lang, nsfw, apkName, icon /* SY --> */, repoUrl /* SY <-- */)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getApkUrl(extension: Extension.Available): String {
|
fun getApkUrl(extension: Extension.Available): String {
|
||||||
return "$REPO_URL_PREFIX/apk/${extension.apkName}"
|
return /* SY --> */ "${extension.repoUrl}/apk/${extension.apkName}" /* SY <-- */
|
||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
fun Extension.isBlacklisted(
|
private fun Extension.isBlacklisted(
|
||||||
blacklistEnabled: Boolean =
|
blacklistEnabled: Boolean = preferences.enableSourceBlacklist().get()
|
||||||
preferences.enableSourceBlacklist().get()
|
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS && blacklistEnabled
|
return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS && blacklistEnabled
|
||||||
}
|
}
|
||||||
@@ -94,6 +100,6 @@ internal class ExtensionGithubApi {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val BASE_URL = "https://raw.githubusercontent.com/"
|
const val BASE_URL = "https://raw.githubusercontent.com/"
|
||||||
const val REPO_URL_PREFIX = "${BASE_URL}inorichi/tachiyomi-extensions/repo/"
|
const val REPO_URL_PREFIX = "${BASE_URL}tachiyomiorg/tachiyomi-extensions/repo/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import kotlinx.serialization.json.JsonArray
|
|||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Url
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,6 +28,8 @@ interface ExtensionGithubService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET("${ExtensionGithubApi.REPO_URL_PREFIX}index.min.json")
|
// SY -->
|
||||||
suspend fun getRepo(): JsonArray
|
@GET
|
||||||
|
suspend fun getRepo(@Url url: String = "${ExtensionGithubApi.REPO_URL_PREFIX}index.min.json"): JsonArray
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ sealed class Extension {
|
|||||||
override val lang: String,
|
override val lang: String,
|
||||||
override val isNsfw: Boolean,
|
override val isNsfw: Boolean,
|
||||||
val apkName: String,
|
val apkName: String,
|
||||||
val iconUrl: String
|
val iconUrl: String,
|
||||||
|
// SY -->
|
||||||
|
val repoUrl: String
|
||||||
|
// SY <--
|
||||||
) : Extension()
|
) : Extension()
|
||||||
|
|
||||||
data class Untrusted(
|
data class Untrusted(
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.content.pm.PackageInfo
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import dalvik.system.PathClassLoader
|
import dalvik.system.PathClassLoader
|
||||||
import eu.kanade.tachiyomi.annotations.Nsfw
|
import eu.kanade.tachiyomi.annotations.Nsfw
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||||
@@ -26,8 +25,8 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
internal object ExtensionLoader {
|
internal object ExtensionLoader {
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
private val allowNsfwSource by lazy {
|
private val loadNsfwSource by lazy {
|
||||||
preferences.allowNsfwSource().get()
|
preferences.showNsfwSource().get()
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val EXTENSION_FEATURE = "tachiyomi.extension"
|
private const val EXTENSION_FEATURE = "tachiyomi.extension"
|
||||||
@@ -133,7 +132,7 @@ internal object ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1
|
val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1
|
||||||
if (allowNsfwSource == PreferenceValues.NsfwAllowance.BLOCKED && isNsfw) {
|
if (!loadNsfwSource && isNsfw) {
|
||||||
return LoadResult.Error("NSFW extension $pkgName not allowed")
|
return LoadResult.Error("NSFW extension $pkgName not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +217,7 @@ internal object ExtensionLoader {
|
|||||||
* Checks whether a Source or SourceFactory is annotated with @Nsfw.
|
* Checks whether a Source or SourceFactory is annotated with @Nsfw.
|
||||||
*/
|
*/
|
||||||
private fun isSourceNsfw(clazz: Any): Boolean {
|
private fun isSourceNsfw(clazz: Any): Boolean {
|
||||||
if (allowNsfwSource == PreferenceValues.NsfwAllowance.ALLOWED) {
|
if (loadNsfwSource) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,14 +62,18 @@ interface Source : tachiyomi.source.Source {
|
|||||||
/**
|
/**
|
||||||
* [1.x API] Get the updated details for a manga.
|
* [1.x API] Get the updated details for a manga.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo {
|
override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo {
|
||||||
return fetchMangaDetails(manga.toSManga()).awaitSingle()
|
val sManga = manga.toSManga()
|
||||||
.toMangaInfo()
|
val networkManga = fetchMangaDetails(sManga).awaitSingle()
|
||||||
|
sManga.copyFrom(networkManga)
|
||||||
|
return sManga.toMangaInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [1.x API] Get all the available chapters for a manga.
|
* [1.x API] Get all the available chapters for a manga.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
override suspend fun getChapterList(manga: MangaInfo): List<ChapterInfo> {
|
override suspend fun getChapterList(manga: MangaInfo): List<ChapterInfo> {
|
||||||
return fetchChapterList(manga.toSManga()).awaitSingle()
|
return fetchChapterList(manga.toSManga()).awaitSingle()
|
||||||
.map { it.toChapterInfo() }
|
.map { it.toChapterInfo() }
|
||||||
@@ -78,6 +82,7 @@ interface Source : tachiyomi.source.Source {
|
|||||||
/**
|
/**
|
||||||
* [1.x API] Get the list of pages a chapter has.
|
* [1.x API] Get the list of pages a chapter has.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
override suspend fun getPageList(chapter: ChapterInfo): List<tachiyomi.source.model.Page> {
|
override suspend fun getPageList(chapter: ChapterInfo): List<tachiyomi.source.model.Page> {
|
||||||
return fetchPageList(chapter.toSChapter()).awaitSingle()
|
return fetchPageList(chapter.toSChapter()).awaitSingle()
|
||||||
.map { it.toPageUrl() }
|
.map { it.toPageUrl() }
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ open class SourceManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
internal fun registerSource(source: Source, overwrite: Boolean = false) {
|
internal fun registerSource(source: Source) {
|
||||||
// EXH -->
|
// EXH -->
|
||||||
val sourceQName = source::class.qualifiedName
|
val sourceQName = source::class.qualifiedName
|
||||||
val factories = DELEGATED_SOURCES.entries.filter { it.value.factory }.map { it.value.originalSourceQualifiedClassName }
|
val factories = DELEGATED_SOURCES.entries.filter { it.value.factory }.map { it.value.originalSourceQualifiedClassName }
|
||||||
@@ -130,7 +130,7 @@ open class SourceManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
// EXH <--
|
// EXH <--
|
||||||
|
|
||||||
if (overwrite || !sourcesMap.containsKey(source.id)) {
|
if (!sourcesMap.containsKey(source.id)) {
|
||||||
sourcesMap[source.id] = newSource
|
sourcesMap[source.id] = newSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,10 +69,10 @@ class MergedSource : SuspendHttpSource() {
|
|||||||
val mergedManga = db.getManga(manga.url, id).await() ?: throw Exception("merged manga not in db")
|
val mergedManga = db.getManga(manga.url, id).await() ?: throw Exception("merged manga not in db")
|
||||||
val mangaReferences = mergedManga.id?.let { withContext(Dispatchers.IO) { db.getMergedMangaReferences(it).await() } } ?: throw Exception("merged manga id is null")
|
val mangaReferences = mergedManga.id?.let { withContext(Dispatchers.IO) { db.getMergedMangaReferences(it).await() } } ?: throw Exception("merged manga id is null")
|
||||||
if (mangaReferences.isEmpty()) throw IllegalArgumentException("Manga references are empty, info unavailable, merge is likely corrupted")
|
if (mangaReferences.isEmpty()) throw IllegalArgumentException("Manga references are empty, info unavailable, merge is likely corrupted")
|
||||||
if (mangaReferences.size == 1 || {
|
if (mangaReferences.size == 1 || run {
|
||||||
val mangaReference = mangaReferences.firstOrNull()
|
val mangaReference = mangaReferences.firstOrNull()
|
||||||
mangaReference == null || (mangaReference.mangaSourceId == MERGED_SOURCE_ID)
|
mangaReference == null || (mangaReference.mangaSourceId == MERGED_SOURCE_ID)
|
||||||
}()
|
}
|
||||||
) throw IllegalArgumentException("Manga references contain only the merged reference, merge is likely corrupted")
|
) throw IllegalArgumentException("Manga references contain only the merged reference, merge is likely corrupted")
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
|
|||||||
+1
-24
@@ -4,25 +4,15 @@ import android.content.res.Configuration
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.viewbinding.ViewBinding
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
||||||
|
|
||||||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
abstract class BaseThemedActivity : AppCompatActivity() {
|
||||||
|
|
||||||
val preferences: PreferencesHelper by injectLazy()
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
val scope = lifecycleScope
|
|
||||||
lateinit var binding: VB
|
|
||||||
|
|
||||||
@Suppress("LeakingThis")
|
|
||||||
private val secureActivityDelegate = SecureActivityDelegate(this)
|
|
||||||
|
|
||||||
private val isDarkMode: Boolean by lazy {
|
private val isDarkMode: Boolean by lazy {
|
||||||
val themeMode = preferences.themeMode().get()
|
val themeMode = preferences.themeMode().get()
|
||||||
(themeMode == Values.ThemeMode.dark) ||
|
(themeMode == Values.ThemeMode.dark) ||
|
||||||
@@ -63,11 +53,6 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
@Suppress("LeakingThis")
|
|
||||||
LocaleHelper.updateConfiguration(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
setTheme(
|
setTheme(
|
||||||
when {
|
when {
|
||||||
@@ -77,13 +62,5 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
secureActivityDelegate.onCreate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
|
|
||||||
secureActivityDelegate.onResume()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.base.activity
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
|
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||||
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
|
||||||
|
abstract class BaseViewBindingActivity<VB : ViewBinding> : BaseThemedActivity() {
|
||||||
|
|
||||||
|
val scope = lifecycleScope
|
||||||
|
lateinit var binding: VB
|
||||||
|
|
||||||
|
@Suppress("LeakingThis")
|
||||||
|
private val secureActivityDelegate = SecureActivityDelegate(this)
|
||||||
|
|
||||||
|
init {
|
||||||
|
@Suppress("LeakingThis")
|
||||||
|
LocaleHelper.updateConfiguration(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
secureActivityDelegate.onCreate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
secureActivityDelegate.onResume()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ import com.bluelinelabs.conductor.ControllerChangeHandler
|
|||||||
import com.bluelinelabs.conductor.ControllerChangeType
|
import com.bluelinelabs.conductor.ControllerChangeType
|
||||||
import com.bluelinelabs.conductor.RestoreViewOnCreateController
|
import com.bluelinelabs.conductor.RestoreViewOnCreateController
|
||||||
import kotlinx.android.extensions.LayoutContainer
|
import kotlinx.android.extensions.LayoutContainer
|
||||||
import kotlinx.android.synthetic.clearFindViewByIdCache
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
||||||
@@ -54,11 +53,6 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
|||||||
return inflateView(inflater, container)
|
return inflateView(inflater, container)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
|
||||||
super.onDestroyView(view)
|
|
||||||
clearFindViewByIdCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun inflateView(inflater: LayoutInflater, container: ViewGroup): View
|
abstract fun inflateView(inflater: LayoutInflater, container: ViewGroup): View
|
||||||
|
|
||||||
open fun onViewCreated(view: View) {}
|
open fun onViewCreated(view: View) {}
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ import android.view.View
|
|||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.databinding.ExtensionCardItemBinding
|
import eu.kanade.tachiyomi.databinding.ExtensionCardItemBinding
|
||||||
|
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
||||||
@@ -17,6 +20,10 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
|||||||
|
|
||||||
private val binding = ExtensionCardItemBinding.bind(view)
|
private val binding = ExtensionCardItemBinding.bind(view)
|
||||||
|
|
||||||
|
private val shouldLabelNsfw by lazy {
|
||||||
|
Injekt.get<PreferencesHelper>().labelNsfwExtension()
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding.extButton.setOnClickListener {
|
binding.extButton.setOnClickListener {
|
||||||
adapter.buttonClickListener.onButtonClick(bindingAdapterPosition)
|
adapter.buttonClickListener.onButtonClick(bindingAdapterPosition)
|
||||||
@@ -35,9 +42,9 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
|||||||
extension is Extension.Installed && extension.isUnofficial -> itemView.context.getString(R.string.ext_unofficial)
|
extension is Extension.Installed && extension.isUnofficial -> itemView.context.getString(R.string.ext_unofficial)
|
||||||
// SY -->
|
// SY -->
|
||||||
extension is Extension.Installed && extension.isRedundant -> itemView.context.getString(R.string.ext_redundant)
|
extension is Extension.Installed && extension.isRedundant -> itemView.context.getString(R.string.ext_redundant)
|
||||||
|
extension.isNsfw && shouldLabelNsfw -> itemView.context.getString(R.string.ext_nsfw_short).plusRepo(extension)
|
||||||
|
else -> "".plusRepo(extension)
|
||||||
// SY <--
|
// SY <--
|
||||||
extension.isNsfw -> itemView.context.getString(R.string.ext_nsfw_short)
|
|
||||||
else -> ""
|
|
||||||
}.toUpperCase()
|
}.toUpperCase()
|
||||||
|
|
||||||
GlideApp.with(itemView.context).clear(binding.image)
|
GlideApp.with(itemView.context).clear(binding.image)
|
||||||
@@ -51,6 +58,23 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
|||||||
bindButton(item)
|
bindButton(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
private fun String.plusRepo(extension: Extension): String {
|
||||||
|
return if (extension is Extension.Available) {
|
||||||
|
when (extension.repoUrl) {
|
||||||
|
ExtensionGithubApi.REPO_URL_PREFIX -> this
|
||||||
|
else -> {
|
||||||
|
this + if (this.isEmpty()) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
" • "
|
||||||
|
} + itemView.context.getString(R.string.repo_source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else this
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
@Suppress("ResourceType")
|
@Suppress("ResourceType")
|
||||||
fun bindButton(item: ExtensionItem) = with(binding.extButton) {
|
fun bindButton(item: ExtensionItem) = with(binding.extButton) {
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.browse.extension
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
@@ -56,7 +55,7 @@ open class ExtensionPresenter(
|
|||||||
private fun toItems(tuple: ExtensionTuple): List<ExtensionItem> {
|
private fun toItems(tuple: ExtensionTuple): List<ExtensionItem> {
|
||||||
val context = Injekt.get<Application>()
|
val context = Injekt.get<Application>()
|
||||||
val activeLangs = preferences.enabledLanguages().get()
|
val activeLangs = preferences.enabledLanguages().get()
|
||||||
val showNsfwExtensions = preferences.allowNsfwSource().get() != PreferenceValues.NsfwAllowance.BLOCKED
|
val showNsfwExtensions = preferences.showNsfwExtension().get()
|
||||||
|
|
||||||
val (installed, untrusted, available) = tuple
|
val (installed, untrusted, available) = tuple
|
||||||
|
|
||||||
|
|||||||
-1
@@ -635,7 +635,6 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
val adapter = adapter ?: return
|
val adapter = adapter ?: return
|
||||||
|
|
||||||
preferences.sourceDisplayMode().set(mode)
|
preferences.sourceDisplayMode().set(mode)
|
||||||
presenter.refreshDisplayMode()
|
|
||||||
activity?.invalidateOptionsMenu()
|
activity?.invalidateOptionsMenu()
|
||||||
setupRecycler(view)
|
setupRecycler(view)
|
||||||
|
|
||||||
|
|||||||
+26
-49
@@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.browse.source.browse
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.davidea.flexibleadapter.items.ISectionable
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
@@ -38,10 +37,13 @@ import eu.kanade.tachiyomi.util.lang.launchUI
|
|||||||
import eu.kanade.tachiyomi.util.removeCovers
|
import eu.kanade.tachiyomi.util.removeCovers
|
||||||
import exh.savedsearches.EXHSavedSearch
|
import exh.savedsearches.EXHSavedSearch
|
||||||
import exh.savedsearches.JsonSavedSearch
|
import exh.savedsearches.JsonSavedSearch
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
@@ -127,11 +129,6 @@ open class BrowseSourcePresenter(
|
|||||||
private val filterSerializer = FilterSerializer()
|
private val filterSerializer = FilterSerializer()
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
/**
|
|
||||||
* Job to initialize manga details.
|
|
||||||
*/
|
|
||||||
private var initializerJob: Job? = null
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
@@ -169,8 +166,6 @@ open class BrowseSourcePresenter(
|
|||||||
this.query = query
|
this.query = query
|
||||||
this.appliedFilters = filters
|
this.appliedFilters = filters
|
||||||
|
|
||||||
initializeManga()
|
|
||||||
|
|
||||||
// Create a new pager.
|
// Create a new pager.
|
||||||
pager = createPager(query, filters)
|
pager = createPager(query, filters)
|
||||||
|
|
||||||
@@ -226,27 +221,6 @@ open class BrowseSourcePresenter(
|
|||||||
return pager.hasNextPage
|
return pager.hasNextPage
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribes to the initializer of manga details and updates the view if needed.
|
|
||||||
*/
|
|
||||||
private fun initializeManga() {
|
|
||||||
initializerJob?.cancel()
|
|
||||||
initializerJob = launchIO {
|
|
||||||
mangaDetailsFlow
|
|
||||||
.onEach { mangas ->
|
|
||||||
if (!isActive) return@onEach
|
|
||||||
|
|
||||||
try {
|
|
||||||
mangas.filter { it.thumbnail_url == null && !it.initialized }
|
|
||||||
.map { getMangaDetails(it) }
|
|
||||||
.forEach { launchUI { view?.onMangaInitialized(it) } }
|
|
||||||
} catch (error: Exception) {
|
|
||||||
launchUI { Timber.e(error) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a manga from the database for the given manga from network. It creates a new entry
|
* Returns a manga from the database for the given manga from network. It creates a new entry
|
||||||
* if the manga is not yet in the database.
|
* if the manga is not yet in the database.
|
||||||
@@ -272,7 +246,19 @@ open class BrowseSourcePresenter(
|
|||||||
* @param mangas the list of manga to initialize.
|
* @param mangas the list of manga to initialize.
|
||||||
*/
|
*/
|
||||||
fun initializeMangas(mangas: List<Manga>) {
|
fun initializeMangas(mangas: List<Manga>) {
|
||||||
launchIO { mangaDetailsFlow.emit(mangas) }
|
launchIO {
|
||||||
|
mangas.asFlow()
|
||||||
|
.filter { it.thumbnail_url == null && !it.initialized }
|
||||||
|
.map { getMangaDetails(it) }
|
||||||
|
.onEach {
|
||||||
|
launchUI {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
view?.onMangaInitialized(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.catch { e -> Timber.e(e) }
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -282,17 +268,15 @@ open class BrowseSourcePresenter(
|
|||||||
* @return the initialized manga
|
* @return the initialized manga
|
||||||
*/
|
*/
|
||||||
private suspend fun getMangaDetails(manga: Manga): Manga {
|
private suspend fun getMangaDetails(manga: Manga): Manga {
|
||||||
return try {
|
try {
|
||||||
source.getMangaDetails(manga.toMangaInfo())
|
val networkManga = source.getMangaDetails(manga.toMangaInfo())
|
||||||
.let { networkManga ->
|
manga.copyFrom(networkManga.toSManga())
|
||||||
manga.copyFrom(networkManga.toSManga())
|
manga.initialized = true
|
||||||
manga.initialized = true
|
db.insertManga(manga).executeAsBlocking()
|
||||||
db.insertManga(manga).executeAsBlocking()
|
|
||||||
manga
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
manga
|
Timber.e(e)
|
||||||
}
|
}
|
||||||
|
return manga
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -316,13 +300,6 @@ open class BrowseSourcePresenter(
|
|||||||
db.insertManga(manga).executeAsBlocking()
|
db.insertManga(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Refreshes the active display mode.
|
|
||||||
*/
|
|
||||||
fun refreshDisplayMode() {
|
|
||||||
initializeManga()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the filter states for the current source.
|
* Set the filter states for the current source.
|
||||||
*
|
*
|
||||||
@@ -363,7 +340,7 @@ open class BrowseSourcePresenter(
|
|||||||
is Filter.AutoComplete -> AutoCompleteSectionItem(it)
|
is Filter.AutoComplete -> AutoCompleteSectionItem(it)
|
||||||
// SY <--
|
// SY <--
|
||||||
else -> null
|
else -> null
|
||||||
} as? ISectionable<*, *>
|
}
|
||||||
}
|
}
|
||||||
subItems.forEach { it.header = group }
|
subItems.forEach { it.header = group }
|
||||||
group.subItems = subItems
|
group.subItems = subItems
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.category.repos
|
||||||
|
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom adapter for repos.
|
||||||
|
*
|
||||||
|
* @param controller The containing controller.
|
||||||
|
*/
|
||||||
|
class RepoAdapter(controller: RepoController) :
|
||||||
|
FlexibleAdapter<RepoItem>(null, controller, true) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the active selections from the list and the model.
|
||||||
|
*/
|
||||||
|
override fun clearSelection() {
|
||||||
|
super.clearSelection()
|
||||||
|
(0 until itemCount).forEach { getItem(it)?.isSelected = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the selection of the given position.
|
||||||
|
*
|
||||||
|
* @param position The position to toggle.
|
||||||
|
*/
|
||||||
|
override fun toggleSelection(position: Int) {
|
||||||
|
super.toggleSelection(position)
|
||||||
|
getItem(position)?.isSelected = isSelected(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.category.repos
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.view.ActionMode
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.SelectableAdapter
|
||||||
|
import eu.davidea.flexibleadapter.helpers.UndoHelper
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.databinding.CategoriesControllerBinding
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import eu.kanade.tachiyomi.util.view.shrinkOnScroll
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import reactivecircus.flowbinding.android.view.clicks
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller to manage the categories for the users' library.
|
||||||
|
*/
|
||||||
|
class RepoController :
|
||||||
|
NucleusController<CategoriesControllerBinding, RepoPresenter>(),
|
||||||
|
FabController,
|
||||||
|
ActionMode.Callback,
|
||||||
|
FlexibleAdapter.OnItemClickListener,
|
||||||
|
FlexibleAdapter.OnItemLongClickListener,
|
||||||
|
RepoCreateDialog.Listener,
|
||||||
|
UndoHelper.OnActionListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object used to show ActionMode toolbar.
|
||||||
|
*/
|
||||||
|
private var actionMode: ActionMode? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter containing repo items.
|
||||||
|
*/
|
||||||
|
private var adapter: RepoAdapter? = null
|
||||||
|
|
||||||
|
private var actionFab: ExtendedFloatingActionButton? = null
|
||||||
|
private var actionFabScrollListener: RecyclerView.OnScrollListener? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo helper used for restoring a deleted repo.
|
||||||
|
*/
|
||||||
|
private var undoHelper: UndoHelper? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the presenter for this controller. Not to be manually called.
|
||||||
|
*/
|
||||||
|
override fun createPresenter() = RepoPresenter()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the toolbar title to show when this controller is attached.
|
||||||
|
*/
|
||||||
|
override fun getTitle(): String? {
|
||||||
|
return resources?.getString(R.string.action_edit_repos)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after view inflation. Used to initialize the view.
|
||||||
|
*
|
||||||
|
* @param view The view of this controller.
|
||||||
|
*/
|
||||||
|
override fun onViewCreated(view: View) {
|
||||||
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
adapter = RepoAdapter(this@RepoController)
|
||||||
|
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
|
binding.recycler.setHasFixedSize(true)
|
||||||
|
binding.recycler.adapter = adapter
|
||||||
|
adapter?.isPermanentDelete = false
|
||||||
|
|
||||||
|
actionFabScrollListener = actionFab?.shrinkOnScroll(binding.recycler)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureFab(fab: ExtendedFloatingActionButton) {
|
||||||
|
actionFab = fab
|
||||||
|
fab.setText(R.string.action_add)
|
||||||
|
fab.setIconResource(R.drawable.ic_add_24dp)
|
||||||
|
fab.clicks()
|
||||||
|
.onEach {
|
||||||
|
RepoCreateDialog(this@RepoController).showDialog(router, null)
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
|
||||||
|
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
|
||||||
|
actionFab = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the view is being destroyed. Used to release references and remove callbacks.
|
||||||
|
*
|
||||||
|
* @param view The view of this controller.
|
||||||
|
*/
|
||||||
|
override fun onDestroyView(view: View) {
|
||||||
|
// Manually call callback to delete repos if required
|
||||||
|
undoHelper?.onDeleteConfirmed(Snackbar.Callback.DISMISS_EVENT_MANUAL)
|
||||||
|
undoHelper = null
|
||||||
|
actionMode = null
|
||||||
|
adapter = null
|
||||||
|
super.onDestroyView(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from the presenter when the repos are updated.
|
||||||
|
*
|
||||||
|
* @param repos The new list of repos to display.
|
||||||
|
*/
|
||||||
|
fun setRepos(repos: List<RepoItem>) {
|
||||||
|
actionMode?.finish()
|
||||||
|
adapter?.updateDataSet(repos)
|
||||||
|
if (repos.isNotEmpty()) {
|
||||||
|
binding.emptyView.hide()
|
||||||
|
val selected = repos.filter { it.isSelected }
|
||||||
|
if (selected.isNotEmpty()) {
|
||||||
|
selected.forEach { onItemLongClick(repos.indexOf(it)) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.emptyView.show(R.string.information_empty_repos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when action mode is first created. The menu supplied will be used to generate action
|
||||||
|
* buttons for the action mode.
|
||||||
|
*
|
||||||
|
* @param mode ActionMode being created.
|
||||||
|
* @param menu Menu used to populate action buttons.
|
||||||
|
* @return true if the action mode should be created, false if entering this mode should be
|
||||||
|
* aborted.
|
||||||
|
*/
|
||||||
|
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||||
|
// Inflate menu.
|
||||||
|
mode.menuInflater.inflate(R.menu.category_selection, menu)
|
||||||
|
// Enable adapter multi selection.
|
||||||
|
adapter?.mode = SelectableAdapter.Mode.MULTI
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to refresh an action mode's action menu whenever it is invalidated.
|
||||||
|
*
|
||||||
|
* @param mode ActionMode being prepared.
|
||||||
|
* @param menu Menu used to populate action buttons.
|
||||||
|
* @return true if the menu or action mode was updated, false otherwise.
|
||||||
|
*/
|
||||||
|
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||||
|
val adapter = adapter ?: return false
|
||||||
|
val count = adapter.selectedItemCount
|
||||||
|
mode.title = count.toString()
|
||||||
|
|
||||||
|
// Show edit button only when one item is selected
|
||||||
|
val editItem = mode.menu.findItem(R.id.action_edit)
|
||||||
|
editItem.isVisible = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to report a user click on an action button.
|
||||||
|
*
|
||||||
|
* @param mode The current ActionMode.
|
||||||
|
* @param item The item that was clicked.
|
||||||
|
* @return true if this callback handled the event, false if the standard MenuItem invocation
|
||||||
|
* should continue.
|
||||||
|
*/
|
||||||
|
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||||
|
val adapter = adapter ?: return false
|
||||||
|
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.action_delete -> {
|
||||||
|
undoHelper = UndoHelper(adapter, this)
|
||||||
|
undoHelper?.start(
|
||||||
|
adapter.selectedPositions,
|
||||||
|
(activity as? MainActivity)?.binding?.rootCoordinator!!,
|
||||||
|
R.string.snack_repo_deleted,
|
||||||
|
R.string.action_undo,
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
|
||||||
|
mode.finish()
|
||||||
|
}
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an action mode is about to be exited and destroyed.
|
||||||
|
*
|
||||||
|
* @param mode The current ActionMode being destroyed.
|
||||||
|
*/
|
||||||
|
override fun onDestroyActionMode(mode: ActionMode) {
|
||||||
|
// Reset adapter to single selection
|
||||||
|
adapter?.mode = SelectableAdapter.Mode.IDLE
|
||||||
|
adapter?.clearSelection()
|
||||||
|
actionMode = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an item in the list is clicked.
|
||||||
|
*
|
||||||
|
* @param position The position of the clicked item.
|
||||||
|
* @return true if this click should enable selection mode.
|
||||||
|
*/
|
||||||
|
override fun onItemClick(view: View, position: Int): Boolean {
|
||||||
|
// Check if action mode is initialized and selected item exist.
|
||||||
|
return if (actionMode != null && position != RecyclerView.NO_POSITION) {
|
||||||
|
toggleSelection(position)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an item in the list is long clicked.
|
||||||
|
*
|
||||||
|
* @param position The position of the clicked item.
|
||||||
|
*/
|
||||||
|
override fun onItemLongClick(position: Int) {
|
||||||
|
val activity = activity as? AppCompatActivity ?: return
|
||||||
|
|
||||||
|
// Check if action mode is initialized.
|
||||||
|
if (actionMode == null) {
|
||||||
|
// Initialize action mode
|
||||||
|
actionMode = activity.startSupportActionMode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set item as selected
|
||||||
|
toggleSelection(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the selection state of an item.
|
||||||
|
* If the item was the last one in the selection and is unselected, the ActionMode is finished.
|
||||||
|
*
|
||||||
|
* @param position The position of the item to toggle.
|
||||||
|
*/
|
||||||
|
private fun toggleSelection(position: Int) {
|
||||||
|
val adapter = adapter ?: return
|
||||||
|
|
||||||
|
// Mark the position selected
|
||||||
|
adapter.toggleSelection(position)
|
||||||
|
|
||||||
|
if (adapter.selectedItemCount == 0) {
|
||||||
|
actionMode?.finish()
|
||||||
|
} else {
|
||||||
|
actionMode?.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the undo action is clicked in the snackbar.
|
||||||
|
*
|
||||||
|
* @param action The action performed.
|
||||||
|
*/
|
||||||
|
override fun onActionCanceled(action: Int, positions: MutableList<Int>?) {
|
||||||
|
adapter?.restoreDeletedItems()
|
||||||
|
undoHelper = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the time to restore the items expires.
|
||||||
|
*
|
||||||
|
* @param action The action performed.
|
||||||
|
* @param event The event that triggered the action
|
||||||
|
*/
|
||||||
|
override fun onActionConfirmed(action: Int, event: Int) {
|
||||||
|
val adapter = adapter ?: return
|
||||||
|
presenter.deleteRepos(adapter.deletedItems.map { it.repo })
|
||||||
|
undoHelper = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new repo with the given name.
|
||||||
|
*
|
||||||
|
* @param name The name of the new repo.
|
||||||
|
*/
|
||||||
|
override fun createRepo(name: String) {
|
||||||
|
presenter.createRepo(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from the presenter when a repo already exists.
|
||||||
|
*/
|
||||||
|
fun onRepoExistsError() {
|
||||||
|
activity?.toast(R.string.error_repo_exists)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from the presenter when a invalid repo is made
|
||||||
|
*/
|
||||||
|
fun onRepoInvalidNameError() {
|
||||||
|
activity?.toast(R.string.invalid_repo_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.category.repos
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import com.afollestad.materialdialogs.input.input
|
||||||
|
import com.bluelinelabs.conductor.Controller
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog to create a new repo for the library.
|
||||||
|
*/
|
||||||
|
class RepoCreateDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
||||||
|
where T : Controller, T : RepoCreateDialog.Listener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the new repo. Value updated with each input from the user.
|
||||||
|
*/
|
||||||
|
private var currentName = ""
|
||||||
|
|
||||||
|
constructor(target: T) : this() {
|
||||||
|
targetController = target
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when creating the dialog for this controller.
|
||||||
|
*
|
||||||
|
* @param savedViewState The saved state of this dialog.
|
||||||
|
* @return a new dialog instance.
|
||||||
|
*/
|
||||||
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
|
return MaterialDialog(activity!!)
|
||||||
|
.title(R.string.action_add_repo)
|
||||||
|
.message(R.string.action_add_repo_message)
|
||||||
|
.negativeButton(android.R.string.cancel)
|
||||||
|
.input(
|
||||||
|
hint = resources?.getString(R.string.name),
|
||||||
|
prefill = currentName
|
||||||
|
) { _, input ->
|
||||||
|
currentName = input.toString()
|
||||||
|
}
|
||||||
|
.positiveButton(android.R.string.ok) {
|
||||||
|
(targetController as? Listener)?.createRepo(currentName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun createRepo(name: String)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.category.repos
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.databinding.CategoriesItemBinding
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holder used to display repo items.
|
||||||
|
*
|
||||||
|
* @param view The view used by repo items.
|
||||||
|
* @param adapter The adapter containing this holder.
|
||||||
|
*/
|
||||||
|
class RepoHolder(view: View, val adapter: RepoAdapter) : FlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
|
private val binding = CategoriesItemBinding.bind(view)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds this holder with the given category.
|
||||||
|
*
|
||||||
|
* @param category The category to bind.
|
||||||
|
*/
|
||||||
|
fun bind(category: String) {
|
||||||
|
// Set capitalized title.
|
||||||
|
binding.title.text = category
|
||||||
|
binding.reorder.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.category.repos
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||||
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repo item for a recycler view.
|
||||||
|
*/
|
||||||
|
class RepoItem(val repo: String) : AbstractFlexibleItem<RepoHolder>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this item is currently selected.
|
||||||
|
*/
|
||||||
|
var isSelected = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the layout resource for this item.
|
||||||
|
*/
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return R.layout.categories_item
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new view holder for this item.
|
||||||
|
*
|
||||||
|
* @param view The view of this item.
|
||||||
|
* @param adapter The adapter of this item.
|
||||||
|
*/
|
||||||
|
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): RepoHolder {
|
||||||
|
return RepoHolder(view, adapter as RepoAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the given view holder with this item.
|
||||||
|
*
|
||||||
|
* @param adapter The adapter of this item.
|
||||||
|
* @param holder The holder to bind.
|
||||||
|
* @param position The position of this item in the adapter.
|
||||||
|
* @param payloads List of partial changes.
|
||||||
|
*/
|
||||||
|
override fun bindViewHolder(
|
||||||
|
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||||
|
holder: RepoHolder,
|
||||||
|
position: Int,
|
||||||
|
payloads: List<Any?>?
|
||||||
|
) {
|
||||||
|
holder.bind(repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return repo.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.category.repos
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import rx.Observable
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter of [RepoController]. Used to manage the repos for the extensions.
|
||||||
|
*/
|
||||||
|
class RepoPresenter(
|
||||||
|
private val preferences: PreferencesHelper = Injekt.get()
|
||||||
|
) : BasePresenter<RepoController>() {
|
||||||
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List containing repos.
|
||||||
|
*/
|
||||||
|
private var repos: List<String> = emptyList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the presenter is created.
|
||||||
|
*
|
||||||
|
* @param savedState The saved state of this presenter.
|
||||||
|
*/
|
||||||
|
override fun onCreate(savedState: Bundle?) {
|
||||||
|
super.onCreate(savedState)
|
||||||
|
|
||||||
|
preferences.extensionRepos().asFlow().onEach { repos ->
|
||||||
|
this.repos = repos.toList().sortedBy { it.toLowerCase() }
|
||||||
|
|
||||||
|
Observable.just(this.repos)
|
||||||
|
.map { it.map(::RepoItem) }
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribeLatestCache(RepoController::setRepos)
|
||||||
|
}.launchIn(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and adds a new repo to the database.
|
||||||
|
*
|
||||||
|
* @param name The name of the repo to create.
|
||||||
|
*/
|
||||||
|
fun createRepo(name: String) {
|
||||||
|
// Do not allow duplicate repos.
|
||||||
|
if (repoExists(name)) {
|
||||||
|
Observable.just(Unit).subscribeFirst({ view, _ -> view.onRepoExistsError() })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not allow invalid formats
|
||||||
|
if (!name.matches(repoRegex)) {
|
||||||
|
Observable.just(Unit).subscribeFirst({ view, _ -> view.onRepoInvalidNameError() })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences.extensionRepos().set((repos + name).toSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given repos from the database.
|
||||||
|
*
|
||||||
|
* @param repos The list of repos to delete.
|
||||||
|
*/
|
||||||
|
fun deleteRepos(repos: List<String>) {
|
||||||
|
preferences.extensionRepos().set(
|
||||||
|
this.repos.filterNot { it in repos }.toSet()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a repo with the given name already exists.
|
||||||
|
*/
|
||||||
|
private fun repoExists(name: String): Boolean {
|
||||||
|
return repos.any { it.equals(name, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val repoRegex = """^[a-zA-Z-_.]*?\/[a-zA-Z-_.]*?$""".toRegex()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
|
import eu.kanade.tachiyomi.databinding.LibraryCategoryBinding
|
||||||
import eu.kanade.tachiyomi.util.view.inflate
|
import eu.kanade.tachiyomi.util.view.inflate
|
||||||
import eu.kanade.tachiyomi.widget.RecyclerViewPagerAdapter
|
import eu.kanade.tachiyomi.widget.RecyclerViewPagerAdapter
|
||||||
|
|
||||||
@@ -34,8 +35,9 @@ class LibraryAdapter(private val controller: LibraryController) : RecyclerViewPa
|
|||||||
* @return a new view.
|
* @return a new view.
|
||||||
*/
|
*/
|
||||||
override fun createView(container: ViewGroup): View {
|
override fun createView(container: ViewGroup): View {
|
||||||
val view = container.inflate(R.layout.library_category) as LibraryCategoryView
|
val binding = LibraryCategoryBinding.inflate(LayoutInflater.from(container.context), container, false)
|
||||||
view.onCreate(controller)
|
val view: LibraryCategoryView = binding.root
|
||||||
|
view.onCreate(controller, binding)
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,13 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
|||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
|
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.databinding.LibraryCategoryBinding
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryAdapter
|
import eu.kanade.tachiyomi.ui.category.CategoryAdapter
|
||||||
import eu.kanade.tachiyomi.util.lang.plusAssign
|
import eu.kanade.tachiyomi.util.lang.plusAssign
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import eu.kanade.tachiyomi.util.view.inflate
|
import eu.kanade.tachiyomi.util.view.inflate
|
||||||
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
||||||
import exh.ui.LoadingHandle
|
import exh.ui.LoadingHandle
|
||||||
import kotlinx.android.synthetic.main.library_category.view.fast_scroller
|
|
||||||
import kotlinx.android.synthetic.main.library_category.view.swipe_refresh
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -96,15 +95,15 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
|
|||||||
}
|
}
|
||||||
// EXH <--
|
// EXH <--
|
||||||
|
|
||||||
fun onCreate(controller: LibraryController) {
|
fun onCreate(controller: LibraryController, binding: LibraryCategoryBinding) {
|
||||||
this.controller = controller
|
this.controller = controller
|
||||||
|
|
||||||
recycler = if (preferences.libraryDisplayMode().get() == DisplayMode.LIST) {
|
recycler = if (preferences.libraryDisplayMode().get() == DisplayMode.LIST) {
|
||||||
(swipe_refresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply {
|
(binding.swipeRefresh.inflate(R.layout.library_list_recycler) as RecyclerView).apply {
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(swipe_refresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply {
|
(binding.swipeRefresh.inflate(R.layout.library_grid_recycler) as AutofitRecyclerView).apply {
|
||||||
spanCount = controller.mangaPerRow
|
spanCount = controller.mangaPerRow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,21 +112,21 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
|
|||||||
|
|
||||||
recycler.setHasFixedSize(true)
|
recycler.setHasFixedSize(true)
|
||||||
recycler.adapter = adapter
|
recycler.adapter = adapter
|
||||||
swipe_refresh.addView(recycler)
|
binding.swipeRefresh.addView(recycler)
|
||||||
adapter.fastScroller = fast_scroller
|
adapter.fastScroller = binding.fastScroller
|
||||||
|
|
||||||
recycler.scrollStateChanges()
|
recycler.scrollStateChanges()
|
||||||
.onEach {
|
.onEach {
|
||||||
// Disable swipe refresh when view is not at the top
|
// Disable swipe refresh when view is not at the top
|
||||||
val firstPos = (recycler.layoutManager as LinearLayoutManager)
|
val firstPos = (recycler.layoutManager as LinearLayoutManager)
|
||||||
.findFirstCompletelyVisibleItemPosition()
|
.findFirstCompletelyVisibleItemPosition()
|
||||||
swipe_refresh.isEnabled = firstPos <= 0
|
binding.swipeRefresh.isEnabled = firstPos <= 0
|
||||||
}
|
}
|
||||||
.launchIn(scope)
|
.launchIn(scope)
|
||||||
|
|
||||||
// Double the distance required to trigger sync
|
// Double the distance required to trigger sync
|
||||||
swipe_refresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt())
|
binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt())
|
||||||
swipe_refresh.refreshes()
|
binding.swipeRefresh.refreshes()
|
||||||
.onEach {
|
.onEach {
|
||||||
// SY -->
|
// SY -->
|
||||||
if (LibraryUpdateService.start(context, if (controller.presenter.groupType == LibraryGroup.BY_DEFAULT) category else null, group = controller.presenter.groupType, groupExtra = getGroupExtra())) {
|
if (LibraryUpdateService.start(context, if (controller.presenter.groupType == LibraryGroup.BY_DEFAULT) category else null, group = controller.presenter.groupType, groupExtra = getGroupExtra())) {
|
||||||
@@ -147,7 +146,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
|
|||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
// It can be a very long operation, so we disable swipe refresh and show a toast.
|
// It can be a very long operation, so we disable swipe refresh and show a toast.
|
||||||
swipe_refresh.isRefreshing = false
|
binding.swipeRefresh.isRefreshing = false
|
||||||
}
|
}
|
||||||
.launchIn(scope)
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ class LibraryController(
|
|||||||
if (preferences.categoryTabs().get()) {
|
if (preferences.categoryTabs().get()) {
|
||||||
currentTitle = resources?.getString(R.string.label_library)
|
currentTitle = resources?.getString(R.string.label_library)
|
||||||
} else {
|
} else {
|
||||||
adapter?.categories?.get(binding.libraryPager.currentItem)?.let {
|
adapter?.categories?.getOrNull(binding.libraryPager.currentItem)?.let {
|
||||||
currentTitle = it.name
|
currentTitle = it.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -684,7 +684,7 @@ class LibraryPresenter(
|
|||||||
libraryManga.forEach { libraryItem ->
|
libraryManga.forEach { libraryItem ->
|
||||||
when (groupType) {
|
when (groupType) {
|
||||||
LibraryGroup.BY_TRACK_STATUS -> {
|
LibraryGroup.BY_TRACK_STATUS -> {
|
||||||
val status: String = {
|
val status: String = run {
|
||||||
val tracks = db.getTracks(libraryItem.manga).executeAsBlocking()
|
val tracks = db.getTracks(libraryItem.manga).executeAsBlocking()
|
||||||
val track = tracks.find { track ->
|
val track = tracks.find { track ->
|
||||||
loggedServices.any { it.id == track?.sync_id }
|
loggedServices.any { it.id == track?.sync_id }
|
||||||
@@ -695,30 +695,30 @@ class LibraryPresenter(
|
|||||||
} else {
|
} else {
|
||||||
"not tracked"
|
"not tracked"
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
val group = grouping.find { it.first == trackManager.mapTrackingOrder(status, context).toString() }
|
val group = grouping.find { it.first == trackManager.mapTrackingOrder(status, context).toString() }
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
map[group.second]?.plusAssign(libraryItem) ?: map.put(group.second, mutableListOf(libraryItem))
|
map.getOrPut(group.second) { mutableListOf() } += libraryItem
|
||||||
} else {
|
} else {
|
||||||
map[7]?.plusAssign(libraryItem) ?: map.put(7, mutableListOf(libraryItem))
|
map.getOrPut(7) { mutableListOf() } += libraryItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LibraryGroup.BY_SOURCE -> {
|
LibraryGroup.BY_SOURCE -> {
|
||||||
val group = grouping.find { it.first.toLongOrNull() == libraryItem.manga.source }
|
val group = grouping.find { it.first.toLongOrNull() == libraryItem.manga.source }
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
map[group.second]?.plusAssign(libraryItem) ?: map.put(group.second, mutableListOf(libraryItem))
|
map.getOrPut(group.second) { mutableListOf() } += libraryItem
|
||||||
} else {
|
} else {
|
||||||
if (grouping.all { it.second != Int.MAX_VALUE }) grouping += Triple(Int.MAX_VALUE.toString(), Int.MAX_VALUE, context.getString(R.string.unknown))
|
if (grouping.all { it.second != Int.MAX_VALUE }) grouping += Triple(Int.MAX_VALUE.toString(), Int.MAX_VALUE, context.getString(R.string.unknown))
|
||||||
map[Int.MAX_VALUE]?.plusAssign(libraryItem) ?: map.put(Int.MAX_VALUE, mutableListOf(libraryItem))
|
map.getOrPut(Int.MAX_VALUE) { mutableListOf() } += libraryItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val group = grouping.find { it.second == libraryItem.manga.status }
|
val group = grouping.find { it.second == libraryItem.manga.status }
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
map[group.second]?.plusAssign(libraryItem) ?: map.put(group.second, mutableListOf(libraryItem))
|
map.getOrPut(group.second) { mutableListOf() } += libraryItem
|
||||||
} else {
|
} else {
|
||||||
if (grouping.all { it.second != Int.MAX_VALUE }) grouping += Triple(Int.MAX_VALUE.toString(), Int.MAX_VALUE, context.getString(R.string.unknown))
|
if (grouping.all { it.second != Int.MAX_VALUE }) grouping += Triple(Int.MAX_VALUE.toString(), Int.MAX_VALUE, context.getString(R.string.unknown))
|
||||||
map[Int.MAX_VALUE]?.plusAssign(libraryItem) ?: map.put(Int.MAX_VALUE, mutableListOf(libraryItem))
|
map.getOrPut(Int.MAX_VALUE) { mutableListOf() } += libraryItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -730,11 +730,12 @@ class LibraryPresenter(
|
|||||||
LibraryGroup.BY_TRACK_STATUS, LibraryGroup.BY_STATUS -> grouping.filter { it.second in map.keys }
|
LibraryGroup.BY_TRACK_STATUS, LibraryGroup.BY_STATUS -> grouping.filter { it.second in map.keys }
|
||||||
else -> grouping
|
else -> grouping
|
||||||
}
|
}
|
||||||
).map {
|
)
|
||||||
val category = Category.create(it.third)
|
.map {
|
||||||
category.id = it.second
|
val category = Category.create(it.third)
|
||||||
category
|
category.id = it.second
|
||||||
}
|
category
|
||||||
|
}
|
||||||
|
|
||||||
return map to categories
|
return map to categories
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
|||||||
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
|
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
|
||||||
import eu.kanade.tachiyomi.databinding.MainActivityBinding
|
import eu.kanade.tachiyomi.databinding.MainActivityBinding
|
||||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
import eu.kanade.tachiyomi.ui.base.activity.BaseViewBindingActivity
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
|
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
|
||||||
@@ -54,7 +54,7 @@ import java.util.Date
|
|||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class MainActivity : BaseActivity<MainActivityBinding>() {
|
class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
|
||||||
|
|
||||||
private lateinit var router: Router
|
private lateinit var router: Router
|
||||||
|
|
||||||
|
|||||||
@@ -62,17 +62,13 @@ class EditMangaDialog : DialogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
|
binding = EditMangaDialogBinding.inflate(activity!!.layoutInflater)
|
||||||
val dialog = MaterialDialog(activity!!).apply {
|
val dialog = MaterialDialog(activity!!).apply {
|
||||||
customView(viewRes = R.layout.edit_manga_dialog, scrollable = true)
|
customView(view = binding.root, scrollable = true)
|
||||||
negativeButton(android.R.string.cancel)
|
negativeButton(android.R.string.cancel)
|
||||||
positiveButton(R.string.action_save) { onPositiveButtonClick() }
|
positiveButton(R.string.action_save) { onPositiveButtonClick() }
|
||||||
}
|
}
|
||||||
binding = EditMangaDialogBinding.bind(dialog.view.contentLayout.customView!!)
|
|
||||||
onViewCreated()
|
onViewCreated()
|
||||||
dialog.setOnShowListener {
|
|
||||||
val dView = (it as? MaterialDialog)?.view
|
|
||||||
dView?.contentLayout?.scrollView?.scrollTo(0, 0)
|
|
||||||
}
|
|
||||||
return dialog
|
return dialog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1333,7 +1333,7 @@ class MangaController :
|
|||||||
private fun markPreviousAsRead(chapters: List<ChapterItem>) {
|
private fun markPreviousAsRead(chapters: List<ChapterItem>) {
|
||||||
val adapter = chaptersAdapter ?: return
|
val adapter = chaptersAdapter ?: return
|
||||||
val prevChapters = if (presenter.sortDescending()) adapter.items.reversed() else adapter.items
|
val prevChapters = if (presenter.sortDescending()) adapter.items.reversed() else adapter.items
|
||||||
val chapterPos = prevChapters.indexOf(chapters.last())
|
val chapterPos = prevChapters.indexOf(chapters.lastOrNull())
|
||||||
if (chapterPos != -1) {
|
if (chapterPos != -1) {
|
||||||
markAsRead(prevChapters.take(chapterPos))
|
markAsRead(prevChapters.take(chapterPos))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.manga.merged
|
|||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
@@ -19,9 +18,6 @@ import exh.merged.sql.models.MergedMangaReference
|
|||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMergedMangaItemListener {
|
class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMergedMangaItemListener {
|
||||||
|
|
||||||
private var dialogView: View? = null
|
|
||||||
|
|
||||||
private val manga: Manga
|
private val manga: Manga
|
||||||
|
|
||||||
val mergedMangas: MutableList<Pair<Manga?, MergedMangaReference>> = mutableListOf()
|
val mergedMangas: MutableList<Pair<Manga?, MergedMangaReference>> = mutableListOf()
|
||||||
@@ -55,22 +51,17 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
|
|||||||
private var mergedMangaAdapter: EditMergedMangaAdapter? = null
|
private var mergedMangaAdapter: EditMergedMangaAdapter? = null
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
val dialog = MaterialDialog(activity!!).apply {
|
binding = EditMergedSettingsDialogBinding.inflate(activity!!.layoutInflater)
|
||||||
customView(viewRes = R.layout.edit_merged_settings_dialog, scrollable = true)
|
val dialog = MaterialDialog(activity!!)
|
||||||
negativeButton(android.R.string.cancel)
|
.customView(view = binding.root, scrollable = true)
|
||||||
positiveButton(R.string.action_save) { onPositiveButtonClick() }
|
.negativeButton(android.R.string.cancel)
|
||||||
}
|
.positiveButton(R.string.action_save) { onPositiveButtonClick() }
|
||||||
dialogView = dialog.view
|
|
||||||
onViewCreated(dialog.view)
|
onViewCreated()
|
||||||
dialog.setOnShowListener {
|
|
||||||
val dView = (it as? MaterialDialog)?.view
|
|
||||||
dView?.contentLayout?.scrollView?.scrollTo(0, 0)
|
|
||||||
}
|
|
||||||
return dialog
|
return dialog
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onViewCreated(view: View) {
|
fun onViewCreated() {
|
||||||
binding = EditMergedSettingsDialogBinding.bind(view)
|
|
||||||
val mergedManga = db.getMergedMangas(manga.id!!).executeAsBlocking()
|
val mergedManga = db.getMergedMangas(manga.id!!).executeAsBlocking()
|
||||||
val mergedReferences = db.getMergedMangaReferences(manga.id!!).executeAsBlocking()
|
val mergedReferences = db.getMergedMangaReferences(manga.id!!).executeAsBlocking()
|
||||||
if (mergedReferences.isEmpty() || mergedReferences.size == 1) {
|
if (mergedReferences.isEmpty() || mergedReferences.size == 1) {
|
||||||
@@ -86,18 +77,13 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
|
|||||||
mergedHeaderAdapter = EditMergedSettingsHeaderAdapter(this, mergedMangaAdapter!!)
|
mergedHeaderAdapter = EditMergedSettingsHeaderAdapter(this, mergedMangaAdapter!!)
|
||||||
|
|
||||||
binding.recycler.adapter = ConcatAdapter(mergedHeaderAdapter, mergedMangaAdapter)
|
binding.recycler.adapter = ConcatAdapter(mergedHeaderAdapter, mergedMangaAdapter)
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
binding.recycler.layoutManager = LinearLayoutManager(activity!!)
|
||||||
|
|
||||||
mergedMangaAdapter?.isHandleDragEnabled = isPriorityOrder
|
mergedMangaAdapter?.isHandleDragEnabled = isPriorityOrder
|
||||||
|
|
||||||
mergedMangaAdapter?.updateDataSet(mergedMangas.map { it.toModel() })
|
mergedMangaAdapter?.updateDataSet(mergedMangas.map { it.toModel() })
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
|
||||||
super.onDestroyView(view)
|
|
||||||
dialogView = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onPositiveButtonClick() {
|
private fun onPositiveButtonClick() {
|
||||||
mangaController.presenter.updateMergeSettings(mergeReference, mergedMangas.map { it.second })
|
mangaController.presenter.updateMergeSettings(mergeReference, mergedMangas.map { it.second })
|
||||||
}
|
}
|
||||||
@@ -115,7 +101,7 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
|
|||||||
val mergedMangaAdapter = mergedMangaAdapter ?: return
|
val mergedMangaAdapter = mergedMangaAdapter ?: return
|
||||||
val mergeMangaReference = mergedMangaAdapter.currentItems.getOrNull(position)?.mergedMangaReference ?: return
|
val mergeMangaReference = mergedMangaAdapter.currentItems.getOrNull(position)?.mergedMangaReference ?: return
|
||||||
|
|
||||||
MaterialDialog(dialogView!!.context)
|
MaterialDialog(activity!!)
|
||||||
.title(R.string.delete_merged_manga)
|
.title(R.string.delete_merged_manga)
|
||||||
.message(R.string.delete_merged_manga_desc)
|
.message(R.string.delete_merged_manga_desc)
|
||||||
.positiveButton(android.R.string.ok) {
|
.positiveButton(android.R.string.ok) {
|
||||||
@@ -128,7 +114,7 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onToggleChapterUpdatesClicked(position: Int) {
|
override fun onToggleChapterUpdatesClicked(position: Int) {
|
||||||
MaterialDialog(dialogView!!.context)
|
MaterialDialog(activity!!)
|
||||||
.title(R.string.chapter_updates_merged_manga)
|
.title(R.string.chapter_updates_merged_manga)
|
||||||
.message(R.string.chapter_updates_merged_manga_desc)
|
.message(R.string.chapter_updates_merged_manga_desc)
|
||||||
.positiveButton(android.R.string.ok) {
|
.positiveButton(android.R.string.ok) {
|
||||||
@@ -152,7 +138,7 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onToggleChapterDownloadsClicked(position: Int) {
|
override fun onToggleChapterDownloadsClicked(position: Int) {
|
||||||
MaterialDialog(dialogView!!.context)
|
MaterialDialog(activity!!)
|
||||||
.title(R.string.download_merged_manga)
|
.title(R.string.download_merged_manga)
|
||||||
.message(R.string.download_merged_manga_desc)
|
.message(R.string.download_merged_manga_desc)
|
||||||
.positiveButton(android.R.string.ok) {
|
.positiveButton(android.R.string.ok) {
|
||||||
|
|||||||
+6
-6
@@ -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"
|
||||||
)
|
)
|
||||||
@@ -54,8 +54,8 @@ class EditMergedSettingsHeaderAdapter(private val controller: EditMergedSettings
|
|||||||
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
|
99 -> 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
|
||||||
}
|
}
|
||||||
XLog.d(controller.mergeReference?.chapterSortMode)
|
XLog.d(controller.mergeReference?.chapterSortMode)
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ import java.util.TimeZone
|
|||||||
|
|
||||||
class AboutController : SettingsController() {
|
class AboutController : SettingsController() {
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for new releases
|
|
||||||
*/
|
|
||||||
private val updateChecker by lazy { GithubUpdateChecker() }
|
private val updateChecker by lazy { GithubUpdateChecker() }
|
||||||
|
|
||||||
private val dateFormat: DateFormat = preferences.dateFormat()
|
private val dateFormat: DateFormat = preferences.dateFormat()
|
||||||
@@ -119,7 +116,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 "
|
||||||
val url = "https://github.com/inorichi/tachiyomi"
|
val url = "https://github.com/tachiyomiorg/tachiyomi"
|
||||||
summary = url
|
summary = url
|
||||||
onClick {
|
onClick {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||||
@@ -130,7 +127,7 @@ class AboutController : SettingsController() {
|
|||||||
preference {
|
preference {
|
||||||
key = "pref_about_label_extensions"
|
key = "pref_about_label_extensions"
|
||||||
titleRes = R.string.label_extensions
|
titleRes = R.string.label_extensions
|
||||||
val url = "https://github.com/inorichi/tachiyomi-extensions"
|
val url = "https://github.com/tachiyomiorg/tachiyomi-extensions"
|
||||||
summary = url
|
summary = url
|
||||||
onClick {
|
onClick {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : BottomSheetDia
|
|||||||
binding.webtoonSidePadding.bindToIntPreference(preferences.webtoonSidePadding(), R.array.webtoon_side_padding_values)
|
binding.webtoonSidePadding.bindToIntPreference(preferences.webtoonSidePadding(), R.array.webtoon_side_padding_values)
|
||||||
// SY -->
|
// SY -->
|
||||||
binding.zoomOutWebtoon.bindToPreference(preferences.webtoonEnableZoomOut())
|
binding.zoomOutWebtoon.bindToPreference(preferences.webtoonEnableZoomOut())
|
||||||
|
binding.cropBordersContinuesVertical.bindToPreference(preferences.cropBordersContinuesVertical())
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class DirectoryPageLoader(val file: File) : PageLoader() {
|
|||||||
override fun getPages(): Observable<List<ReaderPage>> {
|
override fun getPages(): Observable<List<ReaderPage>> {
|
||||||
return file.listFiles()
|
return file.listFiles()
|
||||||
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
|
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
|
||||||
.sortedWith(Comparator<File> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
|
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
|
||||||
.mapIndexed { i, file ->
|
.mapIndexed { i, file ->
|
||||||
val streamFn = { FileInputStream(file) }
|
val streamFn = { FileInputStream(file) }
|
||||||
ReaderPage(i).apply {
|
ReaderPage(i).apply {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.util.system.ImageUtil
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.zip.ZipEntry
|
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,7 +39,7 @@ class ZipPageLoader(file: File) : PageLoader() {
|
|||||||
override fun getPages(): Observable<List<ReaderPage>> {
|
override fun getPages(): Observable<List<ReaderPage>> {
|
||||||
return zip.entries().toList()
|
return zip.entries().toList()
|
||||||
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
|
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
|
||||||
.sortedWith(Comparator<ZipEntry> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
|
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
|
||||||
.mapIndexed { i, entry ->
|
.mapIndexed { i, entry ->
|
||||||
val streamFn = { zip.getInputStream(entry) }
|
val streamFn = { zip.getInputStream(entry) }
|
||||||
ReaderPage(i).apply {
|
ReaderPage(i).apply {
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
|||||||
* activity of the change and requests the preload of the next chapter if this is the last page.
|
* activity of the change and requests the preload of the next chapter if this is the last page.
|
||||||
*/
|
*/
|
||||||
private fun onReaderPageSelected(page: ReaderPage, allowPreload: Boolean) {
|
private fun onReaderPageSelected(page: ReaderPage, allowPreload: Boolean) {
|
||||||
val pages = page.chapter.pages!! // Won't be null because it's the loaded chapter
|
val pages = page.chapter.pages ?: return
|
||||||
Timber.d("onReaderPageSelected: ${page.number}/${pages.size}")
|
Timber.d("onReaderPageSelected: ${page.number}/${pages.size}")
|
||||||
activity.onPageSelected(page)
|
activity.onPageSelected(page)
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ class WebtoonConfig(preferences: PreferencesHelper = Injekt.get()) : ViewerConfi
|
|||||||
// SY -->
|
// SY -->
|
||||||
var enableZoomOut = false
|
var enableZoomOut = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var continuesCropBorders = false
|
||||||
|
private set
|
||||||
|
|
||||||
var zoomPropertyChangedListener: ((Boolean) -> Unit)? = null
|
var zoomPropertyChangedListener: ((Boolean) -> Unit)? = null
|
||||||
|
|
||||||
// SY <--
|
// SY <--
|
||||||
@@ -32,6 +36,9 @@ class WebtoonConfig(preferences: PreferencesHelper = Injekt.get()) : ViewerConfi
|
|||||||
// SY -->
|
// SY -->
|
||||||
preferences.webtoonEnableZoomOut()
|
preferences.webtoonEnableZoomOut()
|
||||||
.register({ enableZoomOut = it }, { zoomPropertyChangedListener?.invoke(it) })
|
.register({ enableZoomOut = it }, { zoomPropertyChangedListener?.invoke(it) })
|
||||||
|
|
||||||
|
preferences.cropBordersContinuesVertical()
|
||||||
|
.register({ continuesCropBorders = it }, { imagePropertyChangedListener?.invoke() })
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ class WebtoonPageHolder(
|
|||||||
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH)
|
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH)
|
||||||
setMinimumDpi(90)
|
setMinimumDpi(90)
|
||||||
setMinimumTileDpi(180)
|
setMinimumTileDpi(180)
|
||||||
setCropBorders(config.imageCropBorders)
|
setCropBorders(/* SY --> */ if (viewer.isContinuous) config.continuesCropBorders else /* SY <-- */ config.imageCropBorders)
|
||||||
setOnImageEventListener(
|
setOnImageEventListener(
|
||||||
object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
||||||
override fun onReady() {
|
override fun onReady() {
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
|
|||||||
* activity of the change and requests the preload of the next chapter if this is the last page.
|
* activity of the change and requests the preload of the next chapter if this is the last page.
|
||||||
*/
|
*/
|
||||||
private fun onPageSelected(page: ReaderPage, allowPreload: Boolean) {
|
private fun onPageSelected(page: ReaderPage, allowPreload: Boolean) {
|
||||||
val pages = page.chapter.pages!! // Won't be null because it's the loaded chapter
|
val pages = page.chapter.pages ?: return
|
||||||
Timber.d("onPageSelected: ${page.number}/${pages.size}")
|
Timber.d("onPageSelected: ${page.number}/${pages.size}")
|
||||||
activity.onPageSelected(page)
|
activity.onPageSelected(page)
|
||||||
|
|
||||||
@@ -320,11 +320,11 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
|
|||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_MENU -> if (isUp) activity.toggleMenu()
|
KeyEvent.KEYCODE_MENU -> if (isUp) activity.toggleMenu()
|
||||||
|
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT,
|
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||||
KeyEvent.KEYCODE_DPAD_UP,
|
KeyEvent.KEYCODE_DPAD_UP,
|
||||||
KeyEvent.KEYCODE_PAGE_UP -> if (isUp) scrollUp()
|
KeyEvent.KEYCODE_PAGE_UP -> if (isUp) scrollUp()
|
||||||
|
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||||
KeyEvent.KEYCODE_PAGE_DOWN -> if (isUp) scrollDown()
|
KeyEvent.KEYCODE_PAGE_DOWN -> if (isUp) scrollDown()
|
||||||
else -> return false
|
else -> return false
|
||||||
|
|||||||
@@ -2,26 +2,26 @@ package eu.kanade.tachiyomi.ui.recent
|
|||||||
|
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
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 java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class DateSectionItem(val date: Date) : AbstractHeaderItem<DateSectionItem.Holder>() {
|
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.recent_section_item
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): DateSectionItemHolder {
|
||||||
return Holder(view, adapter)
|
return DateSectionItemHolder(view, adapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: DateSectionItemHolder, position: Int, payloads: List<Any?>?) {
|
||||||
holder.bind(this)
|
holder.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,14 +37,14 @@ class DateSectionItem(val date: Date) : AbstractHeaderItem<DateSectionItem.Holde
|
|||||||
return date.hashCode()
|
return date.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder(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 now = Date().time
|
private val now = Date().time
|
||||||
|
|
||||||
val section_text: TextView = view.findViewById(R.id.section_text)
|
|
||||||
|
|
||||||
fun bind(item: DateSectionItem) {
|
fun bind(item: DateSectionItem) {
|
||||||
section_text.text = DateUtils.getRelativeTimeSpanString(item.date.time, now, DateUtils.DAY_IN_MILLIS)
|
binding.sectionText.text = DateUtils.getRelativeTimeSpanString(item.date.time, now, DateUtils.DAY_IN_MILLIS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class SettingsAdvancedController : SettingsController() {
|
|||||||
switchPreference {
|
switchPreference {
|
||||||
key = Keys.enableDoh
|
key = Keys.enableDoh
|
||||||
titleRes = R.string.pref_dns_over_https
|
titleRes = R.string.pref_dns_over_https
|
||||||
summaryRes = R.string.pref_dns_over_https_summary
|
summaryRes = R.string.requires_app_restart
|
||||||
defaultValue = false
|
defaultValue = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ 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.system.getFilePicker
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@@ -125,13 +124,11 @@ class SettingsBackupController : SettingsController() {
|
|||||||
titleRes = R.string.pref_backup_directory
|
titleRes = R.string.pref_backup_directory
|
||||||
|
|
||||||
onClick {
|
onClick {
|
||||||
val currentDir = preferences.backupsDirectory().get()
|
|
||||||
try {
|
try {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||||
startActivityForResult(intent, CODE_BACKUP_DIR)
|
startActivityForResult(intent, CODE_BACKUP_DIR)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
// Fall back to custom picker on error
|
activity?.toast(R.string.file_picker_error)
|
||||||
startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_DIR)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +202,7 @@ class SettingsBackupController : SettingsController() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
CODE_BACKUP_RESTORE -> {
|
CODE_BACKUP_RESTORE -> {
|
||||||
uri?.path?.let { path ->
|
uri?.path?.let {
|
||||||
val fileName = DocumentFile.fromSingleUri(activity, uri)!!.name!!
|
val fileName = DocumentFile.fromSingleUri(activity, uri)!!.name!!
|
||||||
when {
|
when {
|
||||||
fileName.endsWith(".proto.gz") -> {
|
fileName.endsWith(".proto.gz") -> {
|
||||||
@@ -258,7 +255,6 @@ class SettingsBackupController : SettingsController() {
|
|||||||
|
|
||||||
fun createBackup(flags: Int, type: Int) {
|
fun createBackup(flags: Int, type: Int) {
|
||||||
backupFlags = flags
|
backupFlags = flags
|
||||||
val currentDir = preferences.backupsDirectory().get()
|
|
||||||
val code = when (type) {
|
val code = when (type) {
|
||||||
BackupConst.BACKUP_TYPE_FULL -> CODE_FULL_BACKUP_CREATE
|
BackupConst.BACKUP_TYPE_FULL -> CODE_FULL_BACKUP_CREATE
|
||||||
else -> CODE_LEGACY_BACKUP_CREATE
|
else -> CODE_LEGACY_BACKUP_CREATE
|
||||||
@@ -277,8 +273,7 @@ class SettingsBackupController : SettingsController() {
|
|||||||
|
|
||||||
startActivityForResult(intent, code)
|
startActivityForResult(intent, code)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
// Handle errors where the Android ROM doesn't support the built in picker
|
activity?.toast(R.string.file_picker_error)
|
||||||
startActivityForResult(preferences.context.getFilePicker(currentDir), code)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package eu.kanade.tachiyomi.ui.setting
|
|||||||
|
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
|
import eu.kanade.tachiyomi.ui.category.repos.RepoController
|
||||||
import eu.kanade.tachiyomi.ui.category.sources.SourceCategoryController
|
import eu.kanade.tachiyomi.ui.category.sources.SourceCategoryController
|
||||||
import eu.kanade.tachiyomi.util.preference.defaultValue
|
import eu.kanade.tachiyomi.util.preference.defaultValue
|
||||||
|
import eu.kanade.tachiyomi.util.preference.infoPreference
|
||||||
import eu.kanade.tachiyomi.util.preference.onChange
|
import eu.kanade.tachiyomi.util.preference.onChange
|
||||||
import eu.kanade.tachiyomi.util.preference.onClick
|
import eu.kanade.tachiyomi.util.preference.onClick
|
||||||
import eu.kanade.tachiyomi.util.preference.preference
|
import eu.kanade.tachiyomi.util.preference.preference
|
||||||
@@ -13,6 +16,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 kotlinx.coroutines.flow.launchIn
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||||
|
|
||||||
class SettingsBrowseController : SettingsController() {
|
class SettingsBrowseController : SettingsController() {
|
||||||
@@ -81,6 +85,19 @@ class SettingsBrowseController : SettingsController() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// SY -->
|
||||||
|
preference {
|
||||||
|
key = "pref_edit_extension_repos"
|
||||||
|
titleRes = R.string.action_edit_repos
|
||||||
|
|
||||||
|
val catCount = preferences.extensionRepos().get().count()
|
||||||
|
summary = context.resources.getQuantityString(R.plurals.num_repos, catCount, catCount)
|
||||||
|
|
||||||
|
onClick {
|
||||||
|
router.pushController(RepoController().withFadeTransaction())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
preferenceCategory {
|
preferenceCategory {
|
||||||
@@ -93,27 +110,29 @@ class SettingsBrowseController : SettingsController() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// preferenceCategory {
|
preferenceCategory {
|
||||||
// titleRes = R.string.pref_category_nsfw_content
|
titleRes = R.string.pref_category_nsfw_content
|
||||||
//
|
|
||||||
// listPreference {
|
switchPreference {
|
||||||
// key = Keys.allowNsfwSource
|
key = Keys.showNsfwSource
|
||||||
// titleRes = R.string.pref_allow_nsfw_sources
|
titleRes = R.string.pref_show_nsfw_source
|
||||||
// entriesRes = arrayOf(
|
summaryRes = R.string.requires_app_restart
|
||||||
// R.string.pref_allow_nsfw_sources_allowed,
|
defaultValue = true
|
||||||
// R.string.pref_allow_nsfw_sources_allowed_multisource,
|
}
|
||||||
// R.string.pref_allow_nsfw_sources_blocked
|
switchPreference {
|
||||||
// )
|
key = Keys.showNsfwExtension
|
||||||
// entryValues = arrayOf(
|
titleRes = R.string.pref_show_nsfw_extension
|
||||||
// PreferenceValues.NsfwAllowance.ALLOWED.name,
|
defaultValue = true
|
||||||
// PreferenceValues.NsfwAllowance.PARTIAL.name,
|
}
|
||||||
// PreferenceValues.NsfwAllowance.BLOCKED.name
|
switchPreference {
|
||||||
// )
|
key = Keys.labelNsfwExtension
|
||||||
// defaultValue = PreferenceValues.NsfwAllowance.ALLOWED.name
|
titleRes = R.string.pref_label_nsfw_extension
|
||||||
// summary = "%s"
|
defaultValue = true
|
||||||
// }
|
|
||||||
//
|
preferences.showNsfwExtension().asImmediateFlow { isVisible = it }.launchIn(scope)
|
||||||
// infoPreference(R.string.parental_controls_info)
|
}
|
||||||
// }
|
|
||||||
|
infoPreference(R.string.parental_controls_info)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import eu.kanade.tachiyomi.util.preference.preference
|
|||||||
import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
||||||
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.system.getFilePicker
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@@ -203,12 +203,12 @@ class SettingsDownloadController : SettingsController() {
|
|||||||
preferences.downloadsDirectory().set(path.toString())
|
preferences.downloadsDirectory().set(path.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun customDirectorySelected(currentDir: String) {
|
fun customDirectorySelected() {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||||
try {
|
try {
|
||||||
startActivityForResult(intent, DOWNLOAD_DIR)
|
startActivityForResult(intent, DOWNLOAD_DIR)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
startActivityForResult(preferences.context.getFilePicker(currentDir), DOWNLOAD_DIR)
|
activity?.toast(R.string.file_picker_error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ class SettingsDownloadController : SettingsController() {
|
|||||||
) { _, position, text ->
|
) { _, position, text ->
|
||||||
val target = targetController as? SettingsDownloadController
|
val target = targetController as? SettingsDownloadController
|
||||||
if (position == externalDirs.lastIndex) {
|
if (position == externalDirs.lastIndex) {
|
||||||
target?.customDirectorySelected(currentDir)
|
target?.customDirectorySelected()
|
||||||
} else {
|
} else {
|
||||||
target?.predefinedDirectorySelected(text.toString())
|
target?.predefinedDirectorySelected(text.toString())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,6 +317,11 @@ class SettingsReaderController : SettingsController() {
|
|||||||
summaryRes = R.string.tap_scroll_page_summary
|
summaryRes = R.string.tap_scroll_page_summary
|
||||||
defaultValue = false
|
defaultValue = false
|
||||||
}
|
}
|
||||||
|
switchPreference {
|
||||||
|
key = Keys.cropBordersContinuesVertical
|
||||||
|
titleRes = R.string.pref_crop_borders
|
||||||
|
defaultValue = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.setting
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
@@ -20,7 +19,7 @@ import eu.kanade.tachiyomi.util.preference.onClick
|
|||||||
import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
||||||
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.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
import eu.kanade.tachiyomi.widget.preference.LoginPreference
|
import eu.kanade.tachiyomi.widget.preference.LoginPreference
|
||||||
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
|
||||||
@@ -47,11 +46,9 @@ class SettingsTrackingController :
|
|||||||
startActivity(MyAnimeListLoginActivity.newIntent(activity!!))
|
startActivity(MyAnimeListLoginActivity.newIntent(activity!!))
|
||||||
}
|
}
|
||||||
trackPreference(trackManager.aniList) {
|
trackPreference(trackManager.aniList) {
|
||||||
val tabsIntent = CustomTabsIntent.Builder()
|
activity?.openInBrowser(AnilistApi.authUrl(), trackManager.aniList.getLogoColor()) {
|
||||||
.setToolbarColor(context.getResourceColor(R.attr.colorPrimary))
|
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||||
.build()
|
}
|
||||||
tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
|
||||||
tabsIntent.launchUrl(activity!!, AnilistApi.authUrl())
|
|
||||||
}
|
}
|
||||||
trackPreference(trackManager.kitsu) {
|
trackPreference(trackManager.kitsu) {
|
||||||
val dialog = TrackLoginDialog(trackManager.kitsu, R.string.email)
|
val dialog = TrackLoginDialog(trackManager.kitsu, R.string.email)
|
||||||
@@ -59,18 +56,14 @@ class SettingsTrackingController :
|
|||||||
dialog.showDialog(router)
|
dialog.showDialog(router)
|
||||||
}
|
}
|
||||||
trackPreference(trackManager.shikimori) {
|
trackPreference(trackManager.shikimori) {
|
||||||
val tabsIntent = CustomTabsIntent.Builder()
|
activity?.openInBrowser(ShikimoriApi.authUrl(), trackManager.shikimori.getLogoColor()) {
|
||||||
.setToolbarColor(context.getResourceColor(R.attr.colorPrimary))
|
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||||
.build()
|
}
|
||||||
tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
|
||||||
tabsIntent.launchUrl(activity!!, ShikimoriApi.authUrl())
|
|
||||||
}
|
}
|
||||||
trackPreference(trackManager.bangumi) {
|
trackPreference(trackManager.bangumi) {
|
||||||
val tabsIntent = CustomTabsIntent.Builder()
|
activity?.openInBrowser(BangumiApi.authUrl(), trackManager.bangumi.getLogoColor()) {
|
||||||
.setToolbarColor(context.getResourceColor(R.attr.colorPrimary))
|
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||||
.build()
|
}
|
||||||
tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
|
||||||
tabsIntent.launchUrl(activity!!, BangumiApi.authUrl())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
preferenceCategory {
|
preferenceCategory {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ object SettingsSearchHelper {
|
|||||||
* All subclasses of `SettingsController` should be listed here, in order to have their preferences searchable.
|
* All subclasses of `SettingsController` should be listed here, in order to have their preferences searchable.
|
||||||
*/
|
*/
|
||||||
// SY -->
|
// SY -->
|
||||||
private val settingControllersList: List<KClass<out SettingsController>> = {
|
private val settingControllersList: List<KClass<out SettingsController>> = run {
|
||||||
val controllers = mutableListOf(
|
val controllers = mutableListOf(
|
||||||
SettingsAdvancedController::class,
|
SettingsAdvancedController::class,
|
||||||
SettingsBackupController::class,
|
SettingsBackupController::class,
|
||||||
@@ -55,7 +55,7 @@ object SettingsSearchHelper {
|
|||||||
controllers += SettingsEhController::class
|
controllers += SettingsEhController::class
|
||||||
}
|
}
|
||||||
controllers
|
controllers
|
||||||
}()
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,7 +119,7 @@ object SettingsSearchHelper {
|
|||||||
(pref.title != null) -> {
|
(pref.title != null) -> {
|
||||||
// Is an actual preference
|
// Is an actual preference
|
||||||
val title = pref.title.toString()
|
val title = pref.title.toString()
|
||||||
val summary = if (pref.summary != null) pref.summary.toString() else ""
|
val summary = pref.summary?.toString() ?: ""
|
||||||
val breadcrumbsStr = addLocalizedBreadcrumb(breadcrumbs, "${pref.title}")
|
val breadcrumbsStr = addLocalizedBreadcrumb(breadcrumbs, "${pref.title}")
|
||||||
|
|
||||||
prefSearchResultList.add(
|
prefSearchResultList.add(
|
||||||
|
|||||||
@@ -1,30 +1,14 @@
|
|||||||
package eu.kanade.tachiyomi.ui.setting.track
|
package eu.kanade.tachiyomi.ui.setting.track
|
||||||
|
|
||||||
import android.content.Intent
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.Gravity.CENTER
|
|
||||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.ProgressBar
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class AnilistLoginActivity : AppCompatActivity() {
|
class AnilistLoginActivity : BaseOAuthLoginActivity() {
|
||||||
|
|
||||||
private val trackManager: TrackManager by injectLazy()
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
|
||||||
super.onCreate(savedState)
|
|
||||||
|
|
||||||
val view = ProgressBar(this)
|
|
||||||
setContentView(view, FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, CENTER))
|
|
||||||
|
|
||||||
|
override fun handleResult(data: Uri?) {
|
||||||
val regex = "(?:access_token=)(.*?)(?:&)".toRegex()
|
val regex = "(?:access_token=)(.*?)(?:&)".toRegex()
|
||||||
val matchResult = regex.find(intent.data?.fragment.toString())
|
val matchResult = regex.find(data?.fragment.toString())
|
||||||
if (matchResult?.groups?.get(1) != null) {
|
if (matchResult?.groups?.get(1) != null) {
|
||||||
trackManager.aniList.login(matchResult.groups[1]!!.value)
|
trackManager.aniList.login(matchResult.groups[1]!!.value)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
@@ -42,12 +26,4 @@ class AnilistLoginActivity : AppCompatActivity() {
|
|||||||
returnToSettings()
|
returnToSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun returnToSettings() {
|
|
||||||
finish()
|
|
||||||
|
|
||||||
val intent = Intent(this, MainActivity::class.java)
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,13 @@
|
|||||||
package eu.kanade.tachiyomi.ui.setting.track
|
package eu.kanade.tachiyomi.ui.setting.track
|
||||||
|
|
||||||
import android.content.Intent
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.Gravity.CENTER
|
|
||||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.ProgressBar
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class BangumiLoginActivity : AppCompatActivity() {
|
class BangumiLoginActivity : BaseOAuthLoginActivity() {
|
||||||
|
|
||||||
private val trackManager: TrackManager by injectLazy()
|
override fun handleResult(data: Uri?) {
|
||||||
|
val code = data?.getQueryParameter("code")
|
||||||
override fun onCreate(savedState: Bundle?) {
|
|
||||||
super.onCreate(savedState)
|
|
||||||
|
|
||||||
val view = ProgressBar(this)
|
|
||||||
setContentView(view, FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, CENTER))
|
|
||||||
|
|
||||||
val code = intent.data?.getQueryParameter("code")
|
|
||||||
if (code != null) {
|
if (code != null) {
|
||||||
trackManager.bangumi.login(code)
|
trackManager.bangumi.login(code)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
@@ -41,12 +25,4 @@ class BangumiLoginActivity : AppCompatActivity() {
|
|||||||
returnToSettings()
|
returnToSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun returnToSettings() {
|
|
||||||
finish()
|
|
||||||
|
|
||||||
val intent = Intent(this, MainActivity::class.java)
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.setting.track
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
|
import eu.kanade.tachiyomi.ui.base.activity.BaseThemedActivity
|
||||||
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
abstract class BaseOAuthLoginActivity : BaseThemedActivity() {
|
||||||
|
|
||||||
|
internal val trackManager: TrackManager by injectLazy()
|
||||||
|
|
||||||
|
abstract fun handleResult(data: Uri?)
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val view = ProgressBar(this)
|
||||||
|
setContentView(
|
||||||
|
view,
|
||||||
|
FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
Gravity.CENTER
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
handleResult(intent.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun returnToSettings() {
|
||||||
|
finish()
|
||||||
|
|
||||||
|
val intent = Intent(this, MainActivity::class.java)
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +1,13 @@
|
|||||||
package eu.kanade.tachiyomi.ui.setting.track
|
package eu.kanade.tachiyomi.ui.setting.track
|
||||||
|
|
||||||
import android.content.Intent
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.Gravity.CENTER
|
|
||||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.ProgressBar
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class ShikimoriLoginActivity : AppCompatActivity() {
|
class ShikimoriLoginActivity : BaseOAuthLoginActivity() {
|
||||||
|
|
||||||
private val trackManager: TrackManager by injectLazy()
|
override fun handleResult(data: Uri?) {
|
||||||
|
val code = data?.getQueryParameter("code")
|
||||||
override fun onCreate(savedState: Bundle?) {
|
|
||||||
super.onCreate(savedState)
|
|
||||||
|
|
||||||
val view = ProgressBar(this)
|
|
||||||
setContentView(view, FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, CENTER))
|
|
||||||
|
|
||||||
val code = intent.data?.getQueryParameter("code")
|
|
||||||
if (code != null) {
|
if (code != null) {
|
||||||
trackManager.shikimori.login(code)
|
trackManager.shikimori.login(code)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
@@ -41,12 +25,4 @@ class ShikimoriLoginActivity : AppCompatActivity() {
|
|||||||
returnToSettings()
|
returnToSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun returnToSettings() {
|
|
||||||
finish()
|
|
||||||
|
|
||||||
val intent = Intent(this, MainActivity::class.java)
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import androidx.core.view.isVisible
|
|||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.databinding.WebviewActivityBinding
|
import eu.kanade.tachiyomi.databinding.WebviewActivityBinding
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
import eu.kanade.tachiyomi.ui.base.activity.BaseViewBindingActivity
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
@@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import reactivecircus.flowbinding.appcompat.navigationClicks
|
import reactivecircus.flowbinding.appcompat.navigationClicks
|
||||||
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
|
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
|
||||||
|
|
||||||
open class BaseWebViewActivity : BaseActivity<WebviewActivityBinding>() {
|
open class BaseWebViewActivity : BaseViewBindingActivity<WebviewActivityBinding>() {
|
||||||
|
|
||||||
internal var bundle: Bundle? = null
|
internal var bundle: Bundle? = null
|
||||||
|
|
||||||
@@ -31,15 +31,17 @@ open class BaseWebViewActivity : BaseActivity<WebviewActivityBinding>() {
|
|||||||
if (!WebViewUtil.supportsWebView(this)) {
|
if (!WebViewUtil.supportsWebView(this)) {
|
||||||
toast(R.string.information_webview_required, Toast.LENGTH_LONG)
|
toast(R.string.information_webview_required, Toast.LENGTH_LONG)
|
||||||
finish()
|
finish()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
binding = WebviewActivityBinding.inflate(layoutInflater)
|
binding = WebviewActivityBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
} catch (e: Exception) {
|
} catch (e: Throwable) {
|
||||||
// Potentially throws errors like "Error inflating class android.webkit.WebView"
|
// Potentially throws errors like "Error inflating class android.webkit.WebView"
|
||||||
toast(R.string.information_webview_required, Toast.LENGTH_LONG)
|
toast(R.string.information_webview_required, Toast.LENGTH_LONG)
|
||||||
finish()
|
finish()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
title = intent.extras?.getString(TITLE_KEY)
|
title = intent.extras?.getString(TITLE_KEY)
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ private suspend fun <T> Observable<T>.awaitOne(): T = suspendCancellableCoroutin
|
|||||||
internal fun <T> CancellableContinuation<T>.unsubscribeOnCancellation(sub: Subscription) =
|
internal fun <T> CancellableContinuation<T>.unsubscribeOnCancellation(sub: Subscription) =
|
||||||
invokeOnCancellation { sub.unsubscribe() }
|
invokeOnCancellation { sub.unsubscribe() }
|
||||||
|
|
||||||
fun <T : Any> Observable<T>.asFlow(): Flow<T> = callbackFlow {
|
fun <T : Any?> Observable<T>.asFlow(): Flow<T> = callbackFlow {
|
||||||
val observer = object : Observer<T> {
|
val observer = object : Observer<T> {
|
||||||
override fun onNext(t: T) {
|
override fun onNext(t: T) {
|
||||||
offer(t)
|
offer(t)
|
||||||
@@ -199,7 +199,7 @@ fun <T : Any> Observable<T>.asFlow(): Flow<T> = callbackFlow {
|
|||||||
awaitClose { subscription.unsubscribe() }
|
awaitClose { subscription.unsubscribe() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> Flow<T>.asObservable(backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE): Observable<T> {
|
fun <T : Any?> Flow<T>.asObservable(backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE): Observable<T> {
|
||||||
return Observable.create(
|
return Observable.create(
|
||||||
{ emitter ->
|
{ emitter ->
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ import android.content.pm.PackageManager
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.Uri
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@@ -29,10 +31,8 @@ import androidx.core.graphics.green
|
|||||||
import androidx.core.graphics.red
|
import androidx.core.graphics.red
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import com.nononsenseapps.filepicker.FilePickerActivity
|
|
||||||
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 eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -109,19 +109,6 @@ fun Context.notification(channelId: String, block: (NotificationCompat.Builder.(
|
|||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method to construct an Intent to use a custom file picker.
|
|
||||||
* @param currentDir the path the file picker will open with.
|
|
||||||
* @return an Intent to start the file picker activity.
|
|
||||||
*/
|
|
||||||
fun Context.getFilePicker(currentDir: String): Intent {
|
|
||||||
return Intent(this, CustomLayoutPickerActivity::class.java)
|
|
||||||
.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
|
|
||||||
.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
|
|
||||||
.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
|
|
||||||
.putExtra(FilePickerActivity.EXTRA_START_PATH, currentDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the give permission is granted.
|
* Checks if the give permission is granted.
|
||||||
*
|
*
|
||||||
@@ -250,12 +237,21 @@ fun Context.isServiceRunning(serviceClass: Class<*>): Boolean {
|
|||||||
/**
|
/**
|
||||||
* Opens a URL in a custom tab.
|
* Opens a URL in a custom tab.
|
||||||
*/
|
*/
|
||||||
fun Context.openInBrowser(url: String) {
|
fun Context.openInBrowser(url: String, @ColorInt toolbarColor: Int? = null, block: CustomTabsIntent.() -> Unit = {}) {
|
||||||
|
this.openInBrowser(url.toUri(), toolbarColor, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.openInBrowser(uri: Uri, @ColorInt toolbarColor: Int? = null, block: CustomTabsIntent.() -> Unit = {}) {
|
||||||
try {
|
try {
|
||||||
val intent = CustomTabsIntent.Builder()
|
val intent = CustomTabsIntent.Builder()
|
||||||
.setToolbarColor(getResourceColor(R.attr.colorPrimary))
|
.setDefaultColorSchemeParams(
|
||||||
|
CustomTabColorSchemeParams.Builder()
|
||||||
|
.setToolbarColor(toolbarColor ?: getResourceColor(R.attr.colorPrimary))
|
||||||
|
.build()
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
intent.launchUrl(this, url.toUri())
|
block(intent)
|
||||||
|
intent.launchUrl(this, uri)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
toast(e.message)
|
toast(e.message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ object WebViewUtil {
|
|||||||
|
|
||||||
const val REQUESTED_WITH = "com.android.browser"
|
const val REQUESTED_WITH = "com.android.browser"
|
||||||
|
|
||||||
const val MINIMUM_WEBVIEW_VERSION = 84
|
const val MINIMUM_WEBVIEW_VERSION = 86
|
||||||
|
|
||||||
fun supportsWebView(context: Context): Boolean {
|
fun supportsWebView(context: Context): Boolean {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.widget
|
|
||||||
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.nononsenseapps.filepicker.AbstractFilePickerFragment
|
|
||||||
import com.nononsenseapps.filepicker.FilePickerActivity
|
|
||||||
import com.nononsenseapps.filepicker.FilePickerFragment
|
|
||||||
import com.nononsenseapps.filepicker.LogicHandler
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.view.inflate
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class CustomLayoutPickerActivity : FilePickerActivity() {
|
|
||||||
|
|
||||||
override fun getFragment(startPath: String?, mode: Int, allowMultiple: Boolean, allowCreateDir: Boolean):
|
|
||||||
AbstractFilePickerFragment<File> {
|
|
||||||
val fragment = CustomLayoutFilePickerFragment()
|
|
||||||
fragment.setArgs(startPath, mode, allowMultiple, allowCreateDir)
|
|
||||||
return fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CustomLayoutFilePickerFragment : FilePickerFragment() {
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
||||||
return when (viewType) {
|
|
||||||
LogicHandler.VIEWTYPE_DIR -> {
|
|
||||||
val view = parent.inflate(R.layout.common_listitem_dir)
|
|
||||||
DirViewHolder(view)
|
|
||||||
}
|
|
||||||
else -> super.onCreateViewHolder(parent, viewType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||||
@@ -14,6 +15,8 @@ import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
|||||||
import eu.kanade.tachiyomi.util.lang.awaitSingle
|
import eu.kanade.tachiyomi.util.lang.awaitSingle
|
||||||
import exh.source.getMainSource
|
import exh.source.getMainSource
|
||||||
import exh.util.executeOnIO
|
import exh.util.executeOnIO
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class GalleryAdder {
|
class GalleryAdder {
|
||||||
@@ -22,6 +25,11 @@ class GalleryAdder {
|
|||||||
|
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
|
private val filters: Pair<Set<String>, Set<Long>> = run {
|
||||||
|
val preferences = Injekt.get<PreferencesHelper>()
|
||||||
|
preferences.enabledLanguages().get() to preferences.disabledSources().get().map { it.toLong() }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
private val logger = XLog.tag("GalleryAdder").enableStackTrace(2).build()
|
private val logger = XLog.tag("GalleryAdder").enableStackTrace(2).build()
|
||||||
|
|
||||||
fun pickSource(url: String): List<UrlImportableSource> {
|
fun pickSource(url: String): List<UrlImportableSource> {
|
||||||
@@ -30,7 +38,7 @@ class GalleryAdder {
|
|||||||
.map { it.getMainSource() }
|
.map { it.getMainSource() }
|
||||||
.filterIsInstance<UrlImportableSource>()
|
.filterIsInstance<UrlImportableSource>()
|
||||||
.filter {
|
.filter {
|
||||||
try {
|
it.lang in filters.first && it.id !in filters.second && try {
|
||||||
it.matchesUri(uri)
|
it.matchesUri(uri)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
false
|
false
|
||||||
@@ -63,7 +71,7 @@ class GalleryAdder {
|
|||||||
.map { it.getMainSource() }
|
.map { it.getMainSource() }
|
||||||
.filterIsInstance<UrlImportableSource>()
|
.filterIsInstance<UrlImportableSource>()
|
||||||
.find {
|
.find {
|
||||||
try {
|
it.lang in filters.first && it.id !in filters.second && try {
|
||||||
it.matchesUri(uri)
|
it.matchesUri(uri)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -35,18 +35,17 @@ object MetadataUtil {
|
|||||||
private const val GB_FACTOR = 1000 * MB_FACTOR
|
private const val GB_FACTOR = 1000 * MB_FACTOR
|
||||||
private const val GIB_FACTOR = 1024 * MIB_FACTOR
|
private const val GIB_FACTOR = 1024 * MIB_FACTOR
|
||||||
|
|
||||||
fun parseHumanReadableByteCount(arg0: String): Double? {
|
fun parseHumanReadableByteCount(bytes: String): Double? {
|
||||||
val spaceNdx = arg0.indexOf(" ")
|
val ret = bytes.substringBefore(' ').toDouble()
|
||||||
val ret = java.lang.Double.parseDouble(arg0.substring(0, spaceNdx))
|
return when (bytes.substringAfter(' ')) {
|
||||||
when (arg0.substring(spaceNdx + 1)) {
|
"GB" -> ret * GB_FACTOR
|
||||||
"GB" -> return ret * GB_FACTOR
|
"GiB" -> ret * GIB_FACTOR
|
||||||
"GiB" -> return ret * GIB_FACTOR
|
"MB" -> ret * MB_FACTOR
|
||||||
"MB" -> return ret * MB_FACTOR
|
"MiB" -> ret * MIB_FACTOR
|
||||||
"MiB" -> return ret * MIB_FACTOR
|
"KB" -> ret * KB_FACTOR
|
||||||
"KB" -> return ret * KB_FACTOR
|
"KiB" -> ret * KIB_FACTOR
|
||||||
"KiB" -> return ret * KIB_FACTOR
|
else -> null
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val ONGOING_SUFFIX = arrayOf(
|
val ONGOING_SUFFIX = arrayOf(
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
titleObj?.let { manga.title = it }
|
titleObj?.let { manga.title = it }
|
||||||
|
|
||||||
// Set artist (if we can find one)
|
// Set artist (if we can find one)
|
||||||
tags.filter { it.namespace == EH_ARTIST_NAMESPACE }.let {
|
tags.filter { it.namespace == EH_ARTIST_NAMESPACE }.let { tags ->
|
||||||
if (it.isNotEmpty()) manga.artist = it.joinToString(transform = { it.name })
|
if (tags.isNotEmpty()) manga.artist = tags.joinToString(transform = { it.name })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy tags -> genres
|
// Copy tags -> genres
|
||||||
@@ -77,35 +77,7 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a nice looking description out of what we know
|
manga.description = "meta"
|
||||||
/* val titleDesc = StringBuilder()
|
|
||||||
title?.let { titleDesc += "Title: $it\n" }
|
|
||||||
altTitle?.let { titleDesc += "Alternate Title: $it\n" }
|
|
||||||
|
|
||||||
val detailsDesc = StringBuilder()
|
|
||||||
genre?.let { detailsDesc += "Genre: $it\n" }
|
|
||||||
uploader?.let { detailsDesc += "Uploader: $it\n" }
|
|
||||||
datePosted?.let { detailsDesc += "Posted: ${EX_DATE_FORMAT.format(Date(it))}\n" }
|
|
||||||
visible?.let { detailsDesc += "Visible: $it\n" }
|
|
||||||
language?.let {
|
|
||||||
detailsDesc += "Language: $it"
|
|
||||||
if (translated == true) detailsDesc += " TR"
|
|
||||||
detailsDesc += "\n"
|
|
||||||
}
|
|
||||||
size?.let { detailsDesc += "File size: ${humanReadableByteCount(it, true)}\n" }
|
|
||||||
length?.let { detailsDesc += "Length: $it pages\n" }
|
|
||||||
favorites?.let { detailsDesc += "Favorited: $it times\n" }
|
|
||||||
averageRating?.let {
|
|
||||||
detailsDesc += "Rating: $it"
|
|
||||||
ratingCount?.let { detailsDesc += " ($it)" }
|
|
||||||
detailsDesc += "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
val tagsDesc = tagsToDescription()*/
|
|
||||||
|
|
||||||
manga.description = "meta" /*listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
|
||||||
.filter(String::isNotBlank)
|
|
||||||
.joinToString(separator = "\n")*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||||
|
|||||||
@@ -29,14 +29,7 @@ class EightMusesSearchMetadata : RaisedSearchMetadata() {
|
|||||||
|
|
||||||
manga.genre = tagsToGenreString()
|
manga.genre = tagsToGenreString()
|
||||||
|
|
||||||
/*val titleDesc = StringBuilder()
|
manga.description = "meta"
|
||||||
title?.let { titleDesc += "Title: $it\n" }
|
|
||||||
|
|
||||||
val tagsDesc = tagsToDescription()*/
|
|
||||||
|
|
||||||
manga.description = "meta" /*listOf(titleDesc.toString(), tagsDesc.toString())
|
|
||||||
.filter(String::isNotBlank)
|
|
||||||
.joinToString(separator = "\n")*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||||
|
|||||||
@@ -37,15 +37,7 @@ class HBrowseSearchMetadata : RaisedSearchMetadata() {
|
|||||||
|
|
||||||
manga.genre = tagsToGenreString()
|
manga.genre = tagsToGenreString()
|
||||||
|
|
||||||
/*val titleDesc = StringBuilder()
|
manga.description = "meta"
|
||||||
title?.let { titleDesc += "Title: $it\n" }
|
|
||||||
length?.let { titleDesc += "Length: $it page(s)\n" }
|
|
||||||
|
|
||||||
val tagsDesc = tagsToDescription()*/
|
|
||||||
|
|
||||||
manga.description = "meta" /*listOf(titleDesc.toString(), tagsDesc.toString())
|
|
||||||
.filter(String::isNotBlank)
|
|
||||||
.joinToString(separator = "\n")*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||||
|
|||||||
@@ -36,14 +36,7 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() {
|
|||||||
|
|
||||||
manga.genre = tagsToGenreString()
|
manga.genre = tagsToGenreString()
|
||||||
|
|
||||||
/* val detailsDesc = "Title: $title\n" +
|
manga.description = "meta"
|
||||||
"Artist: $artist\n"
|
|
||||||
|
|
||||||
val tagsDesc = tagsToDescription()*/
|
|
||||||
|
|
||||||
manga.description = "meta" /*listOf(detailsDesc, tagsDesc.toString())
|
|
||||||
.filter(String::isNotBlank)
|
|
||||||
.joinToString(separator = "\n")*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||||
|
|||||||
@@ -51,45 +51,7 @@ class HitomiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
|
|
||||||
manga.status = SManga.UNKNOWN
|
manga.status = SManga.UNKNOWN
|
||||||
|
|
||||||
/*val titleDesc = StringBuilder()
|
manga.description = "meta"
|
||||||
|
|
||||||
title?.let {
|
|
||||||
titleDesc += "Title: $it\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
val detailsDesc = StringBuilder()
|
|
||||||
|
|
||||||
detailsDesc += "Artist(s): ${manga.artist}\n"
|
|
||||||
|
|
||||||
group?.let {
|
|
||||||
detailsDesc += "Group: $it\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
type?.let {
|
|
||||||
detailsDesc += "Type: ${it.capitalize()}\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
(language ?: "unknown").let {
|
|
||||||
detailsDesc += "Language: ${it.capitalize()}\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (series.isNotEmpty()) {
|
|
||||||
detailsDesc += "Series: ${series.joinToString()}\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (characters.isNotEmpty()) {
|
|
||||||
detailsDesc += "Characters: ${characters.joinToString()}\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadDate?.let {
|
|
||||||
detailsDesc += "Upload date: ${EX_DATE_FORMAT.format(Date(it))}\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
val tagsDesc = tagsToDescription()*/
|
|
||||||
|
|
||||||
manga.description = "meta" /*listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
|
||||||
.filter(String::isNotBlank)
|
|
||||||
.joinToString(separator = "\n")*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set artist (if we can find one)
|
// Set artist (if we can find one)
|
||||||
tags.filter { it.namespace == NHENTAI_ARTIST_NAMESPACE }.let {
|
tags.filter { it.namespace == NHENTAI_ARTIST_NAMESPACE }.let { tags ->
|
||||||
if (it.isNotEmpty()) manga.artist = it.joinToString(transform = { it.name })
|
if (tags.isNotEmpty()) manga.artist = tags.joinToString(transform = { it.name })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy tags -> genres
|
// Copy tags -> genres
|
||||||
|
|||||||
@@ -56,41 +56,7 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
|
|||||||
// Copy tags -> genres
|
// Copy tags -> genres
|
||||||
manga.genre = tagsToGenreString()
|
manga.genre = tagsToGenreString()
|
||||||
|
|
||||||
/*val titleDesc = StringBuilder()
|
manga.description = "meta"
|
||||||
|
|
||||||
title?.let {
|
|
||||||
titleDesc += "Title: $it\n"
|
|
||||||
}
|
|
||||||
if (altTitles.isNotEmpty()) {
|
|
||||||
titleDesc += "Alternate Titles: \n" + altTitles
|
|
||||||
.joinToString(separator = "\n", postfix = "\n") {
|
|
||||||
"▪ $it"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val detailsDesc = StringBuilder()
|
|
||||||
artist?.let {
|
|
||||||
detailsDesc += "Artist: $it\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
type?.let {
|
|
||||||
detailsDesc += "Type: $it\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
status?.let {
|
|
||||||
detailsDesc += "Status: $it\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
rating?.let {
|
|
||||||
detailsDesc += "Rating: %.2\n".format(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val tagsDesc = tagsToDescription()*/
|
|
||||||
|
|
||||||
manga.description = "meta" /*listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
|
||||||
.filter(String::isNotBlank)
|
|
||||||
.joinToString(separator = "\n")*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||||
|
|||||||
@@ -45,21 +45,7 @@ class PururinSearchMetadata : RaisedSearchMetadata() {
|
|||||||
|
|
||||||
manga.genre = tagsToGenreString()
|
manga.genre = tagsToGenreString()
|
||||||
|
|
||||||
/*val titleDesc = StringBuilder()
|
manga.description = "meta"
|
||||||
title?.let { titleDesc += "English Title: $it\n" }
|
|
||||||
altTitle?.let { titleDesc += "Japanese Title: $it\n" }
|
|
||||||
|
|
||||||
val detailsDesc = StringBuilder()
|
|
||||||
(uploaderDisp ?: uploader)?.let { detailsDesc += "Uploader: $it\n" }
|
|
||||||
pages?.let { detailsDesc += "Length: $it pages\n" }
|
|
||||||
fileSize?.let { detailsDesc += "Size: $it\n" }
|
|
||||||
ratingCount?.let { detailsDesc += "Rating: $averageRating ($ratingCount)\n" }
|
|
||||||
|
|
||||||
val tagsDesc = tagsToDescription()*/
|
|
||||||
|
|
||||||
manga.description = "meta" /*listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
|
||||||
.filter(String::isNotBlank)
|
|
||||||
.joinToString(separator = "\n")*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||||
|
|||||||
@@ -52,32 +52,7 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
|
|||||||
// Copy tags -> genres
|
// Copy tags -> genres
|
||||||
manga.genre = tagsToGenreString()
|
manga.genre = tagsToGenreString()
|
||||||
|
|
||||||
/*val titleDesc = "Title: $title\n"
|
manga.description = "meta"
|
||||||
|
|
||||||
val detailsDesc = StringBuilder()
|
|
||||||
uploader?.let { detailsDesc += "Uploader: $it\n" }
|
|
||||||
uploadDate?.let { detailsDesc += "Uploaded: ${EX_DATE_FORMAT.format(Date(it))}\n" }
|
|
||||||
length?.let { detailsDesc += "Length: $it pages\n" }
|
|
||||||
ratingString?.let { detailsDesc += "Rating: $it\n" }
|
|
||||||
category?.let {
|
|
||||||
detailsDesc += "Category: $it\n"
|
|
||||||
}
|
|
||||||
collection?.let { detailsDesc += "Collection: $it\n" }
|
|
||||||
group?.let { detailsDesc += "Group: $it\n" }
|
|
||||||
val parodiesString = parody.joinToString()
|
|
||||||
if (parodiesString.isNotEmpty()) {
|
|
||||||
detailsDesc += "Parody: $parodiesString\n"
|
|
||||||
}
|
|
||||||
val charactersString = character.joinToString()
|
|
||||||
if (charactersString.isNotEmpty()) {
|
|
||||||
detailsDesc += "Character: $charactersString\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
val tagsDesc = tagsToDescription()*/
|
|
||||||
|
|
||||||
manga.description = "meta" /*listOf(titleDesc, detailsDesc.toString(), tagsDesc.toString())
|
|
||||||
.filter(String::isNotBlank)
|
|
||||||
.joinToString(separator = "\n")*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
override fun getExtraInfoPairs(context: Context): List<Pair<String, String>> {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import kotlinx.serialization.json.Json
|
|||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.contentOrNull
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
@@ -103,10 +104,21 @@ class Anilist : API("https://graphql.anilist.co/") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getTitle(obj: JsonObject): String {
|
private fun getTitle(obj: JsonObject): String {
|
||||||
return obj["title"]!!.jsonObject.let {
|
val titleObj = obj["title"]!!.jsonObject
|
||||||
it["romaji"]?.jsonPrimitive?.content
|
|
||||||
?: it["english"]?.jsonPrimitive?.content
|
val english = titleObj["english"]?.jsonPrimitive?.contentOrNull
|
||||||
?: it["native"]!!.jsonPrimitive.content
|
val romaji = titleObj["romaji"]?.jsonPrimitive?.contentOrNull
|
||||||
|
val native = titleObj["native"]?.jsonPrimitive?.contentOrNull
|
||||||
|
val synonym = obj["synonyms"]!!.jsonArray.getOrNull(0)?.jsonPrimitive?.contentOrNull
|
||||||
|
|
||||||
|
val isJP = obj["countryOfOrigin"]!!.jsonPrimitive.content == "JP"
|
||||||
|
|
||||||
|
return when {
|
||||||
|
!english.isNullOrBlank() -> english
|
||||||
|
isJP && !romaji.isNullOrBlank() -> romaji
|
||||||
|
!synonym.isNullOrBlank() -> synonym
|
||||||
|
!isJP && !romaji.isNullOrBlank() -> romaji
|
||||||
|
else -> native ?: "NO NAME FOUND"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,12 +138,14 @@ class Anilist : API("https://graphql.anilist.co/") {
|
|||||||
|edges {
|
|edges {
|
||||||
|node {
|
|node {
|
||||||
|mediaRecommendation {
|
|mediaRecommendation {
|
||||||
|
|countryOfOrigin
|
||||||
|siteUrl
|
|siteUrl
|
||||||
|title {
|
|title {
|
||||||
|romaji
|
|romaji
|
||||||
|english
|
|english
|
||||||
|native
|
|native
|
||||||
|}
|
|}
|
||||||
|
|synonyms
|
||||||
|coverImage {
|
|coverImage {
|
||||||
|large
|
|large
|
||||||
|}
|
|}
|
||||||
@@ -203,7 +217,7 @@ open class RecommendsPager(
|
|||||||
recs
|
recs
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.tag("RECOMMENDATIONS").e("%s > Error: %s", key, e.message)
|
Timber.tag("RECOMMENDATIONS").e("%s > Error: %s", key, e.message)
|
||||||
listOf()
|
listOf<SMangaImpl>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.firstOrNull { it.isNotEmpty() }
|
.firstOrNull { it.isNotEmpty() }
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package exh.ui.base
|
||||||
|
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import nucleus.presenter.Presenter
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
open class CoroutinePresenter<V> : Presenter<V>() {
|
||||||
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
|
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||||
|
@Deprecated("Use launchInView")
|
||||||
|
override fun getView(): V? {
|
||||||
|
return super.getView()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun launchInView(block: (CoroutineScope, V) -> Unit) = scope.launch(Dispatchers.Main) {
|
||||||
|
view?.let { block.invoke(this, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <F> Flow<F>.onEachView(block: (V, F) -> Unit) = onEach {
|
||||||
|
view?.let { view -> block(view, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <F, P> Flow<F>.mapView(block: (V, F) -> P): Flow<P> = mapNotNull {
|
||||||
|
view?.let { view -> block(view, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun destroy() {
|
||||||
|
super.destroy()
|
||||||
|
scope.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import com.afollestad.materialdialogs.list.listItemsSingleChoice
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.databinding.EhActivityInterceptBinding
|
import eu.kanade.tachiyomi.databinding.EhActivityInterceptBinding
|
||||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
import eu.kanade.tachiyomi.ui.base.activity.BaseViewBindingActivity
|
||||||
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
|
||||||
import exh.GalleryAddEvent
|
import exh.GalleryAddEvent
|
||||||
@@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class InterceptActivity : BaseActivity<EhActivityInterceptBinding>() {
|
class InterceptActivity : BaseViewBindingActivity<EhActivityInterceptBinding>() {
|
||||||
private var statusJob: Job? = null
|
private var statusJob: Job? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|||||||
@@ -11,12 +11,9 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.databinding.MetadataViewControllerBinding
|
import eu.kanade.tachiyomi.databinding.MetadataViewControllerBinding
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.online.MetadataSource
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
import exh.metadata.metadata.base.FlatMetadata
|
|
||||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||||
import exh.source.getMainSource
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@@ -74,13 +71,6 @@ class MetadataViewController : NucleusController<MetadataViewControllerBinding,
|
|||||||
binding.recycler.setHasFixedSize(true)
|
binding.recycler.setHasFixedSize(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onNextMetaInfo(flatMetadata: FlatMetadata) {
|
|
||||||
val mainSource = presenter.source.getMainSource()
|
|
||||||
if (mainSource is MetadataSource<*, *>) {
|
|
||||||
presenter.meta = flatMetadata.raise(mainSource.metaClass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onNextMangaInfo(meta: RaisedSearchMetadata?) {
|
fun onNextMangaInfo(meta: RaisedSearchMetadata?) {
|
||||||
val context = view?.context ?: return
|
val context = view?.context ?: return
|
||||||
data = meta?.getExtraInfoPairs(context).orEmpty()
|
data = meta?.getExtraInfoPairs(context).orEmpty()
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
package exh.ui.metadata
|
package exh.ui.metadata
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.elvishew.xlog.XLog
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||||
|
import eu.kanade.tachiyomi.util.lang.asFlow
|
||||||
import exh.metadata.metadata.base.FlatMetadata
|
import exh.metadata.metadata.base.FlatMetadata
|
||||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||||
import rx.Observable
|
import exh.source.getMainSource
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import exh.ui.base.CoroutinePresenter
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.plus
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@@ -20,29 +26,31 @@ class MetadataViewPresenter(
|
|||||||
val source: Source,
|
val source: Source,
|
||||||
val preferences: PreferencesHelper = Injekt.get(),
|
val preferences: PreferencesHelper = Injekt.get(),
|
||||||
private val db: DatabaseHelper = Injekt.get()
|
private val db: DatabaseHelper = Injekt.get()
|
||||||
) : BasePresenter<MetadataViewController>() {
|
) : CoroutinePresenter<MetadataViewController>() {
|
||||||
|
|
||||||
var meta: RaisedSearchMetadata? = null
|
val meta = MutableStateFlow<RaisedSearchMetadata?>(null)
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
getMangaMetaObservable().subscribeLatestCache({ view, flatMetadata -> if (flatMetadata != null) view.onNextMetaInfo(flatMetadata) else XLog.tag("MetadataViewPresenter").disableStackTrace().d("Invalid metadata") })
|
getMangaMetaObservable()
|
||||||
|
.onEach {
|
||||||
|
if (it == null) return@onEach
|
||||||
|
val mainSource = source.getMainSource()
|
||||||
|
if (mainSource is MetadataSource<*, *>) {
|
||||||
|
meta.value = it.raise(mainSource.metaClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(scope + Dispatchers.IO)
|
||||||
|
|
||||||
getMangaObservable()
|
meta
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.onEachView { view, metadata ->
|
||||||
.subscribeLatestCache({ view, _ -> view.onNextMangaInfo(meta) })
|
view.onNextMangaInfo(metadata)
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMangaObservable(): Observable<Manga> {
|
private fun getMangaMetaObservable(): Flow<FlatMetadata?> {
|
||||||
return db.getManga(manga.url, manga.source).asRxObservable()
|
return db.getFlatMetadataForManga(manga.id!!).asRxObservable().asFlow()
|
||||||
}
|
|
||||||
|
|
||||||
private fun getMangaMetaObservable(): Observable<FlatMetadata?> {
|
|
||||||
val mangaId = manga.id
|
|
||||||
return if (mangaId != null) {
|
|
||||||
db.getFlatMetadataForManga(mangaId).asRxObservable()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
} else Observable.just(null)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package exh.widget.preference
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
@@ -40,7 +39,7 @@ class MangadexLoginDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
|||||||
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
var binding: PrefSiteLoginTwoFactorAuthBinding? = null
|
lateinit var binding: PrefSiteLoginTwoFactorAuthBinding
|
||||||
|
|
||||||
constructor(source: MangaDex) : this(
|
constructor(source: MangaDex) : this(
|
||||||
bundleOf(
|
bundleOf(
|
||||||
@@ -51,31 +50,31 @@ class MangadexLoginDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
|||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
binding = PrefSiteLoginTwoFactorAuthBinding.inflate(LayoutInflater.from(activity!!))
|
binding = PrefSiteLoginTwoFactorAuthBinding.inflate(LayoutInflater.from(activity!!))
|
||||||
val dialog = MaterialDialog(activity!!)
|
val dialog = MaterialDialog(activity!!)
|
||||||
.customView(view = binding!!.root, scrollable = false)
|
.customView(view = binding.root, scrollable = false)
|
||||||
|
|
||||||
onViewCreated(dialog.view)
|
onViewCreated()
|
||||||
|
|
||||||
return dialog
|
return dialog
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onViewCreated(view: View) {
|
fun onViewCreated() {
|
||||||
binding!!.login.setMode(ActionProcessButton.Mode.ENDLESS)
|
binding.login.setMode(ActionProcessButton.Mode.ENDLESS)
|
||||||
binding!!.login.setOnClickListener { checkLogin() }
|
binding.login.setOnClickListener { checkLogin() }
|
||||||
|
|
||||||
setCredentialsOnView()
|
setCredentialsOnView()
|
||||||
|
|
||||||
binding!!.twoFactorCheck.setOnCheckedChangeListener { _, isChecked ->
|
binding.twoFactorCheck.setOnCheckedChangeListener { _, isChecked ->
|
||||||
binding!!.twoFactorHolder.isVisible = isChecked
|
binding.twoFactorHolder.isVisible = isChecked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setCredentialsOnView() {
|
private fun setCredentialsOnView() {
|
||||||
binding?.username?.setText(service.getUsername())
|
binding.username.setText(service.getUsername())
|
||||||
binding?.password?.setText(service.getPassword())
|
binding.password.setText(service.getPassword())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkLogin() {
|
private fun checkLogin() {
|
||||||
binding?.apply {
|
with(binding) {
|
||||||
if (username.text.isNullOrBlank() || password.text.isNullOrBlank() || (twoFactorCheck.isChecked && twoFactorEdit.text.isNullOrBlank())) {
|
if (username.text.isNullOrBlank() || password.text.isNullOrBlank() || (twoFactorCheck.isChecked && twoFactorEdit.text.isNullOrBlank())) {
|
||||||
errorResult()
|
errorResult()
|
||||||
root.context.toast(R.string.fields_cannot_be_blank)
|
root.context.toast(R.string.fields_cannot_be_blank)
|
||||||
@@ -110,7 +109,7 @@ class MangadexLoginDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun errorResult() {
|
private fun errorResult() {
|
||||||
binding?.apply {
|
with(binding) {
|
||||||
dialog?.setCancelable(true)
|
dialog?.setCancelable(true)
|
||||||
dialog?.setCanceledOnTouchOutside(true)
|
dialog?.setCanceledOnTouchOutside(true)
|
||||||
login.progress = -1
|
login.progress = -1
|
||||||
@@ -127,7 +126,6 @@ class MangadexLoginDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
|||||||
|
|
||||||
private fun onDialogClosed() {
|
private fun onDialogClosed() {
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
binding = null
|
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
(activity as? Listener)?.siteLoginDialogClosed(source!!)
|
(activity as? Listener)?.siteLoginDialogClosed(source!!)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z" />
|
||||||
|
</vector>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user