Add MangaDex only implementation of Mangadex Follows list
Add login dialog that pops up whenever you are not logged in when trying to browse MangaDex Remove attempts at porting over chapter read history from older galleries to new ones Disable latest for ExHentai, it was browse without buttons anyway
This commit is contained in:
parent
8928aa77eb
commit
b93298c411
@ -15,9 +15,9 @@ import java.util.Date
|
|||||||
|
|
||||||
interface ChapterQueries : DbProvider {
|
interface ChapterQueries : DbProvider {
|
||||||
// SY -->
|
// SY -->
|
||||||
fun getChapters(manga: Manga) = getChaptersByMangaId(manga.id)
|
fun getChapters(manga: Manga) = getChapters(manga.id)
|
||||||
|
|
||||||
fun getChaptersByMangaId(mangaId: Long?) = db.get()
|
fun getChapters(mangaId: Long?) = db.get()
|
||||||
.listOfObjects(Chapter::class.java)
|
.listOfObjects(Chapter::class.java)
|
||||||
.withQuery(
|
.withQuery(
|
||||||
Query.builder()
|
Query.builder()
|
||||||
|
@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.util.lang.chop
|
import eu.kanade.tachiyomi.util.lang.chop
|
||||||
import eu.kanade.tachiyomi.util.system.notification
|
import eu.kanade.tachiyomi.util.system.notification
|
||||||
@ -65,7 +66,7 @@ class LibraryUpdateNotifier(private val context: Context) {
|
|||||||
* @param current the current progress.
|
* @param current the current progress.
|
||||||
* @param total the total progress.
|
* @param total the total progress.
|
||||||
*/
|
*/
|
||||||
fun showProgressNotification(manga: Manga, current: Int, total: Int) {
|
fun showProgressNotification(manga: /* SY --> */ SManga /* SY <-- */, current: Int, total: Int) {
|
||||||
val title = if (preferences.hideNotificationContent()) {
|
val title = if (preferences.hideNotificationContent()) {
|
||||||
context.getString(R.string.notification_check_updates)
|
context.getString(R.string.notification_check_updates)
|
||||||
} else {
|
} else {
|
||||||
|
@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryGroup
|
import eu.kanade.tachiyomi.ui.library.LibraryGroup
|
||||||
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
|
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
|
||||||
@ -34,9 +35,16 @@ import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
|||||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||||
import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES
|
import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES
|
||||||
import exh.MERGED_SOURCE_ID
|
import exh.MERGED_SOURCE_ID
|
||||||
|
import exh.md.utils.FollowStatus
|
||||||
|
import exh.md.utils.MdUtil
|
||||||
|
import exh.metadata.metadata.base.insertFlatMetadata
|
||||||
|
import exh.source.EnhancedHttpSource.Companion.getMainSource
|
||||||
import exh.util.asObservable
|
import exh.util.asObservable
|
||||||
|
import exh.util.await
|
||||||
|
import exh.util.awaitSingle
|
||||||
import exh.util.nullIfBlank
|
import exh.util.nullIfBlank
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.Date
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -81,7 +89,10 @@ class LibraryUpdateService(
|
|||||||
enum class Target {
|
enum class Target {
|
||||||
CHAPTERS, // Manga chapters
|
CHAPTERS, // Manga chapters
|
||||||
COVERS, // Manga covers
|
COVERS, // Manga covers
|
||||||
TRACKING // Tracking metadata
|
TRACKING, // Tracking metadata
|
||||||
|
// SY -->
|
||||||
|
SYNC_FOLLOWS // MangaDex specific, pull mangadex manga in reading, rereading
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -215,6 +226,9 @@ class LibraryUpdateService(
|
|||||||
Target.CHAPTERS -> updateChapterList(mangaList)
|
Target.CHAPTERS -> updateChapterList(mangaList)
|
||||||
Target.COVERS -> updateCovers(mangaList)
|
Target.COVERS -> updateCovers(mangaList)
|
||||||
Target.TRACKING -> updateTrackings(mangaList)
|
Target.TRACKING -> updateTrackings(mangaList)
|
||||||
|
// SY -->
|
||||||
|
Target.SYNC_FOLLOWS -> syncFollows()
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
@ -433,9 +447,34 @@ class LibraryUpdateService(
|
|||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
return /* SY --> */ if (source is MergedSource) runBlocking { source.fetchChaptersAndSync(manga, false).asObservable() }
|
// SY -->
|
||||||
else /* SY <-- */ source.fetchChapterList(manga)
|
if (source.getMainSource() is MangaDex) {
|
||||||
.map { syncChaptersWithSource(db, it, manga, source) }
|
val tracks = db.getTracks(manga).executeAsBlocking()
|
||||||
|
if (tracks.isEmpty() || tracks.all { it.sync_id != TrackManager.MDLIST }) {
|
||||||
|
var track = trackManager.mdList.createInitialTracker(manga)
|
||||||
|
track = runBlocking { trackManager.mdList.refresh(track).awaitSingle() }
|
||||||
|
db.insertTrack(track).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
|
return (
|
||||||
|
/* SY --> */ if (source is MergedSource) runBlocking { source.fetchChaptersAndSync(manga, false).asObservable() }
|
||||||
|
else /* SY <-- */ source.fetchChapterList(manga)
|
||||||
|
.map { syncChaptersWithSource(db, it, manga, source) }
|
||||||
|
// SY -->
|
||||||
|
)
|
||||||
|
.doOnNext {
|
||||||
|
if (source.getMainSource() is MangaDex) {
|
||||||
|
val tracks = db.getTracks(manga).executeAsBlocking()
|
||||||
|
if (tracks.isEmpty() || tracks.all { it.sync_id != TrackManager.MDLIST }) {
|
||||||
|
var track = trackManager.mdList.createInitialTracker(manga)
|
||||||
|
track = runBlocking { trackManager.mdList.refresh(track).awaitSingle() }
|
||||||
|
db.insertTrack(track).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateCovers(mangaToUpdate: List<LibraryManga>): Observable<LibraryManga> {
|
private fun updateCovers(mangaToUpdate: List<LibraryManga>): Observable<LibraryManga> {
|
||||||
@ -501,6 +540,48 @@ class LibraryUpdateService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
// filter all follows from Mangadex and only add reading or rereading manga to library
|
||||||
|
private fun syncFollows(): Observable<LibraryManga> {
|
||||||
|
val count = AtomicInteger(0)
|
||||||
|
val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager)!!
|
||||||
|
return mangaDex.fetchAllFollows(true)
|
||||||
|
.asObservable()
|
||||||
|
.map { listManga ->
|
||||||
|
listManga.filter { (_, metadata) ->
|
||||||
|
metadata.follow_status == FollowStatus.RE_READING.int || metadata.follow_status == FollowStatus.READING.int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.doOnNext { listManga ->
|
||||||
|
listManga.forEach { (networkManga, metadata) ->
|
||||||
|
notifier.showProgressNotification(networkManga, count.andIncrement, listManga.size)
|
||||||
|
var dbManga = db.getManga(networkManga.url, mangaDex.id)
|
||||||
|
.executeAsBlocking()
|
||||||
|
if (dbManga == null) {
|
||||||
|
dbManga = Manga.create(
|
||||||
|
networkManga.url,
|
||||||
|
networkManga.title,
|
||||||
|
mangaDex.id
|
||||||
|
)
|
||||||
|
dbManga.date_added = Date().time
|
||||||
|
}
|
||||||
|
|
||||||
|
dbManga.copyFrom(networkManga)
|
||||||
|
dbManga.favorite = true
|
||||||
|
val id = db.insertManga(dbManga).executeAsBlocking().insertedId()
|
||||||
|
if (id != null) {
|
||||||
|
metadata.mangaId = id
|
||||||
|
db.insertFlatMetadata(metadata.flatten()).await()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.doOnCompleted {
|
||||||
|
notifier.cancelProgressNotification()
|
||||||
|
}
|
||||||
|
.map { LibraryManga() }
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes basic file of update errors to cache dir.
|
* Writes basic file of update errors to cache dir.
|
||||||
*/
|
*/
|
||||||
|
@ -291,6 +291,10 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val mangaDexLowQualityCovers = "manga_dex_low_quality_covers"
|
const val mangaDexLowQualityCovers = "manga_dex_low_quality_covers"
|
||||||
|
|
||||||
|
const val mangaDexForceLatestCovers = "manga_dex_force_latest_covers"
|
||||||
|
|
||||||
|
const val preferredMangaDexId = "preferred_mangaDex_id"
|
||||||
|
|
||||||
const val dataSaver = "data_saver"
|
const val dataSaver = "data_saver"
|
||||||
|
|
||||||
const val ignoreJpeg = "ignore_jpeg"
|
const val ignoreJpeg = "ignore_jpeg"
|
||||||
|
@ -396,6 +396,10 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun mangaDexLowQualityCovers() = flowPrefs.getBoolean(Keys.mangaDexLowQualityCovers, false)
|
fun mangaDexLowQualityCovers() = flowPrefs.getBoolean(Keys.mangaDexLowQualityCovers, false)
|
||||||
|
|
||||||
|
fun mangaDexForceLatestCovers() = flowPrefs.getBoolean(Keys.mangaDexForceLatestCovers, false)
|
||||||
|
|
||||||
|
fun preferredMangaDexId() = flowPrefs.getString(Keys.preferredMangaDexId, "0")
|
||||||
|
|
||||||
fun dataSaver() = flowPrefs.getBoolean(Keys.dataSaver, false)
|
fun dataSaver() = flowPrefs.getBoolean(Keys.dataSaver, false)
|
||||||
|
|
||||||
fun ignoreJpeg() = flowPrefs.getBoolean(Keys.ignoreJpeg, false)
|
fun ignoreJpeg() = flowPrefs.getBoolean(Keys.ignoreJpeg, false)
|
||||||
|
@ -4,6 +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.bangumi.Bangumi
|
import eu.kanade.tachiyomi.data.track.bangumi.Bangumi
|
||||||
import eu.kanade.tachiyomi.data.track.kitsu.Kitsu
|
import eu.kanade.tachiyomi.data.track.kitsu.Kitsu
|
||||||
|
import eu.kanade.tachiyomi.data.track.mdlist.MdList
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
|
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
|
import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
|
||||||
|
|
||||||
@ -16,8 +17,8 @@ class TrackManager(context: Context) {
|
|||||||
const val SHIKIMORI = 4
|
const val SHIKIMORI = 4
|
||||||
const val BANGUMI = 5
|
const val BANGUMI = 5
|
||||||
|
|
||||||
// SY --> Mangadex from Neko todo
|
// SY --> Mangadex from Neko
|
||||||
const val MDLIST = 60
|
const val MDLIST = 6
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
@ -31,6 +32,8 @@ class TrackManager(context: Context) {
|
|||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val mdList = MdList(context, MDLIST)
|
||||||
|
|
||||||
val myAnimeList = MyAnimeList(context, MYANIMELIST)
|
val myAnimeList = MyAnimeList(context, MYANIMELIST)
|
||||||
|
|
||||||
val aniList = Anilist(context, ANILIST)
|
val aniList = Anilist(context, ANILIST)
|
||||||
@ -41,11 +44,11 @@ class TrackManager(context: Context) {
|
|||||||
|
|
||||||
val bangumi = Bangumi(context, BANGUMI)
|
val bangumi = Bangumi(context, BANGUMI)
|
||||||
|
|
||||||
val services = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi)
|
val services = listOf(mdList, myAnimeList, aniList, kitsu, shikimori, bangumi)
|
||||||
|
|
||||||
fun getService(id: Int) = services.find { it.id == id }
|
fun getService(id: Int) = services.find { it.id == id }
|
||||||
|
|
||||||
fun hasLoggedServices() = services.any { it.isLogged }
|
fun hasLoggedServices(isMangaDexManga: Boolean = true) = services.any { it.isLogged && ((it.id == MDLIST && isMangaDexManga) || it.id != MDLIST) }
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
fun mapTrackingOrder(status: String, context: Context): Int {
|
fun mapTrackingOrder(status: String, context: Context): Int {
|
||||||
|
@ -0,0 +1,136 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.track.mdlist
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackService
|
||||||
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
|
import exh.md.utils.FollowStatus
|
||||||
|
import exh.md.utils.MdUtil
|
||||||
|
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||||
|
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||||
|
import exh.metadata.metadata.base.insertFlatMetadata
|
||||||
|
import exh.util.asObservable
|
||||||
|
import exh.util.floor
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import rx.Completable
|
||||||
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class MdList(private val context: Context, id: Int) : TrackService(id) {
|
||||||
|
|
||||||
|
private val mdex by lazy { MdUtil.getEnabledMangaDex() }
|
||||||
|
private val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
|
override val name = "MDList"
|
||||||
|
|
||||||
|
override fun getLogo(): Int {
|
||||||
|
return R.drawable.ic_tracker_mangadex_logo
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLogoColor(): Int {
|
||||||
|
return Color.rgb(43, 48, 53)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStatusList(): List<Int> {
|
||||||
|
return FollowStatus.values().map { it.int }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStatus(status: Int): String =
|
||||||
|
context.resources.getStringArray(R.array.md_follows_options).asList()[status]
|
||||||
|
|
||||||
|
override fun getScoreList() = IntRange(0, 10).map(Int::toString)
|
||||||
|
|
||||||
|
override fun displayScore(track: Track) = track.score.toInt().toString()
|
||||||
|
|
||||||
|
override fun add(track: Track): Observable<Track> {
|
||||||
|
return update(track)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(track: Track): Observable<Track> {
|
||||||
|
val mdex = mdex ?: throw Exception("Mangadex not enabled")
|
||||||
|
return Observable.defer {
|
||||||
|
db.getManga(track.tracking_url.substringAfter(".org"), mdex.id)
|
||||||
|
.asRxObservable()
|
||||||
|
.map { manga ->
|
||||||
|
val mangaMetadata = db.getFlatMetadataForManga(manga.id!!).executeAsBlocking()?.raise(MangaDexSearchMetadata::class) ?: throw Exception("Invalid manga metadata")
|
||||||
|
val followStatus = FollowStatus.fromInt(track.status)!!
|
||||||
|
|
||||||
|
// allow follow status to update
|
||||||
|
if (mangaMetadata.follow_status != followStatus.int) {
|
||||||
|
runBlocking { mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), followStatus).collect() }
|
||||||
|
mangaMetadata.follow_status = followStatus.int
|
||||||
|
db.insertFlatMetadata(mangaMetadata.flatten()).await()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track.score.toInt() > 0) {
|
||||||
|
runBlocking { mdex.updateRating(track).collect() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// mangadex wont update chapters if manga is not follows this prevents unneeded network call
|
||||||
|
|
||||||
|
if (followStatus != FollowStatus.UNFOLLOWED) {
|
||||||
|
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
|
||||||
|
track.status = FollowStatus.COMPLETED.int
|
||||||
|
}
|
||||||
|
|
||||||
|
runBlocking { mdex.updateReadingProgress(track).collect() }
|
||||||
|
} else if (track.last_chapter_read != 0) {
|
||||||
|
// When followStatus has been changed to unfollowed 0 out read chapters since dex does
|
||||||
|
track.last_chapter_read = 0
|
||||||
|
}
|
||||||
|
track
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCompletionStatus(): Int = FollowStatus.COMPLETED.int
|
||||||
|
|
||||||
|
override fun bind(track: Track): Observable<Track> {
|
||||||
|
val mdex = mdex ?: throw Exception("Mangadex not enabled")
|
||||||
|
return mdex.fetchTrackingInfo(track.tracking_url).asObservable()
|
||||||
|
.doOnNext { remoteTrack ->
|
||||||
|
track.copyPersonalFrom(remoteTrack)
|
||||||
|
track.total_chapters = if (remoteTrack.total_chapters == 0) {
|
||||||
|
db.getChapters(track.manga_id).executeAsBlocking().maxOfOrNull { it.chapter_number }?.floor() ?: remoteTrack.total_chapters
|
||||||
|
} else {
|
||||||
|
remoteTrack.total_chapters
|
||||||
|
}
|
||||||
|
update(track)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun refresh(track: Track): Observable<Track> {
|
||||||
|
val mdex = mdex ?: throw Exception("Mangadex not enabled")
|
||||||
|
return mdex.fetchTrackingInfo(track.tracking_url).asObservable()
|
||||||
|
.map { remoteTrack ->
|
||||||
|
track.copyPersonalFrom(remoteTrack)
|
||||||
|
track.total_chapters = if (remoteTrack.total_chapters == 0) {
|
||||||
|
db.getChapters(track.manga_id).executeAsBlocking().maxOfOrNull { it.chapter_number }?.floor() ?: remoteTrack.total_chapters
|
||||||
|
} else {
|
||||||
|
remoteTrack.total_chapters
|
||||||
|
}
|
||||||
|
track
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createInitialTracker(manga: Manga): Track {
|
||||||
|
val track = Track.create(TrackManager.MDLIST)
|
||||||
|
track.manga_id = manga.id!!
|
||||||
|
track.status = FollowStatus.UNFOLLOWED.int
|
||||||
|
track.tracking_url = MdUtil.baseUrl + manga.url
|
||||||
|
track.title = manga.title
|
||||||
|
return track
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun search(query: String): Observable<List<TrackSearch>> = throw Exception("not used")
|
||||||
|
|
||||||
|
override fun login(username: String, password: String): Completable = throw Exception("not used")
|
||||||
|
|
||||||
|
override val isLogged = mdex?.isLogged() ?: false
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bluelinelabs.conductor.Controller
|
||||||
|
|
||||||
|
interface BrowseSourceFilterHeader {
|
||||||
|
fun getFilterHeader(controller: Controller): RecyclerView.Adapter<*>
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import exh.md.utils.FollowStatus
|
||||||
|
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
interface FollowsSource {
|
||||||
|
fun fetchFollows(): Observable<MangasPage>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all Follows retrieved by Coroutines
|
||||||
|
*
|
||||||
|
* @param SManga all smanga found for user
|
||||||
|
*/
|
||||||
|
fun fetchAllFollows(forceHd: Boolean = false): Flow<List<Pair<SManga, RaisedSearchMetadata>>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* updates the follow status for a manga
|
||||||
|
*/
|
||||||
|
fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Flow<Boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a MdList Track of the manga
|
||||||
|
*/
|
||||||
|
fun fetchTrackingInfo(url: String): Flow<Track>
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
|
|
||||||
|
interface LoginSource {
|
||||||
|
val needsLogin: Boolean
|
||||||
|
|
||||||
|
fun isLogged(): Boolean
|
||||||
|
|
||||||
|
fun getLoginDialog(source: Source, activity: Activity): DialogController
|
||||||
|
|
||||||
|
suspend fun login(username: String, password: String, twoFactorCode: String = ""): Boolean
|
||||||
|
|
||||||
|
suspend fun logout(): Boolean
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package eu.kanade.tachiyomi.source.online
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface RandomMangaSource {
|
||||||
|
fun fetchRandomMangaUrl(): Flow<String>
|
||||||
|
}
|
@ -91,7 +91,7 @@ class EHentai(
|
|||||||
get() = "https://$domain"
|
get() = "https://$domain"
|
||||||
|
|
||||||
override val lang = "all"
|
override val lang = "all"
|
||||||
override val supportsLatest = true
|
override val supportsLatest = !exh
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
private val updateHelper: EHentaiUpdateHelper by injectLazy()
|
private val updateHelper: EHentaiUpdateHelper by injectLazy()
|
||||||
|
@ -1,41 +1,78 @@
|
|||||||
package eu.kanade.tachiyomi.source.online.all
|
package eu.kanade.tachiyomi.source.online.all
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import com.bluelinelabs.conductor.Controller
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.source.online.BrowseSourceFilterHeader
|
||||||
|
import eu.kanade.tachiyomi.source.online.FollowsSource
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.LoginSource
|
||||||
import eu.kanade.tachiyomi.source.online.MetadataSource
|
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.RandomMangaSource
|
||||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
|
import exh.GalleryAddEvent
|
||||||
|
import exh.GalleryAdder
|
||||||
|
import exh.md.MangaDexFabHeaderAdapter
|
||||||
import exh.md.handlers.ApiChapterParser
|
import exh.md.handlers.ApiChapterParser
|
||||||
import exh.md.handlers.ApiMangaParser
|
import exh.md.handlers.ApiMangaParser
|
||||||
|
import exh.md.handlers.FollowsHandler
|
||||||
import exh.md.handlers.MangaHandler
|
import exh.md.handlers.MangaHandler
|
||||||
import exh.md.handlers.MangaPlusHandler
|
import exh.md.handlers.MangaPlusHandler
|
||||||
|
import exh.md.utils.FollowStatus
|
||||||
import exh.md.utils.MdLang
|
import exh.md.utils.MdLang
|
||||||
import exh.md.utils.MdUtil
|
import exh.md.utils.MdUtil
|
||||||
import exh.metadata.metadata.MangaDexSearchMetadata
|
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||||
import exh.source.DelegatedHttpSource
|
import exh.source.DelegatedHttpSource
|
||||||
import exh.ui.metadata.adapters.MangaDexDescriptionAdapter
|
import exh.ui.metadata.adapters.MangaDexDescriptionAdapter
|
||||||
import exh.util.urlImportFetchSearchManga
|
import exh.util.urlImportFetchSearchManga
|
||||||
|
import exh.widget.preference.MangadexLoginDialog
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
|
import okhttp3.FormBody
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class MangaDex(delegate: HttpSource, val context: Context) :
|
class MangaDex(delegate: HttpSource, val context: Context) :
|
||||||
DelegatedHttpSource(delegate),
|
DelegatedHttpSource(delegate),
|
||||||
MetadataSource<MangaDexSearchMetadata, Response>,
|
MetadataSource<MangaDexSearchMetadata, Response>,
|
||||||
UrlImportableSource {
|
UrlImportableSource,
|
||||||
|
FollowsSource,
|
||||||
|
LoginSource,
|
||||||
|
BrowseSourceFilterHeader,
|
||||||
|
RandomMangaSource {
|
||||||
override val lang: String = delegate.lang
|
override val lang: String = delegate.lang
|
||||||
|
|
||||||
|
override val headers: Headers
|
||||||
|
get() = super.headers.newBuilder().apply {
|
||||||
|
add("X-Requested-With", "XMLHttpRequest")
|
||||||
|
add("Referer", MdUtil.baseUrl)
|
||||||
|
}.build()
|
||||||
|
|
||||||
private val mdLang by lazy {
|
private val mdLang by lazy {
|
||||||
MdLang.values().find { it.lang == lang }?.dexLang ?: lang
|
MdLang.values().find { it.lang == lang }?.dexLang ?: lang
|
||||||
}
|
}
|
||||||
@ -44,25 +81,27 @@ class MangaDex(delegate: HttpSource, val context: Context) :
|
|||||||
|
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
|
||||||
urlImportFetchSearchManga(context, query) {
|
urlImportFetchSearchManga(context, query) {
|
||||||
super.fetchSearchManga(page, query, filters)
|
ImportIdToMdId(query) {
|
||||||
|
super.fetchSearchManga(page, query, filters)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
||||||
|
|
||||||
return if (lcFirstPathSegment == "title" || lcFirstPathSegment == "manga") {
|
return if (lcFirstPathSegment == "title" || lcFirstPathSegment == "manga") {
|
||||||
"/manga/${uri.pathSegments[1]}/"
|
MdUtil.mapMdIdToMangaUrl(uri.pathSegments[1].toInt())
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
return MangaHandler(client, headers, listOf(mdLang)).fetchMangaDetailsObservable(manga)
|
return MangaHandler(client, headers, listOf(mdLang), Injekt.get<PreferencesHelper>().mangaDexForceLatestCovers().get()).fetchMangaDetailsObservable(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
return MangaHandler(client, headers, listOf(mdLang)).fetchChapterListObservable(manga)
|
return MangaHandler(client, headers, listOf(mdLang), Injekt.get<PreferencesHelper>().mangaDexForceLatestCovers().get()).fetchChapterListObservable(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExperimentalSerializationApi
|
@ExperimentalSerializationApi
|
||||||
@ -96,6 +135,130 @@ class MangaDex(delegate: HttpSource, val context: Context) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Response) {
|
override fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Response) {
|
||||||
ApiMangaParser(listOf(mdLang)).parseIntoMetadata(metadata, input)
|
ApiMangaParser(listOf(mdLang)).parseIntoMetadata(metadata, input, Injekt.get<PreferencesHelper>().mangaDexForceLatestCovers().get())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchFollows(): Observable<MangasPage> {
|
||||||
|
return FollowsHandler(client, headers, Injekt.get()).fetchFollows()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val needsLogin: Boolean = true
|
||||||
|
|
||||||
|
override fun getLoginDialog(source: Source, activity: Activity): DialogController {
|
||||||
|
return MangadexLoginDialog(source as MangaDex, activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isLogged(): Boolean {
|
||||||
|
val httpUrl = MdUtil.baseUrl.toHttpUrlOrNull()!!
|
||||||
|
return network.cookieManager.get(httpUrl).any { it.name == REMEMBER_ME }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun login(
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
twoFactorCode: String
|
||||||
|
): Boolean {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val formBody = FormBody.Builder()
|
||||||
|
.add("login_username", username)
|
||||||
|
.add("login_password", password)
|
||||||
|
.add("no_js", "1")
|
||||||
|
.add("remember_me", "1")
|
||||||
|
|
||||||
|
twoFactorCode.let {
|
||||||
|
formBody.add("two_factor", it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = client.newCall(
|
||||||
|
POST(
|
||||||
|
"${MdUtil.baseUrl}/ajax/actions.ajax.php?function=login",
|
||||||
|
headers,
|
||||||
|
formBody.build()
|
||||||
|
)
|
||||||
|
).execute()
|
||||||
|
response.body!!.string().isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun logout(): Boolean {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
// https://mangadex.org/ajax/actions.ajax.php?function=logout
|
||||||
|
val httpUrl = MdUtil.baseUrl.toHttpUrlOrNull()!!
|
||||||
|
val listOfDexCookies = network.cookieManager.get(httpUrl)
|
||||||
|
val cookie = listOfDexCookies.find { it.name == REMEMBER_ME }
|
||||||
|
val token = cookie?.value
|
||||||
|
if (token.isNullOrEmpty()) {
|
||||||
|
return@withContext true
|
||||||
|
}
|
||||||
|
val result = client.newCall(
|
||||||
|
POST("${MdUtil.baseUrl}/ajax/actions.ajax.php?function=logout", headers).newBuilder().addHeader(REMEMBER_ME, token).build()
|
||||||
|
).execute()
|
||||||
|
val resultStr = result.body!!.string()
|
||||||
|
if (resultStr.contains("success", true)) {
|
||||||
|
network.cookieManager.remove(httpUrl)
|
||||||
|
Injekt.get<TrackManager>().mdList.logout()
|
||||||
|
return@withContext true
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchAllFollows(forceHd: Boolean): Flow<List<Pair<SManga, MangaDexSearchMetadata>>> {
|
||||||
|
return flow { emit(FollowsHandler(client, headers, Injekt.get()).fetchAllFollows(forceHd)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateReadingProgress(track: Track): Flow<Boolean> {
|
||||||
|
return flow { FollowsHandler(client, headers, Injekt.get()).updateReadingProgress(track) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateRating(track: Track): Flow<Boolean> {
|
||||||
|
return flow { FollowsHandler(client, headers, Injekt.get()).updateRating(track) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchTrackingInfo(url: String): Flow<Track> {
|
||||||
|
return flow {
|
||||||
|
if (!isLogged()) {
|
||||||
|
throw Exception("Not Logged in")
|
||||||
|
}
|
||||||
|
emit(FollowsHandler(client, headers, Injekt.get()).fetchTrackingInfo(url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Flow<Boolean> {
|
||||||
|
return flow { emit(FollowsHandler(client, headers, Injekt.get()).updateFollowStatus(mangaID, followStatus)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterHeader(controller: Controller): MangaDexFabHeaderAdapter {
|
||||||
|
return MangaDexFabHeaderAdapter(controller, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchRandomMangaUrl(): Flow<String> {
|
||||||
|
return MangaHandler(client, headers, listOf(mdLang)).fetchRandomMangaId()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ImportIdToMdId(query: String, fail: () -> Observable<MangasPage>): Observable<MangasPage> =
|
||||||
|
when {
|
||||||
|
query.toIntOrNull() != null -> {
|
||||||
|
Observable.fromCallable {
|
||||||
|
// MdUtil.
|
||||||
|
val res = GalleryAdder().addGallery(context, MdUtil.baseUrl + MdUtil.mapMdIdToMangaUrl(query.toInt()), false, this)
|
||||||
|
MangasPage(
|
||||||
|
(
|
||||||
|
if (res is GalleryAddEvent.Success) {
|
||||||
|
listOf(res.manga)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val REMEMBER_ME = "mangadex_rememberme_token"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ import eu.kanade.tachiyomi.source.ConfigurableSource
|
|||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
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.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.LoginSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
@ -55,6 +56,7 @@ import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
|||||||
import eu.kanade.tachiyomi.widget.EmptyView
|
import eu.kanade.tachiyomi.widget.EmptyView
|
||||||
import exh.EXHSavedSearch
|
import exh.EXHSavedSearch
|
||||||
import exh.isEhBasedSource
|
import exh.isEhBasedSource
|
||||||
|
import exh.source.EnhancedHttpSource.Companion.getMainSource
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.main_activity.root_coordinator
|
import kotlinx.android.synthetic.main.main_activity.root_coordinator
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -187,6 +189,14 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
setupRecycler(view)
|
setupRecycler(view)
|
||||||
|
|
||||||
binding.progress.isVisible = true
|
binding.progress.isVisible = true
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
val mainSource = presenter.source.getMainSource()
|
||||||
|
if (mainSource is LoginSource && mainSource.needsLogin && !mainSource.isLogged()) {
|
||||||
|
val dialog = mainSource.getLoginDialog(mainSource, activity!!)
|
||||||
|
dialog.showDialog(router)
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun initFilterSheet() {
|
open fun initFilterSheet() {
|
||||||
@ -205,6 +215,8 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
filterSheet = SourceFilterSheet(
|
filterSheet = SourceFilterSheet(
|
||||||
activity!!,
|
activity!!,
|
||||||
// SY -->
|
// SY -->
|
||||||
|
this,
|
||||||
|
presenter.source,
|
||||||
presenter.loadSearches(),
|
presenter.loadSearches(),
|
||||||
// SY <--
|
// SY <--
|
||||||
onFilterClicked = {
|
onFilterClicked = {
|
||||||
|
@ -38,7 +38,6 @@ import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateItem
|
|||||||
import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem
|
import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem
|
||||||
import eu.kanade.tachiyomi.util.removeCovers
|
import eu.kanade.tachiyomi.util.removeCovers
|
||||||
import exh.EXHSavedSearch
|
import exh.EXHSavedSearch
|
||||||
import exh.isEhBasedSource
|
|
||||||
import java.lang.RuntimeException
|
import java.lang.RuntimeException
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -188,7 +187,7 @@ open class BrowseSourcePresenter(
|
|||||||
// SY <--
|
// SY <--
|
||||||
.doOnNext { initializeMangas(it.second) }
|
.doOnNext { initializeMangas(it.second) }
|
||||||
// SY -->
|
// SY -->
|
||||||
.map { triple -> triple.first to triple.second.mapIndexed { index, manga -> SourceItem(manga, sourceDisplayMode, if (prefs.enhancedEHentaiView().get() && source.isEhBasedSource()) triple.third?.getOrNull(index) else null) } }
|
.map { triple -> triple.first to triple.second.mapIndexed { index, manga -> SourceItem(manga, sourceDisplayMode, triple.third?.getOrNull(index)) } }
|
||||||
// SY <--
|
// SY <--
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribeReplay(
|
.subscribeReplay(
|
||||||
|
@ -4,11 +4,15 @@ import android.view.View
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
||||||
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
||||||
|
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||||
|
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||||
import kotlinx.android.synthetic.main.source_comfortable_grid_item.card
|
import kotlinx.android.synthetic.main.source_comfortable_grid_item.card
|
||||||
|
import kotlinx.android.synthetic.main.source_comfortable_grid_item.local_text
|
||||||
import kotlinx.android.synthetic.main.source_comfortable_grid_item.progress
|
import kotlinx.android.synthetic.main.source_comfortable_grid_item.progress
|
||||||
import kotlinx.android.synthetic.main.source_comfortable_grid_item.thumbnail
|
import kotlinx.android.synthetic.main.source_comfortable_grid_item.thumbnail
|
||||||
import kotlinx.android.synthetic.main.source_comfortable_grid_item.title
|
import kotlinx.android.synthetic.main.source_comfortable_grid_item.title
|
||||||
@ -43,6 +47,17 @@ class SourceComfortableGridHolder(private val view: View, private val adapter: F
|
|||||||
setImage(manga)
|
setImage(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
override fun onSetMetadataValues(manga: Manga, metadata: RaisedSearchMetadata) {
|
||||||
|
if (metadata is MangaDexSearchMetadata) {
|
||||||
|
metadata.follow_status?.let {
|
||||||
|
local_text.text = itemView.context.resources.getStringArray(R.array.md_follows_options).asList()[it]
|
||||||
|
local_text.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
override fun setImage(manga: Manga) {
|
override fun setImage(manga: Manga) {
|
||||||
// For rounded corners
|
// For rounded corners
|
||||||
card.clipToOutline = true
|
card.clipToOutline = true
|
||||||
|
@ -56,7 +56,7 @@ class SourceEnhancedEHentaiListHolder(private val view: View, adapter: FlexibleA
|
|||||||
setImage(manga)
|
setImage(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSetMetadataValues(manga: Manga, metadata: RaisedSearchMetadata) {
|
override fun onSetMetadataValues(manga: Manga, metadata: RaisedSearchMetadata) {
|
||||||
if (metadata !is EHentaiSearchMetadata) return
|
if (metadata !is EHentaiSearchMetadata) return
|
||||||
|
|
||||||
if (metadata.uploader != null) {
|
if (metadata.uploader != null) {
|
||||||
@ -64,17 +64,17 @@ class SourceEnhancedEHentaiListHolder(private val view: View, adapter: FlexibleA
|
|||||||
}
|
}
|
||||||
|
|
||||||
val pair = when (metadata.genre) {
|
val pair = when (metadata.genre) {
|
||||||
"doujinshi" -> Pair(SourceTagsUtil.DOUJINSHI_COLOR, R.string.doujinshi)
|
"doujinshi" -> SourceTagsUtil.DOUJINSHI_COLOR to R.string.doujinshi
|
||||||
"manga" -> Pair(SourceTagsUtil.MANGA_COLOR, R.string.manga)
|
"manga" -> SourceTagsUtil.MANGA_COLOR to R.string.manga
|
||||||
"artistcg" -> Pair(SourceTagsUtil.ARTIST_CG_COLOR, R.string.artist_cg)
|
"artistcg" -> SourceTagsUtil.ARTIST_CG_COLOR to R.string.artist_cg
|
||||||
"gamecg" -> Pair(SourceTagsUtil.GAME_CG_COLOR, R.string.game_cg)
|
"gamecg" -> SourceTagsUtil.GAME_CG_COLOR to R.string.game_cg
|
||||||
"western" -> Pair(SourceTagsUtil.WESTERN_COLOR, R.string.western)
|
"western" -> SourceTagsUtil.WESTERN_COLOR to R.string.western
|
||||||
"non-h" -> Pair(SourceTagsUtil.NON_H_COLOR, R.string.non_h)
|
"non-h" -> SourceTagsUtil.NON_H_COLOR to R.string.non_h
|
||||||
"imageset" -> Pair(SourceTagsUtil.IMAGE_SET_COLOR, R.string.image_set)
|
"imageset" -> SourceTagsUtil.IMAGE_SET_COLOR to R.string.image_set
|
||||||
"cosplay" -> Pair(SourceTagsUtil.COSPLAY_COLOR, R.string.cosplay)
|
"cosplay" -> SourceTagsUtil.COSPLAY_COLOR to R.string.cosplay
|
||||||
"asianporn" -> Pair(SourceTagsUtil.ASIAN_PORN_COLOR, R.string.asian_porn)
|
"asianporn" -> SourceTagsUtil.ASIAN_PORN_COLOR to R.string.asian_porn
|
||||||
"misc" -> Pair(SourceTagsUtil.MISC_COLOR, R.string.misc)
|
"misc" -> SourceTagsUtil.MISC_COLOR to R.string.misc
|
||||||
else -> Pair("", 0)
|
else -> "" to 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pair.first.isNotBlank()) {
|
if (pair.first.isNotBlank()) {
|
||||||
|
@ -8,18 +8,24 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bluelinelabs.conductor.Controller
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.databinding.SourceFilterSheetBinding
|
import eu.kanade.tachiyomi.databinding.SourceFilterSheetBinding
|
||||||
import eu.kanade.tachiyomi.util.view.inflate
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.BrowseSourceFilterHeader
|
||||||
import eu.kanade.tachiyomi.widget.SimpleNavigationView
|
import eu.kanade.tachiyomi.widget.SimpleNavigationView
|
||||||
import exh.EXHSavedSearch
|
import exh.EXHSavedSearch
|
||||||
|
import exh.source.EnhancedHttpSource.Companion.getMainSource
|
||||||
|
|
||||||
class SourceFilterSheet(
|
class SourceFilterSheet(
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
// SY -->
|
// SY -->
|
||||||
|
controller: Controller,
|
||||||
|
source: CatalogueSource,
|
||||||
searches: List<EXHSavedSearch> = emptyList(),
|
searches: List<EXHSavedSearch> = emptyList(),
|
||||||
// SY <--
|
// SY <--
|
||||||
onFilterClicked: () -> Unit,
|
onFilterClicked: () -> Unit,
|
||||||
@ -34,7 +40,7 @@ class SourceFilterSheet(
|
|||||||
private var filterNavView: FilterNavigationView
|
private var filterNavView: FilterNavigationView
|
||||||
|
|
||||||
init {
|
init {
|
||||||
filterNavView = FilterNavigationView(activity /* SY --> */, searches = searches/* SY <-- */)
|
filterNavView = FilterNavigationView(activity /* SY --> */, searches = searches, source = source, controller = controller/* SY <-- */)
|
||||||
filterNavView.onFilterClicked = {
|
filterNavView.onFilterClicked = {
|
||||||
onFilterClicked()
|
onFilterClicked()
|
||||||
this.dismiss()
|
this.dismiss()
|
||||||
@ -66,7 +72,7 @@ class SourceFilterSheet(
|
|||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
class FilterNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null /* SY --> */, searches: List<EXHSavedSearch> = emptyList()/* SY <-- */) :
|
class FilterNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null /* SY --> */, searches: List<EXHSavedSearch> = emptyList(), source: CatalogueSource? = null, controller: Controller? = null/* SY <-- */) :
|
||||||
SimpleNavigationView(context, attrs) {
|
SimpleNavigationView(context, attrs) {
|
||||||
|
|
||||||
var onFilterClicked = {}
|
var onFilterClicked = {}
|
||||||
@ -79,6 +85,8 @@ class SourceFilterSheet(
|
|||||||
|
|
||||||
var onSavedSearchDeleteClicked: (Int, String) -> Unit = { _, _ -> }
|
var onSavedSearchDeleteClicked: (Int, String) -> Unit = { _, _ -> }
|
||||||
|
|
||||||
|
val adapters = mutableListOf<RecyclerView.Adapter<*>>()
|
||||||
|
|
||||||
private val savedSearchesAdapter = SavedSearchesAdapter(getSavedSearchesChips(searches))
|
private val savedSearchesAdapter = SavedSearchesAdapter(getSavedSearchesChips(searches))
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
@ -88,7 +96,13 @@ class SourceFilterSheet(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
// SY -->
|
// SY -->
|
||||||
recycler.adapter = ConcatAdapter(savedSearchesAdapter, adapter)
|
val mainSource = source?.getMainSource()
|
||||||
|
if (mainSource is BrowseSourceFilterHeader && controller != null) {
|
||||||
|
adapters += mainSource.getFilterHeader(controller)
|
||||||
|
}
|
||||||
|
adapters += savedSearchesAdapter
|
||||||
|
adapters += adapter
|
||||||
|
recycler.adapter = ConcatAdapter(adapters)
|
||||||
// SY <--
|
// SY <--
|
||||||
recycler.setHasFixedSize(true)
|
recycler.setHasFixedSize(true)
|
||||||
(binding.root.getChildAt(1) as ViewGroup).addView(recycler)
|
(binding.root.getChildAt(1) as ViewGroup).addView(recycler)
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
||||||
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
||||||
|
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||||
|
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||||
import kotlinx.android.synthetic.main.source_compact_grid_item.card
|
import kotlinx.android.synthetic.main.source_compact_grid_item.card
|
||||||
|
import kotlinx.android.synthetic.main.source_compact_grid_item.local_text
|
||||||
import kotlinx.android.synthetic.main.source_compact_grid_item.progress
|
import kotlinx.android.synthetic.main.source_compact_grid_item.progress
|
||||||
import kotlinx.android.synthetic.main.source_compact_grid_item.thumbnail
|
import kotlinx.android.synthetic.main.source_compact_grid_item.thumbnail
|
||||||
import kotlinx.android.synthetic.main.source_compact_grid_item.title
|
import kotlinx.android.synthetic.main.source_compact_grid_item.title
|
||||||
@ -39,6 +44,17 @@ open class SourceGridHolder(private val view: View, private val adapter: Flexibl
|
|||||||
setImage(manga)
|
setImage(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
override fun onSetMetadataValues(manga: Manga, metadata: RaisedSearchMetadata) {
|
||||||
|
if (metadata is MangaDexSearchMetadata) {
|
||||||
|
metadata.follow_status?.let {
|
||||||
|
local_text.text = itemView.context.resources.getStringArray(R.array.md_follows_options).asList()[it]
|
||||||
|
local_text.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
override fun setImage(manga: Manga) {
|
override fun setImage(manga: Manga) {
|
||||||
// For rounded corners
|
// For rounded corners
|
||||||
card.clipToOutline = true
|
card.clipToOutline = true
|
||||||
|
@ -4,6 +4,7 @@ import android.view.View
|
|||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
|
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic class used to hold the displayed data of a manga in the catalogue.
|
* Generic class used to hold the displayed data of a manga in the catalogue.
|
||||||
@ -29,4 +30,8 @@ abstract class SourceHolder(view: View, adapter: FlexibleAdapter<*>) :
|
|||||||
* @param manga the manga to bind.
|
* @param manga the manga to bind.
|
||||||
*/
|
*/
|
||||||
abstract fun setImage(manga: Manga)
|
abstract fun setImage(manga: Manga)
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
abstract fun onSetMetadataValues(manga: Manga, metadata: RaisedSearchMetadata)
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
@ -13,27 +13,38 @@ import eu.davidea.flexibleadapter.items.IFlexible
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
|
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
||||||
|
import exh.EH_SOURCE_ID
|
||||||
|
import exh.EXH_SOURCE_ID
|
||||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||||
import kotlinx.android.synthetic.main.source_compact_grid_item.view.card
|
import kotlinx.android.synthetic.main.source_compact_grid_item.view.card
|
||||||
import kotlinx.android.synthetic.main.source_compact_grid_item.view.gradient
|
import kotlinx.android.synthetic.main.source_compact_grid_item.view.gradient
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMode> /* SY --> */, private val metadata: RaisedSearchMetadata? = null /* SY <-- */) :
|
class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMode> /* SY --> */, private val metadata: RaisedSearchMetadata? = null /* SY <-- */) :
|
||||||
AbstractFlexibleItem<SourceHolder>() {
|
AbstractFlexibleItem<SourceHolder>() {
|
||||||
|
// SY -->
|
||||||
|
val preferences: PreferencesHelper by injectLazy()
|
||||||
|
// SY <--
|
||||||
|
|
||||||
override fun getLayoutRes(): Int {
|
override fun getLayoutRes(): Int {
|
||||||
return /* SY --> */ if (metadata == null) /* SY <-- */ when (displayMode.get()) {
|
return /* SY --> */ if ((manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID) && preferences.enhancedEHentaiView().get()) R.layout.source_enhanced_ehentai_list_item
|
||||||
|
else /* SY <-- */ when (displayMode.get()) {
|
||||||
DisplayMode.COMPACT_GRID -> R.layout.source_compact_grid_item
|
DisplayMode.COMPACT_GRID -> R.layout.source_compact_grid_item
|
||||||
DisplayMode.COMFORTABLE_GRID, /* SY --> */ DisplayMode.NO_TITLE_GRID /* SY <-- */ -> R.layout.source_comfortable_grid_item
|
DisplayMode.COMFORTABLE_GRID, /* SY --> */ DisplayMode.NO_TITLE_GRID /* SY <-- */ -> R.layout.source_comfortable_grid_item
|
||||||
DisplayMode.LIST -> R.layout.source_list_item
|
DisplayMode.LIST -> R.layout.source_list_item
|
||||||
} /* SY --> */ else R.layout.source_enhanced_ehentai_list_item /* SY <-- */
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createViewHolder(
|
override fun createViewHolder(
|
||||||
view: View,
|
view: View,
|
||||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
|
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
|
||||||
): SourceHolder {
|
): SourceHolder {
|
||||||
return /* SY --> */ if (metadata == null) /* SY <-- */ when (displayMode.get()) {
|
return /* SY --> */ if ((manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID) && preferences.enhancedEHentaiView().get()) {
|
||||||
|
SourceEnhancedEHentaiListHolder(view, adapter)
|
||||||
|
} else /* SY <-- */ when (displayMode.get()) {
|
||||||
DisplayMode.COMPACT_GRID -> {
|
DisplayMode.COMPACT_GRID -> {
|
||||||
val parent = adapter.recyclerView as AutofitRecyclerView
|
val parent = adapter.recyclerView as AutofitRecyclerView
|
||||||
val coverHeight = parent.itemWidth / 3 * 4
|
val coverHeight = parent.itemWidth / 3 * 4
|
||||||
@ -60,11 +71,7 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
|
|||||||
DisplayMode.LIST -> {
|
DisplayMode.LIST -> {
|
||||||
SourceListHolder(view, adapter)
|
SourceListHolder(view, adapter)
|
||||||
}
|
}
|
||||||
// SY -->
|
|
||||||
} else {
|
|
||||||
SourceEnhancedEHentaiListHolder(view, adapter)
|
|
||||||
}
|
}
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindViewHolder(
|
override fun bindViewHolder(
|
||||||
@ -76,7 +83,7 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
|
|||||||
holder.onSetValues(manga)
|
holder.onSetValues(manga)
|
||||||
// SY -->
|
// SY -->
|
||||||
if (metadata != null) {
|
if (metadata != null) {
|
||||||
(holder as? SourceEnhancedEHentaiListHolder)?.onSetMetadataValues(manga, metadata)
|
holder.onSetMetadataValues(manga, metadata)
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop
|
import com.bumptech.glide.load.resource.bitmap.CenterCrop
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||||
@ -11,6 +12,9 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
|
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||||
|
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||||
|
import kotlinx.android.synthetic.main.source_list_item.local_text
|
||||||
import kotlinx.android.synthetic.main.source_list_item.thumbnail
|
import kotlinx.android.synthetic.main.source_list_item.thumbnail
|
||||||
import kotlinx.android.synthetic.main.source_list_item.title
|
import kotlinx.android.synthetic.main.source_list_item.title
|
||||||
|
|
||||||
@ -44,6 +48,17 @@ class SourceListHolder(private val view: View, adapter: FlexibleAdapter<*>) :
|
|||||||
setImage(manga)
|
setImage(manga)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
override fun onSetMetadataValues(manga: Manga, metadata: RaisedSearchMetadata) {
|
||||||
|
if (metadata is MangaDexSearchMetadata) {
|
||||||
|
metadata.follow_status?.let {
|
||||||
|
local_text.text = itemView.context.resources.getStringArray(R.array.md_follows_options).asList()[it]
|
||||||
|
local_text.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
override fun setImage(manga: Manga) {
|
override fun setImage(manga: Manga) {
|
||||||
GlideApp.with(view.context).clear(thumbnail)
|
GlideApp.with(view.context).clear(thumbnail)
|
||||||
|
|
||||||
|
@ -198,6 +198,8 @@ open class IndexController :
|
|||||||
filterSheet = SourceFilterSheet(
|
filterSheet = SourceFilterSheet(
|
||||||
activity!!,
|
activity!!,
|
||||||
// SY -->
|
// SY -->
|
||||||
|
this,
|
||||||
|
presenter.source,
|
||||||
presenter.loadSearches(),
|
presenter.loadSearches(),
|
||||||
// SY <--
|
// SY <--
|
||||||
onFilterClicked = {
|
onFilterClicked = {
|
||||||
|
@ -49,6 +49,7 @@ import exh.PERV_EDEN_EN_SOURCE_ID
|
|||||||
import exh.PERV_EDEN_IT_SOURCE_ID
|
import exh.PERV_EDEN_IT_SOURCE_ID
|
||||||
import exh.favorites.FavoritesIntroDialog
|
import exh.favorites.FavoritesIntroDialog
|
||||||
import exh.favorites.FavoritesSyncStatus
|
import exh.favorites.FavoritesSyncStatus
|
||||||
|
import exh.mangaDexSourceIds
|
||||||
import exh.nHentaiSourceIds
|
import exh.nHentaiSourceIds
|
||||||
import exh.ui.LoaderManager
|
import exh.ui.LoaderManager
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -530,6 +531,9 @@ class LibraryController(
|
|||||||
it.source == PERV_EDEN_EN_SOURCE_ID ||
|
it.source == PERV_EDEN_EN_SOURCE_ID ||
|
||||||
it.source == PERV_EDEN_IT_SOURCE_ID
|
it.source == PERV_EDEN_IT_SOURCE_ID
|
||||||
}
|
}
|
||||||
|
binding.actionToolbar.findItem(R.id.action_push_to_mdlist)?.isVisible = selectedMangas.any {
|
||||||
|
it.source in mangaDexSourceIds
|
||||||
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -556,6 +560,7 @@ class LibraryController(
|
|||||||
PreMigrationController.navigateToMigration(skipPre, router, selectedMangaIds)
|
PreMigrationController.navigateToMigration(skipPre, router, selectedMangaIds)
|
||||||
}
|
}
|
||||||
R.id.action_clean -> cleanTitles()
|
R.id.action_clean -> cleanTitles()
|
||||||
|
R.id.action_push_to_mdlist -> pushToMdList()
|
||||||
// SY <--
|
// SY <--
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
@ -658,6 +663,13 @@ class LibraryController(
|
|||||||
presenter.cleanTitles(mangas)
|
presenter.cleanTitles(mangas)
|
||||||
destroyActionModeIfNeeded()
|
destroyActionModeIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun pushToMdList() {
|
||||||
|
val mangas = selectedMangas.filter {
|
||||||
|
it.source in mangaDexSourceIds
|
||||||
|
}.toList()
|
||||||
|
presenter.syncMangaToDex(mangas)
|
||||||
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {
|
override fun updateCategoriesForMangas(mangas: List<Manga>, categories: List<Category>) {
|
||||||
|
@ -30,11 +30,14 @@ import exh.EH_SOURCE_ID
|
|||||||
import exh.EXH_SOURCE_ID
|
import exh.EXH_SOURCE_ID
|
||||||
import exh.MERGED_SOURCE_ID
|
import exh.MERGED_SOURCE_ID
|
||||||
import exh.favorites.FavoritesSyncHelper
|
import exh.favorites.FavoritesSyncHelper
|
||||||
|
import exh.md.utils.FollowStatus
|
||||||
|
import exh.md.utils.MdUtil
|
||||||
import exh.util.await
|
import exh.util.await
|
||||||
import exh.util.isLewd
|
import exh.util.isLewd
|
||||||
import exh.util.nullIfBlank
|
import exh.util.nullIfBlank
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.Comparator
|
import java.util.Comparator
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.singleOrNull
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -194,6 +197,7 @@ class LibraryPresenter(
|
|||||||
}
|
}
|
||||||
if (filterTracked != STATE_IGNORE) {
|
if (filterTracked != STATE_IGNORE) {
|
||||||
val tracks = db.getTracks(item.manga).executeAsBlocking()
|
val tracks = db.getTracks(item.manga).executeAsBlocking()
|
||||||
|
.filterNot { it.sync_id == TrackManager.MDLIST && it.status == FollowStatus.UNFOLLOWED.int }
|
||||||
if (filterTracked == STATE_INCLUDE && tracks.isEmpty()) return@f false
|
if (filterTracked == STATE_INCLUDE && tracks.isEmpty()) return@f false
|
||||||
else if (filterTracked == STATE_EXCLUDE && tracks.isNotEmpty()) return@f false
|
else if (filterTracked == STATE_EXCLUDE && tracks.isNotEmpty()) return@f false
|
||||||
}
|
}
|
||||||
@ -501,6 +505,16 @@ class LibraryPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun syncMangaToDex(mangaList: List<Manga>) {
|
||||||
|
launchIO {
|
||||||
|
MdUtil.getEnabledMangaDex(preferences)?.let { mdex ->
|
||||||
|
mangaList.forEach {
|
||||||
|
mdex.updateFollowStatus(MdUtil.getMangaId(it.url), FollowStatus.READING).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -612,6 +626,9 @@ class LibraryPresenter(
|
|||||||
LibraryGroup.BY_STATUS -> {
|
LibraryGroup.BY_STATUS -> {
|
||||||
grouping += Triple(SManga.ONGOING.toString(), SManga.ONGOING, context.getString(R.string.ongoing))
|
grouping += Triple(SManga.ONGOING.toString(), SManga.ONGOING, context.getString(R.string.ongoing))
|
||||||
grouping += Triple(SManga.LICENSED.toString(), SManga.LICENSED, context.getString(R.string.licensed))
|
grouping += Triple(SManga.LICENSED.toString(), SManga.LICENSED, context.getString(R.string.licensed))
|
||||||
|
grouping += Triple(SManga.CANCELLED.toString(), SManga.CANCELLED, context.getString(R.string.cancelled))
|
||||||
|
grouping += Triple(SManga.HIATUS.toString(), SManga.HIATUS, context.getString(R.string.hiatus))
|
||||||
|
grouping += Triple(SManga.PUBLICATION_COMPLETE.toString(), SManga.PUBLICATION_COMPLETE, context.getString(R.string.publication_complete))
|
||||||
grouping += Triple(SManga.COMPLETED.toString(), SManga.COMPLETED, context.getString(R.string.completed))
|
grouping += Triple(SManga.COMPLETED.toString(), SManga.COMPLETED, context.getString(R.string.completed))
|
||||||
grouping += Triple(SManga.UNKNOWN.toString(), SManga.UNKNOWN, context.getString(R.string.unknown))
|
grouping += Triple(SManga.UNKNOWN.toString(), SManga.UNKNOWN, context.getString(R.string.unknown))
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ import eu.kanade.tachiyomi.source.LocalSource
|
|||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.source.online.MetadataSource.Companion.getMetadataSource
|
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.ToolbarLiftOnScrollController
|
import eu.kanade.tachiyomi.ui.base.controller.ToolbarLiftOnScrollController
|
||||||
@ -92,6 +92,7 @@ import eu.kanade.tachiyomi.util.view.snack
|
|||||||
import exh.MERGED_SOURCE_ID
|
import exh.MERGED_SOURCE_ID
|
||||||
import exh.isEhBasedSource
|
import exh.isEhBasedSource
|
||||||
import exh.metadata.metadata.base.FlatMetadata
|
import exh.metadata.metadata.base.FlatMetadata
|
||||||
|
import exh.source.EnhancedHttpSource.Companion.getMainSource
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlinx.android.synthetic.main.main_activity.root_coordinator
|
import kotlinx.android.synthetic.main.main_activity.root_coordinator
|
||||||
@ -254,9 +255,9 @@ class MangaController :
|
|||||||
|
|
||||||
adapters += mangaInfoAdapter
|
adapters += mangaInfoAdapter
|
||||||
|
|
||||||
val thisSourceAsLewdSource = presenter.source.getMetadataSource()
|
val mainSource = presenter.source.getMainSource()
|
||||||
if (thisSourceAsLewdSource != null) {
|
if (mainSource is MetadataSource<*, *>) {
|
||||||
mangaMetaInfoAdapter = thisSourceAsLewdSource.getDescriptionAdapter(this)
|
mangaMetaInfoAdapter = mainSource.getDescriptionAdapter(this)
|
||||||
mangaMetaInfoAdapter?.let { adapters += it }
|
mangaMetaInfoAdapter?.let { adapters += it }
|
||||||
}
|
}
|
||||||
mangaInfoItemAdapter = MangaInfoItemAdapter(this, fromSource)
|
mangaInfoItemAdapter = MangaInfoItemAdapter(this, fromSource)
|
||||||
@ -277,7 +278,7 @@ class MangaController :
|
|||||||
|
|
||||||
binding.recycler.adapter = ConcatAdapter(adapters)
|
binding.recycler.adapter = ConcatAdapter(adapters)
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
binding.recycler.addItemDecoration(ChapterDividerItemDecoration(view.context, if ((!preferences.recommendsInOverflow().get() || smartSearchConfig != null) && thisSourceAsLewdSource != null) 4 else if (!preferences.recommendsInOverflow().get() || smartSearchConfig != null || thisSourceAsLewdSource != null) 3 else 2))
|
binding.recycler.addItemDecoration(ChapterDividerItemDecoration(view.context, if ((!preferences.recommendsInOverflow().get() || smartSearchConfig != null) && mainSource is MetadataSource<*, *>) 4 else if (!preferences.recommendsInOverflow().get() || smartSearchConfig != null || mainSource is MetadataSource<*, *>) 3 else 2))
|
||||||
// SY <--
|
// SY <--
|
||||||
binding.recycler.setHasFixedSize(true)
|
binding.recycler.setHasFixedSize(true)
|
||||||
chaptersAdapter?.fastScroller = binding.fastScroller
|
chaptersAdapter?.fastScroller = binding.fastScroller
|
||||||
@ -481,9 +482,9 @@ class MangaController :
|
|||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
fun onNextMetaInfo(flatMetadata: FlatMetadata) {
|
fun onNextMetaInfo(flatMetadata: FlatMetadata) {
|
||||||
val thisSourceAsLewdSource = presenter.source.getMetadataSource()
|
val mainSource = presenter.source.getMainSource()
|
||||||
if (thisSourceAsLewdSource != null) {
|
if (mainSource is MetadataSource<*, *>) {
|
||||||
presenter.meta = flatMetadata.raise(thisSourceAsLewdSource.metaClass)
|
presenter.meta = flatMetadata.raise(mainSource.metaClass)
|
||||||
mangaMetaInfoAdapter?.notifyDataSetChanged()
|
mangaMetaInfoAdapter?.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import eu.kanade.tachiyomi.source.LocalSource
|
|||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.online.MetadataSource
|
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||||
import eu.kanade.tachiyomi.source.online.MetadataSource.Companion.isMetadataSource
|
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
||||||
@ -37,11 +37,12 @@ import exh.MERGED_SOURCE_ID
|
|||||||
import exh.debug.DebugToggles
|
import exh.debug.DebugToggles
|
||||||
import exh.eh.EHentaiUpdateHelper
|
import exh.eh.EHentaiUpdateHelper
|
||||||
import exh.isEhBasedSource
|
import exh.isEhBasedSource
|
||||||
|
import exh.md.utils.FollowStatus
|
||||||
import exh.merged.sql.models.MergedMangaReference
|
import exh.merged.sql.models.MergedMangaReference
|
||||||
import exh.metadata.metadata.base.FlatMetadata
|
import exh.metadata.metadata.base.FlatMetadata
|
||||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||||
import exh.source.EnhancedHttpSource
|
import exh.source.EnhancedHttpSource.Companion.getMainSource
|
||||||
import exh.util.asObservable
|
import exh.util.asObservable
|
||||||
import exh.util.await
|
import exh.util.await
|
||||||
import exh.util.trimOrNull
|
import exh.util.trimOrNull
|
||||||
@ -122,7 +123,7 @@ class MangaPresenter(
|
|||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
if (manga.initialized && source.isMetadataSource()) {
|
if (manga.initialized && source.getMainSource() is MetadataSource<*, *>) {
|
||||||
getMangaMetaObservable().subscribeLatestCache({ view, flatMetadata -> if (flatMetadata != null) view.onNextMetaInfo(flatMetadata) else XLog.d("Invalid metadata") })
|
getMangaMetaObservable().subscribeLatestCache({ view, flatMetadata -> if (flatMetadata != null) view.onNextMetaInfo(flatMetadata) else XLog.d("Invalid metadata") })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,14 +208,21 @@ class MangaPresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getTrackingObservable(): Observable<Int> {
|
private fun getTrackingObservable(): Observable<Int> {
|
||||||
if (!trackManager.hasLoggedServices()) {
|
// SY -->
|
||||||
|
val sourceIsMangaDex = source.getMainSource() is MangaDex
|
||||||
|
// SY <--
|
||||||
|
if (!trackManager.hasLoggedServices(/* SY --> */sourceIsMangaDex/* SY <-- */)) {
|
||||||
return Observable.just(0)
|
return Observable.just(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.getTracks(manga).asRxObservable()
|
return db.getTracks(manga).asRxObservable()
|
||||||
.map { tracks ->
|
.map { tracks ->
|
||||||
val loggedServices = trackManager.services.filter { it.isLogged }.map { it.id }
|
val loggedServices = trackManager.services.filter { it.isLogged /* SY --> */ && ((it.id == TrackManager.MDLIST && sourceIsMangaDex) || it.id != TrackManager.MDLIST) /* SY <-- */ }.map { it.id }
|
||||||
tracks.filter { it.sync_id in loggedServices }
|
tracks
|
||||||
|
// SY -->
|
||||||
|
.filterNot { it.sync_id == TrackManager.MDLIST && it.status == FollowStatus.UNFOLLOWED.int }
|
||||||
|
// SY <--
|
||||||
|
.filter { it.sync_id in loggedServices }
|
||||||
}
|
}
|
||||||
.map { it.size }
|
.map { it.size }
|
||||||
}
|
}
|
||||||
@ -244,7 +252,7 @@ class MangaPresenter(
|
|||||||
}
|
}
|
||||||
// SY -->
|
// SY -->
|
||||||
.doOnNext {
|
.doOnNext {
|
||||||
if (source is MetadataSource<*, *> || (source is EnhancedHttpSource && source.enhancedSource is MetadataSource<*, *>)) {
|
if (source.getMainSource() is MetadataSource<*, *>) {
|
||||||
getMangaMetaObservable().subscribeLatestCache({ view, flatMetadata -> if (flatMetadata != null) view.onNextMetaInfo(flatMetadata) else XLog.d("Invalid metadata") })
|
getMangaMetaObservable().subscribeLatestCache({ view, flatMetadata -> if (flatMetadata != null) view.onNextMetaInfo(flatMetadata) else XLog.d("Invalid metadata") })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,12 @@ import eu.kanade.tachiyomi.source.Source
|
|||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.view.setTooltip
|
import eu.kanade.tachiyomi.util.view.setTooltip
|
||||||
import exh.MERGED_SOURCE_ID
|
import exh.MERGED_SOURCE_ID
|
||||||
|
import exh.source.EnhancedHttpSource.Companion.getMainSource
|
||||||
import exh.util.SourceTagsUtil
|
import exh.util.SourceTagsUtil
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -106,7 +108,10 @@ class MangaInfoHeaderAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
with(binding.btnTracking) {
|
with(binding.btnTracking) {
|
||||||
if (trackManager.hasLoggedServices()) {
|
// SY -->
|
||||||
|
val sourceIsMangaDex = source.let { it.getMainSource() is MangaDex }
|
||||||
|
// SY <--
|
||||||
|
if (trackManager.hasLoggedServices(/* SY --> */sourceIsMangaDex/* SY <-- */)) {
|
||||||
isVisible = true
|
isVisible = true
|
||||||
|
|
||||||
if (trackCount > 0) {
|
if (trackCount > 0) {
|
||||||
|
@ -9,6 +9,7 @@ import androidx.core.net.toUri
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.databinding.TrackControllerBinding
|
import eu.kanade.tachiyomi.databinding.TrackControllerBinding
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
@ -91,6 +92,10 @@ class TrackController :
|
|||||||
val atLeastOneLink = trackings.any { it.track != null }
|
val atLeastOneLink = trackings.any { it.track != null }
|
||||||
adapter?.items = trackings
|
adapter?.items = trackings
|
||||||
binding.swipeRefresh.isEnabled = atLeastOneLink
|
binding.swipeRefresh.isEnabled = atLeastOneLink
|
||||||
|
if (presenter.needsRefresh) {
|
||||||
|
presenter.needsRefresh = false
|
||||||
|
presenter.refresh()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSearchResults(results: List<TrackSearch>) {
|
fun onSearchResults(results: List<TrackSearch>) {
|
||||||
@ -126,6 +131,9 @@ class TrackController :
|
|||||||
|
|
||||||
override fun onSetClick(position: Int) {
|
override fun onSetClick(position: Int) {
|
||||||
val item = adapter?.getItem(position) ?: return
|
val item = adapter?.getItem(position) ?: return
|
||||||
|
// SY --> Kill search for now until cesco puts MdList into stable
|
||||||
|
if (item.service.id == TrackManager.MDLIST) return
|
||||||
|
// SY <--
|
||||||
TrackSearchDialog(this, item.service).showDialog(router, TAG_SEARCH_CONTROLLER)
|
TrackSearchDialog(this, item.service).showDialog(router, TAG_SEARCH_CONTROLLER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ 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.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import exh.mangaDexSourceIds
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
@ -35,6 +36,10 @@ class TrackPresenter(
|
|||||||
|
|
||||||
private var refreshSubscription: Subscription? = null
|
private var refreshSubscription: Subscription? = null
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
var needsRefresh = false
|
||||||
|
// SY <--
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
fetchTrackings()
|
fetchTrackings()
|
||||||
@ -50,10 +55,36 @@ class TrackPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
// SY -->
|
||||||
|
.map { trackItems ->
|
||||||
|
if (manga.source in mangaDexSourceIds) {
|
||||||
|
val mdTrack = trackItems.firstOrNull { it.service.id == TrackManager.MDLIST }
|
||||||
|
when {
|
||||||
|
mdTrack == null -> {
|
||||||
|
needsRefresh = true
|
||||||
|
trackItems + createMdListTrack()
|
||||||
|
}
|
||||||
|
mdTrack.track == null -> {
|
||||||
|
needsRefresh = true
|
||||||
|
trackItems - mdTrack + createMdListTrack()
|
||||||
|
}
|
||||||
|
else -> trackItems
|
||||||
|
}
|
||||||
|
} else trackItems
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
.doOnNext { trackList = it }
|
.doOnNext { trackList = it }
|
||||||
.subscribeLatestCache(TrackController::onNextTrackings)
|
.subscribeLatestCache(TrackController::onNextTrackings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
private fun createMdListTrack(): TrackItem {
|
||||||
|
val track = trackManager.mdList.createInitialTracker(manga)
|
||||||
|
track.id = db.insertTrack(track).executeAsBlocking().insertedId()
|
||||||
|
return TrackItem(track, trackManager.mdList)
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
refreshSubscription?.let { remove(it) }
|
refreshSubscription?.let { remove(it) }
|
||||||
refreshSubscription = Observable.from(trackList)
|
refreshSubscription = Observable.from(trackList)
|
||||||
|
@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.util.preference.onClick
|
|||||||
import eu.kanade.tachiyomi.util.preference.preference
|
import eu.kanade.tachiyomi.util.preference.preference
|
||||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
import eu.kanade.tachiyomi.util.preference.titleRes
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
|
import exh.md.utils.MdUtil
|
||||||
|
|
||||||
class SettingsMainController : SettingsController() {
|
class SettingsMainController : SettingsController() {
|
||||||
|
|
||||||
@ -80,6 +81,14 @@ class SettingsMainController : SettingsController() {
|
|||||||
onClick { navigateTo(SettingsEhController()) }
|
onClick { navigateTo(SettingsEhController()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (MdUtil.getEnabledMangaDex(preferences) != null) {
|
||||||
|
preference {
|
||||||
|
iconRes = R.drawable.ic_tracker_mangadex_logo
|
||||||
|
iconTint = tintColor
|
||||||
|
titleRes = R.string.mangadex_specific_settings
|
||||||
|
onClick { navigateTo(SettingsMangaDexController()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
preference {
|
preference {
|
||||||
iconRes = R.drawable.ic_code_24dp
|
iconRes = R.drawable.ic_code_24dp
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.setting
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.util.preference.defaultValue
|
||||||
|
import eu.kanade.tachiyomi.util.preference.listPreference
|
||||||
|
import eu.kanade.tachiyomi.util.preference.onClick
|
||||||
|
import eu.kanade.tachiyomi.util.preference.preference
|
||||||
|
import eu.kanade.tachiyomi.util.preference.summaryRes
|
||||||
|
import eu.kanade.tachiyomi.util.preference.switchPreference
|
||||||
|
import eu.kanade.tachiyomi.util.preference.titleRes
|
||||||
|
import exh.md.utils.MdUtil
|
||||||
|
import exh.widget.preference.MangaDexLoginPreference
|
||||||
|
import exh.widget.preference.MangadexLoginDialog
|
||||||
|
import exh.widget.preference.MangadexLogoutDialog
|
||||||
|
|
||||||
|
class SettingsMangaDexController :
|
||||||
|
SettingsController(),
|
||||||
|
MangadexLoginDialog.Listener,
|
||||||
|
MangadexLogoutDialog.Listener {
|
||||||
|
|
||||||
|
private val mdex by lazy { MdUtil.getEnabledMangaDex() }
|
||||||
|
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
|
||||||
|
titleRes = R.string.mangadex_specific_settings
|
||||||
|
if (mdex == null) router.popCurrentController()
|
||||||
|
val sourcePreference = MangaDexLoginPreference(context, mdex!!).apply {
|
||||||
|
title = mdex!!.name + " Login"
|
||||||
|
key = getSourceKey(source.id)
|
||||||
|
setOnLoginClickListener {
|
||||||
|
if (mdex!!.isLogged()) {
|
||||||
|
val dialog = MangadexLogoutDialog(source)
|
||||||
|
dialog.targetController = this@SettingsMangaDexController
|
||||||
|
dialog.showDialog(router)
|
||||||
|
} else {
|
||||||
|
val dialog = MangadexLoginDialog(source, activity)
|
||||||
|
dialog.targetController = this@SettingsMangaDexController
|
||||||
|
dialog.showDialog(router)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preferenceScreen.addPreference(sourcePreference)
|
||||||
|
|
||||||
|
listPreference {
|
||||||
|
titleRes = R.string.mangadex_preffered_source
|
||||||
|
summaryRes = R.string.mangadex_preffered_source_summary
|
||||||
|
key = PreferenceKeys.preferredMangaDexId
|
||||||
|
val mangaDexs = MdUtil.getEnabledMangaDexs()
|
||||||
|
entries = mangaDexs.map { it.toString() }.toTypedArray()
|
||||||
|
entryValues = mangaDexs.map { it.id.toString() }.toTypedArray()
|
||||||
|
/*setOnPreferenceChangeListener { preference, newValue ->
|
||||||
|
preferences.preferredMangaDexId().set((newValue as? String)?.toLongOrNull() ?: 0)
|
||||||
|
true
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
switchPreference {
|
||||||
|
key = PreferenceKeys.mangaDexLowQualityCovers
|
||||||
|
titleRes = R.string.mangadex_low_quality_covers
|
||||||
|
defaultValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
switchPreference {
|
||||||
|
key = PreferenceKeys.mangaDexForceLatestCovers
|
||||||
|
titleRes = R.string.mangadex_use_latest_cover
|
||||||
|
summaryRes = R.string.mangadex_use_latest_cover_summary
|
||||||
|
defaultValue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
preference {
|
||||||
|
titleRes = R.string.mangadex_sync_follows_to_library
|
||||||
|
summaryRes = R.string.mangadex_sync_follows_to_library_summary
|
||||||
|
|
||||||
|
onClick {
|
||||||
|
LibraryUpdateService.start(
|
||||||
|
context,
|
||||||
|
target = LibraryUpdateService.Target.SYNC_FOLLOWS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun siteLoginDialogClosed(source: Source) {
|
||||||
|
val pref = findPreference(getSourceKey(source.id)) as? MangaDexLoginPreference
|
||||||
|
pref?.notifyChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun siteLogoutDialogClosed(source: Source) {
|
||||||
|
val pref = findPreference(getSourceKey(source.id)) as? MangaDexLoginPreference
|
||||||
|
pref?.notifyChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSourceKey(sourceId: Long): String {
|
||||||
|
return "source_$sourceId"
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,6 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import exh.EH_SOURCE_ID
|
import exh.EH_SOURCE_ID
|
||||||
import exh.EXH_SOURCE_ID
|
import exh.EXH_SOURCE_ID
|
||||||
import exh.debug.DebugToggles
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.TreeSet
|
import java.util.TreeSet
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -145,18 +144,6 @@ fun syncChaptersWithSource(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dbChapters.isEmpty() && !DebugToggles.INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS.enabled) {
|
|
||||||
val readChapters = db.getChaptersReadByUrls(finalAdded.map { it.url }).executeAsBlocking()
|
|
||||||
val readChapterUrls = readChapters.map { it.url }
|
|
||||||
if (readChapters.isNotEmpty()) {
|
|
||||||
toAdd.filter { it.url in readChapterUrls }.onEach { chapter ->
|
|
||||||
readChapters.firstOrNull { it.url == chapter.url }?.let {
|
|
||||||
chapter.read = it.read
|
|
||||||
chapter.last_page_read = it.last_page_read
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// <-- EXH
|
// <-- EXH
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class EHentaiUpdateHelper(context: Context) {
|
|||||||
mangaIds.map { mangaId ->
|
mangaIds.map { mangaId ->
|
||||||
Single.zip(
|
Single.zip(
|
||||||
db.getManga(mangaId).asRxSingle(),
|
db.getManga(mangaId).asRxSingle(),
|
||||||
db.getChaptersByMangaId(mangaId).asRxSingle()
|
db.getChapters(mangaId).asRxSingle()
|
||||||
) { manga, chapters ->
|
) { manga, chapters ->
|
||||||
ChapterChain(manga, chapters)
|
ChapterChain(manga, chapters)
|
||||||
}.toObservable().filter {
|
}.toObservable().filter {
|
||||||
|
@ -152,7 +152,7 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
|
|
||||||
val chapter = db.getChaptersByMangaId(manga.id!!).asRxSingle().await().minByOrNull {
|
val chapter = db.getChapters(manga.id!!).asRxSingle().await().minByOrNull {
|
||||||
it.date_upload
|
it.date_upload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
56
app/src/main/java/exh/md/MangaDexFabHeaderAdapter.kt
Normal file
56
app/src/main/java/exh/md/MangaDexFabHeaderAdapter.kt
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package exh.md
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bluelinelabs.conductor.Controller
|
||||||
|
import eu.kanade.tachiyomi.databinding.SourceFilterMangadexHeaderBinding
|
||||||
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.source.online.RandomMangaSource
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||||
|
import exh.md.follows.MangaDexFollowsController
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
|
import reactivecircus.flowbinding.android.view.clicks
|
||||||
|
|
||||||
|
class MangaDexFabHeaderAdapter(val controller: Controller, val source: CatalogueSource) :
|
||||||
|
RecyclerView.Adapter<MangaDexFabHeaderAdapter.SavedSearchesViewHolder>() {
|
||||||
|
|
||||||
|
private lateinit var binding: SourceFilterMangadexHeaderBinding
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SavedSearchesViewHolder {
|
||||||
|
binding = SourceFilterMangadexHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return SavedSearchesViewHolder(binding.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = 1
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: SavedSearchesViewHolder, position: Int) {
|
||||||
|
holder.bind()
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class SavedSearchesViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
fun bind() {
|
||||||
|
binding.mangadexFollows.clicks()
|
||||||
|
.onEach {
|
||||||
|
controller.router.replaceTopController(MangaDexFollowsController(source).withFadeTransaction())
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
|
binding.mangadexRandom.clicks()
|
||||||
|
.onEach {
|
||||||
|
(source as? RandomMangaSource)?.fetchRandomMangaUrl()?.singleOrNull()?.let { randomMangaId ->
|
||||||
|
controller.router.replaceTopController(BrowseSourceController(source, randomMangaId).withFadeTransaction())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package exh.md.follows
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller that shows the latest manga from the catalogue. Inherit [BrowseSourceController].
|
||||||
|
*/
|
||||||
|
class MangaDexFollowsController(bundle: Bundle) : BrowseSourceController(bundle) {
|
||||||
|
|
||||||
|
constructor(source: CatalogueSource) : this(
|
||||||
|
Bundle().apply {
|
||||||
|
putLong(SOURCE_ID_KEY, source.id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getTitle(): String? {
|
||||||
|
return view?.context?.getString(R.string.mangadex_follows)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createPresenter(): BrowseSourcePresenter {
|
||||||
|
return MangaDexFollowsPresenter(args.getLong(SOURCE_ID_KEY))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
|
super.onPrepareOptionsMenu(menu)
|
||||||
|
menu.findItem(R.id.action_search).isVisible = false
|
||||||
|
menu.findItem(R.id.action_open_in_web_view).isVisible = false
|
||||||
|
menu.findItem(R.id.action_settings).isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initFilterSheet() {
|
||||||
|
// No-op: we don't allow filtering in latest
|
||||||
|
}
|
||||||
|
}
|
21
app/src/main/java/exh/md/follows/MangaDexFollowsPager.kt
Normal file
21
app/src/main/java/exh/md/follows/MangaDexFollowsPager.kt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package exh.md.follows
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.browse.Pager
|
||||||
|
import rx.Observable
|
||||||
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LatestUpdatesPager inherited from the general Pager.
|
||||||
|
*/
|
||||||
|
class MangaDexFollowsPager(val source: MangaDex) : Pager() {
|
||||||
|
|
||||||
|
override fun requestNext(): Observable<MangasPage> {
|
||||||
|
return source.fetchFollows()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.doOnNext { onPageReceived(it) }
|
||||||
|
}
|
||||||
|
}
|
18
app/src/main/java/exh/md/follows/MangaDexFollowsPresenter.kt
Normal file
18
app/src/main/java/exh/md/follows/MangaDexFollowsPresenter.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package exh.md.follows
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.browse.Pager
|
||||||
|
import exh.source.EnhancedHttpSource
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter of [MangaDexFollowsController]. Inherit BrowseCataloguePresenter.
|
||||||
|
*/
|
||||||
|
class MangaDexFollowsPresenter(sourceId: Long) : BrowseSourcePresenter(sourceId) {
|
||||||
|
|
||||||
|
override fun createPager(query: String, filters: FilterList): Pager {
|
||||||
|
val sourceAsMangaDex = (source as EnhancedHttpSource).enhancedSource as MangaDex
|
||||||
|
return MangaDexFollowsPager(sourceAsMangaDex)
|
||||||
|
}
|
||||||
|
}
|
@ -17,8 +17,8 @@ import exh.metadata.metadata.MangaDexSearchMetadata
|
|||||||
import exh.metadata.metadata.base.RaisedTag
|
import exh.metadata.metadata.base.RaisedTag
|
||||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||||
import exh.metadata.metadata.base.insertFlatMetadata
|
import exh.metadata.metadata.base.insertFlatMetadata
|
||||||
|
import exh.util.floor
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import kotlin.math.floor
|
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Completable
|
import rx.Completable
|
||||||
import rx.Single
|
import rx.Single
|
||||||
@ -41,7 +41,7 @@ class ApiMangaParser(private val langs: List<String>) {
|
|||||||
*
|
*
|
||||||
* Will also save the metadata to the DB if possible
|
* Will also save the metadata to the DB if possible
|
||||||
*/
|
*/
|
||||||
fun parseToManga(manga: SManga, input: Response): Completable {
|
fun parseToManga(manga: SManga, input: Response, forceLatestCover: Boolean): Completable {
|
||||||
val mangaId = (manga as? Manga)?.id
|
val mangaId = (manga as? Manga)?.id
|
||||||
val metaObservable = if (mangaId != null) {
|
val metaObservable = if (mangaId != null) {
|
||||||
// We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions
|
// We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions
|
||||||
@ -55,7 +55,7 @@ class ApiMangaParser(private val langs: List<String>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return metaObservable.map {
|
return metaObservable.map {
|
||||||
parseIntoMetadata(it, input)
|
parseIntoMetadata(it, input, forceLatestCover)
|
||||||
it.copyTo(manga)
|
it.copyTo(manga)
|
||||||
it
|
it
|
||||||
}.flatMapCompletable {
|
}.flatMapCompletable {
|
||||||
@ -66,7 +66,7 @@ class ApiMangaParser(private val langs: List<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Response) {
|
fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Response, forceLatestCover: Boolean) {
|
||||||
with(metadata) {
|
with(metadata) {
|
||||||
try {
|
try {
|
||||||
val networkApiManga = MdUtil.jsonParser.decodeFromString(ApiMangaSerializer.serializer(), input.body!!.string())
|
val networkApiManga = MdUtil.jsonParser.decodeFromString(ApiMangaSerializer.serializer(), input.body!!.string())
|
||||||
@ -74,15 +74,18 @@ class ApiMangaParser(private val langs: List<String>) {
|
|||||||
mdId = MdUtil.getMangaId(input.request.url.toString())
|
mdId = MdUtil.getMangaId(input.request.url.toString())
|
||||||
mdUrl = input.request.url.toString()
|
mdUrl = input.request.url.toString()
|
||||||
title = MdUtil.cleanString(networkManga.title)
|
title = MdUtil.cleanString(networkManga.title)
|
||||||
thumbnail_url = MdUtil.cdnUrl + MdUtil.removeTimeParamUrl(networkManga.cover_url)
|
val coverList = networkManga.covers
|
||||||
|
thumbnail_url = MdUtil.cdnUrl +
|
||||||
|
if (forceLatestCover && coverList.isNotEmpty()) {
|
||||||
|
coverList.last()
|
||||||
|
} else {
|
||||||
|
MdUtil.removeTimeParamUrl(networkManga.cover_url)
|
||||||
|
}
|
||||||
description = MdUtil.cleanDescription(networkManga.description)
|
description = MdUtil.cleanDescription(networkManga.description)
|
||||||
author = MdUtil.cleanString(networkManga.author)
|
author = MdUtil.cleanString(networkManga.author)
|
||||||
artist = MdUtil.cleanString(networkManga.artist)
|
artist = MdUtil.cleanString(networkManga.artist)
|
||||||
lang_flag = networkManga.lang_flag
|
lang_flag = networkManga.lang_flag
|
||||||
val lastChapter = networkManga.last_chapter?.toFloatOrNull()
|
last_chapter_number = networkManga.last_chapter?.toFloatOrNull()?.floor()
|
||||||
lastChapter?.let {
|
|
||||||
last_chapter_number = floor(it).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
networkManga.rating?.let {
|
networkManga.rating?.let {
|
||||||
rating = it.bayesian ?: it.mean
|
rating = it.bayesian ?: it.mean
|
||||||
@ -107,10 +110,16 @@ class ApiMangaParser(private val langs: List<String>) {
|
|||||||
status = tempStatus
|
status = tempStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val demographic = FilterHandler.demographics().filter { it.id == networkManga.demographic }.firstOrNull()
|
||||||
|
|
||||||
val genres =
|
val genres =
|
||||||
networkManga.genres.mapNotNull { FilterHandler.allTypes[it.toString()] }
|
networkManga.genres.mapNotNull { FilterHandler.allTypes[it.toString()] }
|
||||||
.toMutableList()
|
.toMutableList()
|
||||||
|
|
||||||
|
if (demographic != null) {
|
||||||
|
genres.add(0, demographic.name)
|
||||||
|
}
|
||||||
|
|
||||||
if (networkManga.hentai == 1) {
|
if (networkManga.hentai == 1) {
|
||||||
genres.add("Hentai")
|
genres.add("Hentai")
|
||||||
}
|
}
|
||||||
@ -135,7 +144,9 @@ class ApiMangaParser(private val langs: List<String>) {
|
|||||||
if (filteredChapters.isEmpty() || serializer.manga.last_chapter.isNullOrEmpty()) {
|
if (filteredChapters.isEmpty() || serializer.manga.last_chapter.isNullOrEmpty()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
val finalChapterNumber = serializer.manga.last_chapter!!
|
// just to fix the stupid lint
|
||||||
|
val lastMangaChapter: String? = serializer.manga.last_chapter
|
||||||
|
val finalChapterNumber = lastMangaChapter!!
|
||||||
if (MdUtil.validOneShotFinalChapters.contains(finalChapterNumber)) {
|
if (MdUtil.validOneShotFinalChapters.contains(finalChapterNumber)) {
|
||||||
filteredChapters.firstOrNull()?.let {
|
filteredChapters.firstOrNull()?.let {
|
||||||
if (isOneShot(it.value, finalChapterNumber)) {
|
if (isOneShot(it.value, finalChapterNumber)) {
|
||||||
@ -144,7 +155,7 @@ class ApiMangaParser(private val langs: List<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val removeOneshots = filteredChapters.filter { !it.value.chapter.isNullOrBlank() }
|
val removeOneshots = filteredChapters.filter { !it.value.chapter.isNullOrBlank() }
|
||||||
return removeOneshots.size.toString() == floor(finalChapterNumber.toDouble()).toInt().toString()
|
return removeOneshots.size.toString() == finalChapterNumber.toDouble().floor().toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun filterChapterForChecking(serializer: ApiMangaSerializer): List<Map.Entry<String, ChapterSerializer>> {
|
private fun filterChapterForChecking(serializer: ApiMangaSerializer): List<Map.Entry<String, ChapterSerializer>> {
|
||||||
@ -269,7 +280,7 @@ class ApiMangaParser(private val langs: List<String>) {
|
|||||||
}
|
}
|
||||||
if ((status == 2 || status == 3)) {
|
if ((status == 2 || status == 3)) {
|
||||||
if ((isOneShot(networkChapter, finalChapterNumber) && totalChapterCount == 1) ||
|
if ((isOneShot(networkChapter, finalChapterNumber) && totalChapterCount == 1) ||
|
||||||
networkChapter.chapter == finalChapterNumber
|
networkChapter.chapter == finalChapterNumber && finalChapterNumber.toIntOrNull() != 0
|
||||||
) {
|
) {
|
||||||
chapterName.add("[END]")
|
chapterName.add("[END]")
|
||||||
}
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
package exh.md.handlers
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import exh.md.handlers.serializers.CoversResult
|
|
||||||
import exh.md.utils.MdUtil
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import okhttp3.CacheControl
|
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
|
|
||||||
// Unused, look into what its used for todo
|
|
||||||
class CoverHandler(val client: OkHttpClient, val headers: Headers) {
|
|
||||||
|
|
||||||
suspend fun getCovers(manga: SManga): List<String> {
|
|
||||||
return withContext(Dispatchers.IO) {
|
|
||||||
val response = client.newCall(GET("${MdUtil.baseUrl}${MdUtil.coversApi}${MdUtil.getMangaId(manga.url)}", headers, CacheControl.FORCE_NETWORK)).execute()
|
|
||||||
val result = MdUtil.jsonParser.decodeFromString(
|
|
||||||
CoversResult.serializer(),
|
|
||||||
response.body!!.string()
|
|
||||||
)
|
|
||||||
result.covers.map { "${MdUtil.baseUrl}$it" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -171,7 +171,8 @@ class FilterHandler {
|
|||||||
Tag("81", "Virtual Reality"),
|
Tag("81", "Virtual Reality"),
|
||||||
Tag("82", "Zombies"),
|
Tag("82", "Zombies"),
|
||||||
Tag("83", "Incest"),
|
Tag("83", "Incest"),
|
||||||
Tag("84", "Mafia")
|
Tag("84", "Mafia"),
|
||||||
|
Tag("85", "Villainess")
|
||||||
).sortedWith(compareBy { it.name })
|
).sortedWith(compareBy { it.name })
|
||||||
|
|
||||||
val allTypes = (contentType() + formats() + genre() + themes()).map { it.id to it.name }.toMap()
|
val allTypes = (contentType() + formats() + genre() + themes()).map { it.id to it.name }.toMap()
|
||||||
|
@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
|||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.asObservable
|
import eu.kanade.tachiyomi.network.asObservable
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
|
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import exh.md.handlers.serializers.FollowsPageResult
|
import exh.md.handlers.serializers.FollowsPageResult
|
||||||
@ -16,26 +17,24 @@ import exh.md.utils.MdUtil
|
|||||||
import exh.md.utils.MdUtil.Companion.baseUrl
|
import exh.md.utils.MdUtil.Companion.baseUrl
|
||||||
import exh.md.utils.MdUtil.Companion.getMangaId
|
import exh.md.utils.MdUtil.Companion.getMangaId
|
||||||
import exh.metadata.metadata.MangaDexSearchMetadata
|
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||||
import kotlin.math.floor
|
import exh.util.floor
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
// Unused, kept for future featues todo
|
|
||||||
class FollowsHandler(val client: OkHttpClient, val headers: Headers, val preferences: PreferencesHelper) {
|
class FollowsHandler(val client: OkHttpClient, val headers: Headers, val preferences: PreferencesHelper) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fetch follows by page
|
* fetch follows by page
|
||||||
*/
|
*/
|
||||||
fun fetchFollows(page: Int): Observable<MetadataMangasPage> {
|
fun fetchFollows(): Observable<MangasPage> {
|
||||||
return client.newCall(followsListRequest(page))
|
return client.newCall(followsListRequest())
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.map { response ->
|
.map { response ->
|
||||||
followsParseMangaPage(response)
|
followsParseMangaPage(response)
|
||||||
@ -96,9 +95,9 @@ class FollowsHandler(val client: OkHttpClient, val headers: Headers, val prefere
|
|||||||
val follow = result.first()
|
val follow = result.first()
|
||||||
track.status = follow.follow_type
|
track.status = follow.follow_type
|
||||||
if (result[0].chapter.isNotBlank()) {
|
if (result[0].chapter.isNotBlank()) {
|
||||||
track.last_chapter_read = floor(follow.chapter.toFloat()).toInt()
|
track.last_chapter_read = follow.chapter.toFloat().floor()
|
||||||
}
|
}
|
||||||
track.tracking_url = MdUtil.baseUrl + follow.manga_id.toString()
|
track.tracking_url = baseUrl + follow.manga_id.toString()
|
||||||
track.title = follow.title
|
track.title = follow.title
|
||||||
}
|
}
|
||||||
return track
|
return track
|
||||||
@ -107,11 +106,8 @@ class FollowsHandler(val client: OkHttpClient, val headers: Headers, val prefere
|
|||||||
/**build Request for follows page
|
/**build Request for follows page
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private fun followsListRequest(page: Int): Request {
|
private fun followsListRequest(): Request {
|
||||||
val url = "${MdUtil.baseUrl}${MdUtil.followsAllApi}".toHttpUrlOrNull()!!.newBuilder()
|
return GET("$baseUrl${MdUtil.followsAllApi}", headers, CacheControl.FORCE_NETWORK)
|
||||||
.addQueryParameter("page", page.toString())
|
|
||||||
|
|
||||||
return GET(url.toString(), headers, CacheControl.FORCE_NETWORK)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,7 +122,7 @@ class FollowsHandler(val client: OkHttpClient, val headers: Headers, val prefere
|
|||||||
title = MdUtil.cleanString(result.title)
|
title = MdUtil.cleanString(result.title)
|
||||||
mdUrl = "/manga/${result.manga_id}/"
|
mdUrl = "/manga/${result.manga_id}/"
|
||||||
thumbnail_url = MdUtil.formThumbUrl(manga.url, lowQualityCovers)
|
thumbnail_url = MdUtil.formThumbUrl(manga.url, lowQualityCovers)
|
||||||
follow_status = FollowStatus.fromInt(result.follow_type)?.ordinal
|
follow_status = FollowStatus.fromInt(result.follow_type)?.int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,21 +201,16 @@ class FollowsHandler(val client: OkHttpClient, val headers: Headers, val prefere
|
|||||||
/**
|
/**
|
||||||
* fetch all manga from all possible pages
|
* fetch all manga from all possible pages
|
||||||
*/
|
*/
|
||||||
suspend fun fetchAllFollows(forceHd: Boolean): List<SManga> {
|
suspend fun fetchAllFollows(forceHd: Boolean): List<Pair<SManga, MangaDexSearchMetadata>> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
val listManga = mutableListOf<SManga>()
|
val listManga = mutableListOf<Pair<SManga, MangaDexSearchMetadata>>()
|
||||||
loop@ for (i in 1..10000) {
|
val response = client.newCall(followsListRequest()).execute()
|
||||||
val response = client.newCall(followsListRequest(i))
|
val mangasPage = followsParseMangaPage(response, forceHd)
|
||||||
.execute()
|
listManga.addAll(
|
||||||
val mangasPage = followsParseMangaPage(response, forceHd)
|
mangasPage.mangas.mapIndexed { index, sManga ->
|
||||||
|
sManga to mangasPage.mangasMetadata[index] as MangaDexSearchMetadata
|
||||||
if (mangasPage.mangas.isNotEmpty()) {
|
|
||||||
listManga.addAll(mangasPage.mangas)
|
|
||||||
}
|
}
|
||||||
if (!mangasPage.hasNextPage) {
|
)
|
||||||
break@loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
listManga
|
listManga
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,7 +218,7 @@ class FollowsHandler(val client: OkHttpClient, val headers: Headers, val prefere
|
|||||||
suspend fun fetchTrackingInfo(url: String): Track {
|
suspend fun fetchTrackingInfo(url: String): Track {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
val request = GET(
|
val request = GET(
|
||||||
"${MdUtil.baseUrl}${MdUtil.followsMangaApi}" + getMangaId(url),
|
"$baseUrl${MdUtil.followsMangaApi}" + getMangaId(url),
|
||||||
headers,
|
headers,
|
||||||
CacheControl.FORCE_NETWORK
|
CacheControl.FORCE_NETWORK
|
||||||
)
|
)
|
||||||
|
@ -3,10 +3,13 @@ package exh.md.handlers
|
|||||||
import com.elvishew.xlog.XLog
|
import com.elvishew.xlog.XLog
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
import eu.kanade.tachiyomi.network.await
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import exh.md.utils.MdUtil
|
import exh.md.utils.MdUtil
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
@ -14,7 +17,7 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: List<String>) {
|
class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: List<String>, val forceLatestCovers: Boolean = false) {
|
||||||
|
|
||||||
// TODO make use of this
|
// TODO make use of this
|
||||||
suspend fun fetchMangaAndChapterDetails(manga: SManga): Pair<SManga, List<SChapter>> {
|
suspend fun fetchMangaAndChapterDetails(manga: SManga): Pair<SManga, List<SChapter>> {
|
||||||
@ -28,7 +31,7 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: Li
|
|||||||
throw Exception("Error from MangaDex Response code ${response.code} ")
|
throw Exception("Error from MangaDex Response code ${response.code} ")
|
||||||
}
|
}
|
||||||
|
|
||||||
parser.parseToManga(manga, response).await()
|
parser.parseToManga(manga, response, forceLatestCovers).await()
|
||||||
val chapterList = parser.chapterListParse(jsonData)
|
val chapterList = parser.chapterListParse(jsonData)
|
||||||
Pair(
|
Pair(
|
||||||
manga,
|
manga,
|
||||||
@ -48,7 +51,7 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: Li
|
|||||||
suspend fun fetchMangaDetails(manga: SManga): SManga {
|
suspend fun fetchMangaDetails(manga: SManga): SManga {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
val response = client.newCall(apiRequest(manga)).execute()
|
val response = client.newCall(apiRequest(manga)).execute()
|
||||||
ApiMangaParser(langs).parseToManga(manga, response).await()
|
ApiMangaParser(langs).parseToManga(manga, response, forceLatestCovers).await()
|
||||||
manga.apply {
|
manga.apply {
|
||||||
initialized = true
|
initialized = true
|
||||||
}
|
}
|
||||||
@ -59,7 +62,7 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: Li
|
|||||||
return client.newCall(apiRequest(manga))
|
return client.newCall(apiRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
ApiMangaParser(langs).parseToManga(manga, it).andThen(
|
ApiMangaParser(langs).parseToManga(manga, it, forceLatestCovers).andThen(
|
||||||
Observable.just(
|
Observable.just(
|
||||||
manga.apply {
|
manga.apply {
|
||||||
initialized = true
|
initialized = true
|
||||||
@ -84,7 +87,7 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: Li
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchRandomMangaId(): Observable<String> {
|
fun fetchRandomMangaIdObservable(): Observable<String> {
|
||||||
return client.newCall(randomMangaRequest())
|
return client.newCall(randomMangaRequest())
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { response ->
|
.map { response ->
|
||||||
@ -92,6 +95,13 @@ class MangaHandler(val client: OkHttpClient, val headers: Headers, val langs: Li
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun fetchRandomMangaId(): Flow<String> {
|
||||||
|
return flow {
|
||||||
|
val response = client.newCall(randomMangaRequest()).await()
|
||||||
|
emit(ApiMangaParser(langs).randomMangaIdParse(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun randomMangaRequest(): Request {
|
private fun randomMangaRequest(): Request {
|
||||||
return GET(MdUtil.baseUrl + MdUtil.randMangaPage)
|
return GET(MdUtil.baseUrl + MdUtil.randMangaPage)
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ class PopularHandler(val client: OkHttpClient, private val headers: Headers) {
|
|||||||
|
|
||||||
val mangas = document.select(popularMangaSelector).map { element ->
|
val mangas = document.select(popularMangaSelector).map { element ->
|
||||||
popularMangaFromElement(element)
|
popularMangaFromElement(element)
|
||||||
}.distinct()
|
}.distinctBy { it.url }
|
||||||
|
|
||||||
val hasNextPage = popularMangaNextPageSelector.let { selector ->
|
val hasNextPage = popularMangaNextPageSelector.let { selector ->
|
||||||
document.select(selector).first()
|
document.select(selector).first()
|
||||||
|
@ -33,7 +33,7 @@ class SearchHandler(val client: OkHttpClient, private val headers: Headers, val
|
|||||||
.map { response ->
|
.map { response ->
|
||||||
val details = SManga.create()
|
val details = SManga.create()
|
||||||
details.url = "/manga/$realQuery/"
|
details.url = "/manga/$realQuery/"
|
||||||
ApiMangaParser(langs).parseToManga(details, response).await()
|
ApiMangaParser(langs).parseToManga(details, response, preferences.mangaDexForceLatestCovers().get()).await()
|
||||||
MangasPage(listOf(details), false)
|
MangasPage(listOf(details), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,9 @@ data class MangaSerializer(
|
|||||||
val author: String,
|
val author: String,
|
||||||
val cover_url: String,
|
val cover_url: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
|
val demographic: String,
|
||||||
val genres: List<Int>,
|
val genres: List<Int>,
|
||||||
|
val covers: List<String>,
|
||||||
val hentai: Int,
|
val hentai: Int,
|
||||||
val lang_flag: String,
|
val lang_flag: String,
|
||||||
val lang_name: String,
|
val lang_name: String,
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
package exh.md.utils
|
package exh.md.utils
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||||
|
import exh.util.floor
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URISyntaxException
|
import java.net.URISyntaxException
|
||||||
import kotlin.math.floor
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.jsoup.parser.Parser
|
import org.jsoup.parser.Parser
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class MdUtil {
|
class MdUtil {
|
||||||
|
|
||||||
@ -63,10 +68,15 @@ class MdUtil {
|
|||||||
"[b][u]Spanish",
|
"[b][u]Spanish",
|
||||||
"[Español]:",
|
"[Español]:",
|
||||||
"[b] Spanish: [/ b]",
|
"[b] Spanish: [/ b]",
|
||||||
|
"정보",
|
||||||
"Spanish/Español",
|
"Spanish/Español",
|
||||||
"Español / Spanish",
|
"Español / Spanish",
|
||||||
"Italian / Italiano",
|
"Italian / Italiano",
|
||||||
|
"Italian/Italiano",
|
||||||
|
"\r\n\r\nItalian\r\n",
|
||||||
"Pasta-Pizza-Mandolino/Italiano",
|
"Pasta-Pizza-Mandolino/Italiano",
|
||||||
|
"Persian /فارسی",
|
||||||
|
"Farsi/Persian/",
|
||||||
"Polish / polski",
|
"Polish / polski",
|
||||||
"Polish / Polski",
|
"Polish / Polski",
|
||||||
"Polish Summary / Polski Opis",
|
"Polish Summary / Polski Opis",
|
||||||
@ -89,6 +99,7 @@ class MdUtil {
|
|||||||
"French - Français:",
|
"French - Français:",
|
||||||
"Turkish / Türkçe",
|
"Turkish / Türkçe",
|
||||||
"Turkish/Türkçe",
|
"Turkish/Türkçe",
|
||||||
|
"Türkçe",
|
||||||
"[b][u]Chinese",
|
"[b][u]Chinese",
|
||||||
"Arabic / العربية",
|
"Arabic / العربية",
|
||||||
"العربية",
|
"العربية",
|
||||||
@ -191,11 +202,11 @@ class MdUtil {
|
|||||||
}.sortedByDescending { it.chapter_number }
|
}.sortedByDescending { it.chapter_number }
|
||||||
|
|
||||||
remove0ChaptersFromCount.firstOrNull()?.let {
|
remove0ChaptersFromCount.firstOrNull()?.let {
|
||||||
val chpNumber = floor(it.chapter_number).toInt()
|
val chpNumber = it.chapter_number.floor()
|
||||||
val allChapters = (1..chpNumber).toMutableSet()
|
val allChapters = (1..chpNumber).toMutableSet()
|
||||||
|
|
||||||
remove0ChaptersFromCount.forEach {
|
remove0ChaptersFromCount.forEach {
|
||||||
allChapters.remove(floor(it.chapter_number).toInt())
|
allChapters.remove(it.chapter_number.floor())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allChapters.size <= 0) return null
|
if (allChapters.size <= 0) return null
|
||||||
@ -203,6 +214,25 @@ class MdUtil {
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getEnabledMangaDex(preferences: PreferencesHelper = Injekt.get(), sourceManager: SourceManager = Injekt.get()): MangaDex? {
|
||||||
|
return getEnabledMangaDexs(preferences, sourceManager).let { mangadexs ->
|
||||||
|
val preferredMangaDexId = preferences.preferredMangaDexId().get().toLongOrNull()
|
||||||
|
mangadexs.firstOrNull { preferredMangaDexId != null && preferredMangaDexId != 0L && it.id == preferredMangaDexId } ?: mangadexs.firstOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getEnabledMangaDexs(preferences: PreferencesHelper = Injekt.get(), sourceManager: SourceManager = Injekt.get()): List<MangaDex> {
|
||||||
|
val languages = preferences.enabledLanguages().get()
|
||||||
|
val disabledSourceIds = preferences.disabledSources().get()
|
||||||
|
|
||||||
|
return sourceManager.getDelegatedCatalogueSources()
|
||||||
|
.filter { it.lang in languages }
|
||||||
|
.filterNot { it.id.toString() in disabledSourceIds }
|
||||||
|
.filterIsInstance(MangaDex::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mapMdIdToMangaUrl(id: Int) = "/manga/$id/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,10 +36,5 @@ private const val EH_UNIVERSAL_INTERCEPTOR = -1L
|
|||||||
private val EH_INTERCEPTORS: Map<Long, List<EHInterceptor>> = mapOf(
|
private val EH_INTERCEPTORS: Map<Long, List<EHInterceptor>> = mapOf(
|
||||||
EH_UNIVERSAL_INTERCEPTOR to listOf(
|
EH_UNIVERSAL_INTERCEPTOR to listOf(
|
||||||
CAPTCHA_DETECTION_PATCH // Auto captcha detection
|
CAPTCHA_DETECTION_PATCH // Auto captcha detection
|
||||||
),
|
)
|
||||||
|
|
||||||
// MangaDex login support
|
|
||||||
*MANGADEX_SOURCE_IDS.map { id ->
|
|
||||||
id to listOf(MANGADEX_LOGIN_PATCH)
|
|
||||||
}.toTypedArray()
|
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package exh.source
|
package exh.source
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
@ -234,4 +235,21 @@ class EnhancedHttpSource(
|
|||||||
originalSource
|
originalSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun Source.getMainSource(): Source {
|
||||||
|
return if (this is EnhancedHttpSource) {
|
||||||
|
this.source()
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun Source.getOriginalSource(): Source {
|
||||||
|
return if (this is EnhancedHttpSource) {
|
||||||
|
this.originalSource
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import android.view.ViewGroup
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import eu.kanade.tachiyomi.databinding.MetadataViewItemBinding
|
import eu.kanade.tachiyomi.databinding.MetadataViewItemBinding
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import kotlin.math.floor
|
import exh.util.floor
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -43,7 +43,7 @@ class MetadataViewAdapter(private var data: List<Pair<String, String>>) :
|
|||||||
private var dataPosition: Int? = null
|
private var dataPosition: Int? = null
|
||||||
fun bind(position: Int) {
|
fun bind(position: Int) {
|
||||||
if (data.isEmpty() || !binding.infoText.text.isNullOrBlank()) return
|
if (data.isEmpty() || !binding.infoText.text.isNullOrBlank()) return
|
||||||
dataPosition = floor(position / 2F).toInt()
|
dataPosition = (position / 2F).floor()
|
||||||
binding.infoText.text = if (position % 2 == 0) data[dataPosition!!].first else data[dataPosition!!].second
|
binding.infoText.text = if (position % 2 == 0) data[dataPosition!!].first else data[dataPosition!!].second
|
||||||
binding.infoText.clicks()
|
binding.infoText.clicks()
|
||||||
.onEach {
|
.onEach {
|
||||||
|
@ -10,11 +10,12 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.databinding.MetadataViewControllerBinding
|
import eu.kanade.tachiyomi.databinding.MetadataViewControllerBinding
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.online.MetadataSource.Companion.getMetadataSource
|
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
import exh.metadata.metadata.base.FlatMetadata
|
import exh.metadata.metadata.base.FlatMetadata
|
||||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||||
|
import exh.source.EnhancedHttpSource.Companion.getMainSource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@ -73,9 +74,9 @@ class MetadataViewController : NucleusController<MetadataViewControllerBinding,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onNextMetaInfo(flatMetadata: FlatMetadata) {
|
fun onNextMetaInfo(flatMetadata: FlatMetadata) {
|
||||||
val thisSourceAsLewdSource = presenter.source.getMetadataSource()
|
val mainSource = presenter.source.getMainSource()
|
||||||
if (thisSourceAsLewdSource != null) {
|
if (mainSource is MetadataSource<*, *>) {
|
||||||
presenter.meta = flatMetadata.raise(thisSourceAsLewdSource.metaClass)
|
presenter.meta = flatMetadata.raise(mainSource.metaClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
5
app/src/main/java/exh/util/Math.kt
Normal file
5
app/src/main/java/exh/util/Math.kt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package exh.util
|
||||||
|
|
||||||
|
fun Float.floor(): Int = kotlin.math.floor(this).toInt()
|
||||||
|
|
||||||
|
fun Double.floor(): Int = kotlin.math.floor(this).toInt()
|
@ -0,0 +1,54 @@
|
|||||||
|
package exh.widget.preference
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceViewHolder
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||||
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
|
import kotlinx.android.synthetic.main.pref_item_mangadex.view.*
|
||||||
|
|
||||||
|
class MangaDexLoginPreference @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
val source: MangaDex,
|
||||||
|
attrs: AttributeSet? = null
|
||||||
|
) : Preference(context, attrs) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
layoutResource = R.layout.pref_item_mangadex
|
||||||
|
}
|
||||||
|
|
||||||
|
private var onLoginClick: () -> Unit = {}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||||
|
super.onBindViewHolder(holder)
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
onLoginClick()
|
||||||
|
}
|
||||||
|
val loginFrame = holder.itemView.login_frame
|
||||||
|
val color = if (source.isLogged()) {
|
||||||
|
context.getResourceColor(R.attr.colorAccent)
|
||||||
|
} else {
|
||||||
|
context.getResourceColor(R.attr.colorSecondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemView.login.setImageResource(R.drawable.ic_outline_people_alt_24dp)
|
||||||
|
holder.itemView.login.drawable.setTint(color)
|
||||||
|
|
||||||
|
loginFrame.isVisible = true
|
||||||
|
loginFrame.setOnClickListener {
|
||||||
|
onLoginClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOnLoginClickListener(block: () -> Unit) {
|
||||||
|
onLoginClick = block
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make method public
|
||||||
|
public override fun notifyChanged() {
|
||||||
|
super.notifyChanged()
|
||||||
|
}
|
||||||
|
}
|
115
app/src/main/java/exh/widget/preference/MangadexLoginDialog.kt
Normal file
115
app/src/main/java/exh/widget/preference/MangadexLoginDialog.kt
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package exh.widget.preference
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import com.afollestad.materialdialogs.customview.customView
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import eu.kanade.tachiyomi.widget.preference.LoginDialogPreference
|
||||||
|
import exh.md.utils.MdUtil
|
||||||
|
import kotlinx.android.synthetic.main.pref_account_login.view.login
|
||||||
|
import kotlinx.android.synthetic.main.pref_account_login.view.password
|
||||||
|
import kotlinx.android.synthetic.main.pref_account_login.view.username
|
||||||
|
import kotlinx.android.synthetic.main.pref_site_login_two_factor_auth.view.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
class MangadexLoginDialog(bundle: Bundle? = null) : LoginDialogPreference(bundle = bundle) {
|
||||||
|
|
||||||
|
val source by lazy { MdUtil.getEnabledMangaDex() }
|
||||||
|
|
||||||
|
val service = Injekt.get<TrackManager>().mdList
|
||||||
|
|
||||||
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
|
constructor(source: MangaDex, activity: Activity? = null) : this(
|
||||||
|
Bundle().apply {
|
||||||
|
putLong(
|
||||||
|
"key",
|
||||||
|
source.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
|
val dialog = MaterialDialog(activity!!).apply {
|
||||||
|
customView(R.layout.pref_site_login_two_factor_auth, scrollable = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
onViewCreated(dialog.view)
|
||||||
|
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setCredentialsOnView(view: View) = with(view) {
|
||||||
|
username.setText(service.getUsername())
|
||||||
|
password.setText(service.getPassword())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun checkLogin() {
|
||||||
|
v?.apply {
|
||||||
|
if (username.text.isNullOrBlank() || password.text.isNullOrBlank() || (two_factor_check.isChecked && two_factor_edit.text.isNullOrBlank())) {
|
||||||
|
errorResult()
|
||||||
|
context.toast(R.string.fields_cannot_be_blank)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
login.progress = 1
|
||||||
|
|
||||||
|
dialog?.setCancelable(false)
|
||||||
|
dialog?.setCanceledOnTouchOutside(false)
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
val result = source?.login(
|
||||||
|
username.text.toString(),
|
||||||
|
password.text.toString(),
|
||||||
|
two_factor_edit.text.toString()
|
||||||
|
) ?: false
|
||||||
|
if (result) {
|
||||||
|
dialog?.dismiss()
|
||||||
|
preferences.setTrackCredentials(Injekt.get<TrackManager>().mdList, username.toString(), password.toString())
|
||||||
|
context.toast(R.string.login_success)
|
||||||
|
} else {
|
||||||
|
errorResult()
|
||||||
|
}
|
||||||
|
} catch (error: Exception) {
|
||||||
|
errorResult()
|
||||||
|
error.message?.let { context.toast(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun errorResult() {
|
||||||
|
v?.apply {
|
||||||
|
dialog?.setCancelable(true)
|
||||||
|
dialog?.setCanceledOnTouchOutside(true)
|
||||||
|
login.progress = -1
|
||||||
|
login.setText(R.string.unknown_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDialogClosed() {
|
||||||
|
super.onDialogClosed()
|
||||||
|
if (activity != null) {
|
||||||
|
(activity as? Listener)?.siteLoginDialogClosed(source!!)
|
||||||
|
} else {
|
||||||
|
(targetController as? Listener)?.siteLoginDialogClosed(source!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun siteLoginDialogClosed(source: Source)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package exh.widget.preference
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
|
import eu.kanade.tachiyomi.util.lang.launchNow
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import exh.md.utils.MdUtil
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class MangadexLogoutDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
||||||
|
|
||||||
|
val source by lazy { MdUtil.getEnabledMangaDex() }
|
||||||
|
|
||||||
|
val trackManager: TrackManager by injectLazy()
|
||||||
|
|
||||||
|
constructor(source: Source) : this(Bundle().apply { putLong("key", source.id) })
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
|
return MaterialDialog(activity!!)
|
||||||
|
.title(R.string.logout)
|
||||||
|
.positiveButton(R.string.logout) {
|
||||||
|
launchNow {
|
||||||
|
source?.let { source ->
|
||||||
|
val loggedOut = withContext(Dispatchers.IO) { source.logout() }
|
||||||
|
|
||||||
|
if (loggedOut) {
|
||||||
|
trackManager.mdList.logout()
|
||||||
|
activity?.toast(R.string.logout_success)
|
||||||
|
(targetController as? Listener)?.siteLogoutDialogClosed(source)
|
||||||
|
} else {
|
||||||
|
activity?.toast(R.string.unknown_error)
|
||||||
|
}
|
||||||
|
} ?: activity!!.toast("Mangadex not enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.negativeButton(android.R.string.cancel)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun siteLogoutDialogClosed(source: Source)
|
||||||
|
}
|
||||||
|
}
|
16
app/src/main/res/drawable/ic_tracker_mangadex_logo.xml
Normal file
16
app/src/main/res/drawable/ic_tracker_mangadex_logo.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<vector android:height="32dp" android:viewportHeight="130"
|
||||||
|
android:viewportWidth="150" android:width="36.923077dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#F79421" android:pathData="M131.158,81.768H30.498c-1.235,0 -2.236,-1.002 -2.236,-2.236c0,-1.235 1.001,-2.237 2.236,-2.237h100.66c1.234,0 2.236,1.002 2.236,2.237C133.395,80.766 132.393,81.768 131.158,81.768"/>
|
||||||
|
<path android:fillColor="#F79421" android:pathData="M131.158,90.248H30.498c-1.235,0 -2.236,-1 -2.236,-2.236c0,-1.234 1.001,-2.236 2.236,-2.236h100.66c1.234,0 2.236,1.002 2.236,2.236C133.395,89.248 132.393,90.248 131.158,90.248"/>
|
||||||
|
<path android:fillColor="#F79421" android:pathData="M114.684,87.732l0,14.961l6.404,-3.711l6.408,3.713l0,-14.963z"/>
|
||||||
|
<path android:fillColor="#272B30" android:pathData="M118.807,40.464h16.834v9.333h-16.834z"/>
|
||||||
|
<path android:fillColor="#F1F1F1" android:pathData="M144.688,52.169c-0.084,-0.072 -0.168,-0.14 -0.252,-0.212c-1.922,-1.671 -3.565,-3.655 -4.84,-5.874c-0.012,-0.022 -0.022,-0.045 -0.033,-0.067h-0.006c-1.408,-2.487 -2.365,-5.265 -2.75,-8.221c-0.019,-0.179 -0.039,-0.358 -0.058,-0.537c0,-0.011 -0.004,-0.022 -0.004,-0.033c-0.162,-1.04 -0.608,-1.99 -1.248,-2.772c-0.062,-0.068 -0.123,-0.14 -0.183,-0.207c-5.282,-5.93 -12.308,-10.278 -20.271,-12.229c-0.018,-0.38 -0.027,-0.765 -0.027,-1.151c0,-4.415 1.135,-8.562 3.125,-12.172c0.11,-0.202 0.172,-0.436 0.172,-0.688c0,-0.799 -0.643,-1.442 -1.44,-1.442c-0.034,0 -0.067,0 -0.101,0.006c-0.084,0.005 -0.168,0.022 -0.246,0.039C106.482,8.28 97.4,12.796 90.141,19.301c-3.06,2.739 -5.797,5.83 -8.145,9.211c-0.988,1.419 -1.905,2.884 -2.749,4.398c-0.504,-0.274 -1.019,-0.536 -1.538,-0.788c-1.632,-0.804 -3.314,-1.514 -5.046,-2.135c-5.193,-1.861 -10.787,-2.873 -16.622,-2.873c-1.531,0 -3.051,0.073 -4.549,0.213c-5.634,0.509 -10.999,1.973 -15.923,4.225C19.653,38.823 8.28,54.316 6.883,72.596c-0.101,1.264 -0.151,2.543 -0.151,3.829v12.362c0,2.137 0.318,4.197 0.905,6.137c1.599,5.281 5.203,9.686 9.926,12.334c2.906,1.633 6.237,2.6 9.786,2.689h31.404c0.156,-0.012 0.313,-0.018 0.469,-0.018c0.151,0 0.308,0.006 0.459,0.018c0.05,0.004 0.1,0.004 0.15,0.012c0.084,0.006 0.163,0.01 0.247,0.021c1.933,0.225 3.638,1.186 4.828,2.6c0.184,0.217 0.363,0.451 0.526,0.697c0.592,0.9 0.994,1.936 1.145,3.045c0.011,0.053 0.017,0.107 0.023,0.164c0.033,0.229 0.05,0.463 0.05,0.703c0.005,0.057 0.005,0.117 0.005,0.174c0,0.051 0,0.096 -0.005,0.141v0.016c0.016,0.922 0.201,1.799 0.531,2.611c0.592,1.475 1.643,2.715 2.979,3.547c1.134,0.705 2.47,1.107 3.9,1.107c3.617,0 6.635,-2.594 7.283,-6.029c0.062,-0.281 0.095,-0.57 0.117,-0.867c0.006,-0.1 0.013,-0.195 0.013,-0.297c0.004,-0.078 0.004,-0.15 0.004,-0.229v-0.184c0,-2.533 -0.425,-4.97 -1.201,-7.232c-1.129,-3.287 -3.006,-6.227 -5.433,-8.617c-2.588,-2.551 -5.796,-4.473 -9.378,-5.518c-1.978,-0.576 -4.08,-0.89 -6.248,-0.89H33.039c-0.062,0.007 -0.129,0.007 -0.19,0.007c-0.062,0 -0.129,0 -0.19,-0.007C26.83,94.83 22.09,90.268 21.71,84.518c-0.017,-0.252 -0.028,-0.504 -0.028,-0.754c0,-6.17 4.996,-11.168 11.167,-11.168h99.816c2.576,0 4.762,-1.688 5.517,-4.012c0.021,-0.09 0.051,-0.186 0.078,-0.273c0.346,-1.123 1.033,-2.102 1.934,-2.817c0.006,-0.006 0.006,-0.006 0.012,-0.006c0.072,-0.061 0.15,-0.117 0.225,-0.167c0.1,-0.078 0.207,-0.146 0.313,-0.212c2.47,-1.845 4.162,-4.673 4.516,-7.897c0.051,-0.414 0.072,-0.839 0.072,-1.264C145.33,54.624 145.105,53.354 144.688,52.169M131.352,47.715c-0.24,0 -0.47,-0.062 -0.67,-0.167c-0.979,-0.565 -2.091,-0.923 -3.275,-1.018c-0.213,-0.017 -0.426,-0.028 -0.643,-0.028c-0.521,0 -1.029,0.05 -1.521,0.151c-0.812,0.151 -1.576,0.436 -2.275,0.822c-0.012,0.005 -0.021,0.016 -0.032,0.022c-0.045,0.017 -0.084,0.045 -0.123,0.072c-0.189,0.096 -0.402,0.146 -0.627,0.146c-0.775,0 -1.408,-0.631 -1.408,-1.408c0,-0.487 0.246,-0.911 0.621,-1.163c0.045,-0.033 0.09,-0.056 0.14,-0.084c0.905,-0.508 1.896,-0.888 2.94,-1.117c0.736,-0.163 1.502,-0.247 2.285,-0.247c1.682,0 3.274,0.386 4.688,1.085c0.185,0.084 0.362,0.179 0.542,0.279c0.051,0.028 0.095,0.056 0.14,0.084c0.375,0.252 0.62,0.682 0.62,1.163C132.754,47.084 132.123,47.715 131.352,47.715"/>
|
||||||
|
<path android:fillColor="#E6E6E6" android:pathData="M144.688,52.169c-0.084,-0.072 -0.168,-0.14 -0.252,-0.212c-1.922,-1.671 -3.565,-3.655 -4.84,-5.874c-0.012,-0.022 -0.022,-0.045 -0.033,-0.067h-0.006c-1.408,-2.487 -2.365,-5.265 -2.75,-8.221c-0.019,-0.179 -0.039,-0.358 -0.058,-0.537c0,-0.011 -0.004,-0.022 -0.004,-0.033c-0.162,-1.04 -0.608,-1.99 -1.248,-2.772c-0.062,-0.068 -0.123,-0.14 -0.183,-0.207c-5.282,-5.93 -12.308,-10.278 -20.271,-12.229c-0.018,-0.38 -0.027,-0.765 -0.027,-1.151c0,-4.415 1.135,-8.562 3.125,-12.172c0.11,-0.202 0.172,-0.436 0.172,-0.688c0,-0.799 -0.643,-1.442 -1.44,-1.442c-0.034,0 -0.067,0 -0.101,0.006c-0.084,0.005 -0.168,0.022 -0.246,0.039C106.482,8.28 97.4,12.796 90.141,19.301c-3.06,2.739 -5.797,5.83 -8.145,9.211c-0.988,1.419 -1.905,2.884 -2.749,4.398c-0.504,-0.274 -1.019,-0.536 -1.538,-0.788c-1.632,-0.804 -3.314,-1.514 -5.046,-2.135c-5.193,-1.861 -10.787,-2.873 -16.622,-2.873c-1.531,0 -3.051,0.073 -4.549,0.213c-5.634,0.509 -10.999,1.973 -15.923,4.225C19.653,38.823 8.28,54.316 6.883,72.596c-0.101,1.264 -0.151,2.543 -0.151,3.829v12.362c0,2.137 0.318,4.197 0.905,6.137c1.599,5.281 5.203,9.686 9.926,12.334c2.906,1.633 6.237,2.6 9.786,2.689h31.404c0.156,-0.012 0.313,-0.018 0.469,-0.018c0.151,0 0.308,0.006 0.459,0.018c0.05,0.004 0.1,0.004 0.15,0.012c0.084,0.006 0.163,0.01 0.247,0.021c1.933,0.225 3.638,1.186 4.828,2.6c0.184,0.217 0.363,0.451 0.526,0.697c0.592,0.9 0.994,1.936 1.145,3.045c0.011,0.053 0.017,0.107 0.023,0.164c0.033,0.229 0.05,0.463 0.05,0.703c0.005,0.057 0.005,0.117 0.005,0.174c0,0.051 0,0.096 -0.005,0.141v0.016c0.016,0.922 0.201,1.799 0.531,2.611c0.592,1.475 1.643,2.715 2.979,3.547c1.134,0.705 2.47,1.107 3.9,1.107c3.617,0 6.635,-2.594 7.283,-6.029c0.062,-0.281 0.095,-0.57 0.117,-0.867c0.006,-0.1 0.013,-0.195 0.013,-0.297c0.004,-0.078 0.004,-0.15 0.004,-0.229v-0.184c0,-2.533 -0.425,-4.97 -1.201,-7.232c-1.129,-3.287 -3.006,-6.227 -5.433,-8.617c-2.588,-2.551 -5.796,-4.473 -9.378,-5.518c-1.978,-0.576 -4.08,-0.89 -6.248,-0.89H33.039c-0.062,0.007 -0.129,0.007 -0.19,0.007c-0.062,0 -0.129,0 -0.19,-0.007C26.83,94.83 22.09,90.268 21.71,84.518c-0.017,-0.252 -0.028,-0.504 -0.028,-0.754c0,-6.17 4.996,-11.168 11.167,-11.168h99.816c2.576,0 4.762,-1.688 5.517,-4.012c0.021,-0.09 0.051,-0.186 0.078,-0.273c0.346,-1.123 1.033,-2.102 1.934,-2.817c0.006,-0.006 0.006,-0.006 0.012,-0.006c0.072,-0.061 0.15,-0.117 0.225,-0.167c0.1,-0.078 0.207,-0.146 0.313,-0.212c2.47,-1.845 4.162,-4.673 4.516,-7.897c0.051,-0.414 0.072,-0.839 0.072,-1.264C145.33,54.624 145.105,53.354 144.688,52.169M131.352,47.715c-0.24,0 -0.47,-0.062 -0.67,-0.167c-0.979,-0.565 -2.091,-0.923 -3.275,-1.018c-0.213,-0.017 -0.426,-0.028 -0.643,-0.028c-0.521,0 -1.029,0.05 -1.521,0.151c-0.812,0.151 -1.576,0.436 -2.275,0.822c-0.012,0.005 -0.021,0.016 -0.032,0.022c-0.045,0.017 -0.084,0.045 -0.123,0.072c-0.189,0.096 -0.402,0.146 -0.627,0.146c-0.775,0 -1.408,-0.631 -1.408,-1.408c0,-0.487 0.246,-0.911 0.621,-1.163c0.045,-0.033 0.09,-0.056 0.14,-0.084c0.905,-0.508 1.896,-0.888 2.94,-1.117c0.736,-0.163 1.502,-0.247 2.285,-0.247c1.682,0 3.274,0.386 4.688,1.085c0.185,0.084 0.362,0.179 0.542,0.279c0.051,0.028 0.095,0.056 0.14,0.084c0.375,0.252 0.62,0.682 0.62,1.163C132.754,47.084 132.123,47.715 131.352,47.715"/>
|
||||||
|
<path android:fillColor="#F79421" android:pathData="M79.398,32.655c2.688,12.921 13.898,22.615 27.328,22.615c7.903,0 15.037,-3.365 20.119,-8.763c-0.027,-0.001 -0.055,-0.004 -0.082,-0.004c-0.521,0 -1.029,0.05 -1.521,0.151c-0.812,0.15 -1.576,0.436 -2.275,0.821c-0.011,0.006 -0.021,0.017 -0.032,0.023c-0.045,0.016 -0.084,0.044 -0.123,0.072c-0.189,0.095 -0.402,0.146 -0.627,0.146c-0.775,0 -1.408,-0.632 -1.408,-1.409c0,-0.486 0.246,-0.911 0.621,-1.162c0.045,-0.034 0.09,-0.056 0.14,-0.084c0.905,-0.509 1.896,-0.889 2.94,-1.118c0.736,-0.162 1.502,-0.246 2.285,-0.246c0.769,0 1.516,0.084 2.237,0.238c2.392,-3.226 4.125,-6.988 5.006,-11.079c-5.11,-5.217 -11.638,-9.044 -18.964,-10.838c-0.018,-0.38 -0.027,-0.766 -0.027,-1.151c0,-4.416 1.135,-8.563 3.125,-12.173c0.11,-0.201 0.172,-0.435 0.172,-0.687c0,-0.799 -0.643,-1.442 -1.44,-1.442c-0.034,0 -0.067,0 -0.101,0.006c-0.084,0.005 -0.168,0.022 -0.246,0.039C106.48,8.28 97.4,12.797 90.141,19.302c-3.059,2.738 -5.797,5.829 -8.145,9.21C81.064,29.85 80.202,31.232 79.398,32.655"/>
|
||||||
|
<path android:fillColor="#272B30" android:pathData="M137.284,59.354c-1.214,-0.36 -2.421,-0.72 -3.65,-0.991c-1.218,-0.312 -2.446,-0.576 -3.679,-0.806c-2.467,-0.449 -4.947,-0.791 -7.438,-0.949c-2.492,-0.209 -4.99,-0.106 -7.478,0.086l-1.863,0.209c-0.62,0.087 -1.228,0.234 -1.845,0.344c-1.256,0.152 -2.43,0.608 -3.678,0.87c2.307,-1.034 4.759,-1.823 7.289,-2.186c2.521,-0.379 5.093,-0.51 7.638,-0.32c2.545,0.186 5.073,0.563 7.539,1.199C132.582,57.441 135.007,58.235 137.284,59.354"/>
|
||||||
|
<path android:fillColor="#272B30" android:pathData="M136.188,61.648c-1.254,-0.176 -2.5,-0.352 -3.758,-0.437c-1.252,-0.128 -2.505,-0.207 -3.759,-0.251c-2.506,-0.077 -5.009,-0.048 -7.496,0.167c-2.495,0.163 -4.95,0.636 -7.381,1.195l-1.813,0.484c-0.599,0.177 -1.179,0.413 -1.772,0.613c-1.218,0.337 -2.312,0.962 -3.506,1.407c2.127,-1.365 4.435,-2.509 6.883,-3.244c2.438,-0.75 4.96,-1.261 7.506,-1.451c2.543,-0.194 5.103,-0.197 7.634,0.065C131.251,60.456 133.77,60.88 136.188,61.648"/>
|
||||||
|
<path android:fillColor="#272B30" android:pathData="M136.016,64.321c-1.264,0.1 -2.518,0.196 -3.765,0.384c-1.248,0.146 -2.489,0.339 -3.724,0.567c-2.463,0.466 -4.9,1.035 -7.282,1.781c-2.401,0.698 -4.696,1.689 -6.949,2.762l-1.665,0.863c-0.547,0.303 -1.063,0.657 -1.599,0.98c-1.117,0.593 -2.05,1.439 -3.121,2.131c1.782,-1.791 3.789,-3.407 6.021,-4.652c2.219,-1.259 4.57,-2.303 7.016,-3.037c2.441,-0.74 4.938,-1.294 7.467,-1.585C130.939,64.221 133.488,64.093 136.016,64.321"/>
|
||||||
|
<path android:fillColor="#F27BAA" android:pathData="M144.436,51.957c-0.422,-0.366 -0.828,-0.751 -1.223,-1.147c-1.07,0.649 -1.79,1.822 -1.79,3.167c0,2.044 1.657,3.702 3.701,3.702c0.021,0 0.038,-0.002 0.058,-0.003c0.022,-0.156 0.059,-0.307 0.075,-0.465c0.05,-0.414 0.072,-0.838 0.072,-1.263c0,-1.325 -0.224,-2.594 -0.644,-3.779C144.604,52.097 144.52,52.03 144.436,51.957"/>
|
||||||
|
<path android:fillColor="#FFFFFF" android:pathData="M113.764,23.924c0,0.173 0,0.352 0.006,0.525c-4.504,1.828 -8.602,4.46 -12.105,7.713c-0.145,-0.995 -0.219,-2.007 -0.219,-3.041c0,-10.216 7.216,-18.75 16.834,-20.779C115.423,12.847 113.764,18.19 113.764,23.924"/>
|
||||||
|
<path android:fillColor="#F79421" android:pathData="M74.843,101.328c-0.49,-0.482 -1.01,-0.936 -1.543,-1.371c-5.61,1.143 -10.229,5.014 -12.41,10.168c1.596,0.367 2.996,1.242 4.016,2.453c0.184,0.219 0.363,0.453 0.525,0.699c0.593,0.9 0.995,1.934 1.146,3.047c0.011,0.049 0.017,0.105 0.022,0.162c0.034,0.229 0.051,0.463 0.051,0.703c0.005,0.057 0.005,0.119 0.005,0.174c0,0.049 0,0.094 -0.005,0.139v0.018c0.016,0.922 0.201,1.801 0.53,2.609c0.593,1.477 1.643,2.717 2.979,3.551c1.135,0.703 2.471,1.105 3.901,1.105c3.616,0 6.635,-2.594 7.283,-6.031c0.062,-0.279 0.095,-0.568 0.117,-0.865c0.006,-0.102 0.011,-0.195 0.011,-0.295c0.006,-0.08 0.006,-0.152 0.006,-0.23v-0.184c0,-2.533 -0.425,-4.971 -1.201,-7.232C79.146,106.66 77.268,103.721 74.843,101.328"/>
|
||||||
|
</vector>
|
61
app/src/main/res/layout/pref_item_mangadex.xml
Normal file
61
app/src/main/res/layout/pref_item_mangadex.xml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
android:baselineAligned="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="42dp"
|
||||||
|
android:paddingStart="?listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||||
|
tools:ignore="RtlHardcoded">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@android:id/widget_frame"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@android:id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?textAppearanceListItem"/>
|
||||||
|
|
||||||
|
<!-- Hidden view -->
|
||||||
|
<TextView
|
||||||
|
android:id="@android:id/summary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/login_frame"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginEnd="-16dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:gravity="end|center_vertical"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/login" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
82
app/src/main/res/layout/pref_site_login_two_factor_auth.xml
Normal file
82
app/src/main/res/layout/pref_site_login_two_factor_auth.xml
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_title"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
tools:text="Log in to MangaDex" />
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/username_input"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/username">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/password"
|
||||||
|
app:endIconMode="password_toggle">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/two_factor_holder"
|
||||||
|
style="@style/Theme.Widget.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:hint="@string/two_factor"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/two_factor_edit"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textNoSuggestions" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.checkbox.MaterialCheckBox
|
||||||
|
android:id="@+id/two_factor_check"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:text="@string/two_factor" />
|
||||||
|
|
||||||
|
<com.dd.processbutton.iml.ActionProcessButton
|
||||||
|
android:id="@+id/login"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:text="@string/login"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
app:pb_textComplete="@string/login_success"
|
||||||
|
app:pb_textError="@string/invalid_login"
|
||||||
|
app:pb_textProgress="@string/loading" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
30
app/src/main/res/layout/source_filter_mangadex_header.xml
Normal file
30
app/src/main/res/layout/source_filter_mangadex_header.xml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/mangadex_random"
|
||||||
|
style="@style/Theme.Widget.Button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:text="@string/random"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/mangadex_follows"
|
||||||
|
style="@style/Theme.Widget.Button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:text="@string/mangadex_follows"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -51,4 +51,11 @@
|
|||||||
app:iconTint="?attr/colorOnPrimary"
|
app:iconTint="?attr/colorOnPrimary"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_push_to_mdlist"
|
||||||
|
android:icon="@drawable/baseline_swap_calls_24"
|
||||||
|
android:title="@string/mangadex_add_to_follows"
|
||||||
|
app:iconTint="?attr/colorOnPrimary"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
@ -5,4 +5,14 @@
|
|||||||
<item>@string/clean_read_downloads</item>
|
<item>@string/clean_read_downloads</item>
|
||||||
<item>@string/clean_read_manga_not_in_library</item>
|
<item>@string/clean_read_manga_not_in_library</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="md_follows_options">
|
||||||
|
<item>@string/md_follows_unfollowed</item>
|
||||||
|
<item>@string/reading</item>
|
||||||
|
<item>@string/completed</item>
|
||||||
|
<item>@string/on_hold</item>
|
||||||
|
<item>@string/plan_to_read</item>
|
||||||
|
<item>@string/dropped</item>
|
||||||
|
<item>@string/repeating</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
@ -521,4 +521,20 @@
|
|||||||
<string name="manga_info_manga">Info manga:</string>
|
<string name="manga_info_manga">Info manga:</string>
|
||||||
<string name="toggle_dedupe">Toggle dedupe</string>
|
<string name="toggle_dedupe">Toggle dedupe</string>
|
||||||
|
|
||||||
|
<!-- MangaDex -->
|
||||||
|
<string name="md_follows_unfollowed">Unfollowed</string>
|
||||||
|
<string name="mangadex_specific_settings">MangaDex settings</string>
|
||||||
|
<string name="mangadex_sync_follows_to_library">Sync Mangadex manga into Neko</string>
|
||||||
|
<string name="mangadex_sync_follows_to_library_summary">Pulls reading/rereading manga from Mangadex into your Neko library</string>
|
||||||
|
<string name="mangadex_low_quality_covers">Use low quality thumbnails</string>
|
||||||
|
<string name="mangadex_use_latest_cover">Use latest uploaded cover</string>
|
||||||
|
<string name="mangadex_use_latest_cover_summary">When enabled, it uses the latest uploaded manga cover under the /covers url instead of using the cover on MangaDex\'s manga page</string>
|
||||||
|
<string name="mangadex_preffered_source">Preferred MangaDex source</string>
|
||||||
|
<string name="mangadex_preffered_source_summary">Set your chosen mangadex source, this will be used for follows and a bunch more features around the app</string>
|
||||||
|
<string name="two_factor">2FA Code</string>
|
||||||
|
<string name="fields_cannot_be_blank">Fields cannot be blank</string>
|
||||||
|
<string name="mangadex_add_to_follows">Add to MangaDex follows</string>
|
||||||
|
<string name="mangadex_follows">MangaDex follows</string>
|
||||||
|
<string name="random">Random</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user