diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..410a50bd97 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: inorichi +ko_fi: inorichi diff --git a/README.md b/README.md index 4b21282d6b..294bb39cea 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index feddf20960..eff4c70578 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -62,8 +62,8 @@ + android:name=".ui.setting.ShikimoriLoginActivity" + android:label="Shikimori"> diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt index a27a11c342..a64a097925 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt @@ -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) = db.delete().objects(mangas).prepare() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaTitlePutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaTitlePutResolver.kt new file mode 100644 index 0000000000..702173afbe --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaTitlePutResolver.kt @@ -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() { + + 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) + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaViewerPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaViewerPutResolver.kt index 86c67f3463..e40f397a8d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaViewerPutResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaViewerPutResolver.kt @@ -20,10 +20,10 @@ class MangaViewerPutResolver : PutResolver() { } fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder() - .table(MangaTable.TABLE) - .where("${MangaTable.COL_ID} = ?") - .whereArgs(manga.id) - .build() + .table(MangaTable.TABLE) + .where("${MangaTable.COL_ID} = ?") + .whereArgs(manga.id) + .build() fun mapToContentValues(manga: Manga) = ContentValues(1).apply { put(MangaTable.COL_VIEWER, manga.viewer) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index 1b2e6bb322..eb5f9d4a1e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -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" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 58ad2b0aff..6545b4a39e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt index 14558d1f1b..70d669af56 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt @@ -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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/OAuth.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/OAuth.kt similarity index 83% rename from app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/OAuth.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/OAuth.kt index ad6adc18a4..118e584e73 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/OAuth.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/OAuth.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/Shikomori.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt similarity index 89% rename from app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/Shikomori.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt index 83fee74cf4..8068e6d55b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/Shikomori.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt @@ -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 { 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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/ShikomoriApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt similarity index 74% rename from app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/ShikomoriApi.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt index 2df1eae635..0180e015eb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/ShikomoriApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt @@ -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,21 +109,36 @@ 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() - if (responseBody.isEmpty()) { - throw Exception("Null Response") - } - val response = parser.parse(responseBody).array - if (response.size() > 1) { - throw Exception("Too much mangas in response") - } - val entry = response.map { - jsonToTrack(it.obj) - } - entry.firstOrNull() + parser.parse(responseBody).obj + }.flatMap { mangas -> + authClient.newCall(request) + .asObservableSuccess() + .map { netResponse -> + val responseBody = netResponse.body()?.string().orEmpty() + if (responseBody.isEmpty()) { + throw Exception("Null Response") + } + val response = parser.parse(responseBody).array + if (response.size() > 1) { + throw Exception("Too much mangas in response") + } + val entry = response.map { + jsonToTrack(it.obj, mangas) + } + entry.firstOrNull() + } } } @@ -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" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/ShikomoriInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt similarity index 79% rename from app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/ShikomoriInterceptor.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt index e46e7cfb4f..1540207277 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/ShikomoriInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriInterceptor.kt @@ -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) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriModels.kt new file mode 100644 index 0000000000..91e556bdd8 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriModels.kt @@ -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") +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/ShikomoriModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/ShikomoriModels.kt deleted file mode 100644 index d66f206495..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikomori/ShikomoriModels.kt +++ /dev/null @@ -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") -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/BrowseCatalogueController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/BrowseCatalogueController.kt index 80f284204f..97cfd76e16 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/BrowseCatalogueController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/browse/BrowseCatalogueController.kt @@ -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. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchPresenter.kt index b0cc97a62a..39bcfc1c65 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchPresenter.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index eb3999ffcf..17ac0cba58 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 00acfa494a..9fc4fa6ccf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -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" } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt index f2a0f412ef..5340627072 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt @@ -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(), // 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(), 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(), } } + /** + * 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, categories: List) { val manga = mangas.firstOrNull() ?: return presenter.moveMangaToCategories(manga, categories) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt index 712006367d..35b6bc06bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationPresenter.kt @@ -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() } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchPresenter.kt index 2a7f218b8c..b4a31c0da6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchPresenter.kt @@ -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 + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index ffa78615ce..82c7ffbe5a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -574,6 +574,9 @@ class ReaderActivity : BaseRxActivity() { subscriptions += preferences.colorFilter().asObservable() .subscribe { setColorFilter(it) } + + subscriptions += preferences.colorFilterMode().asObservable() + .subscribe { setColorFilter(preferences.colorFilter().getOrDefault()) } } /** @@ -722,7 +725,7 @@ class ReaderActivity : BaseRxActivity() { */ private fun setColorFilterValue(value: Int) { color_overlay.visibility = View.VISIBLE - color_overlay.setBackgroundColor(value) + color_overlay.setFilterColor(value, preferences.colorFilterMode().getOrDefault()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt index 263bd5de2c..a0579cdccf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt @@ -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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterView.kt new file mode 100644 index 0000000000..01d91a3b4a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterView.kt @@ -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) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index fbb2f4bbfe..b2aafa0a23 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -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 -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt index 250289cc1c..2a8f1f73ac 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt @@ -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) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/ShikomoriLoginActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/ShikomoriLoginActivity.kt index 6c3ba6f839..d369896edb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/ShikomoriLoginActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/ShikomoriLoginActivity.kt @@ -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() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt index d89b3ce5a4..2c54b236c5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt @@ -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) + } +} diff --git a/app/src/main/res/drawable-xxxhdpi/shikomori.png b/app/src/main/res/drawable-xxxhdpi/shikimori.png similarity index 100% rename from app/src/main/res/drawable-xxxhdpi/shikomori.png rename to app/src/main/res/drawable-xxxhdpi/shikimori.png diff --git a/app/src/main/res/layout-land/reader_color_filter_sheet.xml b/app/src/main/res/layout-land/reader_color_filter_sheet.xml index ba4d45e406..761c279928 100644 --- a/app/src/main/res/layout-land/reader_color_filter_sheet.xml +++ b/app/src/main/res/layout-land/reader_color_filter_sheet.xml @@ -29,7 +29,7 @@ android:layout_height="wrap_content" android:visibility="gone" /> - - + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/color_filter_mode_text"/> @@ -202,4 +229,11 @@ app:layout_constraintBottom_toBottomOf="@id/brightness_seekbar" app:layout_constraintRight_toRightOf="parent"/> + + diff --git a/app/src/main/res/layout/reader_color_filter_sheet.xml b/app/src/main/res/layout/reader_color_filter_sheet.xml index 618a8a77f0..3155bc15cb 100644 --- a/app/src/main/res/layout/reader_color_filter_sheet.xml +++ b/app/src/main/res/layout/reader_color_filter_sheet.xml @@ -21,7 +21,7 @@ android:layout_height="match_parent" android:visibility="gone" /> - + + + + + diff --git a/app/src/main/res/menu/menu_navigation.xml b/app/src/main/res/menu/menu_navigation.xml index 7703c6ef83..1ea7a65415 100644 --- a/app/src/main/res/menu/menu_navigation.xml +++ b/app/src/main/res/menu/menu_navigation.xml @@ -36,5 +36,10 @@ android:icon="@drawable/ic_settings_black_24dp" android:title="@string/label_settings" android:checkable="false" /> + diff --git a/app/src/main/res/values-v28/arrays.xml b/app/src/main/res/values-v28/arrays.xml new file mode 100644 index 0000000000..6a5e11f665 --- /dev/null +++ b/app/src/main/res/values-v28/arrays.xml @@ -0,0 +1,15 @@ + + + + + @string/filter_mode_default + @string/filter_mode_multiply + @string/filter_mode_screen + + + @string/filter_mode_overlay + @string/filter_mode_lighten + @string/filter_mode_darken + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index ebdccc0d03..632566da09 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -103,4 +103,10 @@ 2 + + @string/filter_mode_default + @string/filter_mode_multiply + @string/filter_mode_screen + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bd65d77d58..506a528dd2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -24,6 +24,7 @@ Source migration Extensions Extension info + Help @@ -178,6 +179,13 @@ Crop borders Use custom brightness Use custom color filter + Color filter blend mode + Default + Overlay + Multiply + Screen + Dodge / Lighten + Burn / Darken Keep screen on Skip chapters marked read Navigation