Merge remote-tracking branch 'upstream/master' into Automatic_Reader_Background
This commit is contained in:
commit
b4b78e0f6b
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github: inorichi
|
||||||
|
ko_fi: inorichi
|
@ -11,7 +11,7 @@ Tachiyomi is a free and open source manga reader for Android.
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
Features include:
|
Features include:
|
||||||
* Online reading from sources such as KissManga, MangaFox, [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
|
* 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/), and [Kitsu](https://kitsu.io/explore/anime) support
|
||||||
|
@ -62,8 +62,8 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.ShikomoriLoginActivity"
|
android:name=".ui.setting.ShikimoriLoginActivity"
|
||||||
android:label="Shikomori">
|
android:label="Shikimori">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
@ -82,6 +82,11 @@ interface MangaQueries : DbProvider {
|
|||||||
.withPutResolver(MangaViewerPutResolver())
|
.withPutResolver(MangaViewerPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
fun updateMangaTitle(manga: Manga) = db.put()
|
||||||
|
.`object`(manga)
|
||||||
|
.withPutResolver(MangaTitlePutResolver())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
||||||
|
|
||||||
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.resolvers
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
|
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
|
|
||||||
|
class MangaTitlePutResolver : PutResolver<Manga>() {
|
||||||
|
|
||||||
|
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
|
||||||
|
val updateQuery = mapToUpdateQuery(manga)
|
||||||
|
val contentValues = mapToContentValues(manga)
|
||||||
|
|
||||||
|
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
||||||
|
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.where("${MangaTable.COL_ID} = ?")
|
||||||
|
.whereArgs(manga.id)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||||
|
put(MangaTable.COL_TITLE, manga.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -29,6 +29,8 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val colorFilterValue = "color_filter_value"
|
const val colorFilterValue = "color_filter_value"
|
||||||
|
|
||||||
|
const val colorFilterMode = "color_filter_mode"
|
||||||
|
|
||||||
const val defaultViewer = "pref_default_viewer_key"
|
const val defaultViewer = "pref_default_viewer_key"
|
||||||
|
|
||||||
const val imageScaleType = "pref_image_scale_type_key"
|
const val imageScaleType = "pref_image_scale_type_key"
|
||||||
|
@ -57,6 +57,8 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun colorFilterValue() = rxPrefs.getInteger(Keys.colorFilterValue, 0)
|
fun colorFilterValue() = rxPrefs.getInteger(Keys.colorFilterValue, 0)
|
||||||
|
|
||||||
|
fun colorFilterMode() = rxPrefs.getInteger(Keys.colorFilterMode, 0)
|
||||||
|
|
||||||
fun defaultViewer() = prefs.getInt(Keys.defaultViewer, 1)
|
fun defaultViewer() = prefs.getInt(Keys.defaultViewer, 1)
|
||||||
|
|
||||||
fun imageScaleType() = rxPrefs.getInteger(Keys.imageScaleType, 1)
|
fun imageScaleType() = rxPrefs.getInteger(Keys.imageScaleType, 1)
|
||||||
|
@ -4,7 +4,7 @@ import android.content.Context
|
|||||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.Kitsu
|
import eu.kanade.tachiyomi.data.track.kitsu.Kitsu
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.Myanimelist
|
import eu.kanade.tachiyomi.data.track.myanimelist.Myanimelist
|
||||||
import eu.kanade.tachiyomi.data.track.shikomori.Shikomori
|
import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
|
||||||
|
|
||||||
class TrackManager(private val context: Context) {
|
class TrackManager(private val context: Context) {
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ class TrackManager(private val context: Context) {
|
|||||||
const val MYANIMELIST = 1
|
const val MYANIMELIST = 1
|
||||||
const val ANILIST = 2
|
const val ANILIST = 2
|
||||||
const val KITSU = 3
|
const val KITSU = 3
|
||||||
const val SHIKOMORI = 4
|
const val SHIKIMORI = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
val myAnimeList = Myanimelist(context, MYANIMELIST)
|
val myAnimeList = Myanimelist(context, MYANIMELIST)
|
||||||
@ -21,9 +21,9 @@ class TrackManager(private val context: Context) {
|
|||||||
|
|
||||||
val kitsu = Kitsu(context, KITSU)
|
val kitsu = Kitsu(context, KITSU)
|
||||||
|
|
||||||
val shikomori = Shikomori(context, SHIKOMORI)
|
val shikimori = Shikimori(context, SHIKIMORI)
|
||||||
|
|
||||||
val services = listOf(myAnimeList, aniList, kitsu, shikomori)
|
val services = listOf(myAnimeList, aniList, kitsu, shikimori)
|
||||||
|
|
||||||
fun getService(id: Int) = services.find { it.id == id }
|
fun getService(id: Int) = services.find { it.id == id }
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.shikomori
|
package eu.kanade.tachiyomi.data.track.shikimori
|
||||||
|
|
||||||
data class OAuth(
|
data class OAuth(
|
||||||
val access_token: String,
|
val access_token: String,
|
@ -1,7 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.shikomori
|
package eu.kanade.tachiyomi.data.track.shikimori
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.util.Log
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
@ -11,7 +12,7 @@ import rx.Completable
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class Shikomori(private val context: Context, id: Int) : TrackService(id) {
|
class Shikimori(private val context: Context, id: Int) : TrackService(id) {
|
||||||
|
|
||||||
override fun getScoreList(): List<String> {
|
override fun getScoreList(): List<String> {
|
||||||
return IntRange(0, 10).map(Int::toString)
|
return IntRange(0, 10).map(Int::toString)
|
||||||
@ -75,15 +76,15 @@ class Shikomori(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
const val DEFAULT_SCORE = 0
|
const val DEFAULT_SCORE = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override val name = "Shikomori"
|
override val name = "Shikimori"
|
||||||
|
|
||||||
private val gson: Gson by injectLazy()
|
private val gson: Gson by injectLazy()
|
||||||
|
|
||||||
private val interceptor by lazy { ShikomoriInterceptor(this, gson) }
|
private val interceptor by lazy { ShikimoriInterceptor(this, gson) }
|
||||||
|
|
||||||
private val api by lazy { ShikomoriApi(client, interceptor) }
|
private val api by lazy { ShikimoriApi(client, interceptor) }
|
||||||
|
|
||||||
override fun getLogo() = R.drawable.shikomori
|
override fun getLogo() = R.drawable.shikimori
|
||||||
|
|
||||||
override fun getLogoColor() = Color.rgb(40, 40, 40)
|
override fun getLogoColor() = Color.rgb(40, 40, 40)
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.shikomori
|
package eu.kanade.tachiyomi.data.track.shikimori
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.github.salomonbrys.kotson.array
|
import com.github.salomonbrys.kotson.array
|
||||||
@ -18,7 +18,7 @@ import okhttp3.*
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class ShikomoriApi(private val client: OkHttpClient, interceptor: ShikomoriInterceptor) {
|
class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) {
|
||||||
|
|
||||||
private val gson: Gson by injectLazy()
|
private val gson: Gson by injectLazy()
|
||||||
private val parser = JsonParser()
|
private val parser = JsonParser()
|
||||||
@ -33,7 +33,7 @@ class ShikomoriApi(private val client: OkHttpClient, interceptor: ShikomoriInter
|
|||||||
"target_type" to "Manga",
|
"target_type" to "Manga",
|
||||||
"chapters" to track.last_chapter_read,
|
"chapters" to track.last_chapter_read,
|
||||||
"score" to track.score.toInt(),
|
"score" to track.score.toInt(),
|
||||||
"status" to track.toShikomoriStatus()
|
"status" to track.toShikimoriStatus()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val body = RequestBody.create(jsonime, payload.toString())
|
val body = RequestBody.create(jsonime, payload.toString())
|
||||||
@ -74,7 +74,7 @@ class ShikomoriApi(private val client: OkHttpClient, interceptor: ShikomoriInter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun jsonToSearch(obj: JsonObject): TrackSearch {
|
private fun jsonToSearch(obj: JsonObject): TrackSearch {
|
||||||
return TrackSearch.create(TrackManager.SHIKOMORI).apply {
|
return TrackSearch.create(TrackManager.SHIKIMORI).apply {
|
||||||
media_id = obj["id"].asInt
|
media_id = obj["id"].asInt
|
||||||
title = obj["name"].asString
|
title = obj["name"].asString
|
||||||
total_chapters = obj["chapters"].asInt
|
total_chapters = obj["chapters"].asInt
|
||||||
@ -87,14 +87,15 @@ class ShikomoriApi(private val client: OkHttpClient, interceptor: ShikomoriInter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun jsonToTrack(obj: JsonObject): Track {
|
private fun jsonToTrack(obj: JsonObject, mangas: JsonObject): Track {
|
||||||
return Track.create(TrackManager.SHIKOMORI).apply {
|
return Track.create(TrackManager.SHIKIMORI).apply {
|
||||||
|
title = mangas["name"].asString
|
||||||
media_id = obj["id"].asInt
|
media_id = obj["id"].asInt
|
||||||
title = ""
|
total_chapters = mangas["chapters"].asInt
|
||||||
last_chapter_read = obj["chapters"].asInt
|
last_chapter_read = obj["chapters"].asInt
|
||||||
total_chapters = obj["chapters"].asInt
|
|
||||||
score = (obj["score"].asInt).toFloat()
|
score = (obj["score"].asInt).toFloat()
|
||||||
status = toTrackStatus(obj["status"].asString)
|
status = toTrackStatus(obj["status"].asString)
|
||||||
|
tracking_url = baseUrl + mangas["url"].asString
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +109,21 @@ class ShikomoriApi(private val client: OkHttpClient, interceptor: ShikomoriInter
|
|||||||
.url(url.toString())
|
.url(url.toString())
|
||||||
.get()
|
.get()
|
||||||
.build()
|
.build()
|
||||||
return authClient.newCall(request)
|
|
||||||
|
val urlMangas = Uri.parse("$apiUrl/mangas").buildUpon()
|
||||||
|
.appendPath(track.media_id.toString())
|
||||||
|
.build()
|
||||||
|
val requestMangas = Request.Builder()
|
||||||
|
.url(urlMangas.toString())
|
||||||
|
.get()
|
||||||
|
.build()
|
||||||
|
return authClient.newCall(requestMangas)
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { netResponse ->
|
||||||
|
val responseBody = netResponse.body()?.string().orEmpty()
|
||||||
|
parser.parse(responseBody).obj
|
||||||
|
}.flatMap { mangas ->
|
||||||
|
authClient.newCall(request)
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { netResponse ->
|
.map { netResponse ->
|
||||||
val responseBody = netResponse.body()?.string().orEmpty()
|
val responseBody = netResponse.body()?.string().orEmpty()
|
||||||
@ -120,11 +135,12 @@ class ShikomoriApi(private val client: OkHttpClient, interceptor: ShikomoriInter
|
|||||||
throw Exception("Too much mangas in response")
|
throw Exception("Too much mangas in response")
|
||||||
}
|
}
|
||||||
val entry = response.map {
|
val entry = response.map {
|
||||||
jsonToTrack(it.obj)
|
jsonToTrack(it.obj, mangas)
|
||||||
}
|
}
|
||||||
entry.firstOrNull()
|
entry.firstOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getCurrentUser(): Int {
|
fun getCurrentUser(): Int {
|
||||||
val user = authClient.newCall(GET("$apiUrl/users/whoami")).execute().body()?.string()
|
val user = authClient.newCall(GET("$apiUrl/users/whoami")).execute().body()?.string()
|
||||||
@ -156,10 +172,10 @@ class ShikomoriApi(private val client: OkHttpClient, interceptor: ShikomoriInter
|
|||||||
private const val clientId = "1aaf4cf232372708e98b5abc813d795b539c5a916dbbfe9ac61bf02a360832cc"
|
private const val clientId = "1aaf4cf232372708e98b5abc813d795b539c5a916dbbfe9ac61bf02a360832cc"
|
||||||
private const val clientSecret = "229942c742dd4cde803125d17d64501d91c0b12e14cb1e5120184d77d67024c0"
|
private const val clientSecret = "229942c742dd4cde803125d17d64501d91c0b12e14cb1e5120184d77d67024c0"
|
||||||
|
|
||||||
private const val baseUrl = "https://shikimori.org"
|
private const val baseUrl = "https://shikimori.one"
|
||||||
private const val apiUrl = "https://shikimori.org/api"
|
private const val apiUrl = "https://shikimori.one/api"
|
||||||
private const val oauthUrl = "https://shikimori.org/oauth/token"
|
private const val oauthUrl = "https://shikimori.one/oauth/token"
|
||||||
private const val loginUrl = "https://shikimori.org/oauth/authorize"
|
private const val loginUrl = "https://shikimori.one/oauth/authorize"
|
||||||
|
|
||||||
private const val redirectUrl = "tachiyomi://shikimori-auth"
|
private const val redirectUrl = "tachiyomi://shikimori-auth"
|
||||||
private const val baseMangaUrl = "$apiUrl/mangas"
|
private const val baseMangaUrl = "$apiUrl/mangas"
|
@ -1,26 +1,26 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.shikomori
|
package eu.kanade.tachiyomi.data.track.shikimori
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
|
||||||
class ShikomoriInterceptor(val shikomori: Shikomori, val gson: Gson) : Interceptor {
|
class ShikimoriInterceptor(val shikimori: Shikimori, val gson: Gson) : Interceptor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OAuth object used for authenticated requests.
|
* OAuth object used for authenticated requests.
|
||||||
*/
|
*/
|
||||||
private var oauth: OAuth? = shikomori.restoreToken()
|
private var oauth: OAuth? = shikimori.restoreToken()
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
|
|
||||||
val currAuth = oauth ?: throw Exception("Not authenticated with Shikomori")
|
val currAuth = oauth ?: throw Exception("Not authenticated with Shikimori")
|
||||||
|
|
||||||
val refreshToken = currAuth.refresh_token!!
|
val refreshToken = currAuth.refresh_token!!
|
||||||
|
|
||||||
// Refresh access token if expired.
|
// Refresh access token if expired.
|
||||||
if (currAuth.isExpired()) {
|
if (currAuth.isExpired()) {
|
||||||
val response = chain.proceed(ShikomoriApi.refreshTokenRequest(refreshToken))
|
val response = chain.proceed(ShikimoriApi.refreshTokenRequest(refreshToken))
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
newAuth(gson.fromJson(response.body()!!.string(), OAuth::class.java))
|
newAuth(gson.fromJson(response.body()!!.string(), OAuth::class.java))
|
||||||
} else {
|
} else {
|
||||||
@ -38,6 +38,6 @@ class ShikomoriInterceptor(val shikomori: Shikomori, val gson: Gson) : Intercept
|
|||||||
|
|
||||||
fun newAuth(oauth: OAuth?) {
|
fun newAuth(oauth: OAuth?) {
|
||||||
this.oauth = oauth
|
this.oauth = oauth
|
||||||
shikomori.saveToken(oauth)
|
shikimori.saveToken(oauth)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track.shikimori
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
|
||||||
|
fun Track.toShikimoriStatus() = when (status) {
|
||||||
|
Shikimori.READING -> "watching"
|
||||||
|
Shikimori.COMPLETED -> "completed"
|
||||||
|
Shikimori.ON_HOLD -> "on_hold"
|
||||||
|
Shikimori.DROPPED -> "dropped"
|
||||||
|
Shikimori.PLANNING -> "planned"
|
||||||
|
Shikimori.REPEATING -> "rewatching"
|
||||||
|
else -> throw NotImplementedError("Unknown status")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toTrackStatus(status: String) = when (status) {
|
||||||
|
"watching" -> Shikimori.READING
|
||||||
|
"completed" -> Shikimori.COMPLETED
|
||||||
|
"on_hold" -> Shikimori.ON_HOLD
|
||||||
|
"dropped" -> Shikimori.DROPPED
|
||||||
|
"planned" -> Shikimori.PLANNING
|
||||||
|
"rewatching" -> Shikimori.REPEATING
|
||||||
|
|
||||||
|
else -> throw Exception("Unknown status")
|
||||||
|
}
|
@ -1,24 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.shikomori
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
|
||||||
|
|
||||||
fun Track.toShikomoriStatus() = when (status) {
|
|
||||||
Shikomori.READING -> "watching"
|
|
||||||
Shikomori.COMPLETED -> "completed"
|
|
||||||
Shikomori.ON_HOLD -> "on_hold"
|
|
||||||
Shikomori.DROPPED -> "dropped"
|
|
||||||
Shikomori.PLANNING -> "planned"
|
|
||||||
Shikomori.REPEATING -> "rewatching"
|
|
||||||
else -> throw NotImplementedError("Unknown status")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toTrackStatus(status: String) = when (status) {
|
|
||||||
"watching" -> Shikomori.READING
|
|
||||||
"completed" -> Shikomori.COMPLETED
|
|
||||||
"on_hold" -> Shikomori.ON_HOLD
|
|
||||||
"dropped" -> Shikomori.DROPPED
|
|
||||||
"planned" -> Shikomori.PLANNING
|
|
||||||
"rewatching" -> Shikomori.REPEATING
|
|
||||||
|
|
||||||
else -> throw Exception("Unknown status")
|
|
||||||
}
|
|
@ -17,11 +17,13 @@ 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.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.SecondaryDrawerController
|
import eu.kanade.tachiyomi.ui.base.controller.SecondaryDrawerController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
|
import eu.kanade.tachiyomi.ui.manga.info.MangaWebViewController
|
||||||
import eu.kanade.tachiyomi.util.*
|
import eu.kanade.tachiyomi.util.*
|
||||||
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
||||||
import kotlinx.android.synthetic.main.catalogue_controller.*
|
import kotlinx.android.synthetic.main.catalogue_controller.*
|
||||||
@ -259,15 +261,38 @@ open class BrowseCatalogueController(bundle: Bundle) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
|
super.onPrepareOptionsMenu(menu)
|
||||||
|
|
||||||
|
val isHttpSource = presenter.source is HttpSource
|
||||||
|
menu.findItem(R.id.action_open_in_browser).isVisible = isHttpSource
|
||||||
|
menu.findItem(R.id.action_open_in_web_view).isVisible = isHttpSource
|
||||||
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.action_display_mode -> swapDisplayMode()
|
R.id.action_display_mode -> swapDisplayMode()
|
||||||
R.id.action_set_filter -> navView?.let { activity?.drawer?.openDrawer(Gravity.END) }
|
R.id.action_set_filter -> navView?.let { activity?.drawer?.openDrawer(Gravity.END) }
|
||||||
|
R.id.action_open_in_browser -> openInBrowser()
|
||||||
|
R.id.action_open_in_web_view -> openInWebView()
|
||||||
else -> return super.onOptionsItemSelected(item)
|
else -> return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openInBrowser() {
|
||||||
|
val source = presenter.source as? HttpSource ?: return
|
||||||
|
|
||||||
|
activity?.openInBrowser(source.baseUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openInWebView() {
|
||||||
|
val source = presenter.source as? HttpSource ?: return
|
||||||
|
|
||||||
|
router.pushController(MangaWebViewController(source.id, source.baseUrl)
|
||||||
|
.withFadeTransaction())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restarts the request with a new query.
|
* Restarts the request with a new query.
|
||||||
*
|
*
|
||||||
|
@ -239,7 +239,7 @@ open class CatalogueSearchPresenter(
|
|||||||
* @param sManga the manga from the source.
|
* @param sManga the manga from the source.
|
||||||
* @return a manga from the database.
|
* @return a manga from the database.
|
||||||
*/
|
*/
|
||||||
private fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
|
protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
|
||||||
var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking()
|
var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking()
|
||||||
if (localManga == null) {
|
if (localManga == null) {
|
||||||
val newManga = Manga.create(sManga.url, sManga.title, sourceId)
|
val newManga = Manga.create(sManga.url, sManga.title, sourceId)
|
||||||
|
@ -185,7 +185,7 @@ class LibraryPresenter(
|
|||||||
|
|
||||||
val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
|
val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
|
||||||
when (sortingMode) {
|
when (sortingMode) {
|
||||||
LibrarySort.ALPHA -> i1.manga.title.compareTo(i2.manga.title)
|
LibrarySort.ALPHA -> i1.manga.title.compareTo(i2.manga.title, true)
|
||||||
LibrarySort.LAST_READ -> {
|
LibrarySort.LAST_READ -> {
|
||||||
// Get index of manga, set equal to list if size unknown.
|
// Get index of manga, set equal to list if size unknown.
|
||||||
val manga1LastRead = lastReadManga[i1.manga.id!!] ?: lastReadManga.size
|
val manga1LastRead = lastReadManga[i1.manga.id!!] ?: lastReadManga.size
|
||||||
|
@ -24,6 +24,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
|
|||||||
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
|
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
|
||||||
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
|
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
|
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
|
||||||
|
import eu.kanade.tachiyomi.util.openInBrowser
|
||||||
import kotlinx.android.synthetic.main.main_activity.*
|
import kotlinx.android.synthetic.main.main_activity.*
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
@ -91,6 +92,9 @@ class MainActivity : BaseActivity() {
|
|||||||
R.id.nav_drawer_settings -> {
|
R.id.nav_drawer_settings -> {
|
||||||
router.pushController(SettingsMainController().withFadeTransaction())
|
router.pushController(SettingsMainController().withFadeTransaction())
|
||||||
}
|
}
|
||||||
|
R.id.nav_drawer_help -> {
|
||||||
|
openInBrowser(URL_HELP)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawer.closeDrawer(GravityCompat.START)
|
drawer.closeDrawer(GravityCompat.START)
|
||||||
@ -271,6 +275,8 @@ class MainActivity : BaseActivity() {
|
|||||||
const val INTENT_SEARCH = "eu.kanade.tachiyomi.SEARCH"
|
const val INTENT_SEARCH = "eu.kanade.tachiyomi.SEARCH"
|
||||||
const val INTENT_SEARCH_QUERY = "query"
|
const val INTENT_SEARCH_QUERY = "query"
|
||||||
const val INTENT_SEARCH_FILTER = "filter"
|
const val INTENT_SEARCH_FILTER = "filter"
|
||||||
|
|
||||||
|
private const val URL_HELP = "https://github.com/inorichi/tachiyomi/wiki"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
|||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
import eu.kanade.tachiyomi.util.getResourceColor
|
import eu.kanade.tachiyomi.util.getResourceColor
|
||||||
|
import eu.kanade.tachiyomi.util.openInBrowser
|
||||||
import eu.kanade.tachiyomi.util.snack
|
import eu.kanade.tachiyomi.util.snack
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import eu.kanade.tachiyomi.util.truncateCenter
|
import eu.kanade.tachiyomi.util.truncateCenter
|
||||||
@ -87,6 +88,9 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
// Set onclickListener to toggle favorite when FAB clicked.
|
// Set onclickListener to toggle favorite when FAB clicked.
|
||||||
fab_favorite.clicks().subscribeUntilDestroy { onFabClick() }
|
fab_favorite.clicks().subscribeUntilDestroy { onFabClick() }
|
||||||
|
|
||||||
|
// Set onLongClickListener to manage categories when FAB is clicked.
|
||||||
|
fab_favorite.longClicks().subscribeUntilDestroy{ onFabLongClick() }
|
||||||
|
|
||||||
// Set SwipeRefresh to refresh manga data.
|
// Set SwipeRefresh to refresh manga data.
|
||||||
swipe_refresh.refreshes().subscribeUntilDestroy { fetchMangaFromSource() }
|
swipe_refresh.refreshes().subscribeUntilDestroy { fetchMangaFromSource() }
|
||||||
|
|
||||||
@ -287,15 +291,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
val context = view?.context ?: return
|
val context = view?.context ?: return
|
||||||
val source = presenter.source as? HttpSource ?: return
|
val source = presenter.source as? HttpSource ?: return
|
||||||
|
|
||||||
try {
|
context.openInBrowser(source.mangaDetailsRequest(presenter.manga).url().toString())
|
||||||
val url = Uri.parse(source.mangaDetailsRequest(presenter.manga).url().toString())
|
|
||||||
val intent = CustomTabsIntent.Builder()
|
|
||||||
.setToolbarColor(context.getResourceColor(R.attr.colorPrimary))
|
|
||||||
.build()
|
|
||||||
intent.launchUrl(activity, url)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
context.toast(e.message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openInWebView() {
|
private fun openInWebView() {
|
||||||
@ -407,6 +403,30 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the fab is long clicked.
|
||||||
|
*/
|
||||||
|
private fun onFabLongClick() {
|
||||||
|
val manga = presenter.manga
|
||||||
|
if (!manga.favorite) {
|
||||||
|
toggleFavorite()
|
||||||
|
activity?.toast(activity?.getString(R.string.manga_added_library))
|
||||||
|
}
|
||||||
|
val categories = presenter.getCategories()
|
||||||
|
if (categories.size <= 1) {
|
||||||
|
// default or the one from the user then just add to favorite.
|
||||||
|
presenter.moveMangaToCategory(manga, categories.firstOrNull())
|
||||||
|
} else {
|
||||||
|
val ids = presenter.getMangaCategoryIds(manga)
|
||||||
|
val preselected = ids.mapNotNull { id ->
|
||||||
|
categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
|
||||||
|
}.toTypedArray()
|
||||||
|
|
||||||
|
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
|
||||||
|
.showDialog(router)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {
|
override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {
|
||||||
val manga = mangas.firstOrNull() ?: return
|
val manga = mangas.firstOrNull() ?: return
|
||||||
presenter.moveMangaToCategories(manga, categories)
|
presenter.moveMangaToCategories(manga, categories)
|
||||||
|
@ -146,6 +146,9 @@ class MigrationPresenter(
|
|||||||
}
|
}
|
||||||
manga.favorite = true
|
manga.favorite = true
|
||||||
db.updateMangaFavorite(manga).executeAsBlocking()
|
db.updateMangaFavorite(manga).executeAsBlocking()
|
||||||
|
|
||||||
|
// SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title
|
||||||
|
db.updateMangaTitle(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.migration
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchCardItem
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchCardItem
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchItem
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchItem
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter
|
||||||
@ -21,4 +22,11 @@ class SearchPresenter(
|
|||||||
//Set the catalogue search item as highlighted if the source matches that of the selected manga
|
//Set the catalogue search item as highlighted if the source matches that of the selected manga
|
||||||
return CatalogueSearchItem(source, results, source.id == manga.source)
|
return CatalogueSearchItem(source, results, source.id == manga.source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
|
||||||
|
val localManga = super.networkToLocalManga(sManga, sourceId)
|
||||||
|
// For migration, displayed title should always match source rather than local DB
|
||||||
|
localManga.title = sManga.title
|
||||||
|
return localManga
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -574,6 +574,9 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
|
|
||||||
subscriptions += preferences.colorFilter().asObservable()
|
subscriptions += preferences.colorFilter().asObservable()
|
||||||
.subscribe { setColorFilter(it) }
|
.subscribe { setColorFilter(it) }
|
||||||
|
|
||||||
|
subscriptions += preferences.colorFilterMode().asObservable()
|
||||||
|
.subscribe { setColorFilter(preferences.colorFilter().getOrDefault()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -722,7 +725,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
|
|||||||
*/
|
*/
|
||||||
private fun setColorFilterValue(value: Int) {
|
private fun setColorFilterValue(value: Int) {
|
||||||
color_overlay.visibility = View.VISIBLE
|
color_overlay.visibility = View.VISIBLE
|
||||||
color_overlay.setBackgroundColor(value)
|
color_overlay.setFilterColor(value, preferences.colorFilterMode().getOrDefault())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
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.util.plusAssign
|
import eu.kanade.tachiyomi.util.plusAssign
|
||||||
|
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
|
||||||
import eu.kanade.tachiyomi.widget.SimpleSeekBarListener
|
import eu.kanade.tachiyomi.widget.SimpleSeekBarListener
|
||||||
import kotlinx.android.synthetic.main.reader_color_filter.*
|
import kotlinx.android.synthetic.main.reader_color_filter.*
|
||||||
import kotlinx.android.synthetic.main.reader_color_filter_sheet.*
|
import kotlinx.android.synthetic.main.reader_color_filter_sheet.*
|
||||||
@ -54,6 +55,9 @@ class ReaderColorFilterSheet(activity: ReaderActivity) : BottomSheetDialog(activ
|
|||||||
subscriptions += preferences.colorFilter().asObservable()
|
subscriptions += preferences.colorFilter().asObservable()
|
||||||
.subscribe { setColorFilter(it, view) }
|
.subscribe { setColorFilter(it, view) }
|
||||||
|
|
||||||
|
subscriptions += preferences.colorFilterMode().asObservable()
|
||||||
|
.subscribe { setColorFilter(preferences.colorFilter().getOrDefault(), view) }
|
||||||
|
|
||||||
subscriptions += preferences.customBrightness().asObservable()
|
subscriptions += preferences.customBrightness().asObservable()
|
||||||
.subscribe { setCustomBrightness(it, view) }
|
.subscribe { setCustomBrightness(it, view) }
|
||||||
|
|
||||||
@ -84,6 +88,11 @@ class ReaderColorFilterSheet(activity: ReaderActivity) : BottomSheetDialog(activ
|
|||||||
preferences.customBrightness().set(isChecked)
|
preferences.customBrightness().set(isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
color_filter_mode.onItemSelectedListener = IgnoreFirstSpinnerListener { position ->
|
||||||
|
preferences.colorFilterMode().set(position)
|
||||||
|
}
|
||||||
|
color_filter_mode.setSelection(preferences.colorFilterMode().getOrDefault(), false)
|
||||||
|
|
||||||
seekbar_color_filter_alpha.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
|
seekbar_color_filter_alpha.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
|
||||||
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
|
||||||
if (fromUser) {
|
if (fromUser) {
|
||||||
@ -248,7 +257,7 @@ class ReaderColorFilterSheet(activity: ReaderActivity) : BottomSheetDialog(activ
|
|||||||
*/
|
*/
|
||||||
private fun setColorFilterValue(@ColorInt color: Int, view: View) = with(view) {
|
private fun setColorFilterValue(@ColorInt color: Int, view: View) = with(view) {
|
||||||
color_overlay.visibility = View.VISIBLE
|
color_overlay.visibility = View.VISIBLE
|
||||||
color_overlay.setBackgroundColor(color)
|
color_overlay.setFilterColor(color, preferences.colorFilterMode().getOrDefault())
|
||||||
setValues(color, view)
|
setValues(color, view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.*
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
class ReaderColorFilterView(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null
|
||||||
|
) : View(context, attrs) {
|
||||||
|
|
||||||
|
private val colorFilterPaint: Paint = Paint()
|
||||||
|
|
||||||
|
fun setFilterColor(color: Int, filterMode: Int) {
|
||||||
|
colorFilterPaint.setColor(color)
|
||||||
|
colorFilterPaint.xfermode = PorterDuffXfermode(when (filterMode) {
|
||||||
|
1 -> PorterDuff.Mode.MULTIPLY
|
||||||
|
2 -> PorterDuff.Mode.SCREEN
|
||||||
|
3 -> PorterDuff.Mode.OVERLAY
|
||||||
|
4 -> PorterDuff.Mode.LIGHTEN
|
||||||
|
5 -> PorterDuff.Mode.DARKEN
|
||||||
|
else -> PorterDuff.Mode.SRC_OVER
|
||||||
|
})
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
super.onDraw(canvas)
|
||||||
|
canvas.drawPaint(colorFilterPaint)
|
||||||
|
}
|
||||||
|
}
|
@ -147,10 +147,9 @@ class ReaderPresenter(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the user pressed the back button and is going to leave the reader. Used to
|
* Called when the user pressed the back button and is going to leave the reader. Used to
|
||||||
* update tracking services and trigger deletion of the downloaded chapters.
|
* trigger deletion of the downloaded chapters.
|
||||||
*/
|
*/
|
||||||
fun onBackPressed() {
|
fun onBackPressed() {
|
||||||
updateTrackLastChapterRead()
|
|
||||||
deletePendingChapters()
|
deletePendingChapters()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +307,7 @@ class ReaderPresenter(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called every time a page changes on the reader. Used to mark the flag of chapters being
|
* Called every time a page changes on the reader. Used to mark the flag of chapters being
|
||||||
* read, enqueue downloaded chapter deletion, and updating the active chapter if this
|
* read, update tracking services, enqueue downloaded chapter deletion, and updating the active chapter if this
|
||||||
* [page]'s chapter is different from the currently active.
|
* [page]'s chapter is different from the currently active.
|
||||||
*/
|
*/
|
||||||
fun onPageSelected(page: ReaderPage) {
|
fun onPageSelected(page: ReaderPage) {
|
||||||
@ -320,6 +319,7 @@ class ReaderPresenter(
|
|||||||
selectedChapter.chapter.last_page_read = page.index
|
selectedChapter.chapter.last_page_read = page.index
|
||||||
if (selectedChapter.pages?.lastIndex == page.index) {
|
if (selectedChapter.pages?.lastIndex == page.index) {
|
||||||
selectedChapter.chapter.read = true
|
selectedChapter.chapter.read = true
|
||||||
|
updateTrackLastChapterRead()
|
||||||
enqueueDeleteReadChapters(selectedChapter)
|
enqueueDeleteReadChapters(selectedChapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,7 +434,8 @@ class ReaderPresenter(
|
|||||||
|
|
||||||
// Build destination file.
|
// Build destination file.
|
||||||
val filename = DiskUtil.buildValidFilename(
|
val filename = DiskUtil.buildValidFilename(
|
||||||
"${manga.title} - ${chapter.name}") + " - ${page.number}.${type.extension}"
|
"${manga.title} - ${chapter.name}".take(225)
|
||||||
|
) + " - ${page.number}.${type.extension}"
|
||||||
|
|
||||||
val destFile = File(directory, filename)
|
val destFile = File(directory, filename)
|
||||||
stream().use { input ->
|
stream().use { input ->
|
||||||
|
@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
|
import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
|
||||||
import eu.kanade.tachiyomi.data.track.shikomori.ShikomoriApi
|
import eu.kanade.tachiyomi.data.track.shikimori.ShikimoriApi
|
||||||
import eu.kanade.tachiyomi.util.getResourceColor
|
import eu.kanade.tachiyomi.util.getResourceColor
|
||||||
import eu.kanade.tachiyomi.widget.preference.LoginPreference
|
import eu.kanade.tachiyomi.widget.preference.LoginPreference
|
||||||
import eu.kanade.tachiyomi.widget.preference.TrackLoginDialog
|
import eu.kanade.tachiyomi.widget.preference.TrackLoginDialog
|
||||||
@ -54,13 +54,13 @@ class SettingsTrackingController : SettingsController(),
|
|||||||
dialog.showDialog(router)
|
dialog.showDialog(router)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trackPreference(trackManager.shikomori) {
|
trackPreference(trackManager.shikimori) {
|
||||||
onClick {
|
onClick {
|
||||||
val tabsIntent = CustomTabsIntent.Builder()
|
val tabsIntent = CustomTabsIntent.Builder()
|
||||||
.setToolbarColor(context.getResourceColor(R.attr.colorPrimary))
|
.setToolbarColor(context.getResourceColor(R.attr.colorPrimary))
|
||||||
.build()
|
.build()
|
||||||
tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||||
tabsIntent.launchUrl(activity, ShikomoriApi.authUrl())
|
tabsIntent.launchUrl(activity, ShikimoriApi.authUrl())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,7 +80,7 @@ class SettingsTrackingController : SettingsController(),
|
|||||||
super.onActivityResumed(activity)
|
super.onActivityResumed(activity)
|
||||||
// Manually refresh anilist holder
|
// Manually refresh anilist holder
|
||||||
updatePreference(trackManager.aniList.id)
|
updatePreference(trackManager.aniList.id)
|
||||||
updatePreference(trackManager.shikomori.id)
|
updatePreference(trackManager.shikimori.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePreference(id: Int) {
|
private fun updatePreference(id: Int) {
|
||||||
|
@ -13,7 +13,7 @@ import rx.android.schedulers.AndroidSchedulers
|
|||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class ShikomoriLoginActivity : AppCompatActivity() {
|
class ShikimoriLoginActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private val trackManager: TrackManager by injectLazy()
|
private val trackManager: TrackManager by injectLazy()
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ class ShikomoriLoginActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
val code = intent.data?.getQueryParameter("code")
|
val code = intent.data?.getQueryParameter("code")
|
||||||
if (code != null) {
|
if (code != null) {
|
||||||
trackManager.shikomori.login(code)
|
trackManager.shikimori.login(code)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
@ -34,7 +34,7 @@ class ShikomoriLoginActivity : AppCompatActivity() {
|
|||||||
returnToSettings()
|
returnToSettings()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
trackManager.shikomori.logout()
|
trackManager.shikimori.logout()
|
||||||
returnToSettings()
|
returnToSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,14 +10,17 @@ import android.content.IntentFilter
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.Uri
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.support.annotation.AttrRes
|
import android.support.annotation.AttrRes
|
||||||
import android.support.annotation.StringRes
|
import android.support.annotation.StringRes
|
||||||
|
import android.support.customtabs.CustomTabsIntent
|
||||||
import android.support.v4.app.NotificationCompat
|
import android.support.v4.app.NotificationCompat
|
||||||
import android.support.v4.content.ContextCompat
|
import android.support.v4.content.ContextCompat
|
||||||
import android.support.v4.content.LocalBroadcastManager
|
import android.support.v4.content.LocalBroadcastManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.nononsenseapps.filepicker.FilePickerActivity
|
import com.nononsenseapps.filepicker.FilePickerActivity
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
|
import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -163,3 +166,18 @@ fun Context.isServiceRunning(serviceClass: Class<*>): Boolean {
|
|||||||
return manager.getRunningServices(Integer.MAX_VALUE)
|
return manager.getRunningServices(Integer.MAX_VALUE)
|
||||||
.any { className == it.service.className }
|
.any { className == it.service.className }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a URL in a custom tab.
|
||||||
|
*/
|
||||||
|
fun Context.openInBrowser(url: String) {
|
||||||
|
try {
|
||||||
|
val url = Uri.parse(url)
|
||||||
|
val intent = CustomTabsIntent.Builder()
|
||||||
|
.setToolbarColor(getResourceColor(R.attr.colorPrimary))
|
||||||
|
.build()
|
||||||
|
intent.launchUrl(this, url)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
toast(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
@ -29,7 +29,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<View
|
<eu.kanade.tachiyomi.ui.reader.ReaderColorFilterView
|
||||||
android:id="@+id/color_overlay"
|
android:id="@+id/color_overlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -111,7 +111,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<View
|
<eu.kanade.tachiyomi.ui.reader.ReaderColorFilterView
|
||||||
android:id="@+id/color_overlay"
|
android:id="@+id/color_overlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@ -6,6 +6,12 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<android.support.v4.widget.Space
|
||||||
|
android:id="@+id/spinner_end"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintLeft_toRightOf="parent" />
|
||||||
|
|
||||||
<!-- Color filter -->
|
<!-- Color filter -->
|
||||||
|
|
||||||
<android.support.v7.widget.SwitchCompat
|
<android.support.v7.widget.SwitchCompat
|
||||||
@ -157,6 +163,27 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="@id/seekbar_color_filter_alpha"
|
app:layout_constraintBottom_toBottomOf="@id/seekbar_color_filter_alpha"
|
||||||
app:layout_constraintRight_toRightOf="parent"/>
|
app:layout_constraintRight_toRightOf="parent"/>
|
||||||
|
|
||||||
|
<!-- Filter mode -->
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/color_filter_mode_text"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/pref_color_filter_mode"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toLeftOf="@id/color_filter_mode"
|
||||||
|
app:layout_constraintBaseline_toBaselineOf="@id/color_filter_mode"/>
|
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatSpinner
|
||||||
|
android:id="@+id/color_filter_mode"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:entries="@array/color_filter_modes"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/seekbar_color_filter_alpha"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/verticalcenter"
|
||||||
|
app:layout_constraintRight_toRightOf="@id/spinner_end" />
|
||||||
|
|
||||||
<!-- Brightness -->
|
<!-- Brightness -->
|
||||||
|
|
||||||
<android.support.v7.widget.SwitchCompat
|
<android.support.v7.widget.SwitchCompat
|
||||||
@ -165,7 +192,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:text="@string/pref_custom_brightness"
|
android:text="@string/pref_custom_brightness"
|
||||||
app:layout_constraintTop_toBottomOf="@id/seekbar_color_filter_alpha"/>
|
app:layout_constraintTop_toBottomOf="@id/color_filter_mode_text"/>
|
||||||
|
|
||||||
<!-- Brightness value -->
|
<!-- Brightness value -->
|
||||||
|
|
||||||
@ -202,4 +229,11 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="@id/brightness_seekbar"
|
app:layout_constraintBottom_toBottomOf="@id/brightness_seekbar"
|
||||||
app:layout_constraintRight_toRightOf="parent"/>
|
app:layout_constraintRight_toRightOf="parent"/>
|
||||||
|
|
||||||
|
<android.support.constraint.Guideline
|
||||||
|
android:id="@+id/verticalcenter"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_percent="0.5" />
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<View
|
<eu.kanade.tachiyomi.ui.reader.ReaderColorFilterView
|
||||||
android:id="@+id/color_overlay"
|
android:id="@+id/color_overlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@ -19,4 +19,15 @@
|
|||||||
android:id="@+id/action_display_mode"
|
android:id="@+id/action_display_mode"
|
||||||
android:title="@string/action_display_mode"
|
android:title="@string/action_display_mode"
|
||||||
app:showAsAction="ifRoom"/>
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_open_in_browser"
|
||||||
|
android:title="@string/action_open_in_browser"
|
||||||
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_open_in_web_view"
|
||||||
|
android:title="@string/action_open_in_web_view"
|
||||||
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
@ -36,5 +36,10 @@
|
|||||||
android:icon="@drawable/ic_settings_black_24dp"
|
android:icon="@drawable/ic_settings_black_24dp"
|
||||||
android:title="@string/label_settings"
|
android:title="@string/label_settings"
|
||||||
android:checkable="false" />
|
android:checkable="false" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/nav_drawer_help"
|
||||||
|
android:icon="@drawable/ic_help_black_24dp"
|
||||||
|
android:title="@string/label_help"
|
||||||
|
android:checkable="false" />
|
||||||
</group>
|
</group>
|
||||||
</menu>
|
</menu>
|
||||||
|
15
app/src/main/res/values-v28/arrays.xml
Normal file
15
app/src/main/res/values-v28/arrays.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string-array name="color_filter_modes">
|
||||||
|
<item>@string/filter_mode_default</item>
|
||||||
|
<item>@string/filter_mode_multiply</item>
|
||||||
|
<item>@string/filter_mode_screen</item>
|
||||||
|
|
||||||
|
<!-- Attributes specific for SDK 28 and up -->
|
||||||
|
<item>@string/filter_mode_overlay</item>
|
||||||
|
<item>@string/filter_mode_lighten</item>
|
||||||
|
<item>@string/filter_mode_darken</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
</resources>
|
@ -103,4 +103,10 @@
|
|||||||
<item>2</item>
|
<item>2</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="color_filter_modes">
|
||||||
|
<item>@string/filter_mode_default</item>
|
||||||
|
<item>@string/filter_mode_multiply</item>
|
||||||
|
<item>@string/filter_mode_screen</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<string name="label_migration">Source migration</string>
|
<string name="label_migration">Source migration</string>
|
||||||
<string name="label_extensions">Extensions</string>
|
<string name="label_extensions">Extensions</string>
|
||||||
<string name="label_extension_info">Extension info</string>
|
<string name="label_extension_info">Extension info</string>
|
||||||
|
<string name="label_help">Help</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
@ -178,6 +179,13 @@
|
|||||||
<string name="pref_crop_borders">Crop borders</string>
|
<string name="pref_crop_borders">Crop borders</string>
|
||||||
<string name="pref_custom_brightness">Use custom brightness</string>
|
<string name="pref_custom_brightness">Use custom brightness</string>
|
||||||
<string name="pref_custom_color_filter">Use custom color filter</string>
|
<string name="pref_custom_color_filter">Use custom color filter</string>
|
||||||
|
<string name="pref_color_filter_mode">Color filter blend mode</string>
|
||||||
|
<string name="filter_mode_default">Default</string>
|
||||||
|
<string name="filter_mode_overlay">Overlay</string>
|
||||||
|
<string name="filter_mode_multiply">Multiply</string>
|
||||||
|
<string name="filter_mode_screen">Screen</string>
|
||||||
|
<string name="filter_mode_lighten">Dodge / Lighten</string>
|
||||||
|
<string name="filter_mode_darken">Burn / Darken</string>
|
||||||
<string name="pref_keep_screen_on">Keep screen on</string>
|
<string name="pref_keep_screen_on">Keep screen on</string>
|
||||||
<string name="pref_skip_read_chapters">Skip chapters marked read</string>
|
<string name="pref_skip_read_chapters">Skip chapters marked read</string>
|
||||||
<string name="pref_reader_navigation">Navigation</string>
|
<string name="pref_reader_navigation">Navigation</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user