Merge remote-tracking branch 'upstream/master' into Automatic_Reader_Background

This commit is contained in:
Jay 2019-06-08 15:59:29 -07:00
commit b4b78e0f6b
39 changed files with 364 additions and 100 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
github: inorichi
ko_fi: inorichi

View File

@ -11,7 +11,7 @@ Tachiyomi is a free and open source manga reader for Android.
## Features
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
* 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

View File

@ -62,8 +62,8 @@
</intent-filter>
</activity>
<activity
android:name=".ui.setting.ShikomoriLoginActivity"
android:label="Shikomori">
android:name=".ui.setting.ShikimoriLoginActivity"
android:label="Shikimori">
<intent-filter>
<action android:name="android.intent.action.VIEW" />

View File

@ -82,6 +82,11 @@ interface MangaQueries : DbProvider {
.withPutResolver(MangaViewerPutResolver())
.prepare()
fun updateMangaTitle(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaTitlePutResolver())
.prepare()
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()

View File

@ -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)
}
}

View File

@ -29,6 +29,8 @@ object PreferenceKeys {
const val colorFilterValue = "color_filter_value"
const val colorFilterMode = "color_filter_mode"
const val defaultViewer = "pref_default_viewer_key"
const val imageScaleType = "pref_image_scale_type_key"

View File

@ -57,6 +57,8 @@ class PreferencesHelper(val context: Context) {
fun colorFilterValue() = rxPrefs.getInteger(Keys.colorFilterValue, 0)
fun colorFilterMode() = rxPrefs.getInteger(Keys.colorFilterMode, 0)
fun defaultViewer() = prefs.getInt(Keys.defaultViewer, 1)
fun imageScaleType() = rxPrefs.getInteger(Keys.imageScaleType, 1)

View File

@ -4,7 +4,7 @@ import android.content.Context
import eu.kanade.tachiyomi.data.track.anilist.Anilist
import eu.kanade.tachiyomi.data.track.kitsu.Kitsu
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) {
@ -12,7 +12,7 @@ class TrackManager(private val context: Context) {
const val MYANIMELIST = 1
const val ANILIST = 2
const val KITSU = 3
const val SHIKOMORI = 4
const val SHIKIMORI = 4
}
val myAnimeList = Myanimelist(context, MYANIMELIST)
@ -21,9 +21,9 @@ class TrackManager(private val context: Context) {
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 }

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.track.shikomori
package eu.kanade.tachiyomi.data.track.shikimori
data class OAuth(
val access_token: String,

View File

@ -1,7 +1,8 @@
package eu.kanade.tachiyomi.data.track.shikomori
package eu.kanade.tachiyomi.data.track.shikimori
import android.content.Context
import android.graphics.Color
import android.util.Log
import com.google.gson.Gson
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
@ -11,7 +12,7 @@ import rx.Completable
import rx.Observable
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> {
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
}
override val name = "Shikomori"
override val name = "Shikimori"
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)

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.track.shikomori
package eu.kanade.tachiyomi.data.track.shikimori
import android.net.Uri
import com.github.salomonbrys.kotson.array
@ -18,7 +18,7 @@ import okhttp3.*
import rx.Observable
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 parser = JsonParser()
@ -33,7 +33,7 @@ class ShikomoriApi(private val client: OkHttpClient, interceptor: ShikomoriInter
"target_type" to "Manga",
"chapters" to track.last_chapter_read,
"score" to track.score.toInt(),
"status" to track.toShikomoriStatus()
"status" to track.toShikimoriStatus()
)
)
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 {
return TrackSearch.create(TrackManager.SHIKOMORI).apply {
return TrackSearch.create(TrackManager.SHIKIMORI).apply {
media_id = obj["id"].asInt
title = obj["name"].asString
total_chapters = obj["chapters"].asInt
@ -87,14 +87,15 @@ class ShikomoriApi(private val client: OkHttpClient, interceptor: ShikomoriInter
}
}
private fun jsonToTrack(obj: JsonObject): Track {
return Track.create(TrackManager.SHIKOMORI).apply {
private fun jsonToTrack(obj: JsonObject, mangas: JsonObject): Track {
return Track.create(TrackManager.SHIKIMORI).apply {
title = mangas["name"].asString
media_id = obj["id"].asInt
title = ""
total_chapters = mangas["chapters"].asInt
last_chapter_read = obj["chapters"].asInt
total_chapters = obj["chapters"].asInt
score = (obj["score"].asInt).toFloat()
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())
.get()
.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()
.map { netResponse ->
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")
}
val entry = response.map {
jsonToTrack(it.obj)
jsonToTrack(it.obj, mangas)
}
entry.firstOrNull()
}
}
}
fun getCurrentUser(): Int {
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 clientSecret = "229942c742dd4cde803125d17d64501d91c0b12e14cb1e5120184d77d67024c0"
private const val baseUrl = "https://shikimori.org"
private const val apiUrl = "https://shikimori.org/api"
private const val oauthUrl = "https://shikimori.org/oauth/token"
private const val loginUrl = "https://shikimori.org/oauth/authorize"
private const val baseUrl = "https://shikimori.one"
private const val apiUrl = "https://shikimori.one/api"
private const val oauthUrl = "https://shikimori.one/oauth/token"
private const val loginUrl = "https://shikimori.one/oauth/authorize"
private const val redirectUrl = "tachiyomi://shikimori-auth"
private const val baseMangaUrl = "$apiUrl/mangas"

View File

@ -1,26 +1,26 @@
package eu.kanade.tachiyomi.data.track.shikomori
package eu.kanade.tachiyomi.data.track.shikimori
import com.google.gson.Gson
import okhttp3.Interceptor
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.
*/
private var oauth: OAuth? = shikomori.restoreToken()
private var oauth: OAuth? = shikimori.restoreToken()
override fun intercept(chain: Interceptor.Chain): Response {
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!!
// Refresh access token if expired.
if (currAuth.isExpired()) {
val response = chain.proceed(ShikomoriApi.refreshTokenRequest(refreshToken))
val response = chain.proceed(ShikimoriApi.refreshTokenRequest(refreshToken))
if (response.isSuccessful) {
newAuth(gson.fromJson(response.body()!!.string(), OAuth::class.java))
} else {
@ -38,6 +38,6 @@ class ShikomoriInterceptor(val shikomori: Shikomori, val gson: Gson) : Intercept
fun newAuth(oauth: OAuth?) {
this.oauth = oauth
shikomori.saveToken(oauth)
shikimori.saveToken(oauth)
}
}

View File

@ -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")
}

View File

@ -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")
}

View File

@ -17,11 +17,13 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.CatalogueSource
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.SecondaryDrawerController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.manga.info.MangaWebViewController
import eu.kanade.tachiyomi.util.*
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
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 {
when (item.itemId) {
R.id.action_display_mode -> swapDisplayMode()
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)
}
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.
*

View File

@ -239,7 +239,7 @@ open class CatalogueSearchPresenter(
* @param sManga the manga from the source.
* @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()
if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId)

View File

@ -185,7 +185,7 @@ class LibraryPresenter(
val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
when (sortingMode) {
LibrarySort.ALPHA -> i1.manga.title.compareTo(i2.manga.title)
LibrarySort.ALPHA -> i1.manga.title.compareTo(i2.manga.title, true)
LibrarySort.LAST_READ -> {
// Get index of manga, set equal to list if size unknown.
val manga1LastRead = lastReadManga[i1.manga.id!!] ?: lastReadManga.size

View File

@ -24,6 +24,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
import eu.kanade.tachiyomi.util.openInBrowser
import kotlinx.android.synthetic.main.main_activity.*
import uy.kohesive.injekt.injectLazy
@ -91,6 +92,9 @@ class MainActivity : BaseActivity() {
R.id.nav_drawer_settings -> {
router.pushController(SettingsMainController().withFadeTransaction())
}
R.id.nav_drawer_help -> {
openInBrowser(URL_HELP)
}
}
}
drawer.closeDrawer(GravityCompat.START)
@ -271,6 +275,8 @@ class MainActivity : BaseActivity() {
const val INTENT_SEARCH = "eu.kanade.tachiyomi.SEARCH"
const val INTENT_SEARCH_QUERY = "query"
const val INTENT_SEARCH_FILTER = "filter"
private const val URL_HELP = "https://github.com/inorichi/tachiyomi/wiki"
}
}

View File

@ -42,6 +42,7 @@ import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.util.openInBrowser
import eu.kanade.tachiyomi.util.snack
import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.util.truncateCenter
@ -87,6 +88,9 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
// Set onclickListener to toggle favorite when FAB clicked.
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.
swipe_refresh.refreshes().subscribeUntilDestroy { fetchMangaFromSource() }
@ -287,15 +291,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
val context = view?.context ?: return
val source = presenter.source as? HttpSource ?: return
try {
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)
}
context.openInBrowser(source.mangaDetailsRequest(presenter.manga).url().toString())
}
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>) {
val manga = mangas.firstOrNull() ?: return
presenter.moveMangaToCategories(manga, categories)

View File

@ -146,6 +146,9 @@ class MigrationPresenter(
}
manga.favorite = true
db.updateMangaFavorite(manga).executeAsBlocking()
// SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title
db.updateMangaTitle(manga).executeAsBlocking()
}
}
}

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.migration
import eu.kanade.tachiyomi.data.database.models.Manga
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.CatalogueSearchItem
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
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
}
}

