Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
2769e27a2a | |||
76c795d0d0 | |||
b20bced3ca | |||
4f2da9a78f | |||
8e0ba3650b | |||
76f6fe4601 | |||
ca1373f36b | |||
fc6c2e083d | |||
c0789cd6ba | |||
af47103707 | |||
c466baaa25 | |||
670294a427 | |||
21ddae6a86 | |||
9f260c3513 | |||
b55d394a1f | |||
5e2e177aa9 | |||
86e59977de | |||
66baf01e43 | |||
7a33e198dc | |||
4b493ebbaf | |||
565e8cf00b | |||
738a3999b4 | |||
8bedc8f456 | |||
d9000f6fd1 | |||
e90b0aaf8b | |||
fe7c7e72f5 | |||
9ba11a585f | |||
9920ff617b | |||
3f1355c413 | |||
4929e66ecc | |||
02e370c2d8 | |||
4c31e3fc5f | |||
15f49b39b8 | |||
4c8665c9f0 | |||
ba67781431 | |||
4ef25c75b7 | |||
3aafc671f8 | |||
967df6f7a3 | |||
64bdfabbd8 | |||
c8c65ab7b1 | |||
19cd28b66b | |||
4a136ef2aa | |||
9e7a53cb90 | |||
19a7f37efa | |||
c3084ac43a | |||
159146e197 | |||
67ddf4a5b8 | |||
4b9b53a9b8 | |||
e6f025a9fb | |||
aa607e0ecb | |||
65b32ddeb2 | |||
5e9bdc2690 | |||
9ce994168a | |||
748a720199 | |||
8db34eb3dd | |||
b657bba96e | |||
dbaac69fad | |||
b6a1e89535 | |||
cce919750a | |||
9376b223bb | |||
6f047fb5aa | |||
3e6b0117fd | |||
421dfb4a2d | |||
abaca6e676 | |||
8bab1d9798 | |||
13d31669ac | |||
c1dfdeb500 | |||
dda7e677a5 | |||
b1fb401f63 | |||
885ace111e | |||
885552b792 | |||
4f02872a84 | |||
ecec1bd102 | |||
0c07e05a2b | |||
060f0682f4 | |||
88032e11df | |||
493c8b0943 | |||
af2ef0621a | |||
095461e31b | |||
3ddd1033c3 | |||
912687ac78 | |||
40a9595012 | |||
12ff37d052 | |||
4857073f30 | |||
cdbefd9191 | |||
2e9d89574d | |||
569c99496b | |||
ea3b8767de | |||
8e8c30c1eb | |||
d921ba81c8 | |||
ad9f646102 | |||
2ef277bcef | |||
9e396e1624 | |||
9708d84e60 | |||
4efc195548 | |||
0d15cbe334 | |||
e381d9fc8e | |||
85ed7a7457 |
4
.github/ISSUE_TEMPLATE.md
vendored
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated to the latest version of the app (stable is v0.10.0)
|
- I have updated to the latest version of the app (stable is v0.10.5)
|
||||||
- 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/inorichi/tachiyomi-extensions
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ I acknowledge that:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Device information
|
## Device information
|
||||||
* Tachiyomi version: ?
|
* Tachiyomi version: ?
|
||||||
* Android version: ?
|
* Android version: ?
|
||||||
* Device: ?
|
* Device: ?
|
||||||
|
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -9,7 +9,7 @@ labels: "bug"
|
|||||||
|
|
||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated to the latest version of the app (stable is v0.10.0)
|
- I have updated to the latest version of the app (stable is v0.10.5)
|
||||||
- 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/inorichi/tachiyomi-extensions
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ I acknowledge that:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Device information
|
## Device information
|
||||||
* Tachiyomi version: ?
|
* Tachiyomi version: ?
|
||||||
* Android version: ?
|
* Android version: ?
|
||||||
* Device: ?
|
* Device: ?
|
||||||
@ -32,5 +32,5 @@ This should happen.
|
|||||||
### Actual behavior
|
### Actual behavior
|
||||||
This happened instead.
|
This happened instead.
|
||||||
|
|
||||||
### Other details
|
## Other details
|
||||||
Additional details and attachments.
|
Additional details and attachments.
|
||||||
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Tachiyomi help website
|
||||||
|
url: https://tachiyomi.org/help/
|
||||||
|
about: Common questions are answered here.
|
||||||
|
- name: Tachiyomi extensions GitHub repository
|
||||||
|
url: https://github.com/inorichi/tachiyomi-extensions
|
||||||
|
about: Issues about an extension/source/catalogue should be opened here instead.
|
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -9,7 +9,7 @@ labels: "feature"
|
|||||||
|
|
||||||
I acknowledge that:
|
I acknowledge that:
|
||||||
|
|
||||||
- I have updated to the latest version of the app (stable is v0.10.0)
|
- I have updated to the latest version of the app (stable is v0.10.5)
|
||||||
- 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/inorichi/tachiyomi-extensions
|
||||||
|
|
||||||
@ -17,8 +17,8 @@ I acknowledge that:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Why/User Benefit/User Problem
|
## Why/User Benefit/User Problem
|
||||||
(explain why this feature should be added)
|
(explain why this feature should be added)
|
||||||
|
|
||||||
### What/Requirements
|
## What/Requirements
|
||||||
(explain how this feature would behave)
|
(explain how this feature would behave)
|
||||||
|
4
.github/ISSUE_TEMPLATE/source_issue.md
vendored
@ -2,7 +2,7 @@
|
|||||||
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/inorichi/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/inorichi/tachiyomi-extensions"
|
||||||
labels: "catalog"
|
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/inorichi/tachiyomi-extensions
|
||||||
|
BIN
.github/readme-images/app-icon.png
vendored
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
11
.github/workflows/gradle-wrapper-validation.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
name: Validate Gradle Wrapper
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validation:
|
||||||
|
name: Validation
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: gradle/wrapper-validation-action@v1
|
3
.github/workflows/issue_closer.yml
vendored
@ -1,5 +1,6 @@
|
|||||||
name: Issue closer
|
name: Issue closer
|
||||||
on: [issues]
|
on: [issues]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
autoclose:
|
autoclose:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -31,4 +32,4 @@ jobs:
|
|||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
type: body
|
type: body
|
||||||
regex: ".*\\* (Tachiyomi version|Android version|Device): \\?.*"
|
regex: ".*\\* (Tachiyomi version|Android version|Device): \\?.*"
|
||||||
message: "@${issue.user.login} this issue was automatically closed because the requested information was not filled out."
|
message: "@${issue.user.login} this issue was automatically closed because the requested information was not filled out."
|
||||||
|
18
.github/workflows/pr_build_check.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
name: Pull request build check
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone repo
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK 1.8
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 1.8
|
||||||
|
- name: Install NDK
|
||||||
|
run: sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;21.0.6113669"
|
||||||
|
- name: Build project
|
||||||
|
run: ./gradlew assembleDebug
|
@ -1,6 +1,6 @@
|
|||||||
| Build | Stable | Weekly Preview | Contribute | Support Server |
|
| Build | Stable | Weekly Preview | Contribute | Support Server |
|
||||||
|-------|----------|---------|------------|---------|
|
|-------|----------|---------|------------|---------|
|
||||||
| [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) | [](http://tachiyomi.kanade.eu/latest) | [](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [](https://discord.gg/tachiyomi) |
|
| [](https://travis-ci.org/inorichi/tachiyomi) | [](https://github.com/inorichi/tachiyomi/releases) | [](https://github.com/tachiyomiorg/android-app-preview/releases) | [](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [](https://discord.gg/tachiyomi) |
|
||||||
|
|
||||||
|
|
||||||
# Tachiyomi
|
# Tachiyomi
|
||||||
@ -23,7 +23,7 @@ Features include:
|
|||||||
## Download
|
## Download
|
||||||
Get the app from our [releases page](https://github.com/inorichi/tachiyomi/releases).
|
Get the app from our [releases page](https://github.com/inorichi/tachiyomi/releases).
|
||||||
|
|
||||||
If you want to try new features before they get to the stable release, you can download the preview version [here](http://tachiyomi.kanade.eu/latest).
|
If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/tachiyomiorg/android-app-preview/releases).
|
||||||
|
|
||||||
## Issues, Feature Requests and Contributing
|
## Issues, Feature Requests and Contributing
|
||||||
|
|
||||||
|
@ -40,8 +40,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 46
|
versionCode 51
|
||||||
versionName "0.10.0"
|
versionName "0.10.5"
|
||||||
|
|
||||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||||
@ -129,32 +129,32 @@ dependencies {
|
|||||||
|
|
||||||
// AndroidX libraries
|
// AndroidX libraries
|
||||||
implementation 'androidx.annotation:annotation:1.1.0'
|
implementation 'androidx.annotation:annotation:1.1.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.0-alpha01'
|
implementation 'androidx.appcompat:appcompat:1.3.0-alpha02'
|
||||||
implementation 'androidx.biometric:biometric:1.0.1'
|
implementation 'androidx.biometric:biometric:1.1.0-alpha02'
|
||||||
implementation 'androidx.browser:browser:1.2.0'
|
implementation 'androidx.browser:browser:1.2.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
|
||||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
||||||
|
implementation 'androidx.core:core-ktx:1.4.0-alpha01'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
|
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||||
implementation 'androidx.webkit:webkit:1.3.0-rc01'
|
|
||||||
|
|
||||||
final lifecycle_version = '2.3.0-alpha06'
|
final lifecycle_version = '2.3.0-alpha07'
|
||||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||||
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||||
|
|
||||||
// Job scheduling
|
// Job scheduling
|
||||||
final work_version = '2.4.0'
|
final work_version = '2.5.0-alpha01'
|
||||||
implementation "androidx.work:work-runtime:$work_version"
|
implementation "androidx.work:work-runtime:$work_version"
|
||||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||||
|
|
||||||
// UI library
|
// UI library
|
||||||
implementation 'com.google.android.material:material:1.3.0-alpha02'
|
implementation 'com.google.android.material:material:1.3.0-alpha02'
|
||||||
|
|
||||||
standardImplementation 'com.google.firebase:firebase-core:17.4.4'
|
standardImplementation 'com.google.firebase:firebase-core:17.5.0'
|
||||||
|
|
||||||
// ReactiveX
|
// ReactiveX
|
||||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||||
@ -163,14 +163,14 @@ dependencies {
|
|||||||
implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
|
implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
|
||||||
|
|
||||||
// Network client
|
// Network client
|
||||||
final okhttp_version = '4.8.0'
|
final okhttp_version = '4.9.0'
|
||||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
|
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
|
||||||
implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version"
|
implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version"
|
||||||
implementation 'com.squareup.okio:okio:2.7.0'
|
implementation 'com.squareup.okio:okio:2.8.0'
|
||||||
|
|
||||||
// TLS 1.3 support for Android < 10
|
// TLS 1.3 support for Android < 10
|
||||||
implementation 'org.conscrypt:conscrypt-android:2.4.0'
|
implementation 'org.conscrypt:conscrypt-android:2.5.1'
|
||||||
|
|
||||||
// REST
|
// REST
|
||||||
final retrofit_version = '2.9.0'
|
final retrofit_version = '2.9.0'
|
||||||
@ -200,7 +200,7 @@ dependencies {
|
|||||||
implementation 'io.requery:sqlite-android:3.32.2'
|
implementation 'io.requery:sqlite-android:3.32.2'
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
implementation 'com.github.tfcporciuncula:flow-preferences:1.1.1'
|
implementation 'com.github.tfcporciuncula:flow-preferences:1.3.1'
|
||||||
|
|
||||||
// Model View Presenter
|
// Model View Presenter
|
||||||
final nucleus_version = '3.0.0'
|
final nucleus_version = '3.0.0'
|
||||||
@ -260,13 +260,12 @@ dependencies {
|
|||||||
implementation "io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbinding_version"
|
implementation "io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbinding_version"
|
||||||
|
|
||||||
// Licenses
|
// Licenses
|
||||||
final aboutlibraries_version = '8.3.0'
|
// NOTE: REMEMBER TO UPDATE GRADLE PLUGIN
|
||||||
implementation "com.mikepenz:aboutlibraries-core:$aboutlibraries_version"
|
implementation 'com.mikepenz:aboutlibraries:8.3.0'
|
||||||
implementation "com.mikepenz:aboutlibraries:$aboutlibraries_version"
|
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
testImplementation 'junit:junit:4.13'
|
testImplementation 'junit:junit:4.13'
|
||||||
testImplementation 'org.assertj:assertj-core:3.12.2'
|
testImplementation 'org.assertj:assertj-core:3.16.1'
|
||||||
testImplementation 'org.mockito:mockito-core:1.10.19'
|
testImplementation 'org.mockito:mockito-core:1.10.19'
|
||||||
|
|
||||||
final robolectric_version = '3.1.4'
|
final robolectric_version = '3.1.4'
|
||||||
@ -274,28 +273,20 @@ dependencies {
|
|||||||
testImplementation "org.robolectric:shadows-multidex:$robolectric_version"
|
testImplementation "org.robolectric:shadows-multidex:$robolectric_version"
|
||||||
testImplementation "org.robolectric:shadows-play-services:$robolectric_version"
|
testImplementation "org.robolectric:shadows-play-services:$robolectric_version"
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
final coroutines_version = '1.3.9'
|
||||||
|
|
||||||
// Do not update until we bump to Kotlin 1.4, see https://github.com/Kotlin/kotlinx.coroutines/issues/2049
|
|
||||||
final coroutines_version = '1.3.6'
|
|
||||||
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"
|
||||||
|
|
||||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||||
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
|
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
|
||||||
|
|
||||||
// Debug tool; see https://fbflipper.com/
|
|
||||||
// debugImplementation 'com.facebook.flipper:flipper:0.50.0'
|
|
||||||
// debugImplementation 'com.facebook.soloader:soloader:0.9.0'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.72'
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$BuildPluginsVersion.KOTLIN"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,7 +306,7 @@ task copyResources(type: Copy) {
|
|||||||
include '**/*'
|
include '**/*'
|
||||||
}
|
}
|
||||||
|
|
||||||
preBuild.dependsOn(ktlintFormat, copyResources)
|
preBuild.dependsOn(formatKotlin, copyResources)
|
||||||
|
|
||||||
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
|
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
|
||||||
apply plugin: 'com.google.gms.google-services'
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
15
app/proguard-rules.pro
vendored
@ -23,21 +23,6 @@
|
|||||||
<init>();
|
<init>();
|
||||||
}
|
}
|
||||||
|
|
||||||
# OkHttp
|
|
||||||
-dontwarn okhttp3.**
|
|
||||||
-dontwarn okio.**
|
|
||||||
-dontwarn javax.annotation.**
|
|
||||||
-dontwarn retrofit2.Platform$Java8
|
|
||||||
|
|
||||||
# Glide specific rules #
|
|
||||||
# https://github.com/bumptech/glide
|
|
||||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
|
||||||
-keep public class * extends com.bumptech.glide.AppGlideModule
|
|
||||||
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
|
||||||
**[] $VALUES;
|
|
||||||
public *;
|
|
||||||
}
|
|
||||||
|
|
||||||
# RxJava 1.1.0
|
# RxJava 1.1.0
|
||||||
-dontwarn sun.misc.**
|
-dontwarn sun.misc.**
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 13 KiB |
@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.notification.Notifications
|
|||||||
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.ui.security.SecureActivityDelegate
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import java.security.Security
|
|
||||||
import org.acra.ACRA
|
import org.acra.ACRA
|
||||||
import org.acra.annotation.AcraCore
|
import org.acra.annotation.AcraCore
|
||||||
import org.acra.annotation.AcraHttpSender
|
import org.acra.annotation.AcraHttpSender
|
||||||
@ -24,6 +23,7 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.InjektScope
|
import uy.kohesive.injekt.api.InjektScope
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
||||||
|
import java.security.Security
|
||||||
|
|
||||||
@AcraCore(
|
@AcraCore(
|
||||||
buildConfigClass = BuildConfig::class,
|
buildConfigClass = BuildConfig::class,
|
||||||
|
5
app/src/main/java/eu/kanade/tachiyomi/annoations/Nsfw.kt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package eu.kanade.tachiyomi.annoations
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class Nsfw
|
@ -8,9 +8,9 @@ import androidx.work.WorkManager
|
|||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
|
class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
Worker(context, workerParams) {
|
Worker(context, workerParams) {
|
||||||
@ -36,8 +36,10 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
val interval = prefInterval ?: preferences.backupInterval().get()
|
val interval = prefInterval ?: preferences.backupInterval().get()
|
||||||
if (interval > 0) {
|
if (interval > 0) {
|
||||||
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
|
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
|
||||||
interval.toLong(), TimeUnit.HOURS,
|
interval.toLong(),
|
||||||
10, TimeUnit.MINUTES
|
TimeUnit.HOURS,
|
||||||
|
10,
|
||||||
|
TimeUnit.MINUTES
|
||||||
)
|
)
|
||||||
.addTag(TAG)
|
.addTag(TAG)
|
||||||
.build()
|
.build()
|
||||||
|
@ -50,10 +50,10 @@ import eu.kanade.tachiyomi.source.LocalSource
|
|||||||
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.util.chapter.syncChaptersWithSource
|
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||||
import kotlin.math.max
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|||||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
internal class BackupNotifier(private val context: Context) {
|
internal class BackupNotifier(private val context: Context) {
|
||||||
|
|
||||||
|
@ -32,12 +32,9 @@ import eu.kanade.tachiyomi.data.database.models.TrackImpl
|
|||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
|
||||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
import java.io.File
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -45,6 +42,10 @@ import kotlinx.coroutines.launch
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores backup from a JSON file.
|
* Restores backup from a JSON file.
|
||||||
@ -398,7 +399,12 @@ class BackupRestoreService : Service() {
|
|||||||
return backupManager.restoreChapterFetchObservable(source, manga, chapters)
|
return backupManager.restoreChapterFetchObservable(source, manga, chapters)
|
||||||
// If there's any error, return empty update and continue.
|
// If there's any error, return empty update and continue.
|
||||||
.onErrorReturn {
|
.onErrorReturn {
|
||||||
errors.add(Date() to "${manga.title} - ${it.message}")
|
val errorMessage = if (it is NoChaptersException) {
|
||||||
|
getString(R.string.no_chapters_error)
|
||||||
|
} else {
|
||||||
|
it.message
|
||||||
|
}
|
||||||
|
errors.add(Date() to "${manga.title} - $errorMessage")
|
||||||
Pair(emptyList(), emptyList())
|
Pair(emptyList(), emptyList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,16 +7,22 @@ import com.google.gson.JsonParser
|
|||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
object BackupRestoreValidator {
|
object BackupRestoreValidator {
|
||||||
|
|
||||||
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
|
private val trackManager: TrackManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for critical backup file data.
|
* Checks for critical backup file data.
|
||||||
*
|
*
|
||||||
* @throws Exception if version or manga cannot be found.
|
* @throws Exception if version or manga cannot be found.
|
||||||
* @return List of required sources.
|
* @return List of missing sources or missing trackers.
|
||||||
*/
|
*/
|
||||||
fun validate(context: Context, uri: Uri): Map<Long, String> {
|
fun validate(context: Context, uri: Uri): Results {
|
||||||
val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader())
|
val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader())
|
||||||
val json = JsonParser.parseReader(reader).asJsonObject
|
val json = JsonParser.parseReader(reader).asJsonObject
|
||||||
|
|
||||||
@ -26,11 +32,29 @@ object BackupRestoreValidator {
|
|||||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_data))
|
throw Exception(context.getString(R.string.invalid_backup_file_missing_data))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mangasJson.asJsonArray.size() == 0) {
|
val mangas = mangasJson.asJsonArray
|
||||||
|
if (mangas.size() == 0) {
|
||||||
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSourceMapping(json)
|
val sources = getSourceMapping(json)
|
||||||
|
val missingSources = sources
|
||||||
|
.filter { sourceManager.get(it.key) == null }
|
||||||
|
.values
|
||||||
|
.sorted()
|
||||||
|
|
||||||
|
val trackers = mangas
|
||||||
|
.filter { it.asJsonObject.has("track") }
|
||||||
|
.flatMap { it.asJsonObject["track"].asJsonArray }
|
||||||
|
.map { it.asJsonObject["s"].asInt }
|
||||||
|
.distinct()
|
||||||
|
val missingTrackers = trackers
|
||||||
|
.mapNotNull { trackManager.getService(it) }
|
||||||
|
.filter { !it.isLogged }
|
||||||
|
.map { it.name }
|
||||||
|
.sorted()
|
||||||
|
|
||||||
|
return Results(missingSources, missingTrackers)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSourceMapping(json: JsonObject): Map<Long, String> {
|
fun getSourceMapping(json: JsonObject): Map<Long, String> {
|
||||||
@ -43,4 +67,6 @@ object BackupRestoreValidator {
|
|||||||
}
|
}
|
||||||
.toMap()
|
.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,13 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
|||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to create chapter cache
|
* Class used to create chapter cache
|
||||||
|
@ -7,10 +7,10 @@ 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.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache where we dump the downloads directory from the filesystem. This class is needed because
|
* Cache where we dump the downloads directory from the filesystem. This class is needed because
|
||||||
|
@ -8,6 +8,7 @@ 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.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
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.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
@ -24,10 +25,8 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
*/
|
*/
|
||||||
class DownloadManager(private val context: Context) {
|
class DownloadManager(private val context: Context) {
|
||||||
|
|
||||||
/**
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
* The sources manager.
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
*/
|
|
||||||
private val sourceManager by injectLazy<SourceManager>()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads provider, used to retrieve the folders where the chapters are or should be stored.
|
* Downloads provider, used to retrieve the folders where the chapters are or should be stored.
|
||||||
@ -199,14 +198,19 @@ class DownloadManager(private val context: Context) {
|
|||||||
* @param manga the manga of the chapters.
|
* @param manga the manga of the chapters.
|
||||||
* @param source the source of the chapters.
|
* @param source the source of the chapters.
|
||||||
*/
|
*/
|
||||||
fun deleteChapters(chapters: List<Chapter>, manga: Manga, source: Source) {
|
fun deleteChapters(chapters: List<Chapter>, manga: Manga, source: Source): List<Chapter> {
|
||||||
queue.remove(chapters)
|
val filteredChapters = getChaptersToDelete(chapters)
|
||||||
val chapterDirs = provider.findChapterDirs(chapters, manga, source)
|
|
||||||
|
queue.remove(filteredChapters)
|
||||||
|
|
||||||
|
val chapterDirs = provider.findChapterDirs(filteredChapters, manga, source)
|
||||||
chapterDirs.forEach { it.delete() }
|
chapterDirs.forEach { it.delete() }
|
||||||
cache.removeChapters(chapters, manga)
|
cache.removeChapters(filteredChapters, manga)
|
||||||
if (cache.getDownloadCount(manga) == 0) { // Delete manga directory if empty
|
if (cache.getDownloadCount(manga) == 0) { // Delete manga directory if empty
|
||||||
chapterDirs.firstOrNull()?.parentFile?.delete()
|
chapterDirs.firstOrNull()?.parentFile?.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return filteredChapters
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -228,7 +232,7 @@ class DownloadManager(private val context: Context) {
|
|||||||
* @param manga the manga of the chapters.
|
* @param manga the manga of the chapters.
|
||||||
*/
|
*/
|
||||||
fun enqueueDeleteChapters(chapters: List<Chapter>, manga: Manga) {
|
fun enqueueDeleteChapters(chapters: List<Chapter>, manga: Manga) {
|
||||||
pendingDeleter.addChapters(chapters, manga)
|
pendingDeleter.addChapters(getChaptersToDelete(chapters), manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -267,4 +271,12 @@ class DownloadManager(private val context: Context) {
|
|||||||
Timber.e("Could not rename downloaded chapter: %s.", oldNames.joinToString())
|
Timber.e("Could not rename downloaded chapter: %s.", oldNames.joinToString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getChaptersToDelete(chapters: List<Chapter>): List<Chapter> {
|
||||||
|
return if (!preferences.removeBookmarkedChapters()) {
|
||||||
|
chapters.filterNot { it.bookmark }
|
||||||
|
} else {
|
||||||
|
chapters
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|||||||
import eu.kanade.tachiyomi.util.lang.chop
|
import eu.kanade.tachiyomi.util.lang.chop
|
||||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||||
import java.util.regex.Pattern
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DownloadNotifier is used to show notifications when downloading one or multiple chapters.
|
* DownloadNotifier is used to show notifications when downloading one or multiple chapters.
|
||||||
@ -25,12 +24,22 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
private val progressNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
|
private val progressNotificationBuilder by lazy {
|
||||||
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
|
||||||
|
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val completeNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_COMPLETE) {
|
private val completeNotificationBuilder by lazy {
|
||||||
setAutoCancel(false)
|
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_COMPLETE) {
|
||||||
|
setAutoCancel(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val errorNotificationBuilder by lazy {
|
||||||
|
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_ERROR) {
|
||||||
|
setAutoCancel(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,7 +62,7 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
*
|
*
|
||||||
* @param id the id of the notification.
|
* @param id the id of the notification.
|
||||||
*/
|
*/
|
||||||
private fun NotificationCompat.Builder.show(id: Int = Notifications.ID_DOWNLOAD_CHAPTER) {
|
private fun NotificationCompat.Builder.show(id: Int) {
|
||||||
context.notificationManager.notify(id, build())
|
context.notificationManager.notify(id, build())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,8 +79,8 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
* Dismiss the downloader's notification. Downloader error notifications use a different id, so
|
* Dismiss the downloader's notification. Downloader error notifications use a different id, so
|
||||||
* those can only be dismissed by the user.
|
* those can only be dismissed by the user.
|
||||||
*/
|
*/
|
||||||
fun dismiss() {
|
fun dismissProgress() {
|
||||||
context.notificationManager.cancel(Notifications.ID_DOWNLOAD_CHAPTER)
|
context.notificationManager.cancel(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,7 +107,9 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val downloadingProgressText = context.getString(
|
val downloadingProgressText = context.getString(
|
||||||
R.string.chapter_downloading_progress, download.downloadedImages, download.pages!!.size
|
R.string.chapter_downloading_progress,
|
||||||
|
download.downloadedImages,
|
||||||
|
download.pages!!.size
|
||||||
)
|
)
|
||||||
|
|
||||||
if (preferences.hideNotificationContent()) {
|
if (preferences.hideNotificationContent()) {
|
||||||
@ -112,14 +123,15 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setProgress(download.pages!!.size, download.downloadedImages, false)
|
setProgress(download.pages!!.size, download.downloadedImages, false)
|
||||||
|
|
||||||
|
show(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
|
||||||
}
|
}
|
||||||
progressNotificationBuilder.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show notification when download is paused.
|
* Show notification when download is paused.
|
||||||
*/
|
*/
|
||||||
fun onDownloadPaused() {
|
fun onPaused() {
|
||||||
with(progressNotificationBuilder) {
|
with(progressNotificationBuilder) {
|
||||||
setContentTitle(context.getString(R.string.chapter_paused))
|
setContentTitle(context.getString(R.string.chapter_paused))
|
||||||
setContentText(context.getString(R.string.download_notifier_download_paused))
|
setContentText(context.getString(R.string.download_notifier_download_paused))
|
||||||
@ -141,8 +153,9 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
context.getString(R.string.action_cancel_all),
|
context.getString(R.string.action_cancel_all),
|
||||||
NotificationReceiver.clearDownloadsPendingBroadcast(context)
|
NotificationReceiver.clearDownloadsPendingBroadcast(context)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
show(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
|
||||||
}
|
}
|
||||||
progressNotificationBuilder.show()
|
|
||||||
|
|
||||||
// Reset initial values
|
// Reset initial values
|
||||||
isDownloading = false
|
isDownloading = false
|
||||||
@ -151,18 +164,21 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
/**
|
/**
|
||||||
* This function shows a notification to inform download tasks are done.
|
* This function shows a notification to inform download tasks are done.
|
||||||
*/
|
*/
|
||||||
fun downloadFinished() {
|
fun onComplete() {
|
||||||
// Create notification
|
if (!errorThrown) {
|
||||||
with(completeNotificationBuilder) {
|
// Create notification
|
||||||
setContentTitle(context.getString(R.string.download_notifier_downloader_title))
|
with(completeNotificationBuilder) {
|
||||||
setContentText(context.getString(R.string.download_notifier_download_finish))
|
setContentTitle(context.getString(R.string.download_notifier_downloader_title))
|
||||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
setContentText(context.getString(R.string.download_notifier_download_finish))
|
||||||
clearActions()
|
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
setAutoCancel(true)
|
clearActions()
|
||||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
setAutoCancel(true)
|
||||||
setProgress(0, 0, false)
|
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||||
|
setProgress(0, 0, false)
|
||||||
|
|
||||||
|
show(Notifications.ID_DOWNLOAD_CHAPTER_COMPLETE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
completeNotificationBuilder.show(Notifications.ID_DOWNLOAD_CHAPTER_COMPLETE)
|
|
||||||
|
|
||||||
// Reset states to default
|
// Reset states to default
|
||||||
errorThrown = false
|
errorThrown = false
|
||||||
@ -175,7 +191,7 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
* @param reason the text to show.
|
* @param reason the text to show.
|
||||||
*/
|
*/
|
||||||
fun onWarning(reason: String) {
|
fun onWarning(reason: String) {
|
||||||
with(completeNotificationBuilder) {
|
with(errorNotificationBuilder) {
|
||||||
setContentTitle(context.getString(R.string.download_notifier_downloader_title))
|
setContentTitle(context.getString(R.string.download_notifier_downloader_title))
|
||||||
setContentText(reason)
|
setContentText(reason)
|
||||||
setSmallIcon(android.R.drawable.stat_sys_warning)
|
setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||||
@ -183,8 +199,9 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
clearActions()
|
clearActions()
|
||||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||||
setProgress(0, 0, false)
|
setProgress(0, 0, false)
|
||||||
|
|
||||||
|
show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR)
|
||||||
}
|
}
|
||||||
completeNotificationBuilder.show()
|
|
||||||
|
|
||||||
// Reset download information
|
// Reset download information
|
||||||
isDownloading = false
|
isDownloading = false
|
||||||
@ -199,7 +216,7 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
*/
|
*/
|
||||||
fun onError(error: String? = null, chapter: String? = null) {
|
fun onError(error: String? = null, chapter: String? = null) {
|
||||||
// Create notification
|
// Create notification
|
||||||
with(completeNotificationBuilder) {
|
with(errorNotificationBuilder) {
|
||||||
setContentTitle(
|
setContentTitle(
|
||||||
chapter
|
chapter
|
||||||
?: context.getString(R.string.download_notifier_downloader_title)
|
?: context.getString(R.string.download_notifier_downloader_title)
|
||||||
@ -210,8 +227,9 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
setAutoCancel(false)
|
setAutoCancel(false)
|
||||||
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
|
||||||
setProgress(0, 0, false)
|
setProgress(0, 0, false)
|
||||||
|
|
||||||
|
show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR)
|
||||||
}
|
}
|
||||||
completeNotificationBuilder.show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR)
|
|
||||||
|
|
||||||
// Reset download information
|
// Reset download information
|
||||||
errorThrown = true
|
errorThrown = true
|
||||||
|
@ -83,7 +83,7 @@ class DownloadService : Service() {
|
|||||||
*/
|
*/
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
startForeground(Notifications.ID_DOWNLOAD_CHAPTER, getPlaceholderNotification())
|
startForeground(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS, getPlaceholderNotification())
|
||||||
wakeLock = acquireWakeLock(javaClass.name)
|
wakeLock = acquireWakeLock(javaClass.name)
|
||||||
runningRelay.call(true)
|
runningRelay.call(true)
|
||||||
subscriptions = CompositeSubscription()
|
subscriptions = CompositeSubscription()
|
||||||
|
@ -22,7 +22,6 @@ import eu.kanade.tachiyomi.util.lang.plusAssign
|
|||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||||
import java.io.File
|
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -31,6 +30,7 @@ import rx.schedulers.Schedulers
|
|||||||
import rx.subscriptions.CompositeSubscription
|
import rx.subscriptions.CompositeSubscription
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is the one in charge of downloading chapters.
|
* This class is the one in charge of downloading chapters.
|
||||||
@ -137,9 +137,10 @@ class Downloader(
|
|||||||
} else {
|
} else {
|
||||||
if (notifier.paused) {
|
if (notifier.paused) {
|
||||||
notifier.paused = false
|
notifier.paused = false
|
||||||
notifier.onDownloadPaused()
|
notifier.onPaused()
|
||||||
} else {
|
} else {
|
||||||
notifier.downloadFinished()
|
notifier.dismissProgress()
|
||||||
|
notifier.onComplete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,7 +171,7 @@ class Downloader(
|
|||||||
.forEach { it.status = Download.NOT_DOWNLOADED }
|
.forEach { it.status = Download.NOT_DOWNLOADED }
|
||||||
}
|
}
|
||||||
queue.clear()
|
queue.clear()
|
||||||
notifier.dismiss()
|
notifier.dismissProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -266,15 +267,16 @@ class Downloader(
|
|||||||
* @param download the chapter to be downloaded.
|
* @param download the chapter to be downloaded.
|
||||||
*/
|
*/
|
||||||
private fun downloadChapter(download: Download): Observable<Download> = Observable.defer {
|
private fun downloadChapter(download: Download): Observable<Download> = Observable.defer {
|
||||||
val chapterDirname = provider.getChapterDirName(download.chapter)
|
|
||||||
val mangaDir = provider.getMangaDir(download.manga, download.source)
|
val mangaDir = provider.getMangaDir(download.manga, download.source)
|
||||||
|
|
||||||
if (DiskUtil.getAvailableStorageSpace(mangaDir) < MIN_DISK_SPACE) {
|
val availSpace = DiskUtil.getAvailableStorageSpace(mangaDir)
|
||||||
|
if (availSpace != -1L && availSpace < MIN_DISK_SPACE) {
|
||||||
download.status = Download.ERROR
|
download.status = Download.ERROR
|
||||||
notifier.onError(context.getString(R.string.download_insufficient_space), download.chapter.name)
|
notifier.onError(context.getString(R.string.download_insufficient_space), download.chapter.name)
|
||||||
return@defer Observable.just(download)
|
return@defer Observable.just(download)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val chapterDirname = provider.getChapterDirName(download.chapter)
|
||||||
val tmpDir = mangaDir.createDirectory(chapterDirname + TMP_DIR_SUFFIX)
|
val tmpDir = mangaDir.createDirectory(chapterDirname + TMP_DIR_SUFFIX)
|
||||||
|
|
||||||
val pageListObservable = if (download.pages == null) {
|
val pageListObservable = if (download.pages == null) {
|
||||||
|
@ -5,9 +5,9 @@ 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.download.DownloadStore
|
import eu.kanade.tachiyomi.data.download.DownloadStore
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
class DownloadQueue(
|
class DownloadQueue(
|
||||||
private val store: DownloadStore,
|
private val store: DownloadStore,
|
||||||
|
@ -5,12 +5,12 @@ import android.util.Log
|
|||||||
import com.bumptech.glide.Priority
|
import com.bumptech.glide.Priority
|
||||||
import com.bumptech.glide.load.DataSource
|
import com.bumptech.glide.load.DataSource
|
||||||
import com.bumptech.glide.load.data.DataFetcher
|
import com.bumptech.glide.load.data.DataFetcher
|
||||||
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
open class FileFetcher(private val filePath: String = "") : DataFetcher<InputStream> {
|
open class FileFetcher(private val filePath: String = "") : DataFetcher<InputStream> {
|
||||||
|
|
||||||
|
@ -14,10 +14,10 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
|||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.isLocal
|
import eu.kanade.tachiyomi.util.isLocal
|
||||||
import java.io.InputStream
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for loading a cover associated with a [Manga] that can be present in our own cache.
|
* A class for loading a cover associated with a [Manga] that can be present in our own cache.
|
||||||
|
@ -14,9 +14,9 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
|||||||
import com.bumptech.glide.module.AppGlideModule
|
import com.bumptech.glide.module.AppGlideModule
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import java.io.InputStream
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to update Glide module settings
|
* Class used to update Glide module settings
|
||||||
|
@ -9,9 +9,9 @@ import androidx.work.WorkManager
|
|||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
Worker(context, workerParams) {
|
Worker(context, workerParams) {
|
||||||
@ -45,8 +45,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
|
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
|
||||||
interval.toLong(), TimeUnit.HOURS,
|
interval.toLong(),
|
||||||
10, TimeUnit.MINUTES
|
TimeUnit.HOURS,
|
||||||
|
10,
|
||||||
|
TimeUnit.MINUTES
|
||||||
)
|
)
|
||||||
.addTag(TAG)
|
.addTag(TAG)
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
|
@ -22,9 +22,9 @@ import eu.kanade.tachiyomi.util.lang.chop
|
|||||||
import eu.kanade.tachiyomi.util.system.notification
|
import eu.kanade.tachiyomi.util.system.notification
|
||||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.text.DecimalFormatSymbols
|
import java.text.DecimalFormatSymbols
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class LibraryUpdateNotifier(private val context: Context) {
|
class LibraryUpdateNotifier(private val context: Context) {
|
||||||
|
|
||||||
@ -198,18 +198,23 @@ class LibraryUpdateNotifier(private val context: Context) {
|
|||||||
|
|
||||||
// Mark chapters as read action
|
// Mark chapters as read action
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_glasses_black_24dp, context.getString(R.string.action_mark_as_read),
|
R.drawable.ic_glasses_black_24dp,
|
||||||
|
context.getString(R.string.action_mark_as_read),
|
||||||
NotificationReceiver.markAsReadPendingBroadcast(
|
NotificationReceiver.markAsReadPendingBroadcast(
|
||||||
context,
|
context,
|
||||||
manga, chapters, Notifications.ID_NEW_CHAPTERS
|
manga,
|
||||||
|
chapters,
|
||||||
|
Notifications.ID_NEW_CHAPTERS
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// View chapters action
|
// View chapters action
|
||||||
addAction(
|
addAction(
|
||||||
R.drawable.ic_book_24dp, context.getString(R.string.action_view_chapters),
|
R.drawable.ic_book_24dp,
|
||||||
|
context.getString(R.string.action_view_chapters),
|
||||||
NotificationReceiver.openChapterPendingActivity(
|
NotificationReceiver.openChapterPendingActivity(
|
||||||
context,
|
context,
|
||||||
manga, Notifications.ID_NEW_CHAPTERS
|
manga,
|
||||||
|
Notifications.ID_NEW_CHAPTERS
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import android.content.Intent
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
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
|
||||||
@ -21,20 +22,21 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
|
||||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||||
import eu.kanade.tachiyomi.util.prepUpdateCover
|
import eu.kanade.tachiyomi.util.prepUpdateCover
|
||||||
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
|
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
|
||||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
import java.io.File
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class will take care of updating the chapters of the manga from the library. It can be
|
* This class will take care of updating the chapters of the manga from the library. It can be
|
||||||
@ -268,7 +270,12 @@ class LibraryUpdateService(
|
|||||||
updateManga(manga)
|
updateManga(manga)
|
||||||
// If there's any error, return empty update and continue.
|
// If there's any error, return empty update and continue.
|
||||||
.onErrorReturn {
|
.onErrorReturn {
|
||||||
failedUpdates.add(Pair(manga, it.message))
|
val errorMessage = if (it is NoChaptersException) {
|
||||||
|
getString(R.string.no_chapters_error)
|
||||||
|
} else {
|
||||||
|
it.message
|
||||||
|
}
|
||||||
|
failedUpdates.add(Pair(manga, errorMessage))
|
||||||
Pair(emptyList(), emptyList())
|
Pair(emptyList(), emptyList())
|
||||||
}
|
}
|
||||||
// Filter out mangas without new chapters (or failed).
|
// Filter out mangas without new chapters (or failed).
|
||||||
|
@ -7,7 +7,6 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupRestoreService
|
import eu.kanade.tachiyomi.data.backup.BackupRestoreService
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
@ -26,10 +25,11 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
|
|||||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import java.io.File
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.File
|
||||||
|
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global [BroadcastReceiver] that runs on UI thread
|
* Global [BroadcastReceiver] that runs on UI thread
|
||||||
@ -56,19 +56,22 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||||||
// Launch share activity and dismiss notification
|
// Launch share activity and dismiss notification
|
||||||
ACTION_SHARE_IMAGE ->
|
ACTION_SHARE_IMAGE ->
|
||||||
shareImage(
|
shareImage(
|
||||||
context, intent.getStringExtra(EXTRA_FILE_LOCATION),
|
context,
|
||||||
|
intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||||
)
|
)
|
||||||
// Delete image from path and dismiss notification
|
// Delete image from path and dismiss notification
|
||||||
ACTION_DELETE_IMAGE ->
|
ACTION_DELETE_IMAGE ->
|
||||||
deleteImage(
|
deleteImage(
|
||||||
context, intent.getStringExtra(EXTRA_FILE_LOCATION),
|
context,
|
||||||
|
intent.getStringExtra(EXTRA_FILE_LOCATION),
|
||||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||||
)
|
)
|
||||||
// Share backup file
|
// Share backup file
|
||||||
ACTION_SHARE_BACKUP ->
|
ACTION_SHARE_BACKUP ->
|
||||||
shareBackup(
|
shareBackup(
|
||||||
context, intent.getParcelableExtra(EXTRA_URI),
|
context,
|
||||||
|
intent.getParcelableExtra(EXTRA_URI),
|
||||||
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
|
||||||
)
|
)
|
||||||
ACTION_CANCEL_RESTORE -> cancelRestore(
|
ACTION_CANCEL_RESTORE -> cancelRestore(
|
||||||
@ -80,7 +83,8 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||||||
// Open reader activity
|
// Open reader activity
|
||||||
ACTION_OPEN_CHAPTER -> {
|
ACTION_OPEN_CHAPTER -> {
|
||||||
openChapter(
|
openChapter(
|
||||||
context, intent.getLongExtra(EXTRA_MANGA_ID, -1),
|
context,
|
||||||
|
intent.getLongExtra(EXTRA_MANGA_ID, -1),
|
||||||
intent.getLongExtra(EXTRA_CHAPTER_ID, -1)
|
intent.getLongExtra(EXTRA_CHAPTER_ID, -1)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -155,7 +159,7 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||||||
* @param mangaId id of manga
|
* @param mangaId id of manga
|
||||||
* @param chapterId id of chapter
|
* @param chapterId id of chapter
|
||||||
*/
|
*/
|
||||||
internal fun openChapter(context: Context, mangaId: Long, chapterId: Long) {
|
private fun openChapter(context: Context, mangaId: Long, chapterId: Long) {
|
||||||
val db = DatabaseHelper(context)
|
val db = DatabaseHelper(context)
|
||||||
val manga = db.getManga(mangaId).executeAsBlocking()
|
val manga = db.getManga(mangaId).executeAsBlocking()
|
||||||
val chapter = db.getChapter(chapterId).executeAsBlocking()
|
val chapter = db.getChapter(chapterId).executeAsBlocking()
|
||||||
|
@ -32,10 +32,11 @@ object Notifications {
|
|||||||
*/
|
*/
|
||||||
private const val GROUP_DOWNLOADER = "group_downloader"
|
private const val GROUP_DOWNLOADER = "group_downloader"
|
||||||
const val CHANNEL_DOWNLOADER_PROGRESS = "downloader_progress_channel"
|
const val CHANNEL_DOWNLOADER_PROGRESS = "downloader_progress_channel"
|
||||||
const val ID_DOWNLOAD_CHAPTER = -201
|
const val ID_DOWNLOAD_CHAPTER_PROGRESS = -201
|
||||||
const val CHANNEL_DOWNLOADER_COMPLETE = "downloader_complete_channel"
|
const val CHANNEL_DOWNLOADER_COMPLETE = "downloader_complete_channel"
|
||||||
const val ID_DOWNLOAD_CHAPTER_ERROR = -202
|
|
||||||
const val ID_DOWNLOAD_CHAPTER_COMPLETE = -203
|
const val ID_DOWNLOAD_CHAPTER_COMPLETE = -203
|
||||||
|
const val CHANNEL_DOWNLOADER_ERROR = "downloader_error_channel"
|
||||||
|
const val ID_DOWNLOAD_CHAPTER_ERROR = -202
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification channel and ids used by the library updater.
|
* Notification channel and ids used by the library updater.
|
||||||
@ -81,46 +82,62 @@ object Notifications {
|
|||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_COMMON, context.getString(R.string.channel_common),
|
CHANNEL_COMMON,
|
||||||
|
context.getString(R.string.channel_common),
|
||||||
NotificationManager.IMPORTANCE_LOW
|
NotificationManager.IMPORTANCE_LOW
|
||||||
),
|
),
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_LIBRARY, context.getString(R.string.channel_library),
|
CHANNEL_LIBRARY,
|
||||||
|
context.getString(R.string.channel_library),
|
||||||
NotificationManager.IMPORTANCE_LOW
|
NotificationManager.IMPORTANCE_LOW
|
||||||
).apply {
|
).apply {
|
||||||
setShowBadge(false)
|
setShowBadge(false)
|
||||||
},
|
},
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_DOWNLOADER_PROGRESS, context.getString(R.string.channel_progress),
|
CHANNEL_DOWNLOADER_PROGRESS,
|
||||||
|
context.getString(R.string.channel_progress),
|
||||||
NotificationManager.IMPORTANCE_LOW
|
NotificationManager.IMPORTANCE_LOW
|
||||||
).apply {
|
).apply {
|
||||||
group = GROUP_DOWNLOADER
|
group = GROUP_DOWNLOADER
|
||||||
setShowBadge(false)
|
setShowBadge(false)
|
||||||
},
|
},
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_DOWNLOADER_COMPLETE, context.getString(R.string.channel_complete),
|
CHANNEL_DOWNLOADER_COMPLETE,
|
||||||
|
context.getString(R.string.channel_complete),
|
||||||
NotificationManager.IMPORTANCE_LOW
|
NotificationManager.IMPORTANCE_LOW
|
||||||
).apply {
|
).apply {
|
||||||
group = GROUP_DOWNLOADER
|
group = GROUP_DOWNLOADER
|
||||||
setShowBadge(false)
|
setShowBadge(false)
|
||||||
},
|
},
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_NEW_CHAPTERS, context.getString(R.string.channel_new_chapters),
|
CHANNEL_DOWNLOADER_ERROR,
|
||||||
|
context.getString(R.string.channel_errors),
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
).apply {
|
||||||
|
group = GROUP_DOWNLOADER
|
||||||
|
setShowBadge(false)
|
||||||
|
},
|
||||||
|
NotificationChannel(
|
||||||
|
CHANNEL_NEW_CHAPTERS,
|
||||||
|
context.getString(R.string.channel_new_chapters),
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
),
|
),
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_UPDATES_TO_EXTS, context.getString(R.string.channel_ext_updates),
|
CHANNEL_UPDATES_TO_EXTS,
|
||||||
|
context.getString(R.string.channel_ext_updates),
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
),
|
),
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_BACKUP_RESTORE_PROGRESS, context.getString(R.string.channel_progress),
|
CHANNEL_BACKUP_RESTORE_PROGRESS,
|
||||||
|
context.getString(R.string.channel_progress),
|
||||||
NotificationManager.IMPORTANCE_LOW
|
NotificationManager.IMPORTANCE_LOW
|
||||||
).apply {
|
).apply {
|
||||||
group = GROUP_BACKUP_RESTORE
|
group = GROUP_BACKUP_RESTORE
|
||||||
setShowBadge(false)
|
setShowBadge(false)
|
||||||
},
|
},
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_BACKUP_RESTORE_COMPLETE, context.getString(R.string.channel_complete),
|
CHANNEL_BACKUP_RESTORE_COMPLETE,
|
||||||
|
context.getString(R.string.channel_complete),
|
||||||
NotificationManager.IMPORTANCE_HIGH
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
).apply {
|
).apply {
|
||||||
group = GROUP_BACKUP_RESTORE
|
group = GROUP_BACKUP_RESTORE
|
||||||
|
@ -97,6 +97,8 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val removeAfterMarkedAsRead = "pref_remove_after_marked_as_read_key"
|
const val removeAfterMarkedAsRead = "pref_remove_after_marked_as_read_key"
|
||||||
|
|
||||||
|
const val removeBookmarkedChapters = "pref_remove_bookmarked"
|
||||||
|
|
||||||
const val libraryUpdateInterval = "pref_library_update_interval_key"
|
const val libraryUpdateInterval = "pref_library_update_interval_key"
|
||||||
|
|
||||||
const val libraryUpdateRestriction = "library_update_restriction"
|
const val libraryUpdateRestriction = "library_update_restriction"
|
||||||
@ -117,6 +119,8 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val automaticExtUpdates = "automatic_ext_updates"
|
const val automaticExtUpdates = "automatic_ext_updates"
|
||||||
|
|
||||||
|
const val allowNsfwSource = "allow_nsfw_source"
|
||||||
|
|
||||||
const val startScreen = "start_screen"
|
const val startScreen = "start_screen"
|
||||||
|
|
||||||
const val useBiometricLock = "use_biometric_lock"
|
const val useBiometricLock = "use_biometric_lock"
|
||||||
|
@ -37,4 +37,10 @@ object PreferenceValues {
|
|||||||
VERTICAL,
|
VERTICAL,
|
||||||
BOTH
|
BOTH
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class NsfwAllowance {
|
||||||
|
ALLOWED,
|
||||||
|
PARTIAL,
|
||||||
|
BLOCKED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,18 +7,19 @@ import androidx.preference.PreferenceManager
|
|||||||
import com.tfcporciuncula.flow.FlowSharedPreferences
|
import com.tfcporciuncula.flow.FlowSharedPreferences
|
||||||
import com.tfcporciuncula.flow.Preference
|
import com.tfcporciuncula.flow.Preference
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
|
||||||
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 kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||||
import kotlinx.coroutines.flow.Flow
|
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> {
|
fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> {
|
||||||
@ -187,6 +188,8 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun removeAfterMarkedAsRead() = prefs.getBoolean(Keys.removeAfterMarkedAsRead, false)
|
fun removeAfterMarkedAsRead() = prefs.getBoolean(Keys.removeAfterMarkedAsRead, false)
|
||||||
|
|
||||||
|
fun removeBookmarkedChapters() = prefs.getBoolean(Keys.removeBookmarkedChapters, false)
|
||||||
|
|
||||||
fun libraryUpdateInterval() = flowPrefs.getInt(Keys.libraryUpdateInterval, 24)
|
fun libraryUpdateInterval() = flowPrefs.getInt(Keys.libraryUpdateInterval, 24)
|
||||||
|
|
||||||
fun libraryUpdateRestriction() = prefs.getStringSet(Keys.libraryUpdateRestriction, setOf("wifi"))
|
fun libraryUpdateRestriction() = prefs.getStringSet(Keys.libraryUpdateRestriction, setOf("wifi"))
|
||||||
@ -217,6 +220,8 @@ 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 extensionUpdatesCount() = flowPrefs.getInt("ext_updates_count", 0)
|
fun extensionUpdatesCount() = flowPrefs.getInt("ext_updates_count", 0)
|
||||||
|
|
||||||
fun lastExtCheck() = flowPrefs.getLong("last_ext_check", 0)
|
fun lastExtCheck() = flowPrefs.getLong("last_ext_check", 0)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.data.preference
|
package eu.kanade.tachiyomi.data.preference
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceDataStore
|
import androidx.preference.PreferenceDataStore
|
||||||
|
|
||||||
class SharedPreferencesDataStore(private val prefs: SharedPreferences) : PreferenceDataStore() {
|
class SharedPreferencesDataStore(private val prefs: SharedPreferences) : PreferenceDataStore() {
|
||||||
@ -10,7 +11,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun putBoolean(key: String?, value: Boolean) {
|
override fun putBoolean(key: String?, value: Boolean) {
|
||||||
prefs.edit().putBoolean(key, value).apply()
|
prefs.edit {
|
||||||
|
putBoolean(key, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getInt(key: String?, defValue: Int): Int {
|
override fun getInt(key: String?, defValue: Int): Int {
|
||||||
@ -18,7 +21,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun putInt(key: String?, value: Int) {
|
override fun putInt(key: String?, value: Int) {
|
||||||
prefs.edit().putInt(key, value).apply()
|
prefs.edit {
|
||||||
|
putInt(key, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLong(key: String?, defValue: Long): Long {
|
override fun getLong(key: String?, defValue: Long): Long {
|
||||||
@ -26,7 +31,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun putLong(key: String?, value: Long) {
|
override fun putLong(key: String?, value: Long) {
|
||||||
prefs.edit().putLong(key, value).apply()
|
prefs.edit {
|
||||||
|
putLong(key, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFloat(key: String?, defValue: Float): Float {
|
override fun getFloat(key: String?, defValue: Float): Float {
|
||||||
@ -34,7 +41,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun putFloat(key: String?, value: Float) {
|
override fun putFloat(key: String?, value: Float) {
|
||||||
prefs.edit().putFloat(key, value).apply()
|
prefs.edit {
|
||||||
|
putFloat(key, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getString(key: String?, defValue: String?): String? {
|
override fun getString(key: String?, defValue: String?): String? {
|
||||||
@ -42,7 +51,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun putString(key: String?, value: String?) {
|
override fun putString(key: String?, value: String?) {
|
||||||
prefs.edit().putString(key, value).apply()
|
prefs.edit {
|
||||||
|
putString(key, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String>? {
|
override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String>? {
|
||||||
@ -50,6 +61,8 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun putStringSet(key: String?, values: MutableSet<String>?) {
|
override fun putStringSet(key: String?, values: MutableSet<String>?) {
|
||||||
prefs.edit().putStringSet(key, values).apply()
|
prefs.edit {
|
||||||
|
putStringSet(key, values)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,12 @@ import com.google.gson.JsonParser
|
|||||||
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.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import java.util.Calendar
|
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
|
|
||||||
@ -271,9 +271,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ALManga(
|
return ALManga(
|
||||||
struct["id"].asInt, struct["title"]["romaji"].asString, struct["coverImage"]["large"].asString,
|
struct["id"].asInt,
|
||||||
struct["description"].nullString.orEmpty(), struct["type"].asString, struct["status"].nullString.orEmpty(),
|
struct["title"]["romaji"].asString,
|
||||||
date, struct["chapters"].nullInt ?: 0
|
struct["coverImage"]["large"].asString,
|
||||||
|
struct["description"].nullString.orEmpty(),
|
||||||
|
struct["type"].asString,
|
||||||
|
struct["status"].nullString.orEmpty(),
|
||||||
|
date,
|
||||||
|
struct["chapters"].nullInt ?: 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.data.database.models.Track
|
|||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
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 uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
data class ALManga(
|
data class ALManga(
|
||||||
val media_id: Int,
|
val media_id: Int,
|
||||||
|
@ -12,13 +12,13 @@ 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.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import java.net.URLEncoder
|
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
class BangumiApi(private val client: OkHttpClient, interceptor: BangumiInterceptor) {
|
class BangumiApi(private val client: OkHttpClient, interceptor: BangumiInterceptor) {
|
||||||
|
|
||||||
|
@ -7,10 +7,10 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import java.text.DecimalFormat
|
|
||||||
import rx.Completable
|
import rx.Completable
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
class Kitsu(private val context: Context, id: Int) : TrackService(id) {
|
class Kitsu(private val context: Context, id: Int) : TrackService(id) {
|
||||||
|
|
||||||
|
@ -11,13 +11,6 @@ import eu.kanade.tachiyomi.network.asObservableSuccess
|
|||||||
import eu.kanade.tachiyomi.util.lang.toCalendar
|
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 java.io.BufferedReader
|
|
||||||
import java.io.InputStreamReader
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.GregorianCalendar
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.zip.GZIPInputStream
|
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@ -30,6 +23,13 @@ import org.jsoup.nodes.Document
|
|||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import org.jsoup.parser.Parser
|
import org.jsoup.parser.Parser
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.GregorianCalendar
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.zip.GZIPInputStream
|
||||||
|
|
||||||
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
|
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
|
||||||
|
|
||||||
@ -476,7 +476,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
fun copyPersonalFrom(track: Track) {
|
fun copyPersonalFrom(track: Track) {
|
||||||
num_read_chapters = track.last_chapter_read.toString()
|
num_read_chapters = track.last_chapter_read.toString()
|
||||||
val numScore = track.score.toInt()
|
val numScore = track.score.toInt()
|
||||||
if (numScore in 1..9) {
|
if (numScore == 0) {
|
||||||
|
score = ""
|
||||||
|
} else if (numScore in 1..10) {
|
||||||
score = numScore.toString()
|
score = numScore.toString()
|
||||||
}
|
}
|
||||||
status = track.status.toString()
|
status = track.status.toString()
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.updater
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
|
||||||
import eu.kanade.tachiyomi.data.updater.devrepo.DevRepoUpdateChecker
|
|
||||||
import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
|
|
||||||
|
|
||||||
abstract class UpdateChecker {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getUpdateChecker(): UpdateChecker {
|
|
||||||
return if (BuildConfig.DEBUG) {
|
|
||||||
DevRepoUpdateChecker()
|
|
||||||
} else {
|
|
||||||
GithubUpdateChecker()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns observable containing release information
|
|
||||||
*/
|
|
||||||
abstract suspend fun checkForUpdate(): UpdateResult
|
|
||||||
}
|
|
@ -13,9 +13,10 @@ import androidx.work.Worker
|
|||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
|
||||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
Worker(context, workerParams) {
|
Worker(context, workerParams) {
|
||||||
@ -23,7 +24,7 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
|||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
return runBlocking {
|
return runBlocking {
|
||||||
try {
|
try {
|
||||||
val result = UpdateChecker.getUpdateChecker().checkForUpdate()
|
val result = GithubUpdateChecker().checkForUpdate()
|
||||||
|
|
||||||
if (result is UpdateResult.NewUpdate<*>) {
|
if (result is UpdateResult.NewUpdate<*>) {
|
||||||
val url = result.release.downloadLink
|
val url = result.release.downloadLink
|
||||||
@ -65,8 +66,10 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
val request = PeriodicWorkRequestBuilder<UpdaterJob>(
|
val request = PeriodicWorkRequestBuilder<UpdaterJob>(
|
||||||
3, TimeUnit.DAYS,
|
3,
|
||||||
3, TimeUnit.HOURS
|
TimeUnit.DAYS,
|
||||||
|
3,
|
||||||
|
TimeUnit.HOURS
|
||||||
)
|
)
|
||||||
.addTag(TAG)
|
.addTag(TAG)
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
|
@ -20,9 +20,9 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
|
|||||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
import java.io.File
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class UpdaterService : Service() {
|
class UpdaterService : Service() {
|
||||||
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.updater.devrepo
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.updater.Release
|
|
||||||
|
|
||||||
class DevRepoRelease(override val info: String) : Release {
|
|
||||||
|
|
||||||
override val downloadLink: String
|
|
||||||
get() = LATEST_URL
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val LATEST_URL = "https://tachiyomi.kanade.eu/latest"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.updater.devrepo
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateChecker
|
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateResult
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import eu.kanade.tachiyomi.network.await
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class DevRepoUpdateChecker : UpdateChecker() {
|
|
||||||
|
|
||||||
private val client: OkHttpClient by lazy {
|
|
||||||
Injekt.get<NetworkHelper>().client.newBuilder()
|
|
||||||
.followRedirects(false)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val versionRegex: Regex by lazy {
|
|
||||||
Regex("tachiyomi-r(\\d+).apk")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun checkForUpdate(): UpdateResult {
|
|
||||||
val response = withContext(Dispatchers.IO) {
|
|
||||||
client.newCall(GET(DevRepoRelease.LATEST_URL)).await(assertSuccess = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get latest repo version number from header in format "Location: tachiyomi-r1512.apk"
|
|
||||||
val latestVersionNumber: String = versionRegex.find(response.header("Location")!!)!!.groupValues[1]
|
|
||||||
|
|
||||||
return if (latestVersionNumber.toInt() > BuildConfig.COMMIT_COUNT.toInt()) {
|
|
||||||
DevRepoUpdateResult.NewUpdate(DevRepoRelease("v$latestVersionNumber"))
|
|
||||||
} else {
|
|
||||||
DevRepoUpdateResult.NoNewUpdate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.updater.devrepo
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateResult
|
|
||||||
|
|
||||||
sealed class DevRepoUpdateResult : UpdateResult() {
|
|
||||||
|
|
||||||
class NewUpdate(release: DevRepoRelease) : UpdateResult.NewUpdate<DevRepoRelease>(release)
|
|
||||||
class NoNewUpdate : UpdateResult.NoNewUpdate()
|
|
||||||
}
|
|
@ -28,5 +28,5 @@ class GithubRelease(
|
|||||||
* Assets class containing download url.
|
* Assets class containing download url.
|
||||||
* @param downloadLink download url.
|
* @param downloadLink download url.
|
||||||
*/
|
*/
|
||||||
inner class Assets(@SerializedName("browser_download_url") val downloadLink: String)
|
class Assets(@SerializedName("browser_download_url") val downloadLink: String)
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,12 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
|||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to connect with the GitHub API.
|
* Used to connect with the GitHub API to get the latest release version from a repo.
|
||||||
*/
|
*/
|
||||||
interface GithubService {
|
interface GithubService {
|
||||||
|
|
||||||
@ -24,6 +25,6 @@ interface GithubService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET("/repos/inorichi/tachiyomi/releases/latest")
|
@GET("/repos/{repo}/releases/latest")
|
||||||
suspend fun getLatestVersion(): GithubRelease
|
suspend fun getLatestVersion(@Path("repo", encoded = true) repo: String): GithubRelease
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,43 @@
|
|||||||
package eu.kanade.tachiyomi.data.updater.github
|
package eu.kanade.tachiyomi.data.updater.github
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateChecker
|
|
||||||
import eu.kanade.tachiyomi.data.updater.UpdateResult
|
import eu.kanade.tachiyomi.data.updater.UpdateResult
|
||||||
|
|
||||||
class GithubUpdateChecker : UpdateChecker() {
|
class GithubUpdateChecker {
|
||||||
|
|
||||||
private val service: GithubService = GithubService.create()
|
private val service: GithubService = GithubService.create()
|
||||||
|
|
||||||
override suspend fun checkForUpdate(): UpdateResult {
|
private val repo: String by lazy {
|
||||||
val release = service.getLatestVersion()
|
if (BuildConfig.DEBUG) {
|
||||||
|
"tachiyomiorg/android-app-preview"
|
||||||
|
} else {
|
||||||
|
"inorichi/tachiyomi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val newVersion = release.version.replace("[^\\d.]".toRegex(), "")
|
suspend fun checkForUpdate(): UpdateResult {
|
||||||
|
val release = service.getLatestVersion(repo)
|
||||||
|
|
||||||
// Check if latest version is different from current version
|
// Check if latest version is different from current version
|
||||||
return if (newVersion != BuildConfig.VERSION_NAME) {
|
return if (isNewVersion(release.version)) {
|
||||||
GithubUpdateResult.NewUpdate(release)
|
GithubUpdateResult.NewUpdate(release)
|
||||||
} else {
|
} else {
|
||||||
GithubUpdateResult.NoNewUpdate()
|
GithubUpdateResult.NoNewUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isNewVersion(versionTag: String): Boolean {
|
||||||
|
// Removes prefixes like "r" or "v"
|
||||||
|
val newVersion = versionTag.replace("[^\\d.]".toRegex(), "")
|
||||||
|
|
||||||
|
return if (BuildConfig.DEBUG) {
|
||||||
|
// Preview builds: based on releases in "tachiyomiorg/android-app-preview" repo
|
||||||
|
// tagged as something like "r1234"
|
||||||
|
newVersion.toInt() > BuildConfig.COMMIT_COUNT.toInt()
|
||||||
|
} else {
|
||||||
|
// Release builds: based on releases in "inorichi/tachiyomi" repo
|
||||||
|
// tagged as something like "v0.1.2"
|
||||||
|
newVersion != BuildConfig.VERSION_NAME
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,10 @@ import eu.kanade.tachiyomi.data.notification.Notifications
|
|||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||||
import eu.kanade.tachiyomi.util.system.notification
|
import eu.kanade.tachiyomi.util.system.notification
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||||
CoroutineWorker(context, workerParams) {
|
CoroutineWorker(context, workerParams) {
|
||||||
@ -73,8 +73,10 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>(
|
val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>(
|
||||||
12, TimeUnit.HOURS,
|
12,
|
||||||
1, TimeUnit.HOURS
|
TimeUnit.HOURS,
|
||||||
|
1,
|
||||||
|
TimeUnit.HOURS
|
||||||
)
|
)
|
||||||
.addTag(TAG)
|
.addTag(TAG)
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
|
@ -1,43 +1,29 @@
|
|||||||
package eu.kanade.tachiyomi.extension.api
|
package eu.kanade.tachiyomi.extension.api
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.github.salomonbrys.kotson.fromJson
|
|
||||||
import com.github.salomonbrys.kotson.get
|
import com.github.salomonbrys.kotson.get
|
||||||
import com.github.salomonbrys.kotson.int
|
import com.github.salomonbrys.kotson.int
|
||||||
import com.github.salomonbrys.kotson.string
|
import com.github.salomonbrys.kotson.string
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
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
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import eu.kanade.tachiyomi.network.await
|
|
||||||
import java.util.Date
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.Response
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
internal class ExtensionGithubApi {
|
internal class ExtensionGithubApi {
|
||||||
|
|
||||||
private val network: NetworkHelper by injectLazy()
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
private val gson: Gson by injectLazy()
|
|
||||||
|
|
||||||
suspend fun findExtensions(): List<Extension.Available> {
|
suspend fun findExtensions(): List<Extension.Available> {
|
||||||
val call = GET(EXT_URL)
|
val service: ExtensionGithubService = ExtensionGithubService.create()
|
||||||
|
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
val response = network.client.newCall(call).await()
|
val response = service.getRepo()
|
||||||
if (response.isSuccessful) {
|
parseResponse(response)
|
||||||
parseResponse(response)
|
|
||||||
} else {
|
|
||||||
response.close()
|
|
||||||
throw Exception("Failed to get extensions")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,11 +50,7 @@ internal class ExtensionGithubApi {
|
|||||||
return extensionsWithUpdate
|
return extensionsWithUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseResponse(response: Response): List<Extension.Available> {
|
private fun parseResponse(json: JsonArray): List<Extension.Available> {
|
||||||
val text = response.body?.use { it.string() } ?: return emptyList()
|
|
||||||
|
|
||||||
val json = gson.fromJson<JsonArray>(text)
|
|
||||||
|
|
||||||
return json
|
return json
|
||||||
.filter { element ->
|
.filter { element ->
|
||||||
val versionName = element["version"].string
|
val versionName = element["version"].string
|
||||||
@ -82,18 +64,19 @@ internal class ExtensionGithubApi {
|
|||||||
val versionName = element["version"].string
|
val versionName = element["version"].string
|
||||||
val versionCode = element["code"].int
|
val versionCode = element["code"].int
|
||||||
val lang = element["lang"].string
|
val lang = element["lang"].string
|
||||||
val icon = "$REPO_URL/icon/${apkName.replace(".apk", ".png")}"
|
val nsfw = element["nsfw"].int == 1
|
||||||
|
val icon = "$REPO_URL_PREFIX/icon/${apkName.replace(".apk", ".png")}"
|
||||||
|
|
||||||
Extension.Available(name, pkgName, versionName, versionCode, lang, apkName, icon)
|
Extension.Available(name, pkgName, versionName, versionCode, lang, nsfw, apkName, icon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getApkUrl(extension: Extension.Available): String {
|
fun getApkUrl(extension: Extension.Available): String {
|
||||||
return "$REPO_URL/apk/${extension.apkName}"
|
return "$REPO_URL_PREFIX/apk/${extension.apkName}"
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val REPO_URL = "https://raw.githubusercontent.com/inorichi/tachiyomi-extensions/repo"
|
const val BASE_URL = "https://raw.githubusercontent.com/"
|
||||||
private const val EXT_URL = "$REPO_URL/index.json"
|
const val REPO_URL_PREFIX = "${BASE_URL}inorichi/tachiyomi-extensions/repo/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.api
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to get the extension repo listing from GitHub.
|
||||||
|
*/
|
||||||
|
interface ExtensionGithubService {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val client by lazy {
|
||||||
|
val network: NetworkHelper by injectLazy()
|
||||||
|
network.client.newBuilder()
|
||||||
|
.addNetworkInterceptor { chain ->
|
||||||
|
val originalResponse = chain.proceed(chain.request())
|
||||||
|
originalResponse.newBuilder()
|
||||||
|
.header("Content-Encoding", "gzip")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(): ExtensionGithubService {
|
||||||
|
val adapter = Retrofit.Builder()
|
||||||
|
.baseUrl(ExtensionGithubApi.BASE_URL)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.client(client)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return adapter.create(ExtensionGithubService::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET("${ExtensionGithubApi.REPO_URL_PREFIX}index.json.gz")
|
||||||
|
suspend fun getRepo(): JsonArray
|
||||||
|
}
|
@ -9,14 +9,16 @@ sealed class Extension {
|
|||||||
abstract val versionName: String
|
abstract val versionName: String
|
||||||
abstract val versionCode: Int
|
abstract val versionCode: Int
|
||||||
abstract val lang: String?
|
abstract val lang: String?
|
||||||
|
abstract val isNsfw: Boolean
|
||||||
|
|
||||||
data class Installed(
|
data class Installed(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
override val pkgName: String,
|
override val pkgName: String,
|
||||||
override val versionName: String,
|
override val versionName: String,
|
||||||
override val versionCode: Int,
|
override val versionCode: Int,
|
||||||
val sources: List<Source>,
|
|
||||||
override val lang: String,
|
override val lang: String,
|
||||||
|
override val isNsfw: Boolean,
|
||||||
|
val sources: List<Source>,
|
||||||
val hasUpdate: Boolean = false,
|
val hasUpdate: Boolean = false,
|
||||||
val isObsolete: Boolean = false,
|
val isObsolete: Boolean = false,
|
||||||
val isUnofficial: Boolean = false
|
val isUnofficial: Boolean = false
|
||||||
@ -28,6 +30,7 @@ sealed class Extension {
|
|||||||
override val versionName: String,
|
override val versionName: String,
|
||||||
override val versionCode: Int,
|
override val versionCode: Int,
|
||||||
override val lang: String,
|
override val lang: String,
|
||||||
|
override val isNsfw: Boolean,
|
||||||
val apkName: String,
|
val apkName: String,
|
||||||
val iconUrl: String
|
val iconUrl: String
|
||||||
) : Extension()
|
) : Extension()
|
||||||
@ -38,6 +41,7 @@ sealed class Extension {
|
|||||||
override val versionName: String,
|
override val versionName: String,
|
||||||
override val versionCode: Int,
|
override val versionCode: Int,
|
||||||
val signatureHash: String,
|
val signatureHash: String,
|
||||||
override val lang: String? = null
|
override val lang: String? = null,
|
||||||
|
override val isNsfw: Boolean = false
|
||||||
) : Extension()
|
) : Extension()
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,11 @@ import com.jakewharton.rxrelay.PublishRelay
|
|||||||
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.util.storage.getUriCompat
|
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||||
import java.io.File
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The installer which installs, updates and uninstalls the extensions.
|
* The installer which installs, updates and uninstalls the extensions.
|
||||||
|
@ -5,6 +5,8 @@ import android.content.Context
|
|||||||
import android.content.pm.PackageInfo
|
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.annoations.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
|
||||||
@ -15,8 +17,7 @@ import eu.kanade.tachiyomi.util.lang.Hash
|
|||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.injectLazy
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that handles the loading of the extensions installed in the system.
|
* Class that handles the loading of the extensions installed in the system.
|
||||||
@ -24,20 +25,25 @@ import uy.kohesive.injekt.api.get
|
|||||||
@SuppressLint("PackageManagerGetSignatures")
|
@SuppressLint("PackageManagerGetSignatures")
|
||||||
internal object ExtensionLoader {
|
internal object ExtensionLoader {
|
||||||
|
|
||||||
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
private val allowNsfwSource by lazy {
|
||||||
|
preferences.allowNsfwSource().get()
|
||||||
|
}
|
||||||
|
|
||||||
private const val EXTENSION_FEATURE = "tachiyomi.extension"
|
private const val EXTENSION_FEATURE = "tachiyomi.extension"
|
||||||
private const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
|
private const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
|
||||||
|
private const val METADATA_NSFW = "tachiyomi.extension.nsfw"
|
||||||
const val LIB_VERSION_MIN = 1.2
|
const val LIB_VERSION_MIN = 1.2
|
||||||
const val LIB_VERSION_MAX = 1.2
|
const val LIB_VERSION_MAX = 1.2
|
||||||
|
|
||||||
private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
|
private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
|
||||||
|
|
||||||
// inorichi's key
|
// inorichi's key
|
||||||
val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
|
private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
|
||||||
/**
|
/**
|
||||||
* List of the trusted signatures.
|
* List of the trusted signatures.
|
||||||
*/
|
*/
|
||||||
var trustedSignatures = mutableSetOf<String>() +
|
var trustedSignatures = mutableSetOf<String>() + preferences.trustedSignatures().get() + officialSignature
|
||||||
Injekt.get<PreferencesHelper>().trustedSignatures().get() + officialSignature
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a list of all the installed extensions initialized concurrently.
|
* Return a list of all the installed extensions initialized concurrently.
|
||||||
@ -125,6 +131,11 @@ internal object ExtensionLoader {
|
|||||||
return LoadResult.Untrusted(extension)
|
return LoadResult.Untrusted(extension)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1
|
||||||
|
if (allowNsfwSource == PreferenceValues.NsfwAllowance.BLOCKED && isNsfw) {
|
||||||
|
return LoadResult.Error("NSFW extension $pkgName not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
|
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
|
||||||
|
|
||||||
val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!!
|
val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!!
|
||||||
@ -141,7 +152,13 @@ internal object ExtensionLoader {
|
|||||||
try {
|
try {
|
||||||
when (val obj = Class.forName(it, false, classLoader).newInstance()) {
|
when (val obj = Class.forName(it, false, classLoader).newInstance()) {
|
||||||
is Source -> listOf(obj)
|
is Source -> listOf(obj)
|
||||||
is SourceFactory -> obj.createSources()
|
is SourceFactory -> {
|
||||||
|
if (isSourceNsfw(obj)) {
|
||||||
|
emptyList()
|
||||||
|
} else {
|
||||||
|
obj.createSources()
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
|
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
@ -149,10 +166,11 @@ internal object ExtensionLoader {
|
|||||||
return LoadResult.Error(e)
|
return LoadResult.Error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.filter { !isSourceNsfw(it) }
|
||||||
|
|
||||||
val langs = sources.filterIsInstance<CatalogueSource>()
|
val langs = sources.filterIsInstance<CatalogueSource>()
|
||||||
.map { it.lang }
|
.map { it.lang }
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|
||||||
val lang = when (langs.size) {
|
val lang = when (langs.size) {
|
||||||
0 -> ""
|
0 -> ""
|
||||||
1 -> langs.first()
|
1 -> langs.first()
|
||||||
@ -160,7 +178,13 @@ internal object ExtensionLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val extension = Extension.Installed(
|
val extension = Extension.Installed(
|
||||||
extName, pkgName, versionName, versionCode, sources, lang,
|
extName,
|
||||||
|
pkgName,
|
||||||
|
versionName,
|
||||||
|
versionCode,
|
||||||
|
lang,
|
||||||
|
isNsfw,
|
||||||
|
sources,
|
||||||
isUnofficial = signatureHash != officialSignature
|
isUnofficial = signatureHash != officialSignature
|
||||||
)
|
)
|
||||||
return LoadResult.Success(extension)
|
return LoadResult.Success(extension)
|
||||||
@ -188,4 +212,22 @@ internal object ExtensionLoader {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a Source or SourceFactory is annotated with @Nsfw.
|
||||||
|
*/
|
||||||
|
private fun isSourceNsfw(clazz: Any): Boolean {
|
||||||
|
if (allowNsfwSource == PreferenceValues.NsfwAllowance.ALLOWED) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clazz !is Source && clazz !is SourceFactory) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annotations are proxied, hence this janky way of checking for them
|
||||||
|
return clazz.javaClass.annotations
|
||||||
|
.flatMap { it.javaClass.interfaces.map { it.simpleName } }
|
||||||
|
.firstOrNull { it == Nsfw::class.java.simpleName } != null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,31 +2,29 @@ package eu.kanade.tachiyomi.network
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.webkit.WebResourceRequest
|
|
||||||
import android.webkit.WebResourceResponse
|
|
||||||
import android.webkit.WebSettings
|
import android.webkit.WebSettings
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.webkit.WebViewClientCompat
|
|
||||||
import androidx.webkit.WebViewFeature
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
|
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import eu.kanade.tachiyomi.util.system.isOutdated
|
import eu.kanade.tachiyomi.util.system.isOutdated
|
||||||
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
|
||||||
import java.io.IOException
|
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import okhttp3.Cookie
|
import okhttp3.Cookie
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class CloudflareInterceptor(private val context: Context) : Interceptor {
|
class CloudflareInterceptor(private val context: Context) : Interceptor {
|
||||||
|
|
||||||
@ -91,7 +89,8 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|||||||
var isWebViewOutdated = false
|
var isWebViewOutdated = false
|
||||||
|
|
||||||
val origRequestUrl = request.url.toString()
|
val origRequestUrl = request.url.toString()
|
||||||
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }
|
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
|
||||||
|
headers["X-Requested-With"] = WebViewUtil.REQUESTED_WITH
|
||||||
|
|
||||||
handler.post {
|
handler.post {
|
||||||
val webview = WebView(context)
|
val webview = WebView(context)
|
||||||
@ -116,7 +115,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HTTP error codes are only received since M
|
// HTTP error codes are only received since M
|
||||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR) &&
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||||
url == origRequestUrl && !challengeFound
|
url == origRequestUrl && !challengeFound
|
||||||
) {
|
) {
|
||||||
// The first request didn't return the challenge, abort.
|
// The first request didn't return the challenge, abort.
|
||||||
@ -124,13 +123,15 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReceivedHttpError(
|
override fun onReceivedErrorCompat(
|
||||||
view: WebView,
|
view: WebView,
|
||||||
request: WebResourceRequest,
|
errorCode: Int,
|
||||||
errorResponse: WebResourceResponse
|
description: String?,
|
||||||
|
failingUrl: String,
|
||||||
|
isMainFrame: Boolean
|
||||||
) {
|
) {
|
||||||
if (request.isForMainFrame) {
|
if (isMainFrame) {
|
||||||
if (errorResponse.statusCode == 503) {
|
if (errorCode == 503) {
|
||||||
// Found the Cloudflare challenge page.
|
// Found the Cloudflare challenge page.
|
||||||
challengeFound = true
|
challengeFound = true
|
||||||
} else {
|
} else {
|
||||||
|
@ -3,15 +3,15 @@ package eu.kanade.tachiyomi.network
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import java.io.File
|
|
||||||
import java.net.InetAddress
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.dnsoverhttps.DnsOverHttps
|
import okhttp3.dnsoverhttps.DnsOverHttps
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.File
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class NetworkHelper(context: Context) {
|
class NetworkHelper(context: Context) {
|
||||||
|
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.resumeWithException
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Callback
|
import okhttp3.Callback
|
||||||
@ -13,6 +9,10 @@ import okhttp3.Response
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Producer
|
import rx.Producer
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
fun Call.asObservable(): Observable<Response> {
|
fun Call.asObservable(): Observable<Response> {
|
||||||
return Observable.unsafeCreate { subscriber ->
|
return Observable.unsafeCreate { subscriber ->
|
||||||
@ -54,22 +54,24 @@ fun Call.asObservable(): Observable<Response> {
|
|||||||
// Based on https://github.com/gildor/kotlin-coroutines-okhttp
|
// Based on https://github.com/gildor/kotlin-coroutines-okhttp
|
||||||
suspend fun Call.await(assertSuccess: Boolean = false): Response {
|
suspend fun Call.await(assertSuccess: Boolean = false): Response {
|
||||||
return suspendCancellableCoroutine { continuation ->
|
return suspendCancellableCoroutine { continuation ->
|
||||||
enqueue(object : Callback {
|
enqueue(
|
||||||
override fun onResponse(call: Call, response: Response) {
|
object : Callback {
|
||||||
if (assertSuccess && !response.isSuccessful) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
continuation.resumeWithException(Exception("HTTP error ${response.code}"))
|
if (assertSuccess && !response.isSuccessful) {
|
||||||
return
|
continuation.resumeWithException(Exception("HTTP error ${response.code}"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
continuation.resume(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
continuation.resume(response)
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
// Don't bother with resuming the continuation if it is already cancelled.
|
||||||
|
if (continuation.isCancelled) return
|
||||||
|
continuation.resumeWithException(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
|
||||||
// Don't bother with resuming the continuation if it is already cancelled.
|
|
||||||
if (continuation.isCancelled) return
|
|
||||||
continuation.resumeWithException(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
continuation.invokeOnCancellation {
|
continuation.invokeOnCancellation {
|
||||||
try {
|
try {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
import java.io.IOException
|
|
||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
@ -8,6 +7,7 @@ import okio.BufferedSource
|
|||||||
import okio.ForwardingSource
|
import okio.ForwardingSource
|
||||||
import okio.Source
|
import okio.Source
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
|
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit.MINUTES
|
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
|
import java.util.concurrent.TimeUnit.MINUTES
|
||||||
|
|
||||||
private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build()
|
private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build()
|
||||||
private val DEFAULT_HEADERS = Headers.Builder().build()
|
private val DEFAULT_HEADERS = Headers.Builder().build()
|
||||||
|
@ -14,6 +14,10 @@ import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
|||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.storage.EpubFile
|
import eu.kanade.tachiyomi.util.storage.EpubFile
|
||||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||||
|
import junrar.Archive
|
||||||
|
import junrar.rarfile.FileHeader
|
||||||
|
import rx.Observable
|
||||||
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -21,10 +25,6 @@ import java.util.Locale
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
import junrar.Archive
|
|
||||||
import junrar.rarfile.FileHeader
|
|
||||||
import rx.Observable
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
class LocalSource(private val context: Context) : CatalogueSource {
|
class LocalSource(private val context: Context) : CatalogueSource {
|
||||||
companion object {
|
companion object {
|
||||||
@ -80,6 +80,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
|||||||
.mapNotNull { it.listFiles()?.toList() }
|
.mapNotNull { it.listFiles()?.toList() }
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter { it.isDirectory }
|
.filter { it.isDirectory }
|
||||||
|
.filterNot { it.name.startsWith('.') }
|
||||||
.filter { if (time == 0L) it.name.contains(query, ignoreCase = true) else it.lastModified() >= time }
|
.filter { if (time == 0L) it.name.contains(query, ignoreCase = true) else it.lastModified() >= time }
|
||||||
.distinctBy { it.name }
|
.distinctBy { it.name }
|
||||||
|
|
||||||
|
@ -10,15 +10,15 @@ import eu.kanade.tachiyomi.source.model.MangasPage
|
|||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import java.net.URI
|
|
||||||
import java.net.URISyntaxException
|
|
||||||
import java.security.MessageDigest
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple implementation for sources from a website.
|
* A simple implementation for sources from a website.
|
||||||
|
@ -7,11 +7,11 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
|
|
||||||
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.ui.security.SecureActivityDelegate
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
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
|
||||||
|
|
||||||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||||
|
|
||||||
|
@ -22,27 +22,29 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
|||||||
lateinit var binding: VB
|
lateinit var binding: VB
|
||||||
|
|
||||||
init {
|
init {
|
||||||
addLifecycleListener(object : LifecycleListener() {
|
addLifecycleListener(
|
||||||
override fun postCreateView(controller: Controller, view: View) {
|
object : LifecycleListener() {
|
||||||
onViewCreated(view)
|
override fun postCreateView(controller: Controller, view: View) {
|
||||||
}
|
onViewCreated(view)
|
||||||
|
}
|
||||||
|
|
||||||
override fun preCreateView(controller: Controller) {
|
override fun preCreateView(controller: Controller) {
|
||||||
Timber.d("Create view for ${controller.instance()}")
|
Timber.d("Create view for ${controller.instance()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun preAttach(controller: Controller, view: View) {
|
override fun preAttach(controller: Controller, view: View) {
|
||||||
Timber.d("Attach view for ${controller.instance()}")
|
Timber.d("Attach view for ${controller.instance()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun preDetach(controller: Controller, view: View) {
|
override fun preDetach(controller: Controller, view: View) {
|
||||||
Timber.d("Detach view for ${controller.instance()}")
|
Timber.d("Detach view for ${controller.instance()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun preDestroyView(controller: Controller, view: View) {
|
override fun preDestroyView(controller: Controller, view: View) {
|
||||||
Timber.d("Destroy view for ${controller.instance()}")
|
Timber.d("Destroy view for ${controller.instance()}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val containerView: View?
|
override val containerView: View?
|
||||||
@ -98,17 +100,19 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
|||||||
var expandActionViewFromInteraction = false
|
var expandActionViewFromInteraction = false
|
||||||
|
|
||||||
fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) {
|
fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) {
|
||||||
setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
setOnActionExpandListener(
|
||||||
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
object : MenuItem.OnActionExpandListener {
|
||||||
return onExpand?.invoke(item) ?: true
|
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||||
}
|
return onExpand?.invoke(item) ?: true
|
||||||
|
}
|
||||||
|
|
||||||
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||||
activity?.invalidateOptionsMenu()
|
activity?.invalidateOptionsMenu()
|
||||||
|
|
||||||
return onCollapse?.invoke(item) ?: true
|
return onCollapse?.invoke(item) ?: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
if (expandActionViewFromInteraction) {
|
if (expandActionViewFromInteraction) {
|
||||||
expandActionViewFromInteraction = false
|
expandActionViewFromInteraction = false
|
||||||
|
@ -6,7 +6,6 @@ import androidx.core.content.ContextCompat
|
|||||||
import com.bluelinelabs.conductor.Controller
|
import com.bluelinelabs.conductor.Controller
|
||||||
import com.bluelinelabs.conductor.Router
|
import com.bluelinelabs.conductor.Router
|
||||||
import com.bluelinelabs.conductor.RouterTransaction
|
import com.bluelinelabs.conductor.RouterTransaction
|
||||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
|
|
||||||
|
|
||||||
fun Router.popControllerWithTag(tag: String): Boolean {
|
fun Router.popControllerWithTag(tag: String): Boolean {
|
||||||
val controller = getControllerWithTag(tag)
|
val controller = getControllerWithTag(tag)
|
||||||
@ -28,8 +27,8 @@ fun Controller.requestPermissionsSafe(permissions: Array<String>, requestCode: I
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Controller.withFadeTransaction(duration: Long = 150L): RouterTransaction {
|
fun Controller.withFadeTransaction(): RouterTransaction {
|
||||||
return RouterTransaction.with(this)
|
return RouterTransaction.with(this)
|
||||||
.pushChangeHandler(FadeChangeHandler(duration))
|
.pushChangeHandler(OneWayFadeChangeHandler())
|
||||||
.popChangeHandler(FadeChangeHandler(duration))
|
.popChangeHandler(OneWayFadeChangeHandler())
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler
|
|||||||
* A controller that displays a dialog window, floating on top of its activity's window.
|
* A controller that displays a dialog window, floating on top of its activity's window.
|
||||||
* This is a wrapper over [Dialog] object like [android.app.DialogFragment].
|
* This is a wrapper over [Dialog] object like [android.app.DialogFragment].
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* Implementations should override this class and implement [.onCreateDialog] to create a custom dialog, such as an [android.app.AlertDialog]
|
* Implementations should override this class and implement [.onCreateDialog] to create a custom dialog, such as an [android.app.AlertDialog]
|
||||||
*/
|
*/
|
||||||
abstract class DialogController : RestoreViewOnCreateController {
|
abstract class DialogController : RestoreViewOnCreateController {
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.base.controller
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||||
|
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variation of [FadeChangeHandler] that only fades in.
|
||||||
|
*/
|
||||||
|
class OneWayFadeChangeHandler : FadeChangeHandler {
|
||||||
|
constructor()
|
||||||
|
constructor(removesFromViewOnPush: Boolean) : super(removesFromViewOnPush)
|
||||||
|
constructor(duration: Long) : super(duration)
|
||||||
|
constructor(duration: Long, removesFromViewOnPush: Boolean) : super(
|
||||||
|
duration,
|
||||||
|
removesFromViewOnPush
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getAnimator(
|
||||||
|
container: ViewGroup,
|
||||||
|
from: View?,
|
||||||
|
to: View?,
|
||||||
|
isPush: Boolean,
|
||||||
|
toAddedToContainer: Boolean
|
||||||
|
): Animator {
|
||||||
|
if (to != null) {
|
||||||
|
return super.getAnimator(container, from, to, isPush, toAddedToContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from != null && (!isPush || removesFromViewOnPush())) {
|
||||||
|
container.removeView(from)
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnimatorSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun copy(): ControllerChangeHandler {
|
||||||
|
return OneWayFadeChangeHandler(animationDuration, removesFromViewOnPush())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.base.controller
|
||||||
|
|
||||||
|
interface ToolbarLiftOnScrollController
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source
|
package eu.kanade.tachiyomi.ui.browse
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
@ -23,8 +23,8 @@ class SourceDividerItemDecoration(context: Context) : RecyclerView.ItemDecoratio
|
|||||||
for (i in 0 until childCount - 1) {
|
for (i in 0 until childCount - 1) {
|
||||||
val child = parent.getChildAt(i)
|
val child = parent.getChildAt(i)
|
||||||
val holder = parent.getChildViewHolder(child)
|
val holder = parent.getChildViewHolder(child)
|
||||||
if (holder is SourceHolder &&
|
if (holder is SourceListItem &&
|
||||||
parent.getChildViewHolder(parent.getChildAt(i + 1)) is SourceHolder
|
parent.getChildViewHolder(parent.getChildAt(i + 1)) is SourceListItem
|
||||||
) {
|
) {
|
||||||
val top = child.bottom + child.marginBottom
|
val top = child.bottom + child.marginBottom
|
||||||
val bottom = top + divider.intrinsicHeight
|
val bottom = top + divider.intrinsicHeight
|
@ -0,0 +1,5 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.ui.base.holder.SlicedHolder
|
||||||
|
|
||||||
|
interface SourceListItem : SlicedHolder
|
@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.extension.model.Extension
|
|||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.SourceDividerItemDecoration
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsController
|
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsController
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
@ -75,7 +76,7 @@ open class ExtensionController :
|
|||||||
// Create recycler and set adapter.
|
// Create recycler and set adapter.
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
binding.recycler.adapter = adapter
|
binding.recycler.adapter = adapter
|
||||||
binding.recycler.addItemDecoration(ExtensionDividerItemDecoration(view.context))
|
binding.recycler.addItemDecoration(SourceDividerItemDecoration(view.context))
|
||||||
adapter?.fastScroller = binding.fastScroller
|
adapter?.fastScroller = binding.fastScroller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.extension
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.view.View
|
|
||||||
import androidx.core.view.marginBottom
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
|
|
||||||
class ExtensionDividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
|
|
||||||
|
|
||||||
private val divider: Drawable
|
|
||||||
|
|
||||||
init {
|
|
||||||
val a = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider))
|
|
||||||
divider = a.getDrawable(0)!!
|
|
||||||
a.recycle()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
|
||||||
val childCount = parent.childCount
|
|
||||||
for (i in 0 until childCount - 1) {
|
|
||||||
val child = parent.getChildAt(i)
|
|
||||||
val holder = parent.getChildViewHolder(child)
|
|
||||||
if (holder is ExtensionHolder &&
|
|
||||||
parent.getChildViewHolder(parent.getChildAt(i + 1)) is ExtensionHolder
|
|
||||||
) {
|
|
||||||
val top = child.bottom + child.marginBottom
|
|
||||||
val bottom = top + divider.intrinsicHeight
|
|
||||||
val left = parent.paddingStart + holder.margin
|
|
||||||
val right = parent.width - parent.paddingEnd - holder.margin
|
|
||||||
|
|
||||||
divider.setBounds(left, top, right, bottom)
|
|
||||||
divider.draw(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemOffsets(
|
|
||||||
outRect: Rect,
|
|
||||||
view: View,
|
|
||||||
parent: RecyclerView,
|
|
||||||
state: RecyclerView.State
|
|
||||||
) {
|
|
||||||
outRect.set(0, 0, 0, divider.intrinsicHeight)
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,6 +7,7 @@ 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.ui.base.holder.BaseFlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.ui.base.holder.SlicedHolder
|
import eu.kanade.tachiyomi.ui.base.holder.SlicedHolder
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.SourceListItem
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import io.github.mthli.slice.Slice
|
import io.github.mthli.slice.Slice
|
||||||
import kotlinx.android.synthetic.main.extension_card_item.card
|
import kotlinx.android.synthetic.main.extension_card_item.card
|
||||||
@ -19,6 +20,7 @@ import kotlinx.android.synthetic.main.extension_card_item.warning
|
|||||||
|
|
||||||
class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) :
|
class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) :
|
||||||
BaseFlexibleViewHolder(view, adapter),
|
BaseFlexibleViewHolder(view, adapter),
|
||||||
|
SourceListItem,
|
||||||
SlicedHolder {
|
SlicedHolder {
|
||||||
|
|
||||||
override val slice = Slice(card).apply {
|
override val slice = Slice(card).apply {
|
||||||
@ -42,11 +44,12 @@ class ExtensionHolder(view: View, override val adapter: ExtensionAdapter) :
|
|||||||
version.text = extension.versionName
|
version.text = extension.versionName
|
||||||
lang.text = LocaleHelper.getSourceDisplayName(extension.lang, itemView.context)
|
lang.text = LocaleHelper.getSourceDisplayName(extension.lang, itemView.context)
|
||||||
warning.text = when {
|
warning.text = when {
|
||||||
extension is Extension.Untrusted -> itemView.context.getString(R.string.ext_untrusted).toUpperCase()
|
extension is Extension.Untrusted -> itemView.context.getString(R.string.ext_untrusted)
|
||||||
extension is Extension.Installed && extension.isObsolete -> itemView.context.getString(R.string.ext_obsolete).toUpperCase()
|
extension is Extension.Installed && extension.isObsolete -> itemView.context.getString(R.string.ext_obsolete)
|
||||||
extension is Extension.Installed && extension.isUnofficial -> itemView.context.getString(R.string.ext_unofficial).toUpperCase()
|
extension is Extension.Installed && extension.isUnofficial -> itemView.context.getString(R.string.ext_unofficial)
|
||||||
else -> null
|
extension.isNsfw -> itemView.context.getString(R.string.ext_nsfw_short)
|
||||||
}
|
else -> ""
|
||||||
|
}.toUpperCase()
|
||||||
|
|
||||||
GlideApp.with(itemView.context).clear(image)
|
GlideApp.with(itemView.context).clear(image)
|
||||||
if (extension is Extension.Available) {
|
if (extension is Extension.Available) {
|
||||||
|
@ -3,18 +3,19 @@ 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
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
private typealias ExtensionTuple =
|
private typealias ExtensionTuple =
|
||||||
Triple<List<Extension.Installed>, List<Extension.Untrusted>, List<Extension.Available>>
|
Triple<List<Extension.Installed>, List<Extension.Untrusted>, List<Extension.Available>>
|
||||||
@ -55,20 +56,22 @@ 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 (installed, untrusted, available) = tuple
|
val (installed, untrusted, available) = tuple
|
||||||
|
|
||||||
val items = mutableListOf<ExtensionItem>()
|
val items = mutableListOf<ExtensionItem>()
|
||||||
|
|
||||||
val updatesSorted = installed.filter { it.hasUpdate }.sortedBy { it.pkgName }
|
val updatesSorted = installed.filter { it.hasUpdate && (showNsfwExtensions || !it.isNsfw) }.sortedBy { it.pkgName }
|
||||||
val installedSorted = installed.filter { !it.hasUpdate }.sortedWith(compareBy({ !it.isObsolete }, { it.pkgName }))
|
val installedSorted = installed.filter { !it.hasUpdate && (showNsfwExtensions || !it.isNsfw) }.sortedWith(compareBy({ !it.isObsolete }, { it.pkgName }))
|
||||||
val untrustedSorted = untrusted.sortedBy { it.pkgName }
|
val untrustedSorted = untrusted.sortedBy { it.pkgName }
|
||||||
val availableSorted = available
|
val availableSorted = available
|
||||||
// Filter out already installed extensions and disabled languages
|
// Filter out already installed extensions and disabled languages
|
||||||
.filter { avail ->
|
.filter { avail ->
|
||||||
installed.none { it.pkgName == avail.pkgName } &&
|
installed.none { it.pkgName == avail.pkgName } &&
|
||||||
untrusted.none { it.pkgName == avail.pkgName } &&
|
untrusted.none { it.pkgName == avail.pkgName } &&
|
||||||
(avail.lang in activeLangs || avail.lang == "all")
|
(avail.lang in activeLangs || avail.lang == "all") &&
|
||||||
|
(showNsfwExtensions || !avail.isNsfw)
|
||||||
}
|
}
|
||||||
.sortedBy { it.pkgName }
|
.sortedBy { it.pkgName }
|
||||||
|
|
||||||
|
@ -20,8 +20,6 @@ import androidx.preference.PreferenceManager
|
|||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
|
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
|
||||||
@ -34,8 +32,8 @@ import eu.kanade.tachiyomi.source.CatalogueSource
|
|||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.getPreferenceKey
|
import eu.kanade.tachiyomi.source.getPreferenceKey
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.ToolbarLiftOnScrollController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.util.preference.DSL
|
import eu.kanade.tachiyomi.util.preference.DSL
|
||||||
import eu.kanade.tachiyomi.util.preference.onChange
|
import eu.kanade.tachiyomi.util.preference.onChange
|
||||||
@ -50,7 +48,7 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
class ExtensionDetailsController(bundle: Bundle? = null) :
|
class ExtensionDetailsController(bundle: Bundle? = null) :
|
||||||
NucleusController<ExtensionDetailControllerBinding, ExtensionDetailsPresenter>(bundle),
|
NucleusController<ExtensionDetailControllerBinding, ExtensionDetailsPresenter>(bundle),
|
||||||
NoToolbarElevationController {
|
ToolbarLiftOnScrollController {
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
@ -92,7 +90,6 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
|||||||
ExtensionDetailsHeaderAdapter(presenter),
|
ExtensionDetailsHeaderAdapter(presenter),
|
||||||
initPreferencesAdapter(context, extension)
|
initPreferencesAdapter(context, extension)
|
||||||
)
|
)
|
||||||
binding.extensionPrefsRecycler.addItemDecoration(DividerItemDecoration(context, VERTICAL))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initPreferencesAdapter(context: Context, extension: Extension.Installed): PreferenceGroupAdapter {
|
private fun initPreferencesAdapter(context: Context, extension: Extension.Installed): PreferenceGroupAdapter {
|
||||||
@ -112,7 +109,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
|||||||
.forEach {
|
.forEach {
|
||||||
val preferenceBlock = {
|
val preferenceBlock = {
|
||||||
it.value
|
it.value
|
||||||
.sortedWith(compareBy({ !it.isEnabled() }, { it.name }))
|
.sortedWith(compareBy({ !it.isEnabled() }, { it.name.toLowerCase() }))
|
||||||
.forEach { source ->
|
.forEach { source ->
|
||||||
val sourcePrefs = mutableListOf<Preference>()
|
val sourcePrefs = mutableListOf<Preference>()
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ class ExtensionDetailsHeaderAdapter(private val presenter: ExtensionDetailsPrese
|
|||||||
binding.extensionTitle.text = extension.name
|
binding.extensionTitle.text = extension.name
|
||||||
binding.extensionVersion.text = context.getString(R.string.ext_version_info, extension.versionName)
|
binding.extensionVersion.text = context.getString(R.string.ext_version_info, extension.versionName)
|
||||||
binding.extensionLang.text = context.getString(R.string.ext_language_info, LocaleHelper.getSourceDisplayName(extension.lang, context))
|
binding.extensionLang.text = context.getString(R.string.ext_language_info, LocaleHelper.getSourceDisplayName(extension.lang, context))
|
||||||
|
binding.extensionNsfw.isVisible = extension.isNsfw
|
||||||
binding.extensionPkg.text = extension.pkgName
|
binding.extensionPkg.text = extension.pkgName
|
||||||
|
|
||||||
binding.extensionUninstallButton.clicks()
|
binding.extensionUninstallButton.clicks()
|
||||||
|
@ -19,8 +19,6 @@ import androidx.preference.Preference
|
|||||||
import androidx.preference.PreferenceGroupAdapter
|
import androidx.preference.PreferenceGroupAdapter
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
|
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
|
||||||
@ -86,7 +84,6 @@ class SourcePreferencesController(bundle: Bundle? = null) :
|
|||||||
|
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(context)
|
binding.recycler.layoutManager = LinearLayoutManager(context)
|
||||||
binding.recycler.adapter = PreferenceGroupAdapter(screen)
|
binding.recycler.adapter = PreferenceGroupAdapter(screen)
|
||||||
binding.recycler.addItemDecoration(DividerItemDecoration(context, VERTICAL))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
override fun onDestroyView(view: View) {
|
||||||
|
@ -10,8 +10,8 @@ import eu.davidea.flexibleadapter.items.IFlexible
|
|||||||
import eu.kanade.tachiyomi.databinding.MigrationMangaControllerBinding
|
import eu.kanade.tachiyomi.databinding.MigrationMangaControllerBinding
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.SourceDividerItemDecoration
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
|
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourceDividerItemDecoration
|
|
||||||
|
|
||||||
class MigrationMangaController :
|
class MigrationMangaController :
|
||||||
NucleusController<MigrationMangaControllerBinding, MigrationMangaPresenter>,
|
NucleusController<MigrationMangaControllerBinding, MigrationMangaPresenter>,
|
||||||
|
@ -13,10 +13,10 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchCardItem
|
|||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItem
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItem
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter
|
||||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||||
import java.util.Date
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
class SearchPresenter(
|
class SearchPresenter(
|
||||||
initialQuery: String? = "",
|
initialQuery: String? = "",
|
||||||
|
@ -8,8 +8,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
|
|||||||
import eu.kanade.tachiyomi.databinding.MigrationSourcesControllerBinding
|
import eu.kanade.tachiyomi.databinding.MigrationSourcesControllerBinding
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.SourceDividerItemDecoration
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourceDividerItemDecoration
|
|
||||||
|
|
||||||
class MigrationSourcesController :
|
class MigrationSourcesController :
|
||||||
NucleusController<MigrationSourcesControllerBinding, MigrationSourcesPresenter>(),
|
NucleusController<MigrationSourcesControllerBinding, MigrationSourcesPresenter>(),
|
||||||
|
@ -29,7 +29,7 @@ class MigrationSourcesPresenter(
|
|||||||
val header = SelectionHeader()
|
val header = SelectionHeader()
|
||||||
return library.map { it.source }.toSet()
|
return library.map { it.source }.toSet()
|
||||||
.mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null }
|
.mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null }
|
||||||
.sortedBy { it.name }
|
.sortedBy { it.name.toLowerCase() }
|
||||||
.map { SourceItem(it, header) }
|
.map { SourceItem(it, header) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import android.view.View
|
|||||||
import eu.kanade.tachiyomi.source.icon
|
import eu.kanade.tachiyomi.source.icon
|
||||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.ui.base.holder.SlicedHolder
|
import eu.kanade.tachiyomi.ui.base.holder.SlicedHolder
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.SourceListItem
|
||||||
import io.github.mthli.slice.Slice
|
import io.github.mthli.slice.Slice
|
||||||
import kotlinx.android.synthetic.main.source_main_controller_card_item.card
|
import kotlinx.android.synthetic.main.source_main_controller_card_item.card
|
||||||
import kotlinx.android.synthetic.main.source_main_controller_card_item.image
|
import kotlinx.android.synthetic.main.source_main_controller_card_item.image
|
||||||
@ -11,6 +12,7 @@ import kotlinx.android.synthetic.main.source_main_controller_card_item.title
|
|||||||
|
|
||||||
class SourceHolder(view: View, override val adapter: SourceAdapter) :
|
class SourceHolder(view: View, override val adapter: SourceAdapter) :
|
||||||
BaseFlexibleViewHolder(view, adapter),
|
BaseFlexibleViewHolder(view, adapter),
|
||||||
|
SourceListItem,
|
||||||
SlicedHolder {
|
SlicedHolder {
|
||||||
|
|
||||||
override val slice = Slice(card).apply {
|
override val slice = Slice(card).apply {
|
||||||
|
@ -30,6 +30,7 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
|||||||
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
|
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.SourceDividerItemDecoration
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
|
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
|
||||||
@ -148,7 +149,7 @@ class SourceController :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SourceOptionsDialog(item, items).showDialog(router)
|
SourceOptionsDialog(item.source.toString(), items).showDialog(router)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun disableSource(source: Source) {
|
private fun disableSource(source: Source) {
|
||||||
@ -269,17 +270,17 @@ class SourceController :
|
|||||||
|
|
||||||
class SourceOptionsDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
class SourceOptionsDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
||||||
|
|
||||||
private lateinit var item: SourceItem
|
private lateinit var source: String
|
||||||
private lateinit var items: List<Pair<String, () -> Unit>>
|
private lateinit var items: List<Pair<String, () -> Unit>>
|
||||||
|
|
||||||
constructor(item: SourceItem, items: List<Pair<String, () -> Unit>>) : this() {
|
constructor(source: String, items: List<Pair<String, () -> Unit>>) : this() {
|
||||||
this.item = item
|
this.source = source
|
||||||
this.items = items
|
this.items = items
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
return MaterialDialog(activity!!)
|
return MaterialDialog(activity!!)
|
||||||
.title(text = item.source.toString())
|
.title(text = source)
|
||||||
.listItems(
|
.listItems(
|
||||||
items = items.map { it.first },
|
items = items.map { it.first },
|
||||||
waitForPositiveButton = false
|
waitForPositiveButton = false
|
||||||
|
@ -16,9 +16,9 @@ import eu.kanade.tachiyomi.util.preference.onChange
|
|||||||
import eu.kanade.tachiyomi.util.preference.switchPreferenceCategory
|
import eu.kanade.tachiyomi.util.preference.switchPreferenceCategory
|
||||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
import eu.kanade.tachiyomi.util.preference.titleRes
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import java.util.TreeMap
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.TreeMap
|
||||||
|
|
||||||
class SourceFilterController : SettingsController() {
|
class SourceFilterController : SettingsController() {
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ class SourceFilterController : SettingsController() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
orderedLangs.forEach { lang ->
|
orderedLangs.forEach { lang ->
|
||||||
val sources = sourcesByLang[lang].orEmpty().sortedBy { it.name }
|
val sources = sourcesByLang[lang].orEmpty().sortedBy { it.name.toLowerCase() }
|
||||||
|
|
||||||
// Create a preference group and set initial state and change listener
|
// Create a preference group and set initial state and change listener
|
||||||
switchPreferenceCategory {
|
switchPreferenceCategory {
|
||||||
|
@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.source.LocalSource
|
|||||||
import eu.kanade.tachiyomi.source.icon
|
import eu.kanade.tachiyomi.source.icon
|
||||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.ui.base.holder.SlicedHolder
|
import eu.kanade.tachiyomi.ui.base.holder.SlicedHolder
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.SourceListItem
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
import eu.kanade.tachiyomi.util.view.setVectorCompat
|
import eu.kanade.tachiyomi.util.view.setVectorCompat
|
||||||
import io.github.mthli.slice.Slice
|
import io.github.mthli.slice.Slice
|
||||||
@ -18,6 +19,7 @@ import kotlinx.android.synthetic.main.source_main_controller_card_item.title
|
|||||||
|
|
||||||
class SourceHolder(private val view: View, override val adapter: SourceAdapter) :
|
class SourceHolder(private val view: View, override val adapter: SourceAdapter) :
|
||||||
BaseFlexibleViewHolder(view, adapter),
|
BaseFlexibleViewHolder(view, adapter),
|
||||||
|
SourceListItem,
|
||||||
SlicedHolder {
|
SlicedHolder {
|
||||||
|
|
||||||
override val slice = Slice(card).apply {
|
override val slice = Slice(card).apply {
|
||||||
|
@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource
|
|||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import java.util.TreeMap
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -21,6 +20,7 @@ import rx.Observable
|
|||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.util.TreeMap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of [SourceController]
|
* Presenter of [SourceController]
|
||||||
@ -103,7 +103,10 @@ class SourcePresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateLastUsedSource(sourceId: Long) {
|
private fun updateLastUsedSource(sourceId: Long) {
|
||||||
val source = (sourceManager.get(sourceId) as? CatalogueSource)?.let { SourceItem(it) }
|
val source = (sourceManager.get(sourceId) as? CatalogueSource)?.let {
|
||||||
|
val isPinned = it.id.toString() in preferences.pinnedSources().get()
|
||||||
|
SourceItem(it, null, isPinned)
|
||||||
|
}
|
||||||
source?.let { view?.setLastUsedSource(it) }
|
source?.let { view?.setLastUsedSource(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +128,7 @@ class SourcePresenter(
|
|||||||
return sourceManager.getCatalogueSources()
|
return sourceManager.getCatalogueSources()
|
||||||
.filter { it.lang in languages }
|
.filter { it.lang in languages }
|
||||||
.filterNot { it.id.toString() in disabledSourceIds }
|
.filterNot { it.id.toString() in disabledSourceIds }
|
||||||
.sortedBy { "(${it.lang}) ${it.name}" } +
|
.sortedBy { "(${it.lang}) ${it.name.toLowerCase()}" } +
|
||||||
sourceManager.get(LocalSource.ID) as LocalSource
|
sourceManager.get(LocalSource.ID) as LocalSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@ -46,6 +45,7 @@ import eu.kanade.tachiyomi.util.view.shrinkOnScroll
|
|||||||
import eu.kanade.tachiyomi.util.view.snack
|
import eu.kanade.tachiyomi.util.view.snack
|
||||||
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
||||||
import eu.kanade.tachiyomi.widget.EmptyView
|
import eu.kanade.tachiyomi.widget.EmptyView
|
||||||
|
import kotlinx.android.synthetic.main.main_activity.root_coordinator
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
@ -215,7 +215,6 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
id = R.id.recycler
|
id = R.id.recycler
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||||
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(binding.catalogueView.inflate(R.layout.source_recycler_autofit) as AutofitRecyclerView).apply {
|
(binding.catalogueView.inflate(R.layout.source_recycler_autofit) as AutofitRecyclerView).apply {
|
||||||
@ -403,7 +402,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
|
|
||||||
binding.emptyView.show(message, actions)
|
binding.emptyView.show(message, actions)
|
||||||
} else {
|
} else {
|
||||||
snack = binding.catalogueView.snack(message, Snackbar.LENGTH_INDEFINITE) {
|
snack = activity!!.root_coordinator?.snack(message, Snackbar.LENGTH_INDEFINITE) {
|
||||||
setAction(R.string.action_retry, retryAction)
|
setAction(R.string.action_retry, retryAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|