Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
8d5037ce56
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: "🐞 Bug report"
|
||||||
|
about: Report a bug
|
||||||
|
title: "[Bug] Write short description here"
|
||||||
|
labels: "bug"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Device information
|
||||||
|
* Tachiyomi version: ?
|
||||||
|
* Android version: ?
|
||||||
|
|
||||||
|
## Steps to reproduce
|
||||||
|
1. First step
|
||||||
|
2. Second step
|
||||||
|
|
||||||
|
### Expected behavior
|
||||||
|
This should happen.
|
||||||
|
|
||||||
|
### Actual behavior
|
||||||
|
This happened instead.
|
||||||
|
|
||||||
|
### Other details
|
||||||
|
Additional details and attachments.
|
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
name: "🌟 Feature request"
|
||||||
|
about: Suggest a feature to improve Tachiyomi
|
||||||
|
title: "[Feature Request] Write short description here"
|
||||||
|
labels: "feature"
|
||||||
|
|
||||||
|
---
|
||||||
|
### Why/User Benefit/User Problem
|
||||||
|
(explain why this feature should be added)
|
||||||
|
|
||||||
|
### What/Requirements
|
||||||
|
(explain how this feature would behave)
|
@ -2,8 +2,8 @@ dist: trusty
|
|||||||
language: android
|
language: android
|
||||||
android:
|
android:
|
||||||
components:
|
components:
|
||||||
- build-tools-28.0.3
|
- build-tools-29.0.2
|
||||||
- android-27
|
- android-28
|
||||||
- extra-android-m2repository
|
- extra-android-m2repository
|
||||||
- extra-google-m2repository
|
- extra-google-m2repository
|
||||||
- extra-android-support
|
- extra-android-support
|
||||||
@ -11,7 +11,7 @@ android:
|
|||||||
licenses:
|
licenses:
|
||||||
- android-sdk-license-.+
|
- android-sdk-license-.+
|
||||||
before_install:
|
before_install:
|
||||||
- yes | sdkmanager "platforms;android-27" # workaround for accepting the license
|
- yes | sdkmanager "platforms;android-28" # workaround for accepting the license
|
||||||
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
||||||
openssl aes-256-cbc -K $encrypted_e56be693d4fd_key -iv $encrypted_e56be693d4fd_iv -in "$PWD/.travis/secrets.tar.enc" -out secrets.tar -d;
|
openssl aes-256-cbc -K $encrypted_e56be693d4fd_key -iv $encrypted_e56be693d4fd_iv -in "$PWD/.travis/secrets.tar.enc" -out secrets.tar -d;
|
||||||
tar xf secrets.tar;
|
tar xf secrets.tar;
|
||||||
|
@ -11,8 +11,8 @@ Tachiyomi is a free and open source manga reader for Android.
|
|||||||
Features of Tachiyomi include:
|
Features of Tachiyomi include:
|
||||||
* Online reading from sources such as KissManga, MangaDex, [and more](https://github.com/inorichi/tachiyomi-extensions)
|
* Online reading from sources such as KissManga, MangaDex, [and more](https://github.com/inorichi/tachiyomi-extensions)
|
||||||
* Local reading of downloaded manga
|
* Local reading of downloaded manga
|
||||||
* Configurable reader with multiple viewers, reading directions and other settings
|
* A configurable reader with multiple viewers, reading directions and other settings.
|
||||||
* [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), and [Kitsu](https://kitsu.io/explore/anime) support
|
* [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/explore/anime), and [Shikimori](https://shikimori.one) support
|
||||||
* Categories to organize your library
|
* Categories to organize your library
|
||||||
* Light and dark themes
|
* Light and dark themes
|
||||||
* Schedule updating your library for new chapters
|
* Schedule updating your library for new chapters
|
||||||
|
@ -102,7 +102,6 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "1.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -200,9 +199,6 @@ dependencies {
|
|||||||
// Crash reports
|
// Crash reports
|
||||||
implementation 'ch.acra:acra:4.9.2'
|
implementation 'ch.acra:acra:4.9.2'
|
||||||
|
|
||||||
// Sort
|
|
||||||
implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1'
|
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
implementation 'com.dmitrymalkovich.android:material-design-dimens:1.4'
|
implementation 'com.dmitrymalkovich.android:material-design-dimens:1.4'
|
||||||
implementation 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
implementation 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
@ -28,7 +28,9 @@ class DownloadProvider(private val context: Context) {
|
|||||||
* The root directory for downloads.
|
* The root directory for downloads.
|
||||||
*/
|
*/
|
||||||
private var downloadsDir = preferences.downloadsDirectory().getOrDefault().let {
|
private var downloadsDir = preferences.downloadsDirectory().getOrDefault().let {
|
||||||
UniFile.fromUri(context, Uri.parse(it))
|
val dir = UniFile.fromUri(context, Uri.parse(it))
|
||||||
|
DiskUtil.createNoMediaFile(dir, context)
|
||||||
|
dir
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -132,7 +132,7 @@ class DownloadService : Service() {
|
|||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({ state -> onNetworkStateChanged(state)
|
.subscribe({ state -> onNetworkStateChanged(state)
|
||||||
}, { _ ->
|
}, {
|
||||||
toast(R.string.download_queue_error)
|
toast(R.string.download_queue_error)
|
||||||
stopSelf()
|
stopSelf()
|
||||||
})
|
})
|
||||||
|
@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.source.SourceManager
|
|||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList
|
import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList
|
||||||
|
import eu.kanade.tachiyomi.util.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.ImageUtil
|
import eu.kanade.tachiyomi.util.ImageUtil
|
||||||
import eu.kanade.tachiyomi.util.RetryWithDelay
|
import eu.kanade.tachiyomi.util.RetryWithDelay
|
||||||
import eu.kanade.tachiyomi.util.launchNow
|
import eu.kanade.tachiyomi.util.launchNow
|
||||||
@ -431,6 +432,8 @@ class Downloader(
|
|||||||
if (download.status == Download.DOWNLOADED) {
|
if (download.status == Download.DOWNLOADED) {
|
||||||
tmpDir.renameTo(dirname)
|
tmpDir.renameTo(dirname)
|
||||||
cache.addChapter(dirname, mangaDir, download.manga)
|
cache.addChapter(dirname, mangaDir, download.manga)
|
||||||
|
|
||||||
|
DiskUtil.createNoMediaFile(tmpDir, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +45,8 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
|
|||||||
prefs.edit().putString(key, value).apply()
|
prefs.edit().putString(key, value).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String> {
|
override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String>? {
|
||||||
return prefs.getStringSet(key, defValues)!!
|
return prefs.getStringSet(key, defValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putStringSet(key: String?, values: MutableSet<String>?) {
|
override fun putStringSet(key: String?, values: MutableSet<String>?) {
|
||||||
|
@ -22,13 +22,15 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
private val jsonMime = "application/json; charset=utf-8".toMediaTypeOrNull()
|
private val jsonMime = "application/json; charset=utf-8".toMediaTypeOrNull()
|
||||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
|
|
||||||
fun addLibManga(track: Track): Observable<Track> {
|
fun addLibManga(track: Track): Observable<Track> {
|
||||||
val query = """
|
val query = """
|
||||||
mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|
||||||
SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status)
|
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
|
||||||
{ id status } }
|
| id
|
||||||
"""
|
| status
|
||||||
|
|}
|
||||||
|
|}
|
||||||
|
|""".trimMargin()
|
||||||
val variables = jsonObject(
|
val variables = jsonObject(
|
||||||
"mangaId" to track.media_id,
|
"mangaId" to track.media_id,
|
||||||
"progress" to track.last_chapter_read,
|
"progress" to track.last_chapter_read,
|
||||||
@ -59,14 +61,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
|
|
||||||
fun updateLibManga(track: Track): Observable<Track> {
|
fun updateLibManga(track: Track): Observable<Track> {
|
||||||
val query = """
|
val query = """
|
||||||
mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|
||||||
SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
|
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
|
||||||
id
|
|id
|
||||||
status
|
|status
|
||||||
progress
|
|progress
|
||||||
}
|
|}
|
||||||
}
|
|}
|
||||||
"""
|
|""".trimMargin()
|
||||||
val variables = jsonObject(
|
val variables = jsonObject(
|
||||||
"listId" to track.library_id,
|
"listId" to track.library_id,
|
||||||
"progress" to track.last_chapter_read,
|
"progress" to track.last_chapter_read,
|
||||||
@ -91,29 +93,29 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
|
|
||||||
fun search(search: String): Observable<List<TrackSearch>> {
|
fun search(search: String): Observable<List<TrackSearch>> {
|
||||||
val query = """
|
val query = """
|
||||||
query Search(${'$'}query: String) {
|
|query Search(${'$'}query: String) {
|
||||||
Page (perPage: 50) {
|
|Page (perPage: 50) {
|
||||||
media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|
||||||
id
|
|id
|
||||||
title {
|
|title {
|
||||||
romaji
|
|romaji
|
||||||
}
|
|}
|
||||||
coverImage {
|
|coverImage {
|
||||||
large
|
|large
|
||||||
}
|
|}
|
||||||
type
|
|type
|
||||||
status
|
|status
|
||||||
chapters
|
|chapters
|
||||||
description
|
|description
|
||||||
startDate {
|
|startDate {
|
||||||
year
|
|year
|
||||||
month
|
|month
|
||||||
day
|
|day
|
||||||
}
|
|}
|
||||||
}
|
|}
|
||||||
}
|
|}
|
||||||
}
|
|}
|
||||||
"""
|
|""".trimMargin()
|
||||||
val variables = jsonObject(
|
val variables = jsonObject(
|
||||||
"query" to search
|
"query" to search
|
||||||
)
|
)
|
||||||
@ -143,37 +145,37 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun findLibManga(track: Track, userid: Int) : Observable<Track?> {
|
fun findLibManga(track: Track, userid: Int): Observable<Track?> {
|
||||||
val query = """
|
val query = """
|
||||||
query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|
||||||
Page {
|
|Page {
|
||||||
mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
|
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
|
||||||
id
|
|id
|
||||||
status
|
|status
|
||||||
scoreRaw: score(format: POINT_100)
|
|scoreRaw: score(format: POINT_100)
|
||||||
progress
|
|progress
|
||||||
media{
|
|media {
|
||||||
id
|
|id
|
||||||
title {
|
|title {
|
||||||
romaji
|
|romaji
|
||||||
}
|
|}
|
||||||
coverImage {
|
|coverImage {
|
||||||
large
|
|large
|
||||||
}
|
|}
|
||||||
type
|
|type
|
||||||
status
|
|status
|
||||||
chapters
|
|chapters
|
||||||
description
|
|description
|
||||||
startDate {
|
|startDate {
|
||||||
year
|
|year
|
||||||
month
|
|month
|
||||||
day
|
|day
|
||||||
}
|
|}
|
||||||
}
|
|}
|
||||||
}
|
|}
|
||||||
}
|
|}
|
||||||
}
|
|}
|
||||||
"""
|
|""".trimMargin()
|
||||||
val variables = jsonObject(
|
val variables = jsonObject(
|
||||||
"id" to userid,
|
"id" to userid,
|
||||||
"manga_id" to track.media_id
|
"manga_id" to track.media_id
|
||||||
@ -215,16 +217,15 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
|
|
||||||
fun getCurrentUser(): Observable<Pair<Int, String>> {
|
fun getCurrentUser(): Observable<Pair<Int, String>> {
|
||||||
val query = """
|
val query = """
|
||||||
query User
|
|query User {
|
||||||
{
|
|Viewer {
|
||||||
Viewer {
|
|id
|
||||||
id
|
|mediaListOptions {
|
||||||
mediaListOptions {
|
|scoreFormat
|
||||||
scoreFormat
|
|}
|
||||||
}
|
|}
|
||||||
}
|
|}
|
||||||
}
|
|""".trimMargin()
|
||||||
"""
|
|
||||||
val payload = jsonObject(
|
val payload = jsonObject(
|
||||||
"query" to query
|
"query" to query
|
||||||
)
|
)
|
||||||
@ -247,7 +248,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun jsonToALManga(struct: JsonObject): ALManga{
|
private fun jsonToALManga(struct: JsonObject): ALManga {
|
||||||
val date = try {
|
val date = try {
|
||||||
val date = Calendar.getInstance()
|
val date = Calendar.getInstance()
|
||||||
date.set(struct["startDate"]["year"].nullInt ?: 0, (struct["startDate"]["month"].nullInt ?: 0) - 1,
|
date.set(struct["startDate"]["year"].nullInt ?: 0, (struct["startDate"]["month"].nullInt ?: 0) - 1,
|
||||||
@ -262,11 +263,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
date, struct["chapters"].nullInt ?: 0)
|
date, struct["chapters"].nullInt ?: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun jsonToALUserManga(struct: JsonObject): ALUserManga{
|
private fun jsonToALUserManga(struct: JsonObject): ALUserManga {
|
||||||
return ALUserManga(struct["id"].asLong, struct["status"].asString, struct["scoreRaw"].asInt, struct["progress"].asInt, jsonToALManga(struct["media"].obj) )
|
return ALUserManga(struct["id"].asLong, struct["status"].asString, struct["scoreRaw"].asInt, struct["progress"].asInt, jsonToALManga(struct["media"].obj))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val clientId = "385"
|
private const val clientId = "385"
|
||||||
private const val clientUrl = "tachiyomi://anilist-auth"
|
private const val clientUrl = "tachiyomi://anilist-auth"
|
||||||
|
@ -18,13 +18,12 @@ class KitsuSearchManga(obj: JsonObject) {
|
|||||||
private val synopsis by obj.byString
|
private val synopsis by obj.byString
|
||||||
private var startDate = obj.get("startDate").nullString?.let {
|
private var startDate = obj.get("startDate").nullString?.let {
|
||||||
val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||||
outputDf.format(Date(it!!.toLong() * 1000))
|
outputDf.format(Date(it.toLong() * 1000))
|
||||||
}
|
}
|
||||||
private val endDate = obj.get("endDate").nullString
|
private val endDate = obj.get("endDate").nullString
|
||||||
|
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
open fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply {
|
fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply {
|
||||||
media_id = this@KitsuSearchManga.id
|
media_id = this@KitsuSearchManga.id
|
||||||
title = canonicalTitle
|
title = canonicalTitle
|
||||||
total_chapters = chapterCount ?: 0
|
total_chapters = chapterCount ?: 0
|
||||||
@ -55,7 +54,7 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
|
|||||||
private val ratingTwenty = obj["attributes"].obj.get("ratingTwenty").nullString
|
private val ratingTwenty = obj["attributes"].obj.get("ratingTwenty").nullString
|
||||||
val progress by obj["attributes"].byInt
|
val progress by obj["attributes"].byInt
|
||||||
|
|
||||||
open fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply {
|
fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply {
|
||||||
media_id = libraryId
|
media_id = libraryId
|
||||||
title = canonicalTitle
|
title = canonicalTitle
|
||||||
total_chapters = chapterCount ?: 0
|
total_chapters = chapterCount ?: 0
|
||||||
|
@ -113,11 +113,7 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
.toCompletable()
|
.toCompletable()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to login again if cookies have been cleared but credentials are still filled
|
fun refreshLogin() {
|
||||||
fun ensureLoggedIn() {
|
|
||||||
if (isAuthorized) return
|
|
||||||
if (!isLogged) throw Exception("MAL Login Credentials not found")
|
|
||||||
|
|
||||||
val username = getUsername()
|
val username = getUsername()
|
||||||
val password = getPassword()
|
val password = getPassword()
|
||||||
logout()
|
logout()
|
||||||
@ -132,6 +128,14 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attempt to login again if cookies have been cleared but credentials are still filled
|
||||||
|
fun ensureLoggedIn() {
|
||||||
|
if (isAuthorized) return
|
||||||
|
if (!isLogged) throw Exception("MAL Login Credentials not found")
|
||||||
|
|
||||||
|
refreshLogin()
|
||||||
|
}
|
||||||
|
|
||||||
override fun logout() {
|
override fun logout() {
|
||||||
super.logout()
|
super.logout()
|
||||||
preferences.trackToken(this).delete()
|
preferences.trackToken(this).delete()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.myanimelist
|
package eu.kanade.tachiyomi.data.track.myanimelist
|
||||||
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
@ -11,18 +12,27 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor
|
|||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
myanimelist.ensureLoggedIn()
|
myanimelist.ensureLoggedIn()
|
||||||
|
|
||||||
var request = chain.request()
|
val request = chain.request()
|
||||||
request.body?.let {
|
var response = chain.proceed(updateRequest(request))
|
||||||
|
|
||||||
|
if (response.code == 400){
|
||||||
|
myanimelist.refreshLogin()
|
||||||
|
response = chain.proceed(updateRequest(request))
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateRequest(request: Request): Request {
|
||||||
|
return request.body?.let {
|
||||||
val contentType = it.contentType().toString()
|
val contentType = it.contentType().toString()
|
||||||
val updatedBody = when {
|
val updatedBody = when {
|
||||||
contentType.contains("x-www-form-urlencoded") -> updateFormBody(it)
|
contentType.contains("x-www-form-urlencoded") -> updateFormBody(it)
|
||||||
contentType.contains("json") -> updateJsonBody(it)
|
contentType.contains("json") -> updateJsonBody(it)
|
||||||
else -> it
|
else -> it
|
||||||
}
|
}
|
||||||
request = request.newBuilder().post(updatedBody).build()
|
request.newBuilder().post(updatedBody).build()
|
||||||
}
|
} ?: request
|
||||||
|
|
||||||
return chain.proceed(request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bodyToString(requestBody: RequestBody): String {
|
private fun bodyToString(requestBody: RequestBody): String {
|
||||||
|
@ -7,6 +7,10 @@ import android.content.IntentFilter
|
|||||||
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.util.launchNow
|
import eu.kanade.tachiyomi.util.launchNow
|
||||||
|
import kotlinx.coroutines.CoroutineStart
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcast receiver that listens for the system's packages installed, updated or removed, and only
|
* Broadcast receiver that listens for the system's packages installed, updated or removed, and only
|
||||||
@ -90,7 +94,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
|
|||||||
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
|
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
|
||||||
val pkgName = getPackageNameFromIntent(intent) ?:
|
val pkgName = getPackageNameFromIntent(intent) ?:
|
||||||
return LoadResult.Error("Package name not found")
|
return LoadResult.Error("Package name not found")
|
||||||
return ExtensionLoader.loadExtensionFromPkgName(context, pkgName)
|
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT, { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }).await()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,20 +2,24 @@ package eu.kanade.tachiyomi.source
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.model.*
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.util.ChapterRecognition
|
import eu.kanade.tachiyomi.util.ChapterRecognition
|
||||||
|
import eu.kanade.tachiyomi.util.ComparatorUtil.CaseInsensitiveNaturalComparator
|
||||||
import eu.kanade.tachiyomi.util.DiskUtil
|
import eu.kanade.tachiyomi.util.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.EpubFile
|
import eu.kanade.tachiyomi.util.EpubFile
|
||||||
import eu.kanade.tachiyomi.util.ImageUtil
|
import eu.kanade.tachiyomi.util.ImageUtil
|
||||||
import junrar.Archive
|
import junrar.Archive
|
||||||
import junrar.rarfile.FileHeader
|
import junrar.rarfile.FileHeader
|
||||||
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import timber.log.Timber
|
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
|
||||||
import java.util.Comparator
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
@ -125,7 +129,6 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
|||||||
override fun fetchMangaDetails(manga: SManga) = Observable.just(manga)
|
override fun fetchMangaDetails(manga: SManga) = Observable.just(manga)
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
val comparator = CaseInsensitiveSimpleNaturalComparator.getInstance<String>()
|
|
||||||
val chapters = getBaseDirectories(context)
|
val chapters = getBaseDirectories(context)
|
||||||
.mapNotNull { File(it, manga.url).listFiles()?.toList() }
|
.mapNotNull { File(it, manga.url).listFiles()?.toList() }
|
||||||
.flatten()
|
.flatten()
|
||||||
@ -146,7 +149,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
|||||||
}
|
}
|
||||||
.sortedWith(Comparator { c1, c2 ->
|
.sortedWith(Comparator { c1, c2 ->
|
||||||
val c = c2.chapter_number.compareTo(c1.chapter_number)
|
val c = c2.chapter_number.compareTo(c1.chapter_number)
|
||||||
if (c == 0) comparator.compare(c2.name, c1.name) else c
|
if (c == 0) CaseInsensitiveNaturalComparator.compare(c2.name, c1.name) else c
|
||||||
})
|
})
|
||||||
|
|
||||||
return Observable.just(chapters)
|
return Observable.just(chapters)
|
||||||
@ -189,20 +192,19 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
|||||||
|
|
||||||
private fun updateCover(chapter: SChapter, manga: SManga): File? {
|
private fun updateCover(chapter: SChapter, manga: SManga): File? {
|
||||||
val format = getFormat(chapter)
|
val format = getFormat(chapter)
|
||||||
val comparator = CaseInsensitiveSimpleNaturalComparator.getInstance<String>()
|
|
||||||
return when (format) {
|
return when (format) {
|
||||||
is Format.Directory -> {
|
is Format.Directory -> {
|
||||||
val entry = format.file.listFiles()
|
val entry = format.file.listFiles()
|
||||||
.sortedWith(Comparator<File> { f1, f2 -> comparator.compare(f1.name, f2.name) })
|
.sortedWith(Comparator<File> { f1, f2 -> CaseInsensitiveNaturalComparator.compare(f1.name, f2.name) })
|
||||||
.find { !it.isDirectory && ImageUtil.isImage(it.name, { FileInputStream(it) }) }
|
.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
|
||||||
|
|
||||||
entry?.let { updateCover(context, manga, it.inputStream())}
|
entry?.let { updateCover(context, manga, it.inputStream())}
|
||||||
}
|
}
|
||||||
is Format.Zip -> {
|
is Format.Zip -> {
|
||||||
ZipFile(format.file).use { zip ->
|
ZipFile(format.file).use { zip ->
|
||||||
val entry = zip.entries().toList()
|
val entry = zip.entries().toList()
|
||||||
.sortedWith(Comparator<ZipEntry> { f1, f2 -> comparator.compare(f1.name, f2.name) })
|
.sortedWith(Comparator<ZipEntry> { f1, f2 -> CaseInsensitiveNaturalComparator.compare(f1.name, f2.name) })
|
||||||
.find { !it.isDirectory && ImageUtil.isImage(it.name, { zip.getInputStream(it) }) }
|
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
|
||||||
|
|
||||||
entry?.let { updateCover(context, manga, zip.getInputStream(it) )}
|
entry?.let { updateCover(context, manga, zip.getInputStream(it) )}
|
||||||
}
|
}
|
||||||
@ -210,8 +212,8 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
|||||||
is Format.Rar -> {
|
is Format.Rar -> {
|
||||||
Archive(format.file).use { archive ->
|
Archive(format.file).use { archive ->
|
||||||
val entry = archive.fileHeaders
|
val entry = archive.fileHeaders
|
||||||
.sortedWith(Comparator<FileHeader> { f1, f2 -> comparator.compare(f1.fileNameString, f2.fileNameString) })
|
.sortedWith(Comparator<FileHeader> { f1, f2 -> CaseInsensitiveNaturalComparator.compare(f1.fileNameString, f2.fileNameString) })
|
||||||
.find { !it.isDirectory && ImageUtil.isImage(it.fileNameString, { archive.getInputStream(it) }) }
|
.find { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } }
|
||||||
|
|
||||||
entry?.let { updateCover(context, manga, archive.getInputStream(it) )}
|
entry?.let { updateCover(context, manga, archive.getInputStream(it) )}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,9 @@ import android.view.ViewGroup
|
|||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.drawerlayout.widget.DrawerLayout
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.f2prateek.rx.preferences.Preference
|
import com.f2prateek.rx.preferences.Preference
|
||||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
@ -207,10 +210,11 @@ open class BrowseCatalogueController(bundle: Bundle) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
val recycler = if (presenter.isListMode) {
|
val recycler = if (presenter.isListMode) {
|
||||||
androidx.recyclerview.widget.RecyclerView(view.context).apply {
|
RecyclerView(view.context).apply {
|
||||||
id = R.id.recycler
|
id = R.id.recycler
|
||||||
layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
addItemDecoration(androidx.recyclerview.widget.DividerItemDecoration(context, androidx.recyclerview.widget.DividerItemDecoration.VERTICAL))
|
layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||||
|
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(catalogue_view.inflate(R.layout.catalogue_recycler_autofit) as AutofitRecyclerView).apply {
|
(catalogue_view.inflate(R.layout.catalogue_recycler_autofit) as AutofitRecyclerView).apply {
|
||||||
@ -377,9 +381,8 @@ open class BrowseCatalogueController(bundle: Bundle) :
|
|||||||
adapter.onLoadMoreComplete(null)
|
adapter.onLoadMoreComplete(null)
|
||||||
hideProgressBar()
|
hideProgressBar()
|
||||||
|
|
||||||
val message = if (error is NoResultsException) "No results found" else (error.message ?: "")
|
|
||||||
|
|
||||||
snack?.dismiss()
|
snack?.dismiss()
|
||||||
|
val message = if (error is NoResultsException) catalogue_view.context.getString(R.string.no_results_found) else (error.message ?: "")
|
||||||
snack = catalouge_layout?.snack(message, Snackbar.LENGTH_INDEFINITE) {
|
snack = catalouge_layout?.snack(message, Snackbar.LENGTH_INDEFINITE) {
|
||||||
setAction(R.string.action_retry) {
|
setAction(R.string.action_retry) {
|
||||||
// If not the first page, show bottom progress bar.
|
// If not the first page, show bottom progress bar.
|
||||||
@ -388,8 +391,8 @@ open class BrowseCatalogueController(bundle: Bundle) :
|
|||||||
adapter.addScrollableFooterWithDelay(item, 0, true)
|
adapter.addScrollableFooterWithDelay(item, 0, true)
|
||||||
} else {
|
} else {
|
||||||
showProgressBar()
|
showProgressBar()
|
||||||
}
|
|
||||||
presenter.requestNext()
|
presenter.requestNext()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs:
|
|||||||
val view = inflate(R.layout.catalogue_drawer_content)
|
val view = inflate(R.layout.catalogue_drawer_content)
|
||||||
((view as ViewGroup).getChildAt(1) as ViewGroup).addView(recycler)
|
((view as ViewGroup).getChildAt(1) as ViewGroup).addView(recycler)
|
||||||
addView(view)
|
addView(view)
|
||||||
title.text = context?.getString(R.string.source_search_options)
|
title.text = context.getString(R.string.source_search_options)
|
||||||
search_btn.setOnClickListener { onSearchClicked() }
|
search_btn.setOnClickListener { onSearchClicked() }
|
||||||
reset_btn.setOnClickListener { onResetClicked() }
|
reset_btn.setOnClickListener { onResetClicked() }
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@ class CatalogueSearchAdapter(val controller: CatalogueSearchController) :
|
|||||||
*/
|
*/
|
||||||
private var bundle = Bundle()
|
private var bundle = Bundle()
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: androidx.recyclerview.widget.RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: androidx.recyclerview.widget.RecyclerView.ViewHolder, position: Int, payloads: List<Any?>) {
|
||||||
super.onBindViewHolder(holder, position)
|
super.onBindViewHolder(holder, position, payloads)
|
||||||
restoreHolderState(holder)
|
restoreHolderState(holder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +213,6 @@ class MangaController : RxController, TabbedController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val FROM_CATALOGUE_EXTRA = "from_catalogue"
|
const val FROM_CATALOGUE_EXTRA = "from_catalogue"
|
||||||
const val MANGA_EXTRA = "manga"
|
const val MANGA_EXTRA = "manga"
|
||||||
|
|
||||||
@ -225,5 +224,4 @@ class MangaController : RxController, TabbedController {
|
|||||||
.apply { isAccessible = true }
|
.apply { isAccessible = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
|||||||
import kotlinx.android.synthetic.main.catalogue_main_controller_card.*
|
import kotlinx.android.synthetic.main.catalogue_main_controller_card.*
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
|
import kotlinx.android.synthetic.main.catalogue_main_controller_card.title
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item that contains the selection header.
|
* Item that contains the selection header.
|
||||||
@ -38,7 +39,7 @@ class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
|
|||||||
|
|
||||||
class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : BaseFlexibleViewHolder(view, adapter) {
|
class Holder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) : BaseFlexibleViewHolder(view, adapter) {
|
||||||
init {
|
init {
|
||||||
title.text = "Please select a source to migrate from"
|
title.text = view.context.getString(R.string.migration_selection_prompt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,7 +507,7 @@ class ReaderPresenter(
|
|||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribeFirst(
|
.subscribeFirst(
|
||||||
{ view, file -> view.onShareImageResult(file) },
|
{ view, file -> view.onShareImageResult(file) },
|
||||||
{ view, error -> /* Empty */ }
|
{ _, _ -> /* Empty */ }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.reader.loader
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
|
import eu.kanade.tachiyomi.util.ComparatorUtil.CaseInsensitiveNaturalComparator
|
||||||
import eu.kanade.tachiyomi.util.ImageUtil
|
import eu.kanade.tachiyomi.util.ImageUtil
|
||||||
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
@ -18,11 +18,9 @@ class DirectoryPageLoader(val file: File) : PageLoader() {
|
|||||||
* comparator.
|
* comparator.
|
||||||
*/
|
*/
|
||||||
override fun getPages(): Observable<List<ReaderPage>> {
|
override fun getPages(): Observable<List<ReaderPage>> {
|
||||||
val comparator = CaseInsensitiveSimpleNaturalComparator.getInstance<String>()
|
|
||||||
|
|
||||||
return file.listFiles()
|
return file.listFiles()
|
||||||
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
|
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
|
||||||
.sortedWith(Comparator<File> { f1, f2 -> comparator.compare(f1.name, f2.name) })
|
.sortedWith(Comparator<File> { f1, f2 -> CaseInsensitiveNaturalComparator.compare(f1.name, f2.name) })
|
||||||
.mapIndexed { i, file ->
|
.mapIndexed { i, file ->
|
||||||
val streamFn = { FileInputStream(file) }
|
val streamFn = { FileInputStream(file) }
|
||||||
ReaderPage(i).apply {
|
ReaderPage(i).apply {
|
||||||
|
@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
|||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loader used to load a chapter from the downloaded chapters.
|
* Loader used to load a chapter from the downloaded chapters.
|
||||||
|
@ -2,10 +2,10 @@ package eu.kanade.tachiyomi.ui.reader.loader
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
|
import eu.kanade.tachiyomi.util.ComparatorUtil.CaseInsensitiveNaturalComparator
|
||||||
import eu.kanade.tachiyomi.util.ImageUtil
|
import eu.kanade.tachiyomi.util.ImageUtil
|
||||||
import junrar.Archive
|
import junrar.Archive
|
||||||
import junrar.rarfile.FileHeader
|
import junrar.rarfile.FileHeader
|
||||||
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@ -42,11 +42,9 @@ class RarPageLoader(file: File) : PageLoader() {
|
|||||||
* comparator.
|
* comparator.
|
||||||
*/
|
*/
|
||||||
override fun getPages(): Observable<List<ReaderPage>> {
|
override fun getPages(): Observable<List<ReaderPage>> {
|
||||||
val comparator = CaseInsensitiveSimpleNaturalComparator.getInstance<String>()
|
|
||||||
|
|
||||||
return archive.fileHeaders
|
return archive.fileHeaders
|
||||||
.filter { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } }
|
.filter { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } }
|
||||||
.sortedWith(Comparator<FileHeader> { f1, f2 -> comparator.compare(f1.fileNameString, f2.fileNameString) })
|
.sortedWith(Comparator<FileHeader> { f1, f2 -> CaseInsensitiveNaturalComparator.compare(f1.fileNameString, f2.fileNameString) })
|
||||||
.mapIndexed { i, header ->
|
.mapIndexed { i, header ->
|
||||||
val streamFn = { getStream(header) }
|
val streamFn = { getStream(header) }
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.reader.loader
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
|
import eu.kanade.tachiyomi.util.ComparatorUtil.CaseInsensitiveNaturalComparator
|
||||||
import eu.kanade.tachiyomi.util.ImageUtil
|
import eu.kanade.tachiyomi.util.ImageUtil
|
||||||
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
@ -32,11 +32,9 @@ class ZipPageLoader(file: File) : PageLoader() {
|
|||||||
* comparator.
|
* comparator.
|
||||||
*/
|
*/
|
||||||
override fun getPages(): Observable<List<ReaderPage>> {
|
override fun getPages(): Observable<List<ReaderPage>> {
|
||||||
val comparator = CaseInsensitiveSimpleNaturalComparator.getInstance<String>()
|
|
||||||
|
|
||||||
return zip.entries().toList()
|
return zip.entries().toList()
|
||||||
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
|
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
|
||||||
.sortedWith(Comparator<ZipEntry> { f1, f2 -> comparator.compare(f1.name, f2.name) })
|
.sortedWith(Comparator<ZipEntry> { f1, f2 -> CaseInsensitiveNaturalComparator.compare(f1.name, f2.name) })
|
||||||
.mapIndexed { i, entry ->
|
.mapIndexed { i, entry ->
|
||||||
val streamFn = { zip.getInputStream(entry) }
|
val streamFn = { zip.getInputStream(entry) }
|
||||||
ReaderPage(i).apply {
|
ReaderPage(i).apply {
|
||||||
|
@ -50,6 +50,7 @@ class PagerTransitionHolder(
|
|||||||
private var textView = TextView(context).apply {
|
private var textView = TextView(context).apply {
|
||||||
//if (Build.VERSION.SDK_INT >= 23)
|
//if (Build.VERSION.SDK_INT >= 23)
|
||||||
//setTextColor(context.getResourceColor(R.attr.))
|
//setTextColor(context.getResourceColor(R.attr.))
|
||||||
|
textSize = 17.5F
|
||||||
wrapContent()
|
wrapContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ class WebtoonViewer(val activity: ReaderActivity) : BaseViewer {
|
|||||||
recycler.longTapListener = f@ { event ->
|
recycler.longTapListener = f@ { event ->
|
||||||
if (activity.menuVisible || config.longTapEnabled) {
|
if (activity.menuVisible || config.longTapEnabled) {
|
||||||
val child = recycler.findChildViewUnder(event.x, event.y)
|
val child = recycler.findChildViewUnder(event.x, event.y)
|
||||||
if(child != null) {
|
if (child != null) {
|
||||||
val position = recycler.getChildAdapterPosition(child)
|
val position = recycler.getChildAdapterPosition(child)
|
||||||
val item = adapter.items.getOrNull(position)
|
val item = adapter.items.getOrNull(position)
|
||||||
if (item is ReaderPage) {
|
if (item is ReaderPage) {
|
||||||
|
@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.util.DiskUtil
|
|
||||||
import eu.kanade.tachiyomi.util.getFilePicker
|
import eu.kanade.tachiyomi.util.getFilePicker
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -45,15 +44,6 @@ class SettingsDownloadController : SettingsController() {
|
|||||||
.subscribeUntilDestroy { path ->
|
.subscribeUntilDestroy { path ->
|
||||||
val dir = UniFile.fromUri(context, Uri.parse(path))
|
val dir = UniFile.fromUri(context, Uri.parse(path))
|
||||||
summary = dir.filePath ?: path
|
summary = dir.filePath ?: path
|
||||||
|
|
||||||
// Don't display downloaded chapters in gallery apps creating .nomedia
|
|
||||||
if (dir != null && dir.exists()) {
|
|
||||||
val nomedia = dir.findFile(".nomedia")
|
|
||||||
if (nomedia == null) {
|
|
||||||
dir.createFile(".nomedia")
|
|
||||||
applicationContext?.let { DiskUtil.scanMedia(it, dir.uri) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switchPreference {
|
switchPreference {
|
||||||
|
@ -91,7 +91,7 @@ object ChapterRecognition {
|
|||||||
* @param chapter chapter object
|
* @param chapter chapter object
|
||||||
* @return true if volume is found
|
* @return true if volume is found
|
||||||
*/
|
*/
|
||||||
fun updateChapter(match: MatchResult?, chapter: SChapter): Boolean {
|
private fun updateChapter(match: MatchResult?, chapter: SChapter): Boolean {
|
||||||
match?.let {
|
match?.let {
|
||||||
val initial = it.groups[1]?.value?.toFloat()!!
|
val initial = it.groups[1]?.value?.toFloat()!!
|
||||||
val subChapterDecimal = it.groups[2]?.value
|
val subChapterDecimal = it.groups[2]?.value
|
||||||
@ -109,12 +109,12 @@ object ChapterRecognition {
|
|||||||
* @param alpha alpha value of regex
|
* @param alpha alpha value of regex
|
||||||
* @return decimal/alpha float value
|
* @return decimal/alpha float value
|
||||||
*/
|
*/
|
||||||
fun checkForDecimal(decimal: String?, alpha: String?): Float {
|
private fun checkForDecimal(decimal: String?, alpha: String?): Float {
|
||||||
if (!decimal.isNullOrEmpty())
|
if (!decimal.isNullOrEmpty())
|
||||||
return decimal?.toFloat()!!
|
return decimal.toFloat()
|
||||||
|
|
||||||
if (!alpha.isNullOrEmpty()) {
|
if (!alpha.isNullOrEmpty()) {
|
||||||
if (alpha!!.contains("extra"))
|
if (alpha.contains("extra"))
|
||||||
return .99f
|
return .99f
|
||||||
|
|
||||||
if (alpha.contains("omake"))
|
if (alpha.contains("omake"))
|
||||||
@ -138,7 +138,7 @@ object ChapterRecognition {
|
|||||||
* x.a -> x.1, x.b -> x.2, etc
|
* x.a -> x.1, x.b -> x.2, etc
|
||||||
*/
|
*/
|
||||||
private fun parseAlphaPostFix(alpha: Char): Float {
|
private fun parseAlphaPostFix(alpha: Char): Float {
|
||||||
return ("0." + Integer.toString(alpha.toInt() - 96)).toFloat()
|
return ("0." + (alpha.toInt() - 96).toString()).toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
|
object ComparatorUtil {
|
||||||
|
val CaseInsensitiveNaturalComparator = compareBy<String, String>(String.CASE_INSENSITIVE_ORDER) { it }.then(naturalOrder())
|
||||||
|
}
|
@ -172,11 +172,11 @@ fun Context.isServiceRunning(serviceClass: Class<*>): Boolean {
|
|||||||
*/
|
*/
|
||||||
fun Context.openInBrowser(url: String) {
|
fun Context.openInBrowser(url: String) {
|
||||||
try {
|
try {
|
||||||
val url = Uri.parse(url)
|
val parsedUrl = Uri.parse(url)
|
||||||
val intent = CustomTabsIntent.Builder()
|
val intent = CustomTabsIntent.Builder()
|
||||||
.setToolbarColor(getResourceColor(R.attr.colorPrimary))
|
.setToolbarColor(getResourceColor(R.attr.colorPrimary))
|
||||||
.build()
|
.build()
|
||||||
intent.launchUrl(this, url)
|
intent.launchUrl(this, parsedUrl)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
toast(e.message)
|
toast(e.message)
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
|
||||||
fun launchUI(block: suspend CoroutineScope.() -> Unit): Job =
|
fun launchUI(block: suspend CoroutineScope.() -> Unit): Job =
|
||||||
GlobalScope.launch(Dispatchers.Main,CoroutineStart.DEFAULT,block)
|
GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT, block)
|
||||||
|
|
||||||
fun launchNow(block: suspend CoroutineScope.() -> Unit): Job =
|
fun launchNow(block: suspend CoroutineScope.() -> Unit): Job =
|
||||||
GlobalScope.launch(Dispatchers.Main,CoroutineStart.UNDISPATCHED,block)
|
GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED, block)
|
||||||
|
@ -7,6 +7,7 @@ import android.os.Build
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.os.EnvironmentCompat
|
import androidx.core.os.EnvironmentCompat
|
||||||
|
import com.hippo.unifile.UniFile
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object DiskUtil {
|
object DiskUtil {
|
||||||
@ -54,6 +55,19 @@ object DiskUtil {
|
|||||||
return directories
|
return directories
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't display downloaded chapters in gallery apps creating `.nomedia`.
|
||||||
|
*/
|
||||||
|
fun createNoMediaFile(dir: UniFile?, context: Context?) {
|
||||||
|
if (dir != null && dir.exists()) {
|
||||||
|
val nomedia = dir.findFile(".nomedia")
|
||||||
|
if (nomedia == null) {
|
||||||
|
dir.createFile(".nomedia")
|
||||||
|
context?.let { scanMedia(it, dir.uri) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scans the given file so that it can be shown in gallery apps, for example.
|
* Scans the given file so that it can be shown in gallery apps, for example.
|
||||||
*/
|
*/
|
||||||
|
@ -343,6 +343,7 @@
|
|||||||
<string name="select_source">Select a source</string>
|
<string name="select_source">Select a source</string>
|
||||||
<string name="no_valid_sources">Please enable at least one valid source</string>
|
<string name="no_valid_sources">Please enable at least one valid source</string>
|
||||||
<string name="no_more_results">No more results</string>
|
<string name="no_more_results">No more results</string>
|
||||||
|
<string name="no_results_found">No results found</string>
|
||||||
<string name="local_source">Local manga</string>
|
<string name="local_source">Local manga</string>
|
||||||
<string name="other_source">Other</string>
|
<string name="other_source">Other</string>
|
||||||
<string name="invalid_combination">Default can\'t be selected with other categories</string>
|
<string name="invalid_combination">Default can\'t be selected with other categories</string>
|
||||||
@ -477,6 +478,7 @@
|
|||||||
<!-- Source migration screen -->
|
<!-- Source migration screen -->
|
||||||
<string name="migration_info">Tap to select the source to migrate from</string>
|
<string name="migration_info">Tap to select the source to migrate from</string>
|
||||||
<string name="migration_dialog_what_to_include">Select data to include</string>
|
<string name="migration_dialog_what_to_include">Select data to include</string>
|
||||||
|
<string name="migration_selection_prompt">Select a source to migrate from</string>
|
||||||
<string name="select">Select</string>
|
<string name="select">Select</string>
|
||||||
<string name="migrate">Migrate</string>
|
<string name="migrate">Migrate</string>
|
||||||
<string name="copy">Copy</string>
|
<string name="copy">Copy</string>
|
||||||
|
@ -136,8 +136,9 @@
|
|||||||
<style name="Theme.Widget" />
|
<style name="Theme.Widget" />
|
||||||
|
|
||||||
<style name="Theme.Widget.FAB">
|
<style name="Theme.Widget.FAB">
|
||||||
<item name="android:layout_height">@dimen/fab_size</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_width">@dimen/fab_size</item>
|
<item name="android:layout_width">wrap_content</item>
|
||||||
|
<item name="fabCustomSize">@dimen/fab_size</item>
|
||||||
<item name="android:layout_gravity">bottom|end</item>
|
<item name="android:layout_gravity">bottom|end</item>
|
||||||
<item name="android:layout_margin">@dimen/fab_margin</item>
|
<item name="android:layout_margin">@dimen/fab_margin</item>
|
||||||
<item name="android:scaleType">fitCenter</item>
|
<item name="android:scaleType">fitCenter</item>
|
||||||
|
@ -10,7 +10,7 @@ buildscript {
|
|||||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.22.0'
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.22.0'
|
||||||
classpath 'com.github.zellius:android-shortcut-gradle-plugin:0.1.2'
|
classpath 'com.github.zellius:android-shortcut-gradle-plugin:0.1.2'
|
||||||
classpath 'com.google.gms:google-services:4.3.2'
|
classpath 'com.google.gms:google-services:4.3.3'
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user