Initial MergedSource creation UI
This commit is contained in:
parent
4c9be5557d
commit
10d6b3a6ca
@ -8,8 +8,6 @@ apply plugin: 'kotlin-kapt'
|
|||||||
apply plugin: 'com.github.zellius.shortcut-helper'
|
apply plugin: 'com.github.zellius.shortcut-helper'
|
||||||
// Realm (EH)
|
// Realm (EH)
|
||||||
apply plugin: 'realm-android'
|
apply plugin: 'realm-android'
|
||||||
// Firebase (EH)
|
|
||||||
apply plugin: 'io.fabric'
|
|
||||||
|
|
||||||
shortcutHelper.filePath = './shortcuts.xml'
|
shortcutHelper.filePath = './shortcuts.xml'
|
||||||
|
|
||||||
@ -139,7 +137,8 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
implementation 'com.android.support:multidex:1.0.3'
|
||||||
|
|
||||||
standardImplementation 'com.google.firebase:firebase-core:17.0.1'
|
// DO NOT UPGRADE TO 17.0, IT REQUIRES ANDROIDX
|
||||||
|
standardImplementation 'com.google.firebase:firebase-core:16.0.8'
|
||||||
|
|
||||||
// ReactiveX
|
// ReactiveX
|
||||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||||
@ -174,6 +173,7 @@ dependencies {
|
|||||||
|
|
||||||
// Job scheduling
|
// Job scheduling
|
||||||
implementation 'com.evernote:android-job:1.2.5'
|
implementation 'com.evernote:android-job:1.2.5'
|
||||||
|
// DO NOT UPGRADE TO 17.0, IT REQUIRES ANDROIDX
|
||||||
implementation 'com.google.android.gms:play-services-gcm:15.0.1'
|
implementation 'com.google.android.gms:play-services-gcm:15.0.1'
|
||||||
|
|
||||||
// Changelog
|
// Changelog
|
||||||
@ -274,6 +274,7 @@ dependencies {
|
|||||||
|
|
||||||
// RxJava 2 interop for Realm (EH)
|
// RxJava 2 interop for Realm (EH)
|
||||||
implementation 'com.lvla.android:rxjava2-interop-kt:0.2.1'
|
implementation 'com.lvla.android:rxjava2-interop-kt:0.2.1'
|
||||||
|
implementation 'com.github.akarnokd:rxjava2-interop:0.13.7'
|
||||||
|
|
||||||
// Debug network interceptor (EH)
|
// Debug network interceptor (EH)
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:3.12.1"
|
implementation "com.squareup.okhttp3:logging-interceptor:3.12.1"
|
||||||
@ -297,6 +298,8 @@ dependencies {
|
|||||||
|
|
||||||
// Humanize (EH)
|
// Humanize (EH)
|
||||||
implementation 'com.github.mfornos:humanize-slim:1.2.2'
|
implementation 'com.github.mfornos:humanize-slim:1.2.2'
|
||||||
|
|
||||||
|
implementation 'com.android.support:gridlayout-v7:28.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
@ -319,4 +322,6 @@ androidExtensions {
|
|||||||
|
|
||||||
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
|
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
|
||||||
apply plugin: 'com.google.gms.google-services'
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
// Firebase (EH)
|
||||||
|
apply plugin: 'io.fabric'
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,7 @@ import eu.kanade.tachiyomi.source.model.SManga
|
|||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
import eu.kanade.tachiyomi.source.online.all.*
|
||||||
import eu.kanade.tachiyomi.source.online.all.Hitomi
|
|
||||||
import eu.kanade.tachiyomi.source.online.all.NHentai
|
|
||||||
import eu.kanade.tachiyomi.source.online.all.PervEden
|
|
||||||
import eu.kanade.tachiyomi.source.online.english.HentaiCafe
|
import eu.kanade.tachiyomi.source.online.english.HentaiCafe
|
||||||
import eu.kanade.tachiyomi.source.online.english.Tsumino
|
import eu.kanade.tachiyomi.source.online.english.Tsumino
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -51,6 +48,8 @@ open class SourceManager(private val context: Context) {
|
|||||||
Observable.merge(prefEntries).skip(prefEntries.size - 1).subscribe {
|
Observable.merge(prefEntries).skip(prefEntries.size - 1).subscribe {
|
||||||
createEHSources().forEach { registerSource(it) }
|
createEHSources().forEach { registerSource(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerSource(MergedSource())
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun get(sourceKey: Long): Source? {
|
open fun get(sourceKey: Long): Source? {
|
||||||
|
@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.source.Source
|
|||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.*
|
import eu.kanade.tachiyomi.source.model.*
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import exh.MERGED_SOURCE_ID
|
||||||
import exh.util.await
|
import exh.util.await
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
@ -30,6 +31,8 @@ class MergedSource : HttpSource() {
|
|||||||
private val sourceManager: SourceManager by injectLazy()
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
private val gson: Gson by injectLazy()
|
private val gson: Gson by injectLazy()
|
||||||
|
|
||||||
|
override val id: Long = MERGED_SOURCE_ID
|
||||||
|
|
||||||
override val baseUrl = ""
|
override val baseUrl = ""
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException()
|
override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException()
|
||||||
@ -51,14 +54,16 @@ class MergedSource : HttpSource() {
|
|||||||
return GlobalScope.async(Dispatchers.IO) {
|
return GlobalScope.async(Dispatchers.IO) {
|
||||||
val loadedMangas = readMangaConfig(manga).load(db, sourceManager).buffer()
|
val loadedMangas = readMangaConfig(manga).load(db, sourceManager).buffer()
|
||||||
loadedMangas.map { loadedManga ->
|
loadedMangas.map { loadedManga ->
|
||||||
loadedManga.source.fetchChapterList(loadedManga.manga).map { chapterList ->
|
async(Dispatchers.IO) {
|
||||||
chapterList.map { chapter ->
|
loadedManga.source.fetchChapterList(loadedManga.manga).map { chapterList ->
|
||||||
chapter.apply {
|
chapterList.map { chapter ->
|
||||||
url = writeUrlConfig(UrlConfig(loadedManga.source.id, url, loadedManga.manga.url))
|
chapter.apply {
|
||||||
|
url = writeUrlConfig(UrlConfig(loadedManga.source.id, url, loadedManga.manga.url))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}.toSingle().await(Schedulers.io())
|
||||||
}
|
}
|
||||||
}.buffer().map { it.toSingle().await(Schedulers.io()) }.toList().flatten()
|
}.buffer().map { it.await() }.toList().flatten()
|
||||||
}.asSingle(Dispatchers.IO).toV1Single().toObservable()
|
}.asSingle(Dispatchers.IO).toV1Single().toObservable()
|
||||||
}
|
}
|
||||||
override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException()
|
override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException()
|
||||||
@ -102,11 +107,12 @@ class MergedSource : HttpSource() {
|
|||||||
chapter.url = chapterConfig.url
|
chapter.url = chapterConfig.url
|
||||||
source.prepareNewChapter(chapter, copiedManga)
|
source.prepareNewChapter(chapter, copiedManga)
|
||||||
chapter.url = writeUrlConfig(UrlConfig(source.id, chapter.url, chapterConfig.mangaUrl))
|
chapter.url = writeUrlConfig(UrlConfig(source.id, chapter.url, chapterConfig.mangaUrl))
|
||||||
chapter.scanlator = "${source.name}: ${chapter.scanlator}"
|
chapter.scanlator = if(chapter.scanlator.isNullOrBlank()) source.name
|
||||||
|
else "${source.name}: ${chapter.scanlator}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun readMangaConfig(manga: SManga): MangaConfig {
|
fun readMangaConfig(manga: SManga): MangaConfig {
|
||||||
return gson.fromJson(manga.url)
|
return MangaConfig.readFromUrl(gson, manga.url)
|
||||||
}
|
}
|
||||||
fun readUrlConfig(url: String): UrlConfig {
|
fun readUrlConfig(url: String): UrlConfig {
|
||||||
return gson.fromJson(url)
|
return gson.fromJson(url)
|
||||||
@ -126,11 +132,6 @@ class MergedSource : HttpSource() {
|
|||||||
val source = sourceManager.getOrStub(source)
|
val source = sourceManager.getOrStub(source)
|
||||||
return LoadedMangaSource(source, manga)
|
return LoadedMangaSource(source, manga)
|
||||||
}
|
}
|
||||||
companion object {
|
|
||||||
fun fromLoadedMangaSource(loadedMS: LoadedMangaSource): MangaSource {
|
|
||||||
return MangaSource(loadedMS.source.id, loadedMS.manga.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
data class MangaConfig(
|
data class MangaConfig(
|
||||||
@SerializedName("c")
|
@SerializedName("c")
|
||||||
@ -142,6 +143,16 @@ class MergedSource : HttpSource() {
|
|||||||
?: throw IllegalStateException("Missing source manga: $mangaSource")
|
?: throw IllegalStateException("Missing source manga: $mangaSource")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun writeAsUrl(gson: Gson): String {
|
||||||
|
return gson.toJson(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun readFromUrl(gson: Gson, url: String): MangaConfig {
|
||||||
|
return gson.fromJson(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
data class UrlConfig(
|
data class UrlConfig(
|
||||||
@SerializedName("s")
|
@SerializedName("s")
|
||||||
|
@ -257,7 +257,7 @@ class CatalogueController(bundle: Bundle? = null) : NucleusController<CatalogueP
|
|||||||
|
|
||||||
// EXH -->
|
// EXH -->
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class SmartSearchConfig(val title: String) : Parcelable
|
data class SmartSearchConfig(val origTitle: String, val origMangaId: Long) : Parcelable
|
||||||
// EXH <--
|
// EXH <--
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
|
@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
|
|||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.SecondaryDrawerController
|
import eu.kanade.tachiyomi.ui.base.controller.SecondaryDrawerController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
|
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
|
||||||
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
import eu.kanade.tachiyomi.ui.manga.info.MangaWebViewController
|
import eu.kanade.tachiyomi.ui.manga.info.MangaWebViewController
|
||||||
@ -50,11 +51,15 @@ open class BrowseCatalogueController(bundle: Bundle) :
|
|||||||
ChangeMangaCategoriesDialog.Listener {
|
ChangeMangaCategoriesDialog.Listener {
|
||||||
|
|
||||||
constructor(source: CatalogueSource,
|
constructor(source: CatalogueSource,
|
||||||
searchQuery: String? = null) : this(Bundle().apply {
|
searchQuery: String? = null,
|
||||||
|
smartSearchConfig: CatalogueController.SmartSearchConfig? = null) : this(Bundle().apply {
|
||||||
putLong(SOURCE_ID_KEY, source.id)
|
putLong(SOURCE_ID_KEY, source.id)
|
||||||
|
|
||||||
if(searchQuery != null)
|
if(searchQuery != null)
|
||||||
putString(SEARCH_QUERY_KEY, searchQuery)
|
putString(SEARCH_QUERY_KEY, searchQuery)
|
||||||
|
|
||||||
|
if (smartSearchConfig != null)
|
||||||
|
putParcelable(SMART_SEARCH_CONFIG_KEY, smartSearchConfig)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -561,7 +566,9 @@ open class BrowseCatalogueController(bundle: Bundle) :
|
|||||||
*/
|
*/
|
||||||
override fun onItemClick(view: View, position: Int): Boolean {
|
override fun onItemClick(view: View, position: Int): Boolean {
|
||||||
val item = adapter?.getItem(position) as? CatalogueItem ?: return false
|
val item = adapter?.getItem(position) as? CatalogueItem ?: return false
|
||||||
router.pushController(MangaController(item.manga, true).withFadeTransaction())
|
router.pushController(MangaController(item.manga,
|
||||||
|
true,
|
||||||
|
args.getParcelable(SMART_SEARCH_CONFIG_KEY)).withFadeTransaction())
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -628,6 +635,9 @@ open class BrowseCatalogueController(bundle: Bundle) :
|
|||||||
protected companion object {
|
protected companion object {
|
||||||
const val SOURCE_ID_KEY = "sourceId"
|
const val SOURCE_ID_KEY = "sourceId"
|
||||||
const val SEARCH_QUERY_KEY = "searchQuery"
|
const val SEARCH_QUERY_KEY = "searchQuery"
|
||||||
|
// EXH -->
|
||||||
|
const val SMART_SEARCH_CONFIG_KEY = "smartSearchConfig"
|
||||||
|
// EXH <--
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.source.SourceManager
|
|||||||
import eu.kanade.tachiyomi.ui.base.controller.RxController
|
import eu.kanade.tachiyomi.ui.base.controller.RxController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
|
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
|
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
|
||||||
|
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
|
||||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersController
|
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersController
|
||||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter
|
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter
|
||||||
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoController
|
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoController
|
||||||
@ -39,9 +40,14 @@ import java.util.Date
|
|||||||
|
|
||||||
class MangaController : RxController, TabbedController {
|
class MangaController : RxController, TabbedController {
|
||||||
|
|
||||||
constructor(manga: Manga?, fromCatalogue: Boolean = false) : super(Bundle().apply {
|
constructor(manga: Manga?,
|
||||||
|
fromCatalogue: Boolean = false,
|
||||||
|
smartSearchConfig: CatalogueController.SmartSearchConfig? = null,
|
||||||
|
update: Boolean = false) : super(Bundle().apply {
|
||||||
putLong(MANGA_EXTRA, manga?.id ?: 0)
|
putLong(MANGA_EXTRA, manga?.id ?: 0)
|
||||||
putBoolean(FROM_CATALOGUE_EXTRA, fromCatalogue)
|
putBoolean(FROM_CATALOGUE_EXTRA, fromCatalogue)
|
||||||
|
putParcelable(SMART_SEARCH_CONFIG_EXTRA, smartSearchConfig)
|
||||||
|
putBoolean(UPDATE_EXTRA, update)
|
||||||
}) {
|
}) {
|
||||||
this.manga = manga
|
this.manga = manga
|
||||||
if (manga != null) {
|
if (manga != null) {
|
||||||
@ -79,6 +85,10 @@ class MangaController : RxController, TabbedController {
|
|||||||
|
|
||||||
val update = args.getBoolean(UPDATE_EXTRA, false)
|
val update = args.getBoolean(UPDATE_EXTRA, false)
|
||||||
|
|
||||||
|
// EXH -->
|
||||||
|
val smartSearchConfig: CatalogueController.SmartSearchConfig? = args.getParcelable(SMART_SEARCH_CONFIG_EXTRA)
|
||||||
|
// EXH <--
|
||||||
|
|
||||||
val lastUpdateRelay: BehaviorRelay<Date> = BehaviorRelay.create()
|
val lastUpdateRelay: BehaviorRelay<Date> = BehaviorRelay.create()
|
||||||
|
|
||||||
val chapterCountRelay: BehaviorRelay<Float> = BehaviorRelay.create()
|
val chapterCountRelay: BehaviorRelay<Float> = BehaviorRelay.create()
|
||||||
@ -197,6 +207,7 @@ class MangaController : RxController, TabbedController {
|
|||||||
|
|
||||||
// EXH -->
|
// EXH -->
|
||||||
const val UPDATE_EXTRA = "update"
|
const val UPDATE_EXTRA = "update"
|
||||||
|
const val SMART_SEARCH_CONFIG_EXTRA = "smartSearchConfig"
|
||||||
// EXH <--
|
// EXH <--
|
||||||
const val FROM_CATALOGUE_EXTRA = "from_catalogue"
|
const val FROM_CATALOGUE_EXTRA = "from_catalogue"
|
||||||
const val MANGA_EXTRA = "manga"
|
const val MANGA_EXTRA = "manga"
|
||||||
|
@ -22,6 +22,7 @@ import com.bumptech.glide.request.target.SimpleTarget
|
|||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
import com.bumptech.glide.signature.ObjectKey
|
||||||
import com.elvishew.xlog.XLog
|
import com.elvishew.xlog.XLog
|
||||||
|
import com.google.gson.Gson
|
||||||
import com.jakewharton.rxbinding.support.v4.widget.refreshes
|
import com.jakewharton.rxbinding.support.v4.widget.refreshes
|
||||||
import com.jakewharton.rxbinding.view.clicks
|
import com.jakewharton.rxbinding.view.clicks
|
||||||
import com.jakewharton.rxbinding.view.longClicks
|
import com.jakewharton.rxbinding.view.longClicks
|
||||||
@ -33,8 +34,10 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
|||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
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.MergedSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
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
|
||||||
@ -43,21 +46,21 @@ import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
|
|||||||
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
import eu.kanade.tachiyomi.util.openInBrowser
|
import eu.kanade.tachiyomi.util.*
|
||||||
import eu.kanade.tachiyomi.util.snack
|
|
||||||
import eu.kanade.tachiyomi.util.toast
|
|
||||||
import eu.kanade.tachiyomi.util.truncateCenter
|
|
||||||
import exh.EH_SOURCE_ID
|
import exh.EH_SOURCE_ID
|
||||||
import exh.EXH_SOURCE_ID
|
import exh.EXH_SOURCE_ID
|
||||||
|
import exh.MERGED_SOURCE_ID
|
||||||
import exh.NHENTAI_SOURCE_ID
|
import exh.NHENTAI_SOURCE_ID
|
||||||
import exh.ui.webview.WebViewActivity
|
import exh.ui.webview.WebViewActivity
|
||||||
import jp.wasabeef.glide.transformations.CropSquareTransformation
|
import jp.wasabeef.glide.transformations.CropSquareTransformation
|
||||||
import jp.wasabeef.glide.transformations.MaskTransformation
|
import jp.wasabeef.glide.transformations.MaskTransformation
|
||||||
import kotlinx.android.synthetic.main.manga_info_controller.*
|
import kotlinx.android.synthetic.main.manga_info_controller.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment that shows manga information.
|
* Fragment that shows manga information.
|
||||||
@ -65,7 +68,7 @@ import java.util.*
|
|||||||
* UI related actions should be called from here.
|
* UI related actions should be called from here.
|
||||||
*/
|
*/
|
||||||
class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
||||||
ChangeMangaCategoriesDialog.Listener {
|
ChangeMangaCategoriesDialog.Listener, CoroutineScope {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preferences helper.
|
* Preferences helper.
|
||||||
@ -74,6 +77,14 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
|
|
||||||
// EXH -->
|
// EXH -->
|
||||||
private var lastMangaThumbnail: String? = null
|
private var lastMangaThumbnail: String? = null
|
||||||
|
|
||||||
|
private val smartSearchConfig get() = (parentController as MangaController).smartSearchConfig
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext = Job() + Dispatchers.Main
|
||||||
|
|
||||||
|
private val gson: Gson by injectLazy()
|
||||||
|
|
||||||
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
// EXH <--
|
// EXH <--
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -83,7 +94,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
|
|
||||||
override fun createPresenter(): MangaInfoPresenter {
|
override fun createPresenter(): MangaInfoPresenter {
|
||||||
val ctrl = parentController as MangaController
|
val ctrl = parentController as MangaController
|
||||||
return MangaInfoPresenter(ctrl.manga!!, ctrl.source!!,
|
return MangaInfoPresenter(ctrl.manga!!, ctrl.source!!, ctrl.smartSearchConfig,
|
||||||
ctrl.chapterCountRelay, ctrl.lastUpdateRelay, ctrl.mangaFavoriteRelay)
|
ctrl.chapterCountRelay, ctrl.lastUpdateRelay, ctrl.mangaFavoriteRelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +164,34 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
copyToClipboard(view.context.getString(R.string.title), presenter.manga.title)
|
copyToClipboard(view.context.getString(R.string.title), presenter.manga.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EXH -->
|
||||||
|
smartSearchConfig?.let { smartSearchConfig ->
|
||||||
|
smartsearch_buttons.visible()
|
||||||
|
|
||||||
|
smartsearch_merge_btn.clicks().subscribeUntilDestroy {
|
||||||
|
// Init presenter here to avoid threading issues
|
||||||
|
presenter
|
||||||
|
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
val mergedManga = withContext(Dispatchers.IO + NonCancellable) {
|
||||||
|
presenter.smartSearchMerge(presenter.manga, smartSearchConfig.origMangaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
parentController?.router?.pushController(MangaController(mergedManga,
|
||||||
|
true,
|
||||||
|
update = true).withFadeTransaction())
|
||||||
|
applicationContext?.toast("Manga merged!")
|
||||||
|
} catch(e: Exception) {
|
||||||
|
if(e is CancellationException) throw e
|
||||||
|
else {
|
||||||
|
applicationContext?.toast("Failed to merge manga: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// EXH <--
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
@ -176,7 +215,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
|
|
||||||
// EXH -->
|
// EXH -->
|
||||||
private fun openSmartSearch() {
|
private fun openSmartSearch() {
|
||||||
val smartSearchConfig = CatalogueController.SmartSearchConfig(presenter.manga.title)
|
val smartSearchConfig = CatalogueController.SmartSearchConfig(presenter.manga.title, presenter.manga.id!!)
|
||||||
|
|
||||||
parentController?.router?.pushController(CatalogueController(Bundle().apply {
|
parentController?.router?.pushController(CatalogueController(Bundle().apply {
|
||||||
putParcelable(CatalogueController.SMART_SEARCH_CONFIG, smartSearchConfig)
|
putParcelable(CatalogueController.SMART_SEARCH_CONFIG, smartSearchConfig)
|
||||||
@ -237,10 +276,24 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
// If manga source is known update source TextView.
|
// If manga source is known update source TextView.
|
||||||
manga_source.text = if (source == null) {
|
manga_source.text = if (source == null) {
|
||||||
view.context.getString(R.string.unknown)
|
view.context.getString(R.string.unknown)
|
||||||
|
// EXH -->
|
||||||
|
} else if(source.id == MERGED_SOURCE_ID) {
|
||||||
|
MergedSource.MangaConfig.readFromUrl(gson, manga.url).children.map {
|
||||||
|
sourceManager.getOrStub(it.source).toString()
|
||||||
|
}.distinct().joinToString()
|
||||||
|
// EXH <--
|
||||||
} else {
|
} else {
|
||||||
source.toString()
|
source.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EXH -->
|
||||||
|
if(source?.id == MERGED_SOURCE_ID) {
|
||||||
|
manga_source_label.text = "Sources"
|
||||||
|
} else {
|
||||||
|
manga_source_label.setText(R.string.manga_info_source_label)
|
||||||
|
}
|
||||||
|
// EXH <--
|
||||||
|
|
||||||
// Update genres list
|
// Update genres list
|
||||||
if (manga.genre.isNullOrBlank().not()) {
|
if (manga.genre.isNullOrBlank().not()) {
|
||||||
manga_genres_tags.setTags(manga.genre?.split(", "))
|
manga_genres_tags.setTags(manga.genre?.split(", "))
|
||||||
@ -295,6 +348,13 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
super.onDestroyView(view)
|
super.onDestroyView(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EXH -->
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
// EXH <--
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update chapter count TextView.
|
* Update chapter count TextView.
|
||||||
*
|
*
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.manga.info
|
package eu.kanade.tachiyomi.ui.manga.info
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import com.google.gson.Gson
|
||||||
import com.jakewharton.rxrelay.BehaviorRelay
|
import com.jakewharton.rxrelay.BehaviorRelay
|
||||||
import com.jakewharton.rxrelay.PublishRelay
|
import com.jakewharton.rxrelay.PublishRelay
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
@ -10,8 +11,14 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
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.catalogue.CatalogueController
|
||||||
import eu.kanade.tachiyomi.util.isNullOrUnsubscribed
|
import eu.kanade.tachiyomi.util.isNullOrUnsubscribed
|
||||||
|
import exh.MERGED_SOURCE_ID
|
||||||
|
import exh.util.await
|
||||||
|
import kotlinx.coroutines.NonCancellable
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
@ -28,12 +35,14 @@ import java.util.*
|
|||||||
class MangaInfoPresenter(
|
class MangaInfoPresenter(
|
||||||
val manga: Manga,
|
val manga: Manga,
|
||||||
val source: Source,
|
val source: Source,
|
||||||
|
val smartSearchConfig: CatalogueController.SmartSearchConfig?,
|
||||||
private val chapterCountRelay: BehaviorRelay<Float>,
|
private val chapterCountRelay: BehaviorRelay<Float>,
|
||||||
private val lastUpdateRelay: BehaviorRelay<Date>,
|
private val lastUpdateRelay: BehaviorRelay<Date>,
|
||||||
private val mangaFavoriteRelay: PublishRelay<Boolean>,
|
private val mangaFavoriteRelay: PublishRelay<Boolean>,
|
||||||
private val db: DatabaseHelper = Injekt.get(),
|
private val db: DatabaseHelper = Injekt.get(),
|
||||||
private val downloadManager: DownloadManager = Injekt.get(),
|
private val downloadManager: DownloadManager = Injekt.get(),
|
||||||
private val coverCache: CoverCache = Injekt.get()
|
private val coverCache: CoverCache = Injekt.get(),
|
||||||
|
private val gson: Gson = Injekt.get()
|
||||||
) : BasePresenter<MangaInfoController>() {
|
) : BasePresenter<MangaInfoController>() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -170,4 +179,59 @@ class MangaInfoPresenter(
|
|||||||
moveMangaToCategories(manga, listOfNotNull(category))
|
moveMangaToCategories(manga, listOfNotNull(category))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun smartSearchMerge(manga: Manga, originalMangaId: Long): Manga {
|
||||||
|
val originalManga = db.getManga(originalMangaId).await()
|
||||||
|
?: throw IllegalArgumentException("Unknown manga ID: $originalMangaId")
|
||||||
|
val toInsert = if(originalManga.source == MERGED_SOURCE_ID) {
|
||||||
|
originalManga.apply {
|
||||||
|
val originalChildren = MergedSource.MangaConfig.readFromUrl(gson, url).children
|
||||||
|
if(originalChildren.any { it.source == manga.source && it.url == manga.url })
|
||||||
|
throw IllegalArgumentException("This manga is already merged with the current manga!")
|
||||||
|
|
||||||
|
url = MergedSource.MangaConfig(originalChildren + MergedSource.MangaSource(
|
||||||
|
manga.source,
|
||||||
|
manga.url
|
||||||
|
)).writeAsUrl(gson)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val newMangaConfig = MergedSource.MangaConfig(listOf(
|
||||||
|
MergedSource.MangaSource(
|
||||||
|
originalManga.source,
|
||||||
|
originalManga.url
|
||||||
|
),
|
||||||
|
MergedSource.MangaSource(
|
||||||
|
manga.source,
|
||||||
|
manga.url
|
||||||
|
)
|
||||||
|
))
|
||||||
|
Manga.create(newMangaConfig.writeAsUrl(gson), originalManga.title, MERGED_SOURCE_ID).apply {
|
||||||
|
copyFrom(originalManga)
|
||||||
|
favorite = true
|
||||||
|
last_update = originalManga.last_update
|
||||||
|
viewer = originalManga.viewer
|
||||||
|
chapter_flags = originalManga.chapter_flags
|
||||||
|
sorting = Manga.SORTING_NUMBER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that if the manga are merged in a different order, this won't trigger, but I don't care lol
|
||||||
|
val existingManga = db.getManga(toInsert.url, toInsert.source).await()
|
||||||
|
if(existingManga != null) {
|
||||||
|
withContext(NonCancellable) {
|
||||||
|
if(toInsert.id != null) {
|
||||||
|
db.deleteManga(toInsert).await()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingManga
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload chapters immediately
|
||||||
|
toInsert.initialized = false
|
||||||
|
|
||||||
|
val newId = db.insertManga(toInsert).await().insertedId()
|
||||||
|
if(newId != null) toInsert.id = newId
|
||||||
|
|
||||||
|
return toInsert
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ const val NHENTAI_SOURCE_ID = LEWD_SOURCE_SERIES + 7
|
|||||||
val HENTAI_CAFE_SOURCE_ID = delegatedSourceId<HentaiCafe>()
|
val HENTAI_CAFE_SOURCE_ID = delegatedSourceId<HentaiCafe>()
|
||||||
const val TSUMINO_SOURCE_ID = LEWD_SOURCE_SERIES + 9
|
const val TSUMINO_SOURCE_ID = LEWD_SOURCE_SERIES + 9
|
||||||
const val HITOMI_SOURCE_ID = LEWD_SOURCE_SERIES + 10
|
const val HITOMI_SOURCE_ID = LEWD_SOURCE_SERIES + 10
|
||||||
|
const val MERGED_SOURCE_ID = LEWD_SOURCE_SERIES + 69
|
||||||
|
|
||||||
private val DELEGATED_LEWD_SOURCES = listOf(
|
private val DELEGATED_LEWD_SOURCES = listOf(
|
||||||
HentaiCafe::class
|
HentaiCafe::class
|
||||||
|
@ -50,7 +50,7 @@ class SmartSearchController(bundle: Bundle? = null) : NucleusController<SmartSea
|
|||||||
for(event in presenter.smartSearchChannel) {
|
for(event in presenter.smartSearchChannel) {
|
||||||
withContext(NonCancellable) {
|
withContext(NonCancellable) {
|
||||||
if (event is SmartSearchPresenter.SearchResults.Found) {
|
if (event is SmartSearchPresenter.SearchResults.Found) {
|
||||||
val transaction = MangaController(event.manga, true).withFadeTransaction()
|
val transaction = MangaController(event.manga, true, smartSearchConfig).withFadeTransaction()
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
router.replaceTopController(transaction)
|
router.replaceTopController(transaction)
|
||||||
}
|
}
|
||||||
@ -61,7 +61,7 @@ class SmartSearchController(bundle: Bundle? = null) : NucleusController<SmartSea
|
|||||||
applicationContext?.toast("Error performing automatic search!")
|
applicationContext?.toast("Error performing automatic search!")
|
||||||
}
|
}
|
||||||
|
|
||||||
val transaction = BrowseCatalogueController(source, smartSearchConfig.title).withFadeTransaction()
|
val transaction = BrowseCatalogueController(source, smartSearchConfig.origTitle, smartSearchConfig).withFadeTransaction()
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
router.replaceTopController(transaction)
|
router.replaceTopController(transaction)
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ class SmartSearchPresenter(private val source: CatalogueSource?, private val con
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun smartSearch(source: CatalogueSource, config: CatalogueController.SmartSearchConfig): SManga? {
|
private suspend fun smartSearch(source: CatalogueSource, config: CatalogueController.SmartSearchConfig): SManga? {
|
||||||
val cleanedTitle = cleanSmartSearchTitle(config.title)
|
val cleanedTitle = cleanSmartSearchTitle(config.origTitle)
|
||||||
|
|
||||||
val queries = getSmartSearchQueries(cleanedTitle)
|
val queries = getSmartSearchQueries(cleanedTitle)
|
||||||
|
|
||||||
|
@ -254,6 +254,35 @@
|
|||||||
app:atg_textColor="@color/md_blue_A400"
|
app:atg_textColor="@color/md_blue_A400"
|
||||||
android:layout_marginRight="64dp"/>
|
android:layout_marginRight="64dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/smartsearch_buttons"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/manga_genres_tags"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/smartsearch_merge_btn"
|
||||||
|
style="@style/Widget.AppCompat.Button.Colored"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Merge with current" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/smartsearch_replace_btn"
|
||||||
|
style="@style/Widget.AppCompat.Button.Colored"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Replace current" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
|
@ -9,11 +9,8 @@
|
|||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<android.support.constraint.ConstraintLayout
|
<android.support.constraint.ConstraintLayout
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/guideline"
|
android:id="@+id/guideline"
|
||||||
@ -230,12 +227,10 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textIsSelectable="false"
|
android:textIsSelectable="false"
|
||||||
app:layout_constraintBaseline_toBaselineOf="@+id/manga_source_label"
|
|
||||||
app:layout_constraintLeft_toRightOf="@+id/manga_source_label"
|
app:layout_constraintLeft_toRightOf="@+id/manga_source_label"
|
||||||
app:layout_constraintRight_toRightOf="parent"/>
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/manga_status_label" />
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
@ -287,7 +282,34 @@
|
|||||||
app:atg_borderStrokeWidth="1dp"
|
app:atg_borderStrokeWidth="1dp"
|
||||||
app:atg_backgroundColor="@android:color/transparent"
|
app:atg_backgroundColor="@android:color/transparent"
|
||||||
app:atg_borderColor="@color/md_blue_A400"
|
app:atg_borderColor="@color/md_blue_A400"
|
||||||
app:atg_textColor="@color/md_blue_A400" />
|
app:atg_textColor="@color/md_blue_A400" >
|
||||||
|
|
||||||
|
</me.gujun.android.taggroup.TagGroup>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/smartsearch_buttons"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/smartsearch_merge_btn"
|
||||||
|
style="@style/Widget.AppCompat.Button.Colored"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Merge with current" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/smartsearch_replace_btn"
|
||||||
|
style="@style/Widget.AppCompat.Button.Colored"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Replace current" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user