mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 14:27:57 +01:00 
			
		
		
		
	Implemented J2K Auto Source Migration
(cherry picked from commit 8ba75831e6f51f6472d85f813405ede3f679cfd8)
This commit is contained in:
		| @@ -16,7 +16,7 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable | ||||
| import eu.kanade.tachiyomi.data.database.tables.ChapterTable | ||||
| import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable | ||||
| import eu.kanade.tachiyomi.data.database.tables.MangaTable | ||||
| import exh.metadata.sql.tables.SearchMetadataTable | ||||
| import eu.kanade.tachiyomi.data.database.tables.SearchMetadataTable | ||||
|  | ||||
| interface MangaQueries : DbProvider { | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| package exh.smartsearch | ||||
| package eu.kanade.tachiyomi.smartsearch | ||||
| 
 | ||||
| import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.source.CatalogueSource | ||||
| import eu.kanade.tachiyomi.source.model.FilterList | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import exh.ui.smartsearch.SmartSearchPresenter | ||||
| import exh.util.await | ||||
| import eu.kanade.tachiyomi.ui.smartsearch.SmartSearchPresenter | ||||
| import eu.kanade.tachiyomi.util.await | ||||
| import info.debatty.java.stringsimilarity.NormalizedLevenshtein | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| @@ -39,7 +39,8 @@ class SmartSearchEngine( | ||||
|                         "$query ${extraSearchParams.trim()}" | ||||
|                     } else query | ||||
| 
 | ||||
|                     val searchResults = source.fetchSearchManga(1, builtQuery, FilterList()).toSingle().await(Schedulers.io()) | ||||
|                     val searchResults = source.fetchSearchManga(1, builtQuery, FilterList()) | ||||
|                         .toSingle().await(Schedulers.io()) | ||||
| 
 | ||||
|                     searchResults.mangas.map { | ||||
|                         val cleanedMangaTitle = cleanSmartSearchTitle(it.title) | ||||
| @@ -88,11 +89,11 @@ class SmartSearchEngine( | ||||
|         // Search first word | ||||
| 
 | ||||
|         val searchQueries = listOf( | ||||
|                 listOf(cleanedTitle), | ||||
|                 splitSortedByLargest.take(2), | ||||
|                 splitSortedByLargest.take(1), | ||||
|                 splitCleanedTitle.take(2), | ||||
|                 splitCleanedTitle.take(1) | ||||
|             listOf(cleanedTitle), | ||||
|             splitSortedByLargest.take(2), | ||||
|             splitSortedByLargest.take(1), | ||||
|             splitCleanedTitle.take(2), | ||||
|             splitCleanedTitle.take(1) | ||||
|         ) | ||||
| 
 | ||||
|         return searchQueries.map { | ||||
| @@ -120,10 +121,10 @@ class SmartSearchEngine( | ||||
| 
 | ||||
|     private fun removeTextInBrackets(text: String, readForward: Boolean): String { | ||||
|         val bracketPairs = listOf( | ||||
|                 '(' to ')', | ||||
|                 '[' to ']', | ||||
|                 '<' to '>', | ||||
|                 '{' to '}' | ||||
|             '(' to ')', | ||||
|             '[' to ']', | ||||
|             '<' to '>', | ||||
|             '{' to '}' | ||||
|         ) | ||||
|         var openingBracketPairs = bracketPairs.mapIndexed { index, (opening, _) -> | ||||
|             opening to index | ||||
| @@ -171,11 +172,11 @@ class SmartSearchEngine( | ||||
|      * @return a manga from the database. | ||||
|      */ | ||||
|     suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { | ||||
|         var localManga = db.getManga(sManga.url, sourceId).await() | ||||
|         var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking() | ||||
|         if (localManga == null) { | ||||
|             val newManga = Manga.create(sManga.url, sManga.title, sourceId) | ||||
|             newManga.copyFrom(sManga) | ||||
|             val result = db.insertManga(newManga).await() | ||||
|             val result = db.insertManga(newManga).executeAsBlocking() | ||||
|             newManga.id = result.insertedId() | ||||
|             localManga = newManga | ||||
|         } | ||||
| @@ -28,6 +28,11 @@ import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController | ||||
| import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController | ||||
| import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController | ||||
| import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController | ||||
| import eu.kanade.tachiyomi.ui.smartsearch.SmartSearchController | ||||
| import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController | ||||
| import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController | ||||
| import eu.kanade.tachiyomi.ui.source.latest.LatestUpdatesController | ||||
| import kotlinx.android.parcel.Parcelize | ||||
| import kotlinx.coroutines.flow.filter | ||||
| import kotlinx.coroutines.flow.launchIn | ||||
| import kotlinx.coroutines.flow.onEach | ||||
| @@ -56,16 +61,22 @@ class SourceController : | ||||
|      */ | ||||
|     private var adapter: SourceAdapter? = null | ||||
|  | ||||
|     // EXH --> | ||||
|     private val mode = if (smartSearchConfig == null) Mode.CATALOGUE else Mode.SMART_SEARCH | ||||
|     // EXH <-- | ||||
|  | ||||
|     init { | ||||
|         setHasOptionsMenu(true) | ||||
|         // Enable the option menu | ||||
|         setHasOptionsMenu(mode == Mode.CATALOGUE) | ||||
|     } | ||||
|  | ||||
|     override fun getTitle(): String? { | ||||
|         return applicationContext?.getString(R.string.label_sources) | ||||
|     } | ||||
|  | ||||
|         returnwhen (mode) { | ||||
|             Mode.CATALOGUE -> applicationContext?.getString(R.string.label_sources) | ||||
|             Mode.SMART_SEARCH -> "Find in another source" | ||||
|         } | ||||
|     override fun createPresenter(): SourcePresenter { | ||||
|         return SourcePresenter() | ||||
|         return SourcePresenter(controllerMode = mode) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -115,7 +126,16 @@ class SourceController : | ||||
|     override fun onItemClick(view: View, position: Int): Boolean { | ||||
|         val item = adapter?.getItem(position) as? SourceItem ?: return false | ||||
|         val source = item.source | ||||
|         openCatalogue(source, BrowseSourceController(source)) | ||||
|         when (mode) { | ||||
|             Mode.CATALOGUE -> { | ||||
|                 // Open the catalogue view. | ||||
|                 openCatalogue(source, BrowseSourceController(source)) | ||||
|             } | ||||
|             Mode.SMART_SEARCH -> router.pushController(SmartSearchController(Bundle().apply { | ||||
|                 putLong(SmartSearchController.ARG_SOURCE_ID, source.id) | ||||
|                 putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig) | ||||
|             }).withFadeTransaction()) | ||||
|         } | ||||
|         return false | ||||
|     } | ||||
|  | ||||
| @@ -250,4 +270,16 @@ class SourceController : | ||||
|             adapter?.addScrollableHeader(LangItem(SourcePresenter.LAST_USED_KEY)) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Parcelize | ||||
|     data class SmartSearchConfig(val origTitle: String, val origMangaId: Long) : Parcelable | ||||
|  | ||||
|     enum class Mode { | ||||
|         CATALOGUE, | ||||
|         SMART_SEARCH | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         const val SMART_SEARCH_CONFIG = "SMART_SEARCH_CONFIG" | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -614,9 +614,8 @@ open class BrowseSourceController(bundle: Bundle) : | ||||
|  | ||||
|     protected companion object { | ||||
|         const val SOURCE_ID_KEY = "sourceId" | ||||
|  | ||||
|         const val SEARCH_QUERY_KEY = "searchQuery" | ||||
|         // EXH --> | ||||
|         const val SMART_SEARCH_CONFIG_KEY = "smartSearchConfig" | ||||
|         // EXH <-- | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,9 +5,9 @@ import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.database.tables.MangaTable | ||||
| import eu.kanade.tachiyomi.data.database.tables.SearchMetadataTable | ||||
| import eu.kanade.tachiyomi.ui.category.CategoryAdapter | ||||
| import exh.isLewdSource | ||||
| import exh.metadata.sql.tables.SearchMetadataTable | ||||
| import exh.search.SearchEngine | ||||
| import exh.util.await | ||||
| import exh.util.cancellable | ||||
|   | ||||
| @@ -34,9 +34,17 @@ import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction | ||||
| import eu.kanade.tachiyomi.ui.main.MainActivity | ||||
| import eu.kanade.tachiyomi.ui.main.offsetFabAppbarHeight | ||||
| import eu.kanade.tachiyomi.ui.manga.MangaController | ||||
| import eu.kanade.tachiyomi.ui.migration.MigrationController | ||||
| import eu.kanade.tachiyomi.ui.migration.MigrationInterface | ||||
| import eu.kanade.tachiyomi.ui.migration.SearchController | ||||
| import eu.kanade.tachiyomi.ui.migration.manga.design.MigrationDesignController | ||||
| import eu.kanade.tachiyomi.util.system.getResourceColor | ||||
| import eu.kanade.tachiyomi.util.system.toast | ||||
| import eu.kanade.tachiyomi.util.view.inflate | ||||
| import eu.kanade.tachiyomi.util.view.visible | ||||
| import exh.favorites.FavoritesIntroDialog | ||||
| import exh.favorites.FavoritesSyncStatus | ||||
| import exh.ui.LoaderManager | ||||
| import java.io.IOException | ||||
| import kotlinx.android.synthetic.main.main_activity.tabs | ||||
| import kotlinx.coroutines.flow.filter | ||||
| @@ -57,7 +65,8 @@ class LibraryController( | ||||
|     TabbedController, | ||||
|     ActionMode.Callback, | ||||
|     ChangeMangaCategoriesDialog.Listener, | ||||
|     DeleteLibraryMangasDialog.Listener { | ||||
|     DeleteLibraryMangasDialog.Listener, | ||||
|     MigrationInterface { | ||||
|  | ||||
|     /** | ||||
|      * Position of the active category. | ||||
| @@ -86,6 +95,11 @@ class LibraryController( | ||||
|      */ | ||||
|     val selectionRelay: PublishRelay<LibrarySelectionEvent> = PublishRelay.create() | ||||
|  | ||||
|     /** | ||||
|      * Current mangas to move. | ||||
|      */ | ||||
|     private var migratingMangas = mutableSetOf<Manga>() | ||||
|  | ||||
|     /** | ||||
|      * Relay to notify search query changes. | ||||
|      */ | ||||
| @@ -468,10 +482,11 @@ class LibraryController( | ||||
|             R.id.action_delete -> showDeleteMangaDialog() | ||||
|             R.id.action_select_all -> selectAllCategoryManga() | ||||
|             R.id.action_select_inverse -> selectInverseCategoryManga() | ||||
|             R.id.action_auto_source_migration -> { | ||||
|                 router.pushController(MigrationDesignController.create( | ||||
|             R.id.action_migrate -> { | ||||
|                 router.pushController( | ||||
|                     MigrationDesignController.create( | ||||
|                         selectedMangas.mapNotNull { it.id } | ||||
|                 ).withFadeTransaction()) | ||||
|                     ).withFadeTransaction()) | ||||
|                 destroyActionModeIfNeeded() | ||||
|             } | ||||
|             else -> return false | ||||
| @@ -479,6 +494,27 @@ class LibraryController( | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     override fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? { | ||||
|         if (manga.id != prevManga.id) { | ||||
|             presenter.migrateManga(prevManga, manga, replace = replace) | ||||
|         } | ||||
|         val nextManga = migratingMangas.firstOrNull() ?: return null | ||||
|         migratingMangas.remove(nextManga) | ||||
|         return nextManga | ||||
|     } | ||||
|  | ||||
|     private fun startMangaMigration() { | ||||
|         migratingMangas.clear() | ||||
|         migratingMangas.addAll(selectedMangas) | ||||
|         destroyActionModeIfNeeded() | ||||
|         val manga = migratingMangas.firstOrNull() ?: return | ||||
|         val searchController = SearchController(manga) | ||||
|         searchController.totalProgress = migratingMangas.size | ||||
|         searchController.targetController = this | ||||
|         router.pushController(searchController.withFadeTransaction()) | ||||
|         migratingMangas.remove(manga) | ||||
|     } | ||||
|  | ||||
|     override fun onDestroyActionMode(mode: ActionMode?) { | ||||
|         // Clear all the manga selections and notify child views. | ||||
|         selectedMangas.clear() | ||||
|   | ||||
| @@ -10,10 +10,14 @@ import eu.kanade.tachiyomi.data.database.models.MangaCategory | ||||
| import eu.kanade.tachiyomi.data.download.DownloadManager | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.source.LocalSource | ||||
| import eu.kanade.tachiyomi.source.Source | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter | ||||
| import eu.kanade.tachiyomi.ui.migration.MigrationFlags | ||||
| import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource | ||||
| import eu.kanade.tachiyomi.util.lang.combineLatest | ||||
| import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed | ||||
| import java.io.IOException | ||||
| @@ -367,6 +371,84 @@ class LibraryPresenter( | ||||
|         db.setMangaCategories(mc, mangas) | ||||
|     } | ||||
|  | ||||
|     fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) { | ||||
|         val source = sourceManager.get(manga.source) ?: return | ||||
|  | ||||
|         // state = state.copy(isReplacingManga = true) | ||||
|  | ||||
|         Observable.defer { source.fetchChapterList(manga) } | ||||
|             .onErrorReturn { emptyList() } | ||||
|             .doOnNext { migrateMangaInternal(source, it, prevManga, manga, replace) } | ||||
|             .onErrorReturn { emptyList() } | ||||
|             .subscribeOn(Schedulers.io()) | ||||
|             .observeOn(AndroidSchedulers.mainThread()) | ||||
|             // .doOnUnsubscribe { state = state.copy(isReplacingManga = false) } | ||||
|             .subscribe() | ||||
|     } | ||||
|  | ||||
|     private fun migrateMangaInternal( | ||||
|         source: Source, | ||||
|         sourceChapters: List<SChapter>, | ||||
|         prevManga: Manga, | ||||
|         manga: Manga, | ||||
|         replace: Boolean | ||||
|     ) { | ||||
|  | ||||
|         val flags = preferences.migrateFlags().getOrDefault() | ||||
|         val migrateChapters = MigrationFlags.hasChapters(flags) | ||||
|         val migrateCategories = MigrationFlags.hasCategories(flags) | ||||
|         val migrateTracks = MigrationFlags.hasTracks(flags) | ||||
|  | ||||
|         db.inTransaction { | ||||
|             // Update chapters read | ||||
|             if (migrateChapters) { | ||||
|                 try { | ||||
|                     syncChaptersWithSource(db, sourceChapters, manga, source) | ||||
|                 } catch (e: Exception) { | ||||
|                     // Worst case, chapters won't be synced | ||||
|                 } | ||||
|  | ||||
|                 val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking() | ||||
|                 val maxChapterRead = | ||||
|                     prevMangaChapters.filter { it.read }.maxBy { it.chapter_number }?.chapter_number | ||||
|                 if (maxChapterRead != null) { | ||||
|                     val dbChapters = db.getChapters(manga).executeAsBlocking() | ||||
|                     for (chapter in dbChapters) { | ||||
|                         if (chapter.isRecognizedNumber && chapter.chapter_number <= maxChapterRead) { | ||||
|                             chapter.read = true | ||||
|                         } | ||||
|                     } | ||||
|                     db.insertChapters(dbChapters).executeAsBlocking() | ||||
|                 } | ||||
|             } | ||||
|             // Update categories | ||||
|             if (migrateCategories) { | ||||
|                 val categories = db.getCategoriesForManga(prevManga).executeAsBlocking() | ||||
|                 val mangaCategories = categories.map { MangaCategory.create(manga, it) } | ||||
|                 db.setMangaCategories(mangaCategories, listOf(manga)) | ||||
|             } | ||||
|             // Update track | ||||
|             if (migrateTracks) { | ||||
|                 val tracks = db.getTracks(prevManga).executeAsBlocking() | ||||
|                 for (track in tracks) { | ||||
|                     track.id = null | ||||
|                     track.manga_id = manga.id!! | ||||
|                 } | ||||
|                 db.insertTracks(tracks).executeAsBlocking() | ||||
|             } | ||||
|             // Update favorite status | ||||
|             if (replace) { | ||||
|                 prevManga.favorite = false | ||||
|                 db.updateMangaFavorite(prevManga).executeAsBlocking() | ||||
|             } | ||||
|             manga.favorite = true | ||||
|             db.updateMangaFavorite(manga).executeAsBlocking() | ||||
|  | ||||
|             // SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title | ||||
|             db.updateMangaTitle(manga).executeAsBlocking() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Update cover with local file. | ||||
|      * | ||||
|   | ||||
| @@ -1,35 +1,33 @@ | ||||
| package eu.kanade.tachiyomi.ui.migration | ||||
|  | ||||
| import android.app.Dialog | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import com.afollestad.materialdialogs.MaterialDialog | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import eu.davidea.flexibleadapter.items.IFlexible | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.ui.base.controller.DialogController | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.databinding.MigrationControllerBinding | ||||
| import eu.kanade.tachiyomi.ui.base.controller.NucleusController | ||||
| import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag | ||||
| import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction | ||||
| import exh.ui.migration.manga.design.MigrationDesignController | ||||
| import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController | ||||
| import eu.kanade.tachiyomi.util.lang.launchUI | ||||
| import exh.util.RecyclerWindowInsetsListener | ||||
| import exh.util.applyWindowInsetsForController | ||||
| import exh.util.await | ||||
| import kotlinx.android.synthetic.main.migration_controller.migration_recycler | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.GlobalScope | ||||
| import kotlinx.coroutines.launch | ||||
| import kotlinx.coroutines.withContext | ||||
| import rx.schedulers.Schedulers | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
|  | ||||
| class MigrationController : NucleusController<MigrationPresenter>(), | ||||
|         FlexibleAdapter.OnItemClickListener, | ||||
|         SourceAdapter.OnSelectClickListener, | ||||
|         SourceAdapter.OnAutoClickListener { | ||||
| class MigrationController : | ||||
|     NucleusController<MigrationControllerBinding, MigrationPresenter>(), | ||||
|     FlexibleAdapter.OnItemClickListener, | ||||
|     SourceAdapter.OnSelectClickListener, | ||||
|     SourceAdapter.OnAutoClickListener, | ||||
|     MigrationInterface { | ||||
|  | ||||
|     private var adapter: FlexibleAdapter<IFlexible<*>>? = null | ||||
|  | ||||
| @@ -44,15 +42,26 @@ class MigrationController : NucleusController<MigrationPresenter>(), | ||||
|     } | ||||
|  | ||||
|     override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { | ||||
|         return inflater.inflate(R.layout.migration_controller, container, false) | ||||
|         binding = MigrationControllerBinding.inflate(inflater) | ||||
|         return binding.root | ||||
|     } | ||||
|  | ||||
|     fun searchController(manga: Manga): SearchController { | ||||
|         val controller = SearchController(manga) | ||||
|         controller.targetController = this | ||||
|  | ||||
|         return controller | ||||
|     } | ||||
|  | ||||
|     override fun onViewCreated(view: View) { | ||||
|         super.onViewCreated(view) | ||||
|         view.applyWindowInsetsForController() | ||||
|  | ||||
|         adapter = FlexibleAdapter(null, this) | ||||
|         migration_recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context) | ||||
|         migration_recycler.adapter = adapter | ||||
|         binding.migrationRecycler.layoutManager = | ||||
|             androidx.recyclerview.widget.LinearLayoutManager(view.context) | ||||
|         binding.migrationRecycler.adapter = adapter | ||||
|         binding.migrationRecycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) | ||||
|     } | ||||
|  | ||||
|     override fun onDestroyView(view: View) { | ||||
| @@ -75,29 +84,24 @@ class MigrationController : NucleusController<MigrationPresenter>(), | ||||
|  | ||||
|     fun render(state: ViewState) { | ||||
|         if (state.selectedSource == null) { | ||||
|             title = resources?.getString(R.string.label_migration) | ||||
|             title = resources?.getString(R.string.source_migration) | ||||
|             if (adapter !is SourceAdapter) { | ||||
|                 adapter = SourceAdapter(this) | ||||
|                 binding.migrationRecycler.adapter = adapter | ||||
|             } | ||||
|             adapter?.updateDataSet(state.sourcesWithManga) | ||||
|         } else { | ||||
|             // val switching = title == resources?.getString(R.string.source_migration) | ||||
|             title = state.selectedSource.toString() | ||||
|             if (adapter !is MangaAdapter) { | ||||
|                 adapter = MangaAdapter(this) | ||||
|                 binding.migrationRecycler.adapter = adapter | ||||
|             } | ||||
|             adapter?.updateDataSet(state.mangaForSource) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun renderIsReplacingManga(state: ViewState) { | ||||
|         if (state.isReplacingManga) { | ||||
|             if (router.getControllerWithTag(LOADING_DIALOG_TAG) == null) { | ||||
|                 LoadingController().showDialog(router, LOADING_DIALOG_TAG) | ||||
|             } | ||||
|         } else { | ||||
|             router.popControllerWithTag(LOADING_DIALOG_TAG) | ||||
|             adapter?.updateDataSet(state.mangaForSource, true) | ||||
|             /*if (switching) launchUI { | ||||
|                 migration_recycler.alpha = 0f | ||||
|                 migration_recycler.animate().alpha(1f).setStartDelay(100).setDuration(200).start() | ||||
|             }*/ | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -105,10 +109,11 @@ class MigrationController : NucleusController<MigrationPresenter>(), | ||||
|         val item = adapter?.getItem(position) ?: return false | ||||
|  | ||||
|         if (item is MangaItem) { | ||||
|             val controller = SearchController(item.manga) | ||||
|             controller.targetController = this | ||||
|  | ||||
|             router.pushController(controller.withFadeTransaction()) | ||||
|             PreMigrationController.navigateToMigration( | ||||
|                 Injekt.get<PreferencesHelper>().skipPreMigration().get(), | ||||
|                 router, | ||||
|                 listOf(item.manga.id!!) | ||||
|             ) | ||||
|         } else if (item is SourceItem) { | ||||
|             presenter.setSelectedSource(item.source) | ||||
|         } | ||||
| @@ -116,41 +121,34 @@ class MigrationController : NucleusController<MigrationPresenter>(), | ||||
|     } | ||||
|  | ||||
|     override fun onSelectClick(position: Int) { | ||||
|         onItemClick(null, position) | ||||
|         onItemClick(view, position) | ||||
|     } | ||||
|  | ||||
|     override fun onAutoClick(position: Int) { | ||||
|         val item = adapter?.getItem(position) as? SourceItem ?: return | ||||
|  | ||||
|         GlobalScope.launch { | ||||
|             val manga = Injekt.get<DatabaseHelper>().getFavoriteMangas().asRxSingle().await(Schedulers.io()) | ||||
|             val sourceMangas = manga.asSequence().filter { it.source == item.source.id }.map { it.id!! }.toList() | ||||
|         launchUI { | ||||
|             val manga = Injekt.get<DatabaseHelper>().getFavoriteMangas().asRxSingle().await( | ||||
|                 Schedulers.io() | ||||
|             ) | ||||
|             val sourceMangas = | ||||
|                 manga.asSequence().filter { it.source == item.source.id }.map { it.id!! }.toList() | ||||
|             withContext(Dispatchers.Main) { | ||||
|                 router.pushController(MigrationDesignController.create(sourceMangas).withFadeTransaction()) | ||||
|                 PreMigrationController.navigateToMigration( | ||||
|                     Injekt.get<PreferencesHelper>().skipPreMigration().get(), | ||||
|                     router, | ||||
|                     sourceMangas | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun migrateManga(prevManga: Manga, manga: Manga) { | ||||
|         presenter.migrateManga(prevManga, manga, replace = true) | ||||
|     } | ||||
|  | ||||
|     fun copyManga(prevManga: Manga, manga: Manga) { | ||||
|         presenter.migrateManga(prevManga, manga, replace = false) | ||||
|     } | ||||
|  | ||||
|     class LoadingController : DialogController() { | ||||
|  | ||||
|         override fun onCreateDialog(savedViewState: Bundle?): Dialog { | ||||
|             return MaterialDialog.Builder(activity!!) | ||||
|                     .progress(true, 0) | ||||
|                     .content(R.string.migrating) | ||||
|                     .cancelable(false) | ||||
|                     .build() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         const val LOADING_DIALOG_TAG = "LoadingDialog" | ||||
|     override fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? { | ||||
|         presenter.migrateManga(prevManga, manga, replace) | ||||
|         return null | ||||
|     } | ||||
| } | ||||
|  | ||||
| interface MigrationInterface { | ||||
|     fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean): Manga? | ||||
| } | ||||
|   | ||||
| @@ -4,11 +4,16 @@ import android.os.Bundle | ||||
| import com.jakewharton.rxrelay.BehaviorRelay | ||||
| import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.database.models.MangaCategory | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.source.LocalSource | ||||
| import eu.kanade.tachiyomi.source.Source | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import eu.kanade.tachiyomi.source.model.SChapter | ||||
| import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter | ||||
| import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource | ||||
| import eu.kanade.tachiyomi.util.lang.combineLatest | ||||
| import rx.Observable | ||||
| import rx.android.schedulers.AndroidSchedulers | ||||
| import rx.schedulers.Schedulers | ||||
| import uy.kohesive.injekt.Injekt | ||||
| @@ -16,7 +21,8 @@ import uy.kohesive.injekt.api.get | ||||
|  | ||||
| class MigrationPresenter( | ||||
|     private val sourceManager: SourceManager = Injekt.get(), | ||||
|     private val db: DatabaseHelper = Injekt.get() | ||||
|     private val db: DatabaseHelper = Injekt.get(), | ||||
|     private val preferences: PreferencesHelper = Injekt.get() | ||||
| ) : BasePresenter<MigrationController>() { | ||||
|  | ||||
|     var state = ViewState() | ||||
| @@ -45,8 +51,10 @@ class MigrationPresenter( | ||||
|             .doOnNext { state = state.copy(mangaForSource = it) } | ||||
|             .subscribe() | ||||
|  | ||||
|         // Render the view when any field changes | ||||
|         stateRelay.subscribeLatestCache(MigrationController::render) | ||||
|         stateRelay | ||||
|             // Render the view when any field other than isReplacingManga changes | ||||
|             .distinctUntilChanged { t1, t2 -> t1.isReplacingManga != t2.isReplacingManga } | ||||
|             .subscribeLatestCache(MigrationController::render) | ||||
|     } | ||||
|  | ||||
|     fun setSelectedSource(source: Source) { | ||||
| @@ -67,4 +75,78 @@ class MigrationPresenter( | ||||
|     private fun libraryToMigrationItem(library: List<Manga>, sourceId: Long): List<MangaItem> { | ||||
|         return library.filter { it.source == sourceId }.map(::MangaItem) | ||||
|     } | ||||
|  | ||||
|     fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) { | ||||
|         val source = sourceManager.get(manga.source) ?: return | ||||
|  | ||||
|         state = state.copy(isReplacingManga = true) | ||||
|  | ||||
|         Observable.defer { source.fetchChapterList(manga) }.onErrorReturn { emptyList() } | ||||
|             .doOnNext { migrateMangaInternal(source, it, prevManga, manga, replace) } | ||||
|             .onErrorReturn { emptyList() }.subscribeOn(Schedulers.io()) | ||||
|             .observeOn(AndroidSchedulers.mainThread()) | ||||
|             .doOnUnsubscribe { state = state.copy(isReplacingManga = false) }.subscribe() | ||||
|     } | ||||
|  | ||||
|     private fun migrateMangaInternal( | ||||
|         source: Source, | ||||
|         sourceChapters: List<SChapter>, | ||||
|         prevManga: Manga, | ||||
|         manga: Manga, | ||||
|         replace: Boolean | ||||
|     ) { | ||||
|         val flags = preferences.migrateFlags().get() | ||||
|         val migrateChapters = MigrationFlags.hasChapters(flags) | ||||
|         val migrateCategories = MigrationFlags.hasCategories(flags) | ||||
|         val migrateTracks = MigrationFlags.hasTracks(flags) | ||||
|  | ||||
|         db.inTransaction { | ||||
|             // Update chapters read | ||||
|             if (migrateChapters) { | ||||
|                 try { | ||||
|                     syncChaptersWithSource(db, sourceChapters, manga, source) | ||||
|                 } catch (e: Exception) { | ||||
|                     // Worst case, chapters won't be synced | ||||
|                 } | ||||
|  | ||||
|                 val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking() | ||||
|                 val maxChapterRead = | ||||
|                     prevMangaChapters.filter { it.read }.maxBy { it.chapter_number }?.chapter_number | ||||
|                 if (maxChapterRead != null) { | ||||
|                     val dbChapters = db.getChapters(manga).executeAsBlocking() | ||||
|                     for (chapter in dbChapters) { | ||||
|                         if (chapter.isRecognizedNumber && chapter.chapter_number <= maxChapterRead) { | ||||
|                             chapter.read = true | ||||
|                         } | ||||
|                     } | ||||
|                     db.insertChapters(dbChapters).executeAsBlocking() | ||||
|                 } | ||||
|             } | ||||
|             // Update categories | ||||
|             if (migrateCategories) { | ||||
|                 val categories = db.getCategoriesForManga(prevManga).executeAsBlocking() | ||||
|                 val mangaCategories = categories.map { MangaCategory.create(manga, it) } | ||||
|                 db.setMangaCategories(mangaCategories, listOf(manga)) | ||||
|             } | ||||
|             // Update track | ||||
|             if (migrateTracks) { | ||||
|                 val tracks = db.getTracks(prevManga).executeAsBlocking() | ||||
|                 for (track in tracks) { | ||||
|                     track.id = null | ||||
|                     track.manga_id = manga.id!! | ||||
|                 } | ||||
|                 db.insertTracks(tracks).executeAsBlocking() | ||||
|             } | ||||
|             // Update favorite status | ||||
|             if (replace) { | ||||
|                 prevManga.favorite = false | ||||
|                 db.updateMangaFavorite(prevManga).executeAsBlocking() | ||||
|             } | ||||
|             manga.favorite = true | ||||
|             db.updateMangaFavorite(manga).executeAsBlocking() | ||||
|  | ||||
|             // SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title | ||||
|             db.updateMangaTitle(manga).executeAsBlocking() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								app/src/main/java/exh/ui/migration/MigrationStatus.kt → app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationStatus.kt
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										2
									
								
								app/src/main/java/exh/ui/migration/MigrationStatus.kt → app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationStatus.kt
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @@ -1,4 +1,4 @@ | ||||
| package exh.ui.migration | ||||
| package eu.kanade.tachiyomi.ui.migration | ||||
| 
 | ||||
| class MigrationStatus { | ||||
|     companion object { | ||||
| @@ -2,16 +2,26 @@ package eu.kanade.tachiyomi.ui.migration | ||||
|  | ||||
| import android.app.Dialog | ||||
| import android.os.Bundle | ||||
| import android.view.Menu | ||||
| import android.view.MenuInflater | ||||
| import android.view.MenuItem | ||||
| import androidx.appcompat.widget.SearchView | ||||
| import com.afollestad.materialdialogs.MaterialDialog | ||||
| import com.afollestad.materialdialogs.list.listItemsMultiChoice | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import eu.kanade.tachiyomi.ui.base.controller.DialogController | ||||
| import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController | ||||
| import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter | ||||
| import eu.kanade.tachiyomi.util.view.gone | ||||
| import eu.kanade.tachiyomi.util.view.visible | ||||
| import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction | ||||
| import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController | ||||
| import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController | ||||
| import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchPresenter | ||||
| import kotlinx.coroutines.flow.filter | ||||
| import kotlinx.coroutines.flow.launchIn | ||||
| import kotlinx.coroutines.flow.onEach | ||||
| import reactivecircus.flowbinding.appcompat.QueryTextEvent | ||||
| import reactivecircus.flowbinding.appcompat.queryTextEvents | ||||
| import uy.kohesive.injekt.injectLazy | ||||
|  | ||||
| class SearchController( | ||||
| @@ -19,6 +29,23 @@ class SearchController( | ||||
| ) : GlobalSearchController(manga?.title) { | ||||
|  | ||||
|     private var newManga: Manga? = null | ||||
|     private var progress = 1 | ||||
|     var totalProgress = 0 | ||||
|  | ||||
|     /** | ||||
|      * Called when controller is initialized. | ||||
|      */ | ||||
|     init { | ||||
|         setHasOptionsMenu(true) | ||||
|     } | ||||
|  | ||||
|     override fun getTitle(): String? { | ||||
|         if (totalProgress > 1) { | ||||
|             return "($progress/$totalProgress) ${super.getTitle()}" | ||||
|         } else { | ||||
|             return super.getTitle() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun createPresenter(): GlobalSearchPresenter { | ||||
|         return SearchPresenter(initialQuery, manga!!) | ||||
| @@ -36,21 +63,62 @@ class SearchController( | ||||
|         newManga = savedInstanceState.getSerializable(::newManga.name) as? Manga | ||||
|     } | ||||
|  | ||||
|     /*override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { | ||||
|         if (totalProgress > 1) { | ||||
|             val menuItem = menu.add(Menu.NONE, 1, Menu.NONE, R.string.action_skip_manga) | ||||
|             menuItem.icon = VectorDrawableCompat.create(resources!!, R.drawable | ||||
|                 .baseline_skip_next_white_24, null) | ||||
|             menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) | ||||
|         } | ||||
|     } | ||||
|     override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||||
|         when (item.itemId) { | ||||
|             1 -> { | ||||
|                 newManga = manga | ||||
|                 migrateManga() | ||||
|             } | ||||
|         } | ||||
|         return true | ||||
|     }*/ | ||||
|  | ||||
|     fun migrateManga() { | ||||
|         val target = targetController as? MigrationInterface ?: return | ||||
|         val manga = manga ?: return | ||||
|         val newManga = newManga ?: return | ||||
|  | ||||
|         (presenter as? SearchPresenter)?.migrateManga(manga, newManga, true) | ||||
|         val nextManga = target.migrateManga(manga, newManga, true) | ||||
|         replaceWithNewSearchController(nextManga) | ||||
|     } | ||||
|  | ||||
|     fun copyManga() { | ||||
|         val target = targetController as? MigrationInterface ?: return | ||||
|         val manga = manga ?: return | ||||
|         val newManga = newManga ?: return | ||||
|  | ||||
|         (presenter as? SearchPresenter)?.migrateManga(manga, newManga, false) | ||||
|         val nextManga = target.migrateManga(manga, newManga, false) | ||||
|         replaceWithNewSearchController(nextManga) | ||||
|     } | ||||
|  | ||||
|     private fun replaceWithNewSearchController(manga: Manga?) { | ||||
|         if (manga != null) { | ||||
|             // router.popCurrentController() | ||||
|             val searchController = SearchController(manga) | ||||
|             searchController.targetController = targetController | ||||
|             searchController.progress = progress + 1 | ||||
|             searchController.totalProgress = totalProgress | ||||
|             router.replaceTopController(searchController.withFadeTransaction()) | ||||
|         } else router.popController(this) | ||||
|     } | ||||
|  | ||||
|     override fun onMangaClick(manga: Manga) { | ||||
|         if (targetController is MigrationListController) { | ||||
|             val migrationListController = targetController as? MigrationListController | ||||
|             val sourceManager: SourceManager by injectLazy() | ||||
|             val source = sourceManager.get(manga.source) ?: return | ||||
|             migrationListController?.useMangaForMigration(manga, source) | ||||
|             router.popCurrentController() | ||||
|             return | ||||
|         } | ||||
|         newManga = manga | ||||
|         val dialog = MigrationDialog() | ||||
|         dialog.targetController = this | ||||
| @@ -62,15 +130,6 @@ class SearchController( | ||||
|         super.onMangaClick(manga) | ||||
|     } | ||||
|  | ||||
|     fun renderIsReplacingManga(isReplacingManga: Boolean) { | ||||
|         if (isReplacingManga) { | ||||
|             binding.progress.visible() | ||||
|         } else { | ||||
|             binding.progress.gone() | ||||
|             router.popController(this) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     class MigrationDialog : DialogController() { | ||||
|  | ||||
|         private val preferences: PreferencesHelper by injectLazy() | ||||
| @@ -81,7 +140,7 @@ class SearchController( | ||||
|             val preselected = MigrationFlags.getEnabledFlagsPositions(prefValue) | ||||
|  | ||||
|             return MaterialDialog(activity!!) | ||||
|                 .message(R.string.migration_dialog_what_to_include) | ||||
|                 .message(R.string.data_to_include_in_migration) | ||||
|                 .listItemsMultiChoice( | ||||
|                     items = MigrationFlags.titles.map { resources?.getString(it) as CharSequence }, | ||||
|                     initialSelection = preselected.toIntArray() | ||||
| @@ -99,4 +158,40 @@ class SearchController( | ||||
|                 .neutralButton(android.R.string.cancel) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Adds items to the options menu. | ||||
|      * | ||||
|      * @param menu menu containing options. | ||||
|      * @param inflater used to load the menu xml. | ||||
|      */ | ||||
|     override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { | ||||
|         // Inflate menu. | ||||
|         inflater.inflate(R.menu.source_browse, menu) | ||||
|  | ||||
|         // Initialize search menu | ||||
|         val searchItem = menu.findItem(R.id.action_search) | ||||
|         val searchView = searchItem.actionView as SearchView | ||||
|  | ||||
|         searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { | ||||
|             override fun onMenuItemActionExpand(item: MenuItem?): Boolean { | ||||
|                 searchView.onActionViewExpanded() // Required to show the query in the view | ||||
|                 searchView.setQuery(presenter.query, false) | ||||
|                 return true | ||||
|             } | ||||
|  | ||||
|             override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { | ||||
|                 return true | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|         searchView.queryTextEvents() | ||||
|             .filter { it is QueryTextEvent.QuerySubmitted } | ||||
|             .onEach { | ||||
|                 presenter.search(it.queryText.toString()) | ||||
|                 searchItem.collapseActionView() | ||||
|                 setTitle() // Update toolbar title | ||||
|             } | ||||
|             .launchIn(scope) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -21,20 +21,6 @@ class SourceAdapter(val controller: MigrationController) : | ||||
|         setDisplayHeadersAtStartUp(true) | ||||
|     } | ||||
|  | ||||
|     // EXH --> | ||||
|     /** | ||||
|      * Listener for auto item clicks. | ||||
|      */ | ||||
|     val autoClickListener: OnAutoClickListener? = controller | ||||
|  | ||||
|     /** | ||||
|      * Listener which should be called when user clicks select. | ||||
|      */ | ||||
|     interface OnAutoClickListener { | ||||
|         fun onAutoClick(position: Int) | ||||
|     } | ||||
|     // EXH <-- | ||||
|  | ||||
|     /** | ||||
|      * Listener for browse item clicks. | ||||
|      */ | ||||
| @@ -47,6 +33,18 @@ class SourceAdapter(val controller: MigrationController) : | ||||
|         fun onSelectClick(position: Int) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Listener for auto item clicks. | ||||
|      */ | ||||
|     val autoClickListener: OnAutoClickListener? = controller | ||||
|  | ||||
|     /** | ||||
|      * Listener which should be called when user clicks select. | ||||
|      */ | ||||
|     interface OnAutoClickListener { | ||||
|         fun onAutoClick(position: Int) | ||||
|     } | ||||
|  | ||||
|     override fun updateDataSet(items: MutableList<IFlexible<*>>?) { | ||||
|         if (this.items !== items) { | ||||
|             this.items = items | ||||
|   | ||||
| @@ -26,13 +26,13 @@ class SourceHolder(view: View, override val adapter: SourceAdapter) : | ||||
|  | ||||
|     init { | ||||
|         source_latest.text = "Auto" | ||||
|         source_latest.setOnClickListener { | ||||
|             adapter.autoClickListener?.onAutoClick(adapterPosition) | ||||
|         } | ||||
|         source_browse.setText(R.string.select) | ||||
|         source_browse.setOnClickListener { | ||||
|             adapter.selectClickListener?.onSelectClick(bindingAdapterPosition) | ||||
|         } | ||||
|         source_latest.setOnClickListener { | ||||
|             adapter.autoClickListener?.onAutoClick(adapterPosition) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun bind(item: SourceItem) { | ||||
|   | ||||
| @@ -5,5 +5,6 @@ import eu.kanade.tachiyomi.source.Source | ||||
| data class ViewState( | ||||
|     val selectedSource: Source? = null, | ||||
|     val mangaForSource: List<MangaItem> = emptyList(), | ||||
|     val sourcesWithManga: List<SourceItem> = emptyList() | ||||
|     val sourcesWithManga: List<SourceItem> = emptyList(), | ||||
|     val isReplacingManga: Boolean = false | ||||
| ) | ||||
|   | ||||
| @@ -1,43 +1,44 @@ | ||||
| package exh.ui.migration.manga.design | ||||
| package eu.kanade.tachiyomi.ui.migration.manga.design | ||||
| 
 | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import androidx.recyclerview.widget.LinearLayoutManager | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.data.preference.getOrDefault | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.ui.base.controller.BaseController | ||||
| import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction | ||||
| import eu.kanade.tachiyomi.ui.migration.MigrationFlags | ||||
| import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureConfig | ||||
| import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcedureController | ||||
| import eu.kanade.tachiyomi.util.view.gone | ||||
| import eu.kanade.tachiyomi.util.view.visible | ||||
| import exh.ui.base.BaseExhController | ||||
| import exh.ui.migration.manga.process.MigrationProcedureConfig | ||||
| import exh.ui.migration.manga.process.MigrationProcedureController | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.begin_migration_btn | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.copy_manga | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.copy_manga_desc | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.extra_search_param | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.extra_search_param_desc | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.extra_search_param_text | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.fuzzy_search | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.mig_categories | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.mig_chapters | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.migration_mode | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.options_group | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.prioritize_chapter_count | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.recycler | ||||
| import kotlinx.android.synthetic.main.eh_migration_design.use_smart_search | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.begin_migration_btn | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.copy_manga | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.copy_manga_desc | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.extra_search_param | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.extra_search_param_desc | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.extra_search_param_text | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.fuzzy_search | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.mig_categories | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.mig_chapters | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.migration_mode | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.options_group | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.prioritize_chapter_count | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.recycler | ||||
| import kotlinx.android.synthetic.main.migration_design_controller.use_smart_search | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| 
 | ||||
| // TODO Select all in library | ||||
| class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bundle), FlexibleAdapter.OnItemClickListener { | ||||
| class MigrationDesignController(bundle: Bundle? = null) : BaseController(bundle), FlexibleAdapter | ||||
| .OnItemClickListener { | ||||
|     private val sourceManager: SourceManager by injectLazy() | ||||
|     private val prefs: PreferencesHelper by injectLazy() | ||||
| 
 | ||||
|     override val layoutId: Int = R.layout.eh_migration_design | ||||
| 
 | ||||
|     private var adapter: MigrationSourceAdapter? = null | ||||
| 
 | ||||
|     private val config: LongArray = args.getLongArray(MANGA_IDS_EXTRA) ?: LongArray(0) | ||||
| @@ -46,6 +47,10 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund | ||||
| 
 | ||||
|     override fun getTitle() = "Select target sources" | ||||
| 
 | ||||
|     override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { | ||||
|         return inflater.inflate(R.layout.migration_design_controller, container, false) | ||||
|     } | ||||
| 
 | ||||
|     override fun onViewCreated(view: View) { | ||||
|         super.onViewCreated(view) | ||||
| 
 | ||||
| @@ -54,7 +59,7 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund | ||||
|                 this | ||||
|         ) | ||||
|         adapter = ourAdapter | ||||
|         recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context) | ||||
|         recycler.layoutManager = LinearLayoutManager(view.context) | ||||
|         recycler.setHasFixedSize(true) | ||||
|         recycler.adapter = ourAdapter | ||||
|         ourAdapter.itemTouchHelperCallback = null // Reset adapter touch adapter to fix drag after rotation | ||||
| @@ -100,7 +105,8 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseExhController(bund | ||||
|             if (mig_categories.isChecked) flags = flags or MigrationFlags.CATEGORIES | ||||
|             if (mig_categories.isChecked) flags = flags or MigrationFlags.TRACK | ||||
| 
 | ||||
|             router.replaceTopController(MigrationProcedureController.create( | ||||
|             router.replaceTopController( | ||||
|                 MigrationProcedureController.create( | ||||
|                     MigrationProcedureConfig( | ||||
|                             config.toList(), | ||||
|                             ourAdapter.items.filter { | ||||
| @@ -1,8 +1,9 @@ | ||||
| package exh.ui.migration.manga.design | ||||
| package eu.kanade.tachiyomi.ui.migration.manga.design | ||||
| 
 | ||||
| import android.os.Bundle | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import exh.debug.DebugFunctions.sourceManager | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| 
 | ||||
| class MigrationSourceAdapter( | ||||
|     val items: List<MigrationSourceItem>, | ||||
| @@ -21,7 +22,10 @@ class MigrationSourceAdapter( | ||||
|     } | ||||
| 
 | ||||
|     override fun onRestoreInstanceState(savedInstanceState: Bundle) { | ||||
|         savedInstanceState.getParcelableArrayList<MigrationSourceItem.ParcelableSI>(SELECTED_SOURCES_KEY)?.let { | ||||
|         val sourceManager: SourceManager by injectLazy() | ||||
|         savedInstanceState.getParcelableArrayList<MigrationSourceItem.ParcelableSI>( | ||||
|             SELECTED_SOURCES_KEY | ||||
|         )?.let { | ||||
|             updateDataSet(it.map { MigrationSourceItem.fromParcelable(sourceManager, it) }) | ||||
|         } | ||||
| 
 | ||||
| @@ -0,0 +1,58 @@ | ||||
| package eu.kanade.tachiyomi.ui.migration.manga.design | ||||
|  | ||||
| import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG | ||||
| import android.view.View | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.source.icon | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder | ||||
| import kotlinx.android.synthetic.main.migration_source_item.image | ||||
| import kotlinx.android.synthetic.main.migration_source_item.reorder | ||||
| import kotlinx.android.synthetic.main.migration_source_item.title | ||||
| import uy.kohesive.injekt.injectLazy | ||||
|  | ||||
| class MigrationSourceHolder(view: View, val adapter: MigrationSourceAdapter) : | ||||
|     BaseFlexibleViewHolder(view, adapter) { | ||||
|     init { | ||||
|         setDragHandleView(reorder) | ||||
|     } | ||||
|  | ||||
|     fun bind(source: HttpSource, sourceEnabled: Boolean) { | ||||
|         val preferences by injectLazy<PreferencesHelper>() | ||||
|         val isMultiLanguage = preferences.enabledLanguages().get().size > 1 | ||||
|         // Set capitalized title. | ||||
|         val sourceName = if (isMultiLanguage) source.toString() else source.name.capitalize() | ||||
|         title.text = sourceName | ||||
|         // Update circle letter image. | ||||
|         itemView.post { | ||||
|             val icon = source.icon() | ||||
|             if (icon != null) { | ||||
|                 image.setImageDrawable(icon) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (sourceEnabled) { | ||||
|             title.alpha = 1.0f | ||||
|             image.alpha = 1.0f | ||||
|             title.paintFlags = title.paintFlags and STRIKE_THRU_TEXT_FLAG.inv() | ||||
|         } else { | ||||
|             title.alpha = DISABLED_ALPHA | ||||
|             image.alpha = DISABLED_ALPHA | ||||
|             title.paintFlags = title.paintFlags or STRIKE_THRU_TEXT_FLAG | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called when an item is released. | ||||
|      * | ||||
|      * @param position The position of the released item. | ||||
|      */ | ||||
|     override fun onItemReleased(position: Int) { | ||||
|         super.onItemReleased(position) | ||||
|         adapter.updateItems() | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         private const val DISABLED_ALPHA = 0.3f | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +1,8 @@ | ||||
| package exh.ui.migration.manga.design | ||||
| package eu.kanade.tachiyomi.ui.migration.manga.design | ||||
| 
 | ||||
| import android.os.Parcelable | ||||
| import android.view.View | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import eu.davidea.flexibleadapter.items.AbstractFlexibleItem | ||||
| import eu.davidea.flexibleadapter.items.IFlexible | ||||
| @@ -11,9 +12,9 @@ import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import kotlinx.android.parcel.Parcelize | ||||
| 
 | ||||
| class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) : AbstractFlexibleItem<MigrationSourceHolder>() { | ||||
|     override fun getLayoutRes() = R.layout.eh_source_item | ||||
|     override fun getLayoutRes() = R.layout.migration_source_item | ||||
| 
 | ||||
|     override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>): MigrationSourceHolder { | ||||
|     override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MigrationSourceHolder { | ||||
|         return MigrationSourceHolder(view, adapter as MigrationSourceAdapter) | ||||
|     } | ||||
| 
 | ||||
| @@ -26,7 +27,7 @@ class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) : | ||||
|      * @param payloads List of partial changes. | ||||
|      */ | ||||
|     override fun bindViewHolder( | ||||
|         adapter: FlexibleAdapter<IFlexible<androidx.recyclerview.widget.RecyclerView.ViewHolder>>, | ||||
|         adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, | ||||
|         holder: MigrationSourceHolder, | ||||
|         position: Int, | ||||
|         payloads: List<Any?>? | ||||
| @@ -1,10 +1,11 @@ | ||||
| package exh.ui.migration.manga.process | ||||
| package eu.kanade.tachiyomi.ui.migration.manga.process | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.util.AttributeSet | ||||
| import android.view.MotionEvent | ||||
| import androidx.viewpager.widget.ViewPager | ||||
| 
 | ||||
| class DeactivatableViewPager : androidx.viewpager.widget.ViewPager { | ||||
| class DeactivatableViewPager : ViewPager { | ||||
|     constructor(context: Context) : super(context) | ||||
|     constructor(context: Context, attrs: AttributeSet) : super(context, attrs) | ||||
| 
 | ||||
| @@ -1,11 +1,10 @@ | ||||
| package exh.ui.migration.manga.process | ||||
| package eu.kanade.tachiyomi.ui.migration.manga.process | ||||
| 
 | ||||
| import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.source.Source | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import exh.util.DeferredField | ||||
| import exh.util.await | ||||
| import eu.kanade.tachiyomi.util.DeferredField | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.SupervisorJob | ||||
| @@ -27,7 +26,7 @@ class MigratingManga( | ||||
|     @Volatile | ||||
|     private var manga: Manga? = null | ||||
|     suspend fun manga(): Manga? { | ||||
|         if (manga == null) manga = db.getManga(mangaId).await() | ||||
|         if (manga == null) manga = db.getManga(mangaId).executeAsBlocking() | ||||
|         return manga | ||||
|     } | ||||
| 
 | ||||
| @@ -1,9 +1,9 @@ | ||||
| package exh.ui.migration.manga.process | ||||
| package eu.kanade.tachiyomi.ui.migration.manga.process | ||||
| 
 | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import androidx.viewpager.widget.PagerAdapter | ||||
| import com.bumptech.glide.load.engine.DiskCacheStrategy | ||||
| import com.elvishew.xlog.XLog | ||||
| import com.google.gson.Gson | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| @@ -21,29 +21,28 @@ import eu.kanade.tachiyomi.util.view.gone | ||||
| import eu.kanade.tachiyomi.util.view.inflate | ||||
| import eu.kanade.tachiyomi.util.view.visible | ||||
| import exh.MERGED_SOURCE_ID | ||||
| import exh.util.await | ||||
| import java.text.DateFormat | ||||
| import java.text.DecimalFormat | ||||
| import java.util.Date | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.loading_group | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.manga_artist | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.manga_author | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.manga_chapters | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.manga_cover | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.manga_full_title | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.manga_last_chapter | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.manga_last_update | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.manga_source | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.manga_source_label | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.manga_status | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.search_progress | ||||
| import kotlinx.android.synthetic.main.eh_manga_card.view.search_status | ||||
| import kotlinx.android.synthetic.main.eh_migration_process_item.view.accept_migration | ||||
| import kotlinx.android.synthetic.main.eh_migration_process_item.view.eh_manga_card_from | ||||
| import kotlinx.android.synthetic.main.eh_migration_process_item.view.eh_manga_card_to | ||||
| import kotlinx.android.synthetic.main.eh_migration_process_item.view.migrating_frame | ||||
| import kotlinx.android.synthetic.main.eh_migration_process_item.view.skip_migration | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.loading_group | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.manga_artist | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.manga_author | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.manga_chapters | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.manga_cover | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.manga_full_title | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.manga_last_chapter | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.manga_last_update | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.manga_source | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.manga_source_label | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.manga_status | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.search_progress | ||||
| import kotlinx.android.synthetic.main.migration_manga_card.view.search_status | ||||
| import kotlinx.android.synthetic.main.migration_process_item.view.accept_migration | ||||
| import kotlinx.android.synthetic.main.migration_process_item.view.migrating_frame | ||||
| import kotlinx.android.synthetic.main.migration_process_item.view.migration_manga_card_from | ||||
| import kotlinx.android.synthetic.main.migration_process_item.view.migration_manga_card_to | ||||
| import kotlinx.android.synthetic.main.migration_process_item.view.skip_migration | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.Job | ||||
| @@ -58,13 +57,11 @@ class MigrationProcedureAdapter( | ||||
|     val controller: MigrationProcedureController, | ||||
|     val migratingManga: List<MigratingManga>, | ||||
|     override val coroutineContext: CoroutineContext | ||||
| ) : androidx.viewpager.widget.PagerAdapter(), CoroutineScope { | ||||
| ) : PagerAdapter(), CoroutineScope { | ||||
|     private val db: DatabaseHelper by injectLazy() | ||||
|     private val gson: Gson by injectLazy() | ||||
|     private val sourceManager: SourceManager by injectLazy() | ||||
| 
 | ||||
|     private val logger = XLog.tag(this::class.simpleName) | ||||
| 
 | ||||
|     override fun isViewFromObject(p0: View, p1: Any): Boolean { | ||||
|         return p0 == p1 | ||||
|     } | ||||
| @@ -73,7 +70,7 @@ class MigrationProcedureAdapter( | ||||
| 
 | ||||
|     override fun instantiateItem(container: ViewGroup, position: Int): Any { | ||||
|         val item = migratingManga[position] | ||||
|         val view = container.inflate(R.layout.eh_migration_process_item) | ||||
|         val view = container.inflate(R.layout.migration_process_item) | ||||
|         container.addView(view) | ||||
| 
 | ||||
|         view.skip_migration.setOnClickListener { | ||||
| @@ -93,7 +90,6 @@ class MigrationProcedureAdapter( | ||||
|                     } | ||||
|                     controller.nextMigration() | ||||
|                 } catch (e: Exception) { | ||||
|                     logger.e("Migration failure!", e) | ||||
|                     controller.migrationFailure() | ||||
|                 } | ||||
|                 view.migrating_frame.gone() | ||||
| @@ -108,13 +104,13 @@ class MigrationProcedureAdapter( | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|         val toMangaObj = db.getManga(manga.searchResult.get() ?: return).await() ?: return | ||||
|         val toMangaObj = db.getManga(manga.searchResult.get() ?: return).executeAsBlocking() ?: return | ||||
| 
 | ||||
|         withContext(Dispatchers.IO) { | ||||
|             migrateMangaInternal( | ||||
|                     manga.manga() ?: return@withContext, | ||||
|                     toMangaObj, | ||||
|                     !controller.config.copy | ||||
|                     !(controller.config?.copy ?: false) | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| @@ -124,6 +120,7 @@ class MigrationProcedureAdapter( | ||||
|         manga: Manga, | ||||
|         replace: Boolean | ||||
|     ) { | ||||
|         val config = controller.config ?: return | ||||
|         db.inTransaction { | ||||
|             // Update chapters read | ||||
|             if (MigrationFlags.hasChapters(controller.config.migrationFlags)) { | ||||
| @@ -174,9 +171,9 @@ class MigrationProcedureAdapter( | ||||
|             val source = migratingManga.mangaSource() | ||||
|             if (manga != null) { | ||||
|                 withContext(Dispatchers.Main) { | ||||
|                     eh_manga_card_from.loading_group.gone() | ||||
|                     eh_manga_card_from.attachManga(tag, manga, source) | ||||
|                     eh_manga_card_from.setOnClickListener { | ||||
|                     migration_manga_card_from.loading_group.gone() | ||||
|                     migration_manga_card_from.attachManga(tag, manga, source) | ||||
|                     migration_manga_card_from.setOnClickListener { | ||||
|                         controller.router.pushController(MangaController(manga, true).withFadeTransaction()) | ||||
|                     } | ||||
|                 } | ||||
| @@ -184,7 +181,7 @@ class MigrationProcedureAdapter( | ||||
|                 tag.launch { | ||||
|                     migratingManga.progress.asFlow().collect { (max, progress) -> | ||||
|                         withContext(Dispatchers.Main) { | ||||
|                             eh_manga_card_to.search_progress.let { progressBar -> | ||||
|                             migration_manga_card_to.search_progress.let { progressBar -> | ||||
|                                 progressBar.max = max | ||||
|                                 progressBar.progress = progress | ||||
|                             } | ||||
| @@ -193,23 +190,23 @@ class MigrationProcedureAdapter( | ||||
|                 } | ||||
| 
 | ||||
|                 val searchResult = migratingManga.searchResult.get()?.let { | ||||
|                     db.getManga(it).await() | ||||
|                     db.getManga(it).executeAsBlocking() | ||||
|                 } | ||||
|                 val resultSource = searchResult?.source?.let { | ||||
|                     sourceManager.get(it) | ||||
|                 } | ||||
|                 withContext(Dispatchers.Main) { | ||||
|                     if (searchResult != null && resultSource != null) { | ||||
|                         eh_manga_card_to.loading_group.gone() | ||||
|                         eh_manga_card_to.attachManga(tag, searchResult, resultSource) | ||||
|                         eh_manga_card_to.setOnClickListener { | ||||
|                         migration_manga_card_to.loading_group.gone() | ||||
|                         migration_manga_card_to.attachManga(tag, searchResult, resultSource) | ||||
|                         migration_manga_card_to.setOnClickListener { | ||||
|                             controller.router.pushController(MangaController(searchResult, true).withFadeTransaction()) | ||||
|                         } | ||||
|                         accept_migration.isEnabled = true | ||||
|                         accept_migration.alpha = 1.0f | ||||
|                     } else { | ||||
|                         eh_manga_card_to.search_progress.gone() | ||||
|                         eh_manga_card_to.search_status.text = "Found no manga" | ||||
|                         migration_manga_card_to.search_progress.gone() | ||||
|                         migration_manga_card_to.search_status.text = "Found no manga" | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -264,7 +261,7 @@ class MigrationProcedureAdapter( | ||||
|             else -> R.string.unknown | ||||
|         }) | ||||
| 
 | ||||
|         val mangaChapters = db.getChapters(manga).await() | ||||
|         val mangaChapters = db.getChapters(manga).executeAsBlocking() | ||||
|         manga_chapters.text = mangaChapters.size.toString() | ||||
|         val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f | ||||
|         val lastUpdate = Date(mangaChapters.maxBy { it.date_upload }?.date_upload ?: 0) | ||||
| @@ -1,4 +1,4 @@ | ||||
| package exh.ui.migration.manga.process | ||||
| package eu.kanade.tachiyomi.ui.migration.manga.process | ||||
| 
 | ||||
| import android.os.Parcelable | ||||
| import kotlinx.android.parcel.Parcelize | ||||
| @@ -1,22 +1,23 @@ | ||||
| package exh.ui.migration.manga.process | ||||
| package eu.kanade.tachiyomi.ui.migration.manga.process | ||||
| 
 | ||||
| import android.content.pm.ActivityInfo | ||||
| import android.os.Build | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import com.afollestad.materialdialogs.MaterialDialog | ||||
| import com.elvishew.xlog.XLog | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| import eu.kanade.tachiyomi.smartsearch.SmartSearchEngine | ||||
| import eu.kanade.tachiyomi.source.CatalogueSource | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import eu.kanade.tachiyomi.ui.base.controller.BaseController | ||||
| import eu.kanade.tachiyomi.util.await | ||||
| import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource | ||||
| import eu.kanade.tachiyomi.util.system.toast | ||||
| import exh.smartsearch.SmartSearchEngine | ||||
| import exh.ui.base.BaseExhController | ||||
| import exh.util.await | ||||
| import java.util.concurrent.atomic.AtomicInteger | ||||
| import kotlinx.android.synthetic.main.eh_migration_process.pager | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlinx.android.synthetic.main.migration_process.pager | ||||
| import kotlinx.coroutines.CancellationException | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| @@ -32,25 +33,28 @@ import rx.schedulers.Schedulers | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| 
 | ||||
| // TODO Will probably implode if activity is fully destroyed | ||||
| class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(bundle), CoroutineScope { | ||||
|     override val layoutId = R.layout.eh_migration_process | ||||
| class MigrationProcedureController(bundle: Bundle? = null) : BaseController(bundle), CoroutineScope { | ||||
| 
 | ||||
|     private var titleText = "Migrate manga" | ||||
| 
 | ||||
|     private var adapter: MigrationProcedureAdapter? = null | ||||
| 
 | ||||
|     val config: MigrationProcedureConfig = args.getParcelable(CONFIG_EXTRA) | ||||
|     override val coroutineContext: CoroutineContext = Job() + Dispatchers.Default | ||||
| 
 | ||||
|     val config: MigrationProcedureConfig? = args.getParcelable(CONFIG_EXTRA) | ||||
| 
 | ||||
|     private val db: DatabaseHelper by injectLazy() | ||||
|     private val sourceManager: SourceManager by injectLazy() | ||||
| 
 | ||||
|     private val smartSearchEngine = SmartSearchEngine(coroutineContext, config.extraSearchParams) | ||||
| 
 | ||||
|     private val logger = XLog.tag("MigrationProcedureController") | ||||
|     private val smartSearchEngine = SmartSearchEngine(coroutineContext, config?.extraSearchParams) | ||||
| 
 | ||||
|     private var migrationsJob: Job? = null | ||||
|     private var migratingManga: List<MigratingManga>? = null | ||||
| 
 | ||||
|     override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { | ||||
|         return inflater.inflate(R.layout.migration_process, container, false) | ||||
|     } | ||||
| 
 | ||||
|     override fun getTitle(): String { | ||||
|         return titleText | ||||
|     } | ||||
| @@ -58,12 +62,8 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b | ||||
|     override fun onViewCreated(view: View) { | ||||
|         super.onViewCreated(view) | ||||
|         setTitle() | ||||
| 
 | ||||
|         activity?.requestedOrientation = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { | ||||
|             ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT | ||||
|         } else { | ||||
|             ActivityInfo.SCREEN_ORIENTATION_PORTRAIT | ||||
|         } | ||||
|         val config = this.config ?: return | ||||
|         activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT | ||||
| 
 | ||||
|         val newMigratingManga = migratingManga ?: run { | ||||
|             val new = config.mangaIds.map { | ||||
| @@ -121,7 +121,8 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b | ||||
|     } | ||||
| 
 | ||||
|     suspend fun runMigrations(mangas: List<MigratingManga>) { | ||||
|         val sources = config.targetSourceIds.mapNotNull { sourceManager.get(it) as? CatalogueSource } | ||||
|         val sources = config?.targetSourceIds?.mapNotNull { sourceManager.get(it) as? | ||||
|             CatalogueSource } ?: return | ||||
| 
 | ||||
|         for (manga in mangas) { | ||||
|             if (!manga.searchResult.initialized && manga.migrationJob.isActive) { | ||||
| @@ -147,7 +148,8 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b | ||||
|                                 async { | ||||
|                                     sourceSemaphore.withPermit { | ||||
|                                         try { | ||||
|                                             val searchResult = if (config.enableLenientSearch) { | ||||
|                                             val searchResult = if (config?.enableLenientSearch == | ||||
|                                                 true) { | ||||
|                                                 smartSearchEngine.smartSearch(source, mangaObj.title) | ||||
|                                             } else { | ||||
|                                                 smartSearchEngine.normalSearch(source, mangaObj.title) | ||||
| @@ -168,7 +170,6 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b | ||||
|                                             // Ignore cancellations | ||||
|                                             throw e | ||||
|                                         } catch (e: Exception) { | ||||
|                                             logger.e("Failed to search in source: ${source.id}!", e) | ||||
|                                             null | ||||
|                                         } | ||||
|                                     } | ||||
| @@ -195,7 +196,6 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b | ||||
|                                     // Ignore cancellations | ||||
|                                     throw e | ||||
|                                 } catch (e: Exception) { | ||||
|                                     logger.e("Failed to search in source: ${source.id}!", e) | ||||
|                                     null | ||||
|                                 } | ||||
| 
 | ||||
| @@ -220,12 +220,11 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseExhController(b | ||||
|                                 .await() | ||||
|                         result.copyFrom(newManga) | ||||
| 
 | ||||
|                         db.insertManga(result).await() | ||||
|                         db.insertManga(result).executeAsBlocking() | ||||
|                     } catch (e: CancellationException) { | ||||
|                         // Ignore cancellations | ||||
|                         throw e | ||||
|                     } catch (e: Exception) { | ||||
|                         logger.e("Could not load search manga details", e) | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
| @@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Target | ||||
| import eu.kanade.tachiyomi.network.NetworkHelper | ||||
| import eu.kanade.tachiyomi.ui.base.controller.DialogController | ||||
| import eu.kanade.tachiyomi.ui.library.LibraryController | ||||
| import eu.kanade.tachiyomi.ui.migration.MetadataFetchDialog | ||||
| import eu.kanade.tachiyomi.util.preference.defaultValue | ||||
| import eu.kanade.tachiyomi.util.preference.onClick | ||||
| import eu.kanade.tachiyomi.util.preference.preference | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| package exh.ui.smartsearch | ||||
| package eu.kanade.tachiyomi.ui.smartsearch | ||||
| 
 | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| @@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController | ||||
| import eu.kanade.tachiyomi.ui.source.SourceController | ||||
| import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController | ||||
| import eu.kanade.tachiyomi.util.system.toast | ||||
| import kotlinx.android.synthetic.main.eh_smart_search.appbar | ||||
| import kotlinx.android.synthetic.main.smart_search.appbar | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.Job | ||||
| @@ -29,10 +29,12 @@ class SmartSearchController(bundle: Bundle? = null) : NucleusController<SmartSea | ||||
|     private val sourceManager: SourceManager by injectLazy() | ||||
| 
 | ||||
|     private val source = sourceManager.get(bundle?.getLong(ARG_SOURCE_ID, -1) ?: -1) as? CatalogueSource | ||||
|     private val smartSearchConfig: SourceController.SmartSearchConfig? = bundle?.getParcelable(ARG_SMART_SEARCH_CONFIG) | ||||
|     private val smartSearchConfig: SourceController.SmartSearchConfig? = bundle?.getParcelable( | ||||
|         ARG_SMART_SEARCH_CONFIG | ||||
|     ) | ||||
| 
 | ||||
|     override fun inflateView(inflater: LayoutInflater, container: ViewGroup) = | ||||
|             inflater.inflate(R.layout.eh_smart_search, container, false)!! | ||||
|             inflater.inflate(R.layout.smart_search, container, false)!! | ||||
| 
 | ||||
|     override fun getTitle() = source?.name ?: "" | ||||
| 
 | ||||
| @@ -1,13 +1,12 @@ | ||||
| package exh.ui.smartsearch | ||||
| package eu.kanade.tachiyomi.ui.smartsearch | ||||
| 
 | ||||
| import android.os.Bundle | ||||
| import com.elvishew.xlog.XLog | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.smartsearch.SmartSearchEngine | ||||
| import eu.kanade.tachiyomi.source.CatalogueSource | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter | ||||
| import eu.kanade.tachiyomi.ui.source.SourceController | ||||
| import exh.smartsearch.SmartSearchEngine | ||||
| import kotlinx.coroutines.CancellationException | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| @@ -18,7 +17,6 @@ import kotlinx.coroutines.launch | ||||
| 
 | ||||
| class SmartSearchPresenter(private val source: CatalogueSource?, private val config: SourceController.SmartSearchConfig?) : | ||||
|         BasePresenter<SmartSearchController>(), CoroutineScope { | ||||
|     private val logger = XLog.tag("SmartSearchPresenter") | ||||
| 
 | ||||
|     override val coroutineContext = Job() + Dispatchers.Main | ||||
| 
 | ||||
| @@ -43,7 +41,6 @@ class SmartSearchPresenter(private val source: CatalogueSource?, private val con | ||||
|                     if (e is CancellationException) { | ||||
|                         throw e | ||||
|                     } else { | ||||
|                         logger.e("Smart search error", e) | ||||
|                         SearchResults.Error | ||||
|                     } | ||||
|                 } | ||||
							
								
								
									
										47
									
								
								app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| package eu.kanade.tachiyomi.util | ||||
|  | ||||
| import kotlinx.coroutines.sync.Mutex | ||||
| import kotlinx.coroutines.sync.withLock | ||||
|  | ||||
| /** | ||||
|  * Field that can be initialized later. Users can suspend while waiting for the field to initialize. | ||||
|  * | ||||
|  * @author nulldev | ||||
|  */ | ||||
| class DeferredField<T> { | ||||
|  | ||||
|     @Volatile | ||||
|     private var content: T? = null | ||||
|  | ||||
|     @Volatile | ||||
|     var initialized = false | ||||
|         private set | ||||
|  | ||||
|     private val mutex = Mutex(true) | ||||
|  | ||||
|     /** | ||||
|      * Initialize the field | ||||
|      */ | ||||
|     fun initialize(content: T) { | ||||
|         // Fast-path new listeners | ||||
|         this.content = content | ||||
|         initialized = true | ||||
|  | ||||
|         // Notify current listeners | ||||
|         mutex.unlock() | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Will only suspend if !initialized. | ||||
|      */ | ||||
|     suspend fun get(): T { | ||||
|         // Check if field is initialized and return immediately if it is | ||||
|         if (initialized) return content as T | ||||
|  | ||||
|         // Wait for field to initialize | ||||
|         mutex.withLock {} | ||||
|  | ||||
|         // Field is initialized, return value | ||||
|         return content as T | ||||
|     } | ||||
| } | ||||
| @@ -2,7 +2,7 @@ package exh.metadata.metadata.base | ||||
|  | ||||
| import com.pushtorefresh.storio.operations.PreparedOperation | ||||
| import eu.kanade.tachiyomi.data.database.DatabaseHelper | ||||
| import exh.metadata.sql.models.SearchMetadata | ||||
| import eu.kanade.tachiyomi.data.database.models.SearchMetadata | ||||
| import exh.metadata.sql.models.SearchTag | ||||
| import exh.metadata.sql.models.SearchTitle | ||||
| import kotlin.reflect.KClass | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| package exh.metadata.metadata.base | ||||
|  | ||||
| import com.google.gson.GsonBuilder | ||||
| import eu.kanade.tachiyomi.data.database.models.SearchMetadata | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import exh.metadata.forEach | ||||
| import exh.metadata.sql.models.SearchMetadata | ||||
| import exh.metadata.sql.models.SearchTag | ||||
| import exh.metadata.sql.models.SearchTitle | ||||
| import exh.plusAssign | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| package exh.search | ||||
|  | ||||
| import exh.metadata.sql.tables.SearchMetadataTable | ||||
| import eu.kanade.tachiyomi.data.database.tables.SearchMetadataTable | ||||
| import exh.metadata.sql.tables.SearchTagTable | ||||
| import exh.metadata.sql.tables.SearchTitleTable | ||||
|  | ||||
|   | ||||
| @@ -1,42 +0,0 @@ | ||||
| package exh.ui.migration.manga.design | ||||
|  | ||||
| import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG | ||||
| import android.view.View | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder | ||||
| import eu.kanade.tachiyomi.util.view.getRound | ||||
| import kotlinx.android.synthetic.main.eh_source_item.image | ||||
| import kotlinx.android.synthetic.main.eh_source_item.reorder | ||||
| import kotlinx.android.synthetic.main.eh_source_item.title | ||||
|  | ||||
| class MigrationSourceHolder(view: View, val adapter: FlexibleAdapter<MigrationSourceItem>) : | ||||
|         BaseFlexibleViewHolder(view, adapter) { | ||||
|     init { | ||||
|         setDragHandleView(reorder) | ||||
|     } | ||||
|  | ||||
|     fun bind(source: HttpSource, sourceEnabled: Boolean) { | ||||
|         // Set capitalized title. | ||||
|         title.text = source.name.capitalize() | ||||
|  | ||||
|         // Update circle letter image. | ||||
|         itemView.post { | ||||
|             image.setImageDrawable(image.getRound(source.name.take(1).toUpperCase(), false)) | ||||
|         } | ||||
|  | ||||
|         if (sourceEnabled) { | ||||
|             title.alpha = 1.0f | ||||
|             image.alpha = 1.0f | ||||
|             title.paintFlags = title.paintFlags and STRIKE_THRU_TEXT_FLAG.inv() | ||||
|         } else { | ||||
|             title.alpha = DISABLED_ALPHA | ||||
|             image.alpha = DISABLED_ALPHA | ||||
|             title.paintFlags = title.paintFlags or STRIKE_THRU_TEXT_FLAG | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         private const val DISABLED_ALPHA = 0.3f | ||||
|     } | ||||
| } | ||||
							
								
								
									
										10
									
								
								app/src/main/res/drawable/baseline_swap_calls_24.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/src/main/res/drawable/baseline_swap_calls_24.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:width="24dp" | ||||
|     android:height="24dp" | ||||
|     android:viewportWidth="24.0" | ||||
|     android:viewportHeight="24.0" | ||||
|     android:tint="?attr/colorControlNormal"> | ||||
|     <path | ||||
|         android:fillColor="@android:color/white" | ||||
|         android:pathData="M18,4l-4,4h3v7c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2V8c0,-2.21 -1.79,-4 -4,-4S5,5.79 5,8v7H2l4,4 4,-4H7V8c0,-1.1 0.9,-2 2,-2s2,0.9 2,2v7c0,2.21 1.79,4 4,4s4,-1.79 4,-4V8h3l-4,-4z"/> | ||||
| </vector> | ||||
| @@ -16,7 +16,7 @@ | ||||
|         app:layout_constraintHorizontal_bias="0.0" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" | ||||
|         tools:listitem="@layout/eh_source_item"> | ||||
|         tools:listitem="@layout/migration_source_item"> | ||||
| 
 | ||||
|     </androidx.recyclerview.widget.RecyclerView> | ||||
| 
 | ||||
| @@ -25,7 +25,7 @@ | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginBottom="8dp" | ||||
|         android:text="Data to include in migration" | ||||
|         android:text="@string/data_to_include_in_migration" | ||||
|         android:textAppearance="@style/TextAppearance.Medium.Body2" | ||||
|         app:layout_constraintBottom_toTopOf="@+id/mig_chapters" | ||||
|         app:layout_constraintStart_toStartOf="@+id/textView" /> | ||||
| @@ -45,7 +45,6 @@ | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginStart="8dp" | ||||
|         android:layout_marginLeft="8dp" | ||||
|         android:checked="true" | ||||
|         android:text="@string/categories" | ||||
|         app:layout_constraintBottom_toBottomOf="@+id/mig_chapters" | ||||
| @@ -56,7 +55,6 @@ | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginStart="8dp" | ||||
|         android:layout_marginLeft="8dp" | ||||
|         android:checked="true" | ||||
|         android:text="@string/track" | ||||
|         app:layout_constraintBottom_toBottomOf="@+id/mig_categories" | ||||
| @@ -67,9 +65,8 @@ | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginStart="8dp" | ||||
|         android:layout_marginLeft="8dp" | ||||
|         android:layout_marginBottom="8dp" | ||||
|         android:text="Options" | ||||
|         android:text="@string/options" | ||||
|         android:textAppearance="@style/TextAppearance.Medium.Body2" | ||||
|         app:layout_constraintBottom_toTopOf="@+id/prioritize_chapter_count" | ||||
|         app:layout_constraintStart_toStartOf="parent" /> | ||||
| @@ -95,7 +92,8 @@ | ||||
|         android:clickable="true" | ||||
|         app:layout_constraintBottom_toTopOf="@+id/fuzzy_search" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count" /> | ||||
|         app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count" | ||||
|         android:focusable="true" /> | ||||
| 
 | ||||
|     <androidx.appcompat.widget.SwitchCompat | ||||
|         android:id="@+id/use_smart_search" | ||||
| @@ -114,11 +112,12 @@ | ||||
|         android:layout_marginRight="8dp" | ||||
|         android:layout_marginBottom="8dp" | ||||
|         android:gravity="start|center_vertical" | ||||
|         android:text="Use intelligent search algorithm" | ||||
|         android:text="@string/use_intelligent_search" | ||||
|         android:clickable="true" | ||||
|         app:layout_constraintBottom_toTopOf="@+id/copy_manga" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count" /> | ||||
|         app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count" | ||||
|         android:focusable="true" /> | ||||
| 
 | ||||
|     <androidx.appcompat.widget.SwitchCompat | ||||
|         android:id="@+id/copy_manga" | ||||
| @@ -137,11 +136,12 @@ | ||||
|         android:layout_marginRight="8dp" | ||||
|         android:layout_marginBottom="8dp" | ||||
|         android:gravity="start|center_vertical" | ||||
|         android:text="Keep old manga" | ||||
|         android:text="@string/keep_old_manga" | ||||
|         android:clickable="true" | ||||
|         app:layout_constraintBottom_toTopOf="@+id/extra_search_param" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count" /> | ||||
|         app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count" | ||||
|         android:focusable="true" /> | ||||
| 
 | ||||
|     <androidx.appcompat.widget.SwitchCompat | ||||
|         android:id="@+id/extra_search_param" | ||||
| @@ -160,11 +160,12 @@ | ||||
|         android:layout_marginRight="8dp" | ||||
|         android:layout_marginBottom="8dp" | ||||
|         android:gravity="start|center_vertical" | ||||
|         android:text="Include extra search parameter when searching" | ||||
|         android:text="@string/include_extra_search_parameter" | ||||
|         android:clickable="true" | ||||
|         app:layout_constraintBottom_toTopOf="@+id/extra_search_param_text" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count" /> | ||||
|         app:layout_constraintStart_toEndOf="@+id/prioritize_chapter_count" | ||||
|         android:focusable="true" /> | ||||
| 
 | ||||
|     <EditText | ||||
|         android:id="@+id/extra_search_param_text" | ||||
| @@ -175,11 +176,12 @@ | ||||
|         android:layout_marginEnd="8dp" | ||||
|         android:layout_marginRight="8dp" | ||||
|         android:ems="10" | ||||
|         android:hint="Search parameter (e.g. language:english)" | ||||
|         android:hint="@string/search_parameter" | ||||
|         android:inputType="textPersonName" | ||||
|         app:layout_constraintBottom_toTopOf="@+id/begin_migration_btn" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" /> | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         android:importantForAutofill="no" /> | ||||
| 
 | ||||
|     <Button | ||||
|         android:id="@+id/begin_migration_btn" | ||||
| @@ -191,7 +193,7 @@ | ||||
|         android:layout_marginEnd="8dp" | ||||
|         android:layout_marginRight="8dp" | ||||
|         android:layout_marginBottom="8dp" | ||||
|         android:text="Begin migration" | ||||
|         android:text="@string/begin_migration" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" /> | ||||
| @@ -17,7 +17,7 @@ | ||||
|             android:id="@+id/manga_cover" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="0dp" | ||||
|             android:layout_marginLeft="16dp" | ||||
|             android:layout_marginStart="16dp" | ||||
|             android:layout_marginTop="16dp" | ||||
|             android:contentDescription="@string/description_cover" | ||||
|             app:layout_constraintDimensionRatio="l,2:3" | ||||
| @@ -71,7 +71,7 @@ | ||||
|                 style="@style/TextAppearance.Regular.Body1.Secondary" | ||||
|                 android:layout_width="0dp" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginLeft="8dp" | ||||
|                 android:layout_marginStart="8dp" | ||||
|                 android:clickable="false" | ||||
|                 android:ellipsize="end" | ||||
|                 android:maxLines="1" | ||||
| @@ -96,7 +96,7 @@ | ||||
|                 style="@style/TextAppearance.Regular.Body1.Secondary" | ||||
|                 android:layout_width="0dp" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginLeft="8dp" | ||||
|                 android:layout_marginStart="8dp" | ||||
|                 android:clickable="false" | ||||
|                 android:ellipsize="end" | ||||
|                 android:maxLines="1" | ||||
| @@ -121,7 +121,7 @@ | ||||
|                 style="@style/TextAppearance.Regular.Body1.Secondary" | ||||
|                 android:layout_width="0dp" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginLeft="8dp" | ||||
|                 android:layout_marginStart="8dp" | ||||
|                 android:clickable="false" | ||||
|                 android:ellipsize="end" | ||||
|                 android:maxLines="1" | ||||
| @@ -146,7 +146,7 @@ | ||||
|                 style="@style/TextAppearance.Regular.Body1.Secondary" | ||||
|                 android:layout_width="0dp" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginLeft="8dp" | ||||
|                 android:layout_marginStart="8dp" | ||||
|                 android:clickable="false" | ||||
|                 android:textIsSelectable="false" | ||||
|                 app:layout_constraintLeft_toRightOf="@+id/manga_chapters_label" | ||||
| @@ -169,7 +169,7 @@ | ||||
|                 style="@style/TextAppearance.Regular.Body1.Secondary" | ||||
|                 android:layout_width="0dp" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginLeft="8dp" | ||||
|                 android:layout_marginStart="8dp" | ||||
|                 android:clickable="false" | ||||
|                 android:textIsSelectable="false" | ||||
|                 app:layout_constraintLeft_toRightOf="@+id/manga_last_chapter_label" | ||||
| @@ -192,7 +192,7 @@ | ||||
|                 style="@style/TextAppearance.Regular.Body1.Secondary" | ||||
|                 android:layout_width="0dp" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginLeft="8dp" | ||||
|                 android:layout_marginStart="8dp" | ||||
|                 android:clickable="false" | ||||
|                 android:textIsSelectable="false" | ||||
|                 app:layout_constraintLeft_toRightOf="@+id/manga_last_update_label" | ||||
| @@ -215,7 +215,7 @@ | ||||
|                 style="@style/TextAppearance.Regular.Body1.Secondary" | ||||
|                 android:layout_width="0dp" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginLeft="8dp" | ||||
|                 android:layout_marginStart="8dp" | ||||
|                 android:clickable="false" | ||||
|                 android:textIsSelectable="false" | ||||
|                 app:layout_constraintLeft_toRightOf="@+id/manga_source_label" | ||||
| @@ -1,11 +1,13 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| <androidx.constraintlayout.widget.ConstraintLayout | ||||
|     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="match_parent" | ||||
|     android:background="?attr/colorPrimary"> | ||||
|     android:background="?attr/colorPrimary" > | ||||
| 
 | ||||
|     <exh.ui.migration.manga.process.DeactivatableViewPager | ||||
|     <eu.kanade.tachiyomi.ui.migration.manga.process.DeactivatableViewPager | ||||
|         android:id="@+id/pager" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
| @@ -1,6 +1,7 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <FrameLayout 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="match_parent" | ||||
|     android:animateLayoutChanges="true"> | ||||
| @@ -10,8 +11,8 @@ | ||||
|         android:layout_height="match_parent"> | ||||
| 
 | ||||
|         <include | ||||
|             android:id="@+id/eh_manga_card_from" | ||||
|             layout="@layout/eh_manga_card" | ||||
|             android:id="@+id/migration_manga_card_from" | ||||
|             layout="@layout/migration_manga_card" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginStart="16dp" | ||||
| @@ -33,12 +34,12 @@ | ||||
|             android:scaleType="center" | ||||
|             app:layout_constraintEnd_toEndOf="parent" | ||||
|             app:layout_constraintStart_toStartOf="parent" | ||||
|             app:layout_constraintTop_toBottomOf="@+id/eh_manga_card_from" | ||||
|             app:srcCompat="@drawable/eh_ic_arrow_drop_down_white_100dp" /> | ||||
|             app:layout_constraintTop_toBottomOf="@+id/migration_manga_card_from" | ||||
|             app:srcCompat="@drawable/ic_arrow_down_white_32dp" /> | ||||
| 
 | ||||
|         <include | ||||
|             android:id="@+id/eh_manga_card_to" | ||||
|             layout="@layout/eh_manga_card" | ||||
|             android:id="@+id/migration_manga_card_to" | ||||
|             layout="@layout/migration_manga_card" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginStart="16dp" | ||||
| @@ -55,10 +56,8 @@ | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginEnd="8dp" | ||||
|             android:layout_marginRight="8dp" | ||||
|             android:layout_marginBottom="8dp" | ||||
|             android:drawableStart="@drawable/eh_ic_clear_white_24dp" | ||||
|             android:drawableLeft="@drawable/eh_ic_clear_white_24dp" | ||||
|             android:drawableStart="@drawable/ic_clear_grey" | ||||
|             android:drawablePadding="6dp" | ||||
|             android:text="Skip manga" | ||||
|             android:textColor="#ffffff" | ||||
| @@ -73,8 +72,7 @@ | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:alpha="0.5" | ||||
|             android:drawableStart="@drawable/eh_ic_check_white_24dp" | ||||
|             android:drawableLeft="@drawable/eh_ic_check_white_24dp" | ||||
|             android:drawableStart="@drawable/ic_check_box_24dp" | ||||
|             android:drawablePadding="6dp" | ||||
|             android:enabled="false" | ||||
|             android:text="Migrate manga" | ||||
							
								
								
									
										8
									
								
								app/src/main/res/layout/eh_smart_search.xml → app/src/main/res/layout/smart_search.xml
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										8
									
								
								app/src/main/res/layout/eh_smart_search.xml → app/src/main/res/layout/smart_search.xml
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @@ -1,6 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| <androidx.coordinatorlayout.widget.CoordinatorLayout | ||||
|     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="match_parent" | ||||
|     android:fitsSystemWindows="true"> | ||||
| @@ -40,7 +42,7 @@ | ||||
|                 android:layout_marginBottom="8dp" | ||||
|                 android:text="Searching source..." | ||||
|                 android:textAppearance="@style/TextAppearance.Medium.Title" | ||||
|                 android:textColor="@color/white" /> | ||||
|                 android:textColor="@android:color/white" /> | ||||
| 
 | ||||
|             <ProgressBar | ||||
|                 android:id="@+id/intercept_progress" | ||||
| @@ -48,7 +50,7 @@ | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_gravity="center" | ||||
|                 android:indeterminateTint="@color/white" /> | ||||
|                 android:indeterminateTint="@android:color/white" /> | ||||
|         </LinearLayout> | ||||
|     </LinearLayout> | ||||
| 
 | ||||
| @@ -31,8 +31,10 @@ | ||||
|         app:showAsAction="ifRoom" /> | ||||
|  | ||||
|     <item | ||||
|         android:id="@+id/action_auto_source_migration" | ||||
|         android:title="Source migration (automatic)" | ||||
|         app:showAsAction="never"/> | ||||
|         android:id="@+id/action_migrate" | ||||
|         android:icon="@drawable/baseline_swap_calls_24" | ||||
|         android:title="@string/label_migration" | ||||
|         app:iconTint="?attr/colorOnPrimary" | ||||
|         app:showAsAction="ifRoom" /> | ||||
|  | ||||
| </menu> | ||||
|   | ||||
| @@ -644,6 +644,12 @@ | ||||
|     <string name="channel_backup_restore">Backup and restore</string> | ||||
|     <string name="channel_backup_restore_progress">Progress</string> | ||||
|     <string name="channel_backup_restore_complete">Complete</string> | ||||
|     <string name="data_to_include_in_migration">Data to include in migration</string> | ||||
|     <string name="search_parameter">Search parameter (e.g. language:english)</string> | ||||
|     <string name="include_extra_search_parameter">Include extra search parameter when searching</string> | ||||
|     <string name="keep_old_manga">Keep old manga</string> | ||||
|     <string name="use_intelligent_search">Use intelligent search algorithm</string> | ||||
|     <string name="begin_migration">Begin migration</string> | ||||
|  | ||||
|     <!-- EXH --> | ||||
|     <string name="label_login">Login</string> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user