View File

@ -574,6 +574,9 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
subscriptions += preferences.colorFilter().asObservable()
.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) {
color_overlay.visibility = View.VISIBLE
color_overlay.setBackgroundColor(value)
color_overlay.setFilterColor(value, preferences.colorFilterMode().getOrDefault())
}
}

View File

@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.util.plusAssign
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
import eu.kanade.tachiyomi.widget.SimpleSeekBarListener
import kotlinx.android.synthetic.main.reader_color_filter.*
import kotlinx.android.synthetic.main.reader_color_filter_sheet.*
@ -54,6 +55,9 @@ class ReaderColorFilterSheet(activity: ReaderActivity) : BottomSheetDialog(activ
subscriptions += preferences.colorFilter().asObservable()
.subscribe { setColorFilter(it, view) }
subscriptions += preferences.colorFilterMode().asObservable()
.subscribe { setColorFilter(preferences.colorFilter().getOrDefault(), view) }
subscriptions += preferences.customBrightness().asObservable()
.subscribe { setCustomBrightness(it, view) }
@ -84,6 +88,11 @@ class ReaderColorFilterSheet(activity: ReaderActivity) : BottomSheetDialog(activ
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() {
override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
if (fromUser) {
@ -248,7 +257,7 @@ class ReaderColorFilterSheet(activity: ReaderActivity) : BottomSheetDialog(activ
*/
private fun setColorFilterValue(@ColorInt color: Int, view: View) = with(view) {
color_overlay.visibility = View.VISIBLE
color_overlay.setBackgroundColor(color)
color_overlay.setFilterColor(color, preferences.colorFilterMode().getOrDefault())
setValues(color, view)
}

View File

@ -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)
}
}

View File

@ -147,10 +147,9 @@ class ReaderPresenter(
/**
* 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() {
updateTrackLastChapterRead()
deletePendingChapters()
}
@ -308,7 +307,7 @@ class ReaderPresenter(
/**
* 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.
*/
fun onPageSelected(page: ReaderPage) {
@ -320,6 +319,7 @@ class ReaderPresenter(
selectedChapter.chapter.last_page_read = page.index
if (selectedChapter.pages?.lastIndex == page.index) {
selectedChapter.chapter.read = true
updateTrackLastChapterRead()
enqueueDeleteReadChapters(selectedChapter)
}
@ -434,7 +434,8 @@ class ReaderPresenter(
// Build destination file.
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)
stream().use { input ->

View File

@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
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.widget.preference.LoginPreference
import eu.kanade.tachiyomi.widget.preference.TrackLoginDialog
@ -54,13 +54,13 @@ class SettingsTrackingController : SettingsController(),
dialog.showDialog(router)
}
}
trackPreference(trackManager.shikomori) {
trackPreference(trackManager.shikimori) {
onClick {
val tabsIntent = CustomTabsIntent.Builder()
.setToolbarColor(context.getResourceColor(R.attr.colorPrimary))
.build()
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)
// Manually refresh anilist holder
updatePreference(trackManager.aniList.id)
updatePreference(trackManager.shikomori.id)
updatePreference(trackManager.shikimori.id)
}
private fun updatePreference(id: Int) {

View File

@ -13,7 +13,7 @@ import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import uy.kohesive.injekt.injectLazy
class ShikomoriLoginActivity : AppCompatActivity() {
class ShikimoriLoginActivity : AppCompatActivity() {
private val trackManager: TrackManager by injectLazy()
@ -25,7 +25,7 @@ class ShikomoriLoginActivity : AppCompatActivity() {
val code = intent.data?.getQueryParameter("code")
if (code != null) {
trackManager.shikomori.login(code)
trackManager.shikimori.login(code)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
@ -34,7 +34,7 @@ class ShikomoriLoginActivity : AppCompatActivity() {
returnToSettings()
})
} else {
trackManager.shikomori.logout()
trackManager.shikimori.logout()
returnToSettings()
}
}

View File

@ -10,14 +10,17 @@ import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.res.Resources
import android.net.ConnectivityManager
import android.net.Uri
import android.os.PowerManager
import android.support.annotation.AttrRes
import android.support.annotation.StringRes
import android.support.customtabs.CustomTabsIntent
import android.support.v4.app.NotificationCompat
import android.support.v4.content.ContextCompat
import android.support.v4.content.LocalBroadcastManager
import android.widget.Toast
import com.nononsenseapps.filepicker.FilePickerActivity
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
/**
@ -163,3 +166,18 @@ fun Context.isServiceRunning(serviceClass: Class<*>): Boolean {
return manager.getRunningServices(Integer.MAX_VALUE)
.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)
}
}

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -29,7 +29,7 @@
android:layout_height="wrap_content"
android:visibility="gone" />
<View
<eu.kanade.tachiyomi.ui.reader.ReaderColorFilterView
android:id="@+id/color_overlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -111,7 +111,7 @@
android:layout_height="match_parent"
android:visibility="gone"/>
<View
<eu.kanade.tachiyomi.ui.reader.ReaderColorFilterView
android:id="@+id/color_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -6,6 +6,12 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
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 -->
<android.support.v7.widget.SwitchCompat
@ -157,6 +163,27 @@
app:layout_constraintBottom_toBottomOf="@id/seekbar_color_filter_alpha"
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 -->
<android.support.v7.widget.SwitchCompat
@ -165,7 +192,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
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 -->
@ -202,4 +229,11 @@
app:layout_constraintBottom_toBottomOf="@id/brightness_seekbar"
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>

View File

@ -21,7 +21,7 @@
android:layout_height="match_parent"
android:visibility="gone" />
<View
<eu.kanade.tachiyomi.ui.reader.ReaderColorFilterView
android:id="@+id/color_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -19,4 +19,15 @@
android:id="@+id/action_display_mode"
android:title="@string/action_display_mode"
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>

View File

@ -36,5 +36,10 @@
android:icon="@drawable/ic_settings_black_24dp"
android:title="@string/label_settings"
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>
</menu>

View 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>

View File

@ -103,4 +103,10 @@
<item>2</item>
</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>

View File

@ -24,6 +24,7 @@
<string name="label_migration">Source migration</string>
<string name="label_extensions">Extensions</string>
<string name="label_extension_info">Extension info</string>
<string name="label_help">Help</string>
<!-- Actions -->
@ -178,6 +179,13 @@
<string name="pref_crop_borders">Crop borders</string>
<string name="pref_custom_brightness">Use custom brightness</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_skip_read_chapters">Skip chapters marked read</string>
<string name="pref_reader_navigation">Navigation</string>