From 10206ae7b30fbd521308a6d725e107e708b97dd0 Mon Sep 17 00:00:00 2001 From: Jay Date: Sat, 4 Jan 2020 02:28:43 -0800 Subject: [PATCH] Finished Part 1 of new auto source migration --- app/src/debug/res/drawable-anydpi/ic_copy.xml | 11 + app/src/debug/res/drawable-anydpi/ic_done.xml | 11 + .../debug/res/drawable-anydpi/ic_done_all.xml | 11 + app/src/debug/res/drawable-hdpi/ic_copy.png | Bin 0 -> 220 bytes app/src/debug/res/drawable-hdpi/ic_done.png | Bin 0 -> 197 bytes .../debug/res/drawable-hdpi/ic_done_all.png | Bin 0 -> 289 bytes app/src/debug/res/drawable-mdpi/ic_copy.png | Bin 0 -> 146 bytes app/src/debug/res/drawable-mdpi/ic_done.png | Bin 0 -> 151 bytes .../debug/res/drawable-mdpi/ic_done_all.png | Bin 0 -> 213 bytes app/src/debug/res/drawable-xhdpi/ic_copy.png | Bin 0 -> 209 bytes app/src/debug/res/drawable-xhdpi/ic_done.png | Bin 0 -> 219 bytes .../debug/res/drawable-xhdpi/ic_done_all.png | Bin 0 -> 323 bytes app/src/debug/res/drawable-xxhdpi/ic_copy.png | Bin 0 -> 303 bytes app/src/debug/res/drawable-xxhdpi/ic_done.png | Bin 0 -> 279 bytes .../debug/res/drawable-xxhdpi/ic_done_all.png | Bin 0 -> 416 bytes .../res/drawable/ic_migrate_direction.xml | 6 + .../ui/migration/MigrationMangaDialog.kt | 41 ++ .../ui/migration/SearchController.kt | 61 ++- .../manga/design/MigrationDesignController.kt | 9 +- .../migration/manga/process/MigratingManga.kt | 6 + .../manga/process/MigrationListController.kt | 388 ++++++++++++++++++ .../process/MigrationProcedureAdapter.kt | 26 +- .../manga/process/MigrationProcedureConfig.kt | 3 +- .../process/MigrationProcedureController.kt | 3 +- .../manga/process/MigrationProcessAdapter.kt | 144 +++++++ .../manga/process/MigrationProcessHolder.kt | 173 ++++++++ .../manga/process/MigrationProcessItem.kt | 48 +++ .../eu/kanade/tachiyomi/util/DeferredField.kt | 10 +- .../ic_keyboard_arrow_right_black_24dp.xml | 10 + .../layout/migration_design_controller.xml | 26 +- .../res/layout/migration_list_controller.xml | 16 + .../res/layout/migration_new_manga_card.xml | 124 ++++++ .../res/layout/migration_new_process_item.xml | 79 ++++ app/src/main/res/menu/migration_list.xml | 15 + app/src/main/res/menu/migration_single.xml | 21 + app/src/main/res/values/strings.xml | 8 + 36 files changed, 1196 insertions(+), 54 deletions(-) create mode 100644 app/src/debug/res/drawable-anydpi/ic_copy.xml create mode 100644 app/src/debug/res/drawable-anydpi/ic_done.xml create mode 100644 app/src/debug/res/drawable-anydpi/ic_done_all.xml create mode 100644 app/src/debug/res/drawable-hdpi/ic_copy.png create mode 100644 app/src/debug/res/drawable-hdpi/ic_done.png create mode 100644 app/src/debug/res/drawable-hdpi/ic_done_all.png create mode 100644 app/src/debug/res/drawable-mdpi/ic_copy.png create mode 100644 app/src/debug/res/drawable-mdpi/ic_done.png create mode 100644 app/src/debug/res/drawable-mdpi/ic_done_all.png create mode 100644 app/src/debug/res/drawable-xhdpi/ic_copy.png create mode 100644 app/src/debug/res/drawable-xhdpi/ic_done.png create mode 100644 app/src/debug/res/drawable-xhdpi/ic_done_all.png create mode 100644 app/src/debug/res/drawable-xxhdpi/ic_copy.png create mode 100644 app/src/debug/res/drawable-xxhdpi/ic_done.png create mode 100644 app/src/debug/res/drawable-xxhdpi/ic_done_all.png create mode 100644 app/src/debug/res/drawable/ic_migrate_direction.xml create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessItem.kt create mode 100644 app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml create mode 100644 app/src/main/res/layout/migration_list_controller.xml create mode 100644 app/src/main/res/layout/migration_new_manga_card.xml create mode 100644 app/src/main/res/layout/migration_new_process_item.xml create mode 100644 app/src/main/res/menu/migration_list.xml create mode 100644 app/src/main/res/menu/migration_single.xml diff --git a/app/src/debug/res/drawable-anydpi/ic_copy.xml b/app/src/debug/res/drawable-anydpi/ic_copy.xml new file mode 100644 index 0000000000..b11c7d4efc --- /dev/null +++ b/app/src/debug/res/drawable-anydpi/ic_copy.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/debug/res/drawable-anydpi/ic_done.xml b/app/src/debug/res/drawable-anydpi/ic_done.xml new file mode 100644 index 0000000000..28bf053692 --- /dev/null +++ b/app/src/debug/res/drawable-anydpi/ic_done.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/debug/res/drawable-anydpi/ic_done_all.xml b/app/src/debug/res/drawable-anydpi/ic_done_all.xml new file mode 100644 index 0000000000..85d9ffd199 --- /dev/null +++ b/app/src/debug/res/drawable-anydpi/ic_done_all.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/debug/res/drawable-hdpi/ic_copy.png b/app/src/debug/res/drawable-hdpi/ic_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..7821875d32b34ce5f4bb34eb60b036ae3b44a861 GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7JIrlhE&{2PLO!>;Q#;sH4blB zjyPU9FvZG^vm>41V^UV4mu!f;@9}`cj-My+NQ(rz$<7ZnbmW(14>bJ#lW`-TE8im5 zcg&A?ykuOy-eEs#^~9=U*3R`@Cw1(LWL>r%;VTunQ@of@s`SE&W;a=rR|^8!d#yUo z@g2HlA#cEbaq(JPkPxpJJFk!O{CUj+E~U*cEPb3q+$Z`mN7kjZpK)MdaJU=tK=#-* RH=uJFJYD@<);T3K0RW;zPy7G? literal 0 HcmV?d00001 diff --git a/app/src/debug/res/drawable-hdpi/ic_done.png b/app/src/debug/res/drawable-hdpi/ic_done.png new file mode 100644 index 0000000000000000000000000000000000000000..f1f39724ec85d401f124ce4410ef7fd0e5a8e1d7 GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBdOTemLn>~)o#x2ZpdjG7)U~|u z{-WfT=N$Q$?LI8H%)zRdoOep{h5e7)@0n#KpEEE#NEf(xV5aU#7r#d=-bUejRA(|s zo^Uueg-6_Xj;VZ-Ynr`xviGac*^f3Y)mZOUT5Y^@i|Wa}&F7GhKrTWE_Kk+dz+53DVi?R^ z1VcL>(4}Y03f-gq%DM@fw~ZO17uuoVP=tBXm?ML_wRW2@a7<@cx=>i&S_iM8KA3^i zZ=Z#jK`pE?yAZ0?2l!W%$lK&VB>~h49f`f#4N(u#d8Q#seA2T~ceEEk$zT{%=K2h+ npky%?Dh_=&>)(S$qiL8IPcVbh2Jy{y00000NkvXXu0mjf5`BE> literal 0 HcmV?d00001 diff --git a/app/src/debug/res/drawable-mdpi/ic_copy.png b/app/src/debug/res/drawable-mdpi/ic_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..912309132c66b3380284512bff939c9db7e83d98 GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk)AG&Ar-fh6Bda7`2T;uoPa;m zqoYd_f^=qhta`X4%3&6pQB%i0u?dpu3SX;^L^*`bY4{?T&3MwWE&Tin*2Q8yQ4Z|f uD*}J?MmdynuZWHbUMbkH`_MvB1_t&GIm<)$F_r=?VeoYIb6Mw<&;$TUYBH4o literal 0 HcmV?d00001 diff --git a/app/src/debug/res/drawable-mdpi/ic_done.png b/app/src/debug/res/drawable-mdpi/ic_done.png new file mode 100644 index 0000000000000000000000000000000000000000..4d1d9bdfd398fb95fc284df160a073a40ad4b8f4 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjah@)YAr-fh6C_v{H;8=r|G!={ zN-B@(2zP@;$Fw&6WxH4xw*_A>SdgTqaE?*ZL3lc|X@rBR-hnjE&?eR;g0odac#BKC yGlUjqOuL}7^?**K&txTrlamzM0u8td7#X+{o~rcw>FWV)V(@hJb6Mw<&;$VIN-miI literal 0 HcmV?d00001 diff --git a/app/src/debug/res/drawable-mdpi/ic_done_all.png b/app/src/debug/res/drawable-mdpi/ic_done_all.png new file mode 100644 index 0000000000000000000000000000000000000000..c0e88e9fe7c408dc131dbd726952e236853172d9 GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjb39!fLn>}1CrGd^ZV>qK|Nnjy zF+(+lX-nE3ra9~p=xDycbDr%{q{A8?-oxUDbsUUp3|+-~r2JwJFYGdEyusuf!_%?4 zA%f#nIP0UED!XM~Z8{ooJyN0bx#Q8_4G|JrYA#_L8e&>gIxRzbggahujBtvZA)MJM z;VRgb)+s6G{VY&bP0l+XkKkXBAt literal 0 HcmV?d00001 diff --git a/app/src/debug/res/drawable-xhdpi/ic_copy.png b/app/src/debug/res/drawable-xhdpi/ic_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..a15eb3da563cd823f60fd39e48f65aa42606040f GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUt8J;eVAr-gY-aN>AK!Kw*F}=X& z_(f;-7YnX5Gv8=)&-^uck8usVbJNlQ&eLaJ`OKCyUT(DNA^+D0UJl(-ajY-m*)ybB zY!Vn4t~4yLH4N*2#no|7cy;(!mX>#&Q=f6Z&}Z1CuJB}{{X1r$Wyql87ns*@pSeiB s+?-+O`Pb47cjRTv1uXZ^Sn*&N!)c4fi%sUu>j$awboFyt=akR{0J#%L&j0`b literal 0 HcmV?d00001 diff --git a/app/src/debug/res/drawable-xhdpi/ic_done.png b/app/src/debug/res/drawable-xhdpi/ic_done.png new file mode 100644 index 0000000000000000000000000000000000000000..f193eba596c01079e295605f9d2ea8d8356a8fba GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUtMV>B>Ar-gYPCw1rU?9L+{3(sw z?8(`@oC89a9JqR=KPV*qaXDf$v(ffFPsXBij6fZK`X!RnFKeH!o}FN{c?tNv`!`K&4!f683@XMoVhpT_gW-sS3Eq45VN-1@R*du@Xv`qV><`gUkv`x-{*~3*?Yl&)G Q4v@#->FVdQ&MBb@04O6=B>(^b literal 0 HcmV?d00001 diff --git a/app/src/debug/res/drawable-xhdpi/ic_done_all.png b/app/src/debug/res/drawable-xhdpi/ic_done_all.png new file mode 100644 index 0000000000000000000000000000000000000000..d6036992c4f1cbba832ffb0fd03120511502fe51 GIT binary patch literal 323 zcmV-J0lfZ+P)d)WI^tc{J=uu_cPzz((KpfDs$LuO<(yUc3dOI~Bf<@<=jLXsc| zf*=Tj{BEkMN~tTy*(vov9mfP;P)n+DfWO$;6Lk_3eECkI1K;+nCLzGH0s?%~Ttxuz zfCoI_0sr^#a}{{{!i;*PE*$6g+$PMfbJKvo+6A6{5*EHaV4p;A_z`o)L<#zQ#~fIa zSPt@=Z_X04U?Y(-vl;cp%y^I`t5g2dxPiH!<;Xo*2Dm7XNFs@Hj65~?>TPZD?4 zJFF)O-R*%OQG6khYd(Y<*Gr}#k;m{F#Pq%X5G3^BI}*lmHnW?~)y}gmQ*+76bfJ-bv z;~E$1%|oeds(mg|T$3Ld*Tw((`|{+Zw~yK!U9!?{Z!;`ESNmkyo$w{sj?FBZe=)#` z;jMalZ%?pvMqeM0U@YO8!XV3hbhl|!?E3}=MkbaQc@B>M^D^f@H@+UhyP|dfZM*v= zmI2!J+xNbHIXQ&E#JuVA+Zxjgmn1F}nFeI>XvFf0e2e@r{U%5+hk$|u*bp3yRZOOq zEGSC8vR}Eb<-`~ee_Dstp?0maD8tvS0pT52&U-yOIl1Kj(m27NuJ`;|maTZWDEApK P1Q~)y|tIO*+8J-V#v!) zPGVli%_18FH!(B|DF?D0|D$>8?r)Xv$F&b^+YzJUISGiCH1s~MSh}I7SbMep>cty- zjL&cfv9bp-^DSYUv&W&}!GT6*c0L&kC}U3R!S|`Q%h?+~OFlQ{zcIg2gf-oQSySrV zLXY~tPI}F@`*`+oYaY3tCG~-4Nx=M1izhn0EADgR+RJ&TdrDIK9#PG!9}{(hPHhg= z4tmv`t{n6(xRQC2c6YDG&UeLON=x^?dG+9p^QYmdKI;Vst0QSmoO#lD@ literal 0 HcmV?d00001 diff --git a/app/src/debug/res/drawable-xxhdpi/ic_done_all.png b/app/src/debug/res/drawable-xxhdpi/ic_done_all.png new file mode 100644 index 0000000000000000000000000000000000000000..da71795bd68d69dafecafc31e586de922e231daf GIT binary patch literal 416 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@Zgyv2U`+RPaSW-r_4d|zufq-k4IkyY z#Z4DIQeJuV@S&#jkCbQp`Y-rvK_Sy@Z-aMf+kMlfy>H$*)$^z5t(+HW&Mr`pvS!`u zZBk_!M>A8RB*Qm6j(X<&Y+=|OXNm9z<2My+MAOYK$y*$LlXcY7?H7CcjO9^$p@sqX}0Y z)%5Baac8y^pKuB4EY|*PTewkW3Fn-h9#;D&q`s_uvdA___~yp$H!mjYao>BqYk%0^ z<>EVkL^Dn5f0I!>;mV&W*)Bol^A~e^zV&_3iX&5= zZ=D(cJZ`0%wo}y=wz@lP`g167=5225GCd54Ci&y0ngn)_b>KLavoL>5;@K+Hq + + \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt new file mode 100644 index 0000000000..ad29b0a015 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MigrationMangaDialog.kt @@ -0,0 +1,41 @@ +package eu.kanade.tachiyomi.ui.migration + +import android.app.Dialog +import android.os.Bundle +import com.afollestad.materialdialogs.MaterialDialog +import com.bluelinelabs.conductor.Controller +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.base.controller.DialogController +import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController + +class MigrationMangaDialog(bundle: Bundle? = null) : DialogController(bundle) + where T : Controller { + + var copy = false + var mangaSet = 0 + var mangaSkipped = 0 + constructor(target: T, copy: Boolean, mangaSet: Int, mangaSkipped: Int) : this() { + targetController = target + this.copy = copy + this.mangaSet = mangaSet + this.mangaSkipped = mangaSkipped + } + + override fun onCreateDialog(savedViewState: Bundle?): Dialog { + val confirmRes = if (copy) R.string.confirm_copy else R.string.confirm_migration + val confirmString = applicationContext?.getString(confirmRes, mangaSet, ( + if (mangaSkipped > 0) + " " + applicationContext?.getString(R.string.skipping_x, mangaSkipped) ?: "" + else "")) ?: "" + return MaterialDialog.Builder(activity!!) + .content(confirmString) + .positiveText(android.R.string.yes) + .negativeText(android.R.string.no) + .onPositive { _, _ -> + if (copy) + (targetController as? MigrationListController)?.copyMangas() + else + (targetController as? MigrationListController)?.migrateMangas() + }.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt index 8545922bed..f4710342cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/SearchController.kt @@ -5,16 +5,19 @@ import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem -import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat +import androidx.appcompat.widget.SearchView import com.afollestad.materialdialogs.MaterialDialog +import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault +import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter +import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController import uy.kohesive.injekt.injectLazy class SearchController( @@ -25,6 +28,14 @@ class SearchController( 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()}" @@ -49,7 +60,7 @@ class SearchController( newManga = savedInstanceState.getSerializable(::newManga.name) as? Manga } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + /*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 @@ -66,7 +77,7 @@ class SearchController( } } return true - } + }*/ fun migrateManga() { val target = targetController as? MigrationInterface ?: return @@ -98,6 +109,14 @@ class SearchController( } 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 @@ -142,4 +161,40 @@ class SearchController( } + /** + * 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.catalogue_new_list, 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.queryTextChangeEvents() + .filter { it.isSubmitted } + .subscribeUntilDestroy { + presenter.search(it.queryText().toString()) + searchItem.collapseActionView() + setTitle() // Update toolbar title + } + } + + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationDesignController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationDesignController.kt index a824e35440..15363dcd08 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationDesignController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationDesignController.kt @@ -14,7 +14,7 @@ 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.MigrationProcedureController +import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController import eu.kanade.tachiyomi.util.gone import eu.kanade.tachiyomi.util.visible import exh.ui.migration.manga.process.MigrationProcedureConfig @@ -60,10 +60,6 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseController(bundle) use_smart_search.toggle() } - copy_manga_desc.setOnClickListener { - copy_manga.toggle() - } - extra_search_param_desc.setOnClickListener { extra_search_param.toggle() } @@ -93,7 +89,7 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseController(bundle) if(mig_categories.isChecked) flags = flags or MigrationFlags.TRACK router.replaceTopController( - MigrationProcedureController.create( + MigrationListController.create( MigrationProcedureConfig( config.toList(), ourAdapter.items.filter { @@ -102,7 +98,6 @@ class MigrationDesignController(bundle: Bundle? = null) : BaseController(bundle) useSourceWithMostChapters = prioritize_chapter_count.isChecked, enableLenientSearch = use_smart_search.isChecked, migrationFlags = flags, - copy = copy_manga.isChecked, extraSearchParams = if(extra_search_param.isChecked && extra_search_param_text.text.isNotBlank()) { extra_search_param_text.text.toString() } else null diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigratingManga.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigratingManga.kt index 5f952e7d73..2a98fbb01f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigratingManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigratingManga.kt @@ -4,6 +4,7 @@ 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 eu.kanade.tachiyomi.ui.migration.manga.process.MigrationProcessItem import eu.kanade.tachiyomi.util.DeferredField import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -31,4 +32,9 @@ class MigratingManga(private val db: DatabaseHelper, suspend fun mangaSource(): Source { return sourceManager.getOrStub(manga()?.source ?: -1) } + + fun toModal(): MigrationProcessItem { + // Create the model object. + return MigrationProcessItem(this) + } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt new file mode 100644 index 0000000000..a47d350832 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationListController.kt @@ -0,0 +1,388 @@ +package eu.kanade.tachiyomi.ui.migration.manga.process + +import android.content.pm.ActivityInfo +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.core.graphics.ColorUtils +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat +import com.afollestad.materialdialogs.MaterialDialog +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.DatabaseHelper +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.Source +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.ui.base.controller.BaseController +import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import eu.kanade.tachiyomi.ui.migration.MigrationMangaDialog +import eu.kanade.tachiyomi.ui.migration.SearchController +import eu.kanade.tachiyomi.util.RecyclerWindowInsetsListener +import eu.kanade.tachiyomi.util.await +import eu.kanade.tachiyomi.util.launchUI +import eu.kanade.tachiyomi.util.syncChaptersWithSource +import eu.kanade.tachiyomi.util.toast +import exh.ui.migration.manga.process.MigratingManga +import exh.ui.migration.manga.process.MigrationProcedureConfig +import kotlinx.android.synthetic.main.chapters_controller.* +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit +import kotlinx.coroutines.withContext +import rx.schedulers.Schedulers +import uy.kohesive.injekt.injectLazy +import java.util.concurrent.atomic.AtomicInteger +import kotlin.coroutines.CoroutineContext + +class MigrationListController(bundle: Bundle? = null) : BaseController(bundle), + MigrationProcessAdapter.MigrationProcessInterface, + CoroutineScope { + + init { + setHasOptionsMenu(true) + } + + private var titleText = "Migrate manga" + + private var adapter: MigrationProcessAdapter? = null + + 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 var migrationsJob: Job? = null + private var migratingManga: MutableList? = null + private var selectedPosition:Int? = null + + override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + return inflater.inflate(R.layout.migration_list_controller, container, false) + } + + override fun getTitle(): String { + return titleText + } + + override fun onViewCreated(view: View) { + super.onViewCreated(view) + setTitle() + val config = this.config ?: return + activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT + + val newMigratingManga = migratingManga ?: run { + val new = config.mangaIds.map { + MigratingManga(db, sourceManager, it, coroutineContext) + } + migratingManga = new.toMutableList() + new + } + + adapter = MigrationProcessAdapter(this, view.context) + + recycler.adapter = adapter + recycler.layoutManager = LinearLayoutManager(view.context) + //recycler.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration + // .VERTICAL)) + recycler.setHasFixedSize(true) + recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) + //recycler.isEnabled = false + + adapter?.updateDataSet(newMigratingManga.map { it.toModal() } ) + + if(migrationsJob == null) { + migrationsJob = launch { + runMigrations(newMigratingManga) + } + } + } + + /*fun nextMigration() { + adapter?.let { adapter -> + if(pager.currentItem >= adapter.count - 1) { + applicationContext?.toast("All migrations complete!") + router.popCurrentController() + } else { + adapter.migratingManga[pager.currentItem].migrationJob.cancel() + pager.setCurrentItem(pager.currentItem + 1, true) + launch(Dispatchers.Main) { + updateTitle() + } + } + } + }*/ + + fun migrationFailure() { + activity?.let { + MaterialDialog.Builder(it) + .title("Migration failure") + .content("An unknown error occured while migrating this manga!") + .positiveText("Ok") + .show() + } + } + + suspend fun runMigrations(mangas: List) { + val sources = config?.targetSourceIds?.mapNotNull { sourceManager.get(it) as? CatalogueSource } ?: return + + for(manga in mangas) { + if(!manga.searchResult.initialized && manga.migrationJob.isActive) { + val mangaObj = manga.manga() + + if(mangaObj == null) { + manga.searchResult.initialize(null) + continue + } + + val mangaSource = manga.mangaSource() + + val result = try { + CoroutineScope(manga.migrationJob).async { + val validSources = sources.filter { + it.id != mangaSource.id + } + if(config.useSourceWithMostChapters) { + val sourceSemaphore = Semaphore(3) + val processedSources = AtomicInteger() + + validSources.map { source -> + async { + sourceSemaphore.withPermit { + try { + val searchResult = if (config.enableLenientSearch) { + smartSearchEngine.smartSearch(source, mangaObj.title) + } else { + smartSearchEngine.normalSearch(source, mangaObj.title) + } + + if(searchResult != null) { + val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id) + val chapters = source.fetchChapterList(localManga).toSingle().await( + Schedulers.io()) + withContext(Dispatchers.IO) { + syncChaptersWithSource(db, chapters, localManga, source) + } + manga.progress.send(validSources.size to processedSources.incrementAndGet()) + localManga to chapters.size + } else { + null + } + } catch(e: CancellationException) { + // Ignore cancellations + throw e + } catch(e: Exception) { + null + } + } + } + }.mapNotNull { it.await() }.maxBy { it.second }?.first + } else { + validSources.forEachIndexed { index, source -> + val searchResult = try { + val searchResult = if (config.enableLenientSearch) { + smartSearchEngine.smartSearch(source, mangaObj.title) + } else { + smartSearchEngine.normalSearch(source, mangaObj.title) + } + + if (searchResult != null) { + val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id) + val chapters = source.fetchChapterList(localManga).toSingle().await( + Schedulers.io()) + withContext(Dispatchers.IO) { + syncChaptersWithSource(db, chapters, localManga, source) + } + localManga + } else null + } catch(e: CancellationException) { + // Ignore cancellations + throw e + } catch(e: Exception) { + null + } + + manga.progress.send(validSources.size to (index + 1)) + + if(searchResult != null) return@async searchResult + } + + null + } + }.await() + } catch(e: CancellationException) { + // Ignore canceled migrations + continue + } + + if(result != null && result.thumbnail_url == null) { + try { + val newManga = sourceManager.getOrStub(result.source) + .fetchMangaDetails(result) + .toSingle() + .await() + result.copyFrom(newManga) + + db.insertManga(result).executeAsBlocking() + } catch(e: CancellationException) { + // Ignore cancellations + throw e + } catch(e: Exception) { + } + } + + manga.searchResult.initialize(result?.id) + } + } + } + + override fun onDestroy() { + super.onDestroy() + + activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + } + + override fun enableButtons() { + activity?.invalidateOptionsMenu() + } + + override fun removeManga(position: Int) { + val ids = config?.mangaIds?.toMutableList() ?: return + ids.removeAt(position) + migratingManga?.removeAt(position) + config.mangaIds = ids + } + + override fun noMigration() { + activity?.toast(R.string.no_migrations) + router.popCurrentController() + } + + override fun onMenuItemClick(position: Int, item: MenuItem) { + + when (item.itemId) { + R.id.action_search_manually -> { + launchUI { + val manga = adapter?.getItem(position) ?: return@launchUI + selectedPosition = position + val searchController = SearchController(manga.manga.manga()) + searchController.targetController = this@MigrationListController + router.pushController(searchController.withFadeTransaction()) + } + } + R.id.action_skip -> adapter?.removeManga(position) + R.id.action_migrate_now -> adapter?.migrateManga(position, false) + R.id.action_copy_now -> adapter?.migrateManga(position, true) + } + } + + fun useMangaForMigration(manga: Manga, source: Source) { + val firstIndex = selectedPosition ?: return + val migratingManga = adapter?.getItem(firstIndex) ?: return + migratingManga.showSpinner() + launchUI { + val result = CoroutineScope(migratingManga.manga.migrationJob).async { + val localManga = smartSearchEngine.networkToLocalManga(manga, source.id) + val chapters = source.fetchChapterList(localManga).toSingle().await( + Schedulers.io() + ) + withContext(Dispatchers.IO) { + syncChaptersWithSource(db, chapters, localManga, source) + } + localManga + }.await() + + try { + val newManga = + sourceManager.getOrStub(result.source).fetchMangaDetails(result).toSingle() + .await() + result.copyFrom(newManga) + + db.insertManga(result).executeAsBlocking() + } catch (e: CancellationException) { + // Ignore cancellations + throw e + } catch (e: Exception) { + } + + migratingManga.manga.searchResult.set(result.id) + adapter?.notifyDataSetChanged() + } + } + + fun migrateMangas() { + launchUI { + adapter?.performMigrations(false) + router.popCurrentController() + } + } + + fun copyMangas() { + launchUI { + adapter?.performMigrations(true) + router.popCurrentController() + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.migration_list, menu) + } + + override fun onPrepareOptionsMenu(menu: Menu) { + // Initialize menu items. + + val allMangasDone = adapter?.allMangasDone() ?: return + + val menuCopy = menu.findItem(R.id.action_copy_manga) + val menuMigrate = menu.findItem(R.id.action_migrate_manga) + + if (adapter?.itemCount == 1) { + menuMigrate.icon = VectorDrawableCompat.create( + resources!!, R.drawable.ic_done, null + ) + } + val translucentWhite = ColorUtils.setAlphaComponent(Color.WHITE, 127) + menuCopy.icon?.setTint(if (allMangasDone) Color.WHITE else translucentWhite) + menuMigrate?.icon?.setTint(if (allMangasDone) Color.WHITE else translucentWhite) + menuCopy.isEnabled = allMangasDone + menuMigrate.isEnabled = allMangasDone + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val itemsCount = adapter?.itemCount ?: 0 + val mangasSkipped = adapter?.mangasSkipped() ?: 0 + when (item.itemId) { + R.id.action_copy_manga -> MigrationMangaDialog(this, true, itemsCount, mangasSkipped) + .showDialog(router) + R.id.action_migrate_manga -> MigrationMangaDialog(this, false, itemsCount, mangasSkipped) + .showDialog(router) + else -> return super.onOptionsItemSelected(item) + } + return true + } + + companion object { + const val CONFIG_EXTRA = "config_extra" + + fun create(config: MigrationProcedureConfig): MigrationListController { + return MigrationListController(Bundle().apply { + putParcelable(CONFIG_EXTRA, config) + }) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureAdapter.kt index 17a256883c..08df43f241 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureAdapter.kt @@ -4,7 +4,6 @@ import android.view.View import android.view.ViewGroup import androidx.viewpager.widget.PagerAdapter import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.google.gson.Gson import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga @@ -40,7 +39,6 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController, val migratingManga: List, override val coroutineContext: CoroutineContext) : PagerAdapter(), CoroutineScope { private val db: DatabaseHelper by injectLazy() - private val gson: Gson by injectLazy() private val sourceManager: SourceManager by injectLazy() override fun isViewFromObject(p0: View, p1: Any): Boolean { @@ -55,7 +53,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController, container.addView(view) view.skip_migration.setOnClickListener { - controller.nextMigration() + //controller.nextMigration() } val viewTag = ViewTag(coroutineContext) @@ -81,26 +79,26 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController, } suspend fun performMigration(manga: MigratingManga) { - if(!manga.searchResult.initialized) { - return - } + if(!manga.searchResult.initialized) { + return + } - val toMangaObj = db.getManga(manga.searchResult.get() ?: return).executeAsBlocking() ?: return + val toMangaObj = db.getManga(manga.searchResult.get() ?: return).executeAsBlocking() ?: return - withContext(Dispatchers.IO) { - migrateMangaInternal( + withContext(Dispatchers.IO) { + migrateMangaInternal( manga.manga() ?: return@withContext, toMangaObj, - !(controller.config?.copy ?: false) - ) - } + false + ) + } } private fun migrateMangaInternal(prevManga: Manga, manga: Manga, replace: Boolean) { val config = controller.config ?: return - db.inTransaction { + //db.inTransaction { // Update chapters read if (MigrationFlags.hasChapters(controller.config.migrationFlags)) { val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking() @@ -141,7 +139,7 @@ class MigrationProcedureAdapter(val controller: MigrationProcedureController, // SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title db.updateMangaTitle(manga).executeAsBlocking() - } + //} } fun View.setupView(tag: ViewTag, migratingManga: MigratingManga) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureConfig.kt index d11b70bcc6..f5235c06a5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureConfig.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureConfig.kt @@ -5,11 +5,10 @@ import kotlinx.android.parcel.Parcelize @Parcelize data class MigrationProcedureConfig( - val mangaIds: List, + var mangaIds: List, val targetSourceIds: List, val useSourceWithMostChapters: Boolean, val enableLenientSearch: Boolean, val migrationFlags: Int, - val copy: Boolean, val extraSearchParams: String? ): Parcelable \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureController.kt index e3f14e96d7..eed24715bb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcedureController.kt @@ -150,8 +150,7 @@ class MigrationProcedureController(bundle: Bundle? = null) : BaseController(bund async { sourceSemaphore.withPermit { try { - val searchResult = if (config?.enableLenientSearch == - true) { + val searchResult = if (config.enableLenientSearch) { smartSearchEngine.smartSearch(source, mangaObj.title) } else { smartSearchEngine.normalSearch(source, mangaObj.title) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt new file mode 100644 index 0000000000..888a1e2807 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt @@ -0,0 +1,144 @@ +package eu.kanade.tachiyomi.ui.migration.manga.process + +import android.content.Context +import android.view.MenuItem +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.models.MangaCategory +import eu.kanade.tachiyomi.ui.migration.MigrationFlags +import eu.kanade.tachiyomi.util.launchUI +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.isActive +import kotlinx.coroutines.withContext +import uy.kohesive.injekt.injectLazy + +class MigrationProcessAdapter( + val controller: MigrationListController, + context: Context +) : FlexibleAdapter(null, controller, true) { + + + private val db: DatabaseHelper by injectLazy() + var items: List = emptyList() + + val menuItemListener: MigrationProcessInterface = controller + + override fun updateDataSet(items: List?) { + this.items = items ?: emptyList() + super.updateDataSet(items) + } + + fun indexOf(item: MigrationProcessItem): Int { + return items.indexOf(item) + } + + interface MigrationProcessInterface { + fun onMenuItemClick(position: Int, item: MenuItem) + fun enableButtons() + fun removeManga(position: Int) + fun noMigration() + } + + fun sourceFinished() { + if (mangasSkipped() == itemCount || itemCount == 0) menuItemListener.noMigration() + if (allMangasDone()) menuItemListener.enableButtons() + } + + fun allMangasDone() = (items.all { it.manga.searchResult.initialized || !it.manga.migrationJob + .isActive } && items.any { it.manga + .searchResult.content != null }) + + fun mangasSkipped() = (items.count { (!it.manga.searchResult.initialized || it.manga + .searchResult.content == null) && !it.manga.migrationJob.isActive }) + + suspend fun performMigrations(copy: Boolean) { + withContext(Dispatchers.IO) { + db.inTransaction { + currentItems.forEach { migratingManga -> + val manga = migratingManga.manga + if (manga.searchResult.initialized) { + val toMangaObj = + db.getManga(manga.searchResult.get() ?: return@forEach).executeAsBlocking() + ?: return@forEach + migrateMangaInternal( + manga.manga() ?: return@forEach, + toMangaObj, + !copy) + } + } + } + } + } + + fun migrateManga(position: Int, copy: Boolean) { + launchUI { + val manga = getItem(position)?.manga ?: return@launchUI + db.inTransaction { + val toMangaObj = db.getManga(manga.searchResult.get() ?: return@launchUI).executeAsBlocking() + ?: return@launchUI + migrateMangaInternal( + manga.manga() ?: return@launchUI, toMangaObj, !copy + ) + } + removeManga(position) + } + } + + fun removeManga(position: Int) { + menuItemListener.removeManga(position) + getItem(position)?.manga?.migrationJob?.cancel() + removeItem(position) + items = currentItems + sourceFinished() + } + + private fun migrateMangaInternal(prevManga: Manga, + manga: Manga, + replace: Boolean) { + if (controller.config == null) return + //db.inTransaction { + // Update chapters read + if (MigrationFlags.hasChapters(controller.config.migrationFlags)) { + 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 (MigrationFlags.hasCategories(controller.config.migrationFlags)) { + val categories = db.getCategoriesForManga(prevManga).executeAsBlocking() + val mangaCategories = categories.map { MangaCategory.create(manga, it) } + db.setMangaCategories(mangaCategories, listOf(manga)) + } + // Update track + if (MigrationFlags.hasTracks(controller.config.migrationFlags)) { + 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() + //} + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt new file mode 100644 index 0000000000..ac3749dadb --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt @@ -0,0 +1,173 @@ +package eu.kanade.tachiyomi.ui.migration.manga.process + +import android.view.View +import android.widget.PopupMenu +import com.bumptech.glide.load.engine.DiskCacheStrategy +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.glide.GlideApp +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder +import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.util.getResourceColor +import eu.kanade.tachiyomi.util.gone +import eu.kanade.tachiyomi.util.launchUI +import eu.kanade.tachiyomi.util.setVectorCompat +import eu.kanade.tachiyomi.util.visible +import kotlinx.android.synthetic.main.migration_new_manga_card.view.* +import kotlinx.android.synthetic.main.migration_new_process_item.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import uy.kohesive.injekt.injectLazy +import java.text.DecimalFormat + +class MigrationProcessHolder( + private val view: View, + private val adapter: MigrationProcessAdapter +) : BaseFlexibleViewHolder(view, adapter) { + + private val db: DatabaseHelper by injectLazy() + private val sourceManager: SourceManager by injectLazy() + + init { + // We need to post a Runnable to show the popup to make sure that the PopupMenu is + // correctly positioned. The reason being that the view may change position before the + // PopupMenu is shown. + migration_menu.setOnClickListener { it.post { showPopupMenu(it) } } + skip_manga.setOnClickListener { it.post { adapter.removeManga(adapterPosition) } } + } + + fun bind(item: MigrationProcessItem) { + launchUI { + val manga = item.manga.manga() + val source = item.manga.mangaSource() + + migration_menu.setVectorCompat(R.drawable.ic_more_vert_black_24dp, view.context.getResourceColor(R.attr.icon_color)) + skip_manga.setVectorCompat(R.drawable.baseline_close_24, view.context.getResourceColor(R + .attr.icon_color)) + migration_menu.gone() + if (manga != null) { + withContext(Dispatchers.Main) { + migration_manga_card_from.loading_group.gone() + attachManga(migration_manga_card_from, manga, source) + migration_manga_card_from.setOnClickListener { + adapter.controller.router.pushController( + MangaController( + manga, + true + ).withFadeTransaction() + ) + } + } + + /*launchUI { + item.manga.progress.asFlow().collect { (max, progress) -> + withContext(Dispatchers.Main) { + migration_manga_card_to.search_progress.let { progressBar -> + progressBar.max = max + progressBar.progress = progress + } + } + } + }*/ + + val searchResult = item.manga.searchResult.get()?.let { + db.getManga(it).executeAsBlocking() + } + val resultSource = searchResult?.source?.let { + sourceManager.get(it) + } + withContext(Dispatchers.Main) { + if (searchResult != null && resultSource != null) { + migration_manga_card_to.loading_group.gone() + attachManga(migration_manga_card_to, searchResult, resultSource) + migration_manga_card_to.setOnClickListener { + adapter.controller.router.pushController( + MangaController( + searchResult, true + ).withFadeTransaction() + ) + } + } else { + migration_manga_card_to.loading_group.gone() + migration_manga_card_to.title.text = "No Alternatives Found" + } + migration_menu.visible() + skip_manga.gone() + adapter.sourceFinished() + } + } + } + } + + fun showSpinner() { + migration_manga_card_to.loading_group.visible() + } + + fun attachManga(view: View, manga: Manga, source: Source) { + view.loading_group.gone() + GlideApp.with(view.context.applicationContext) + .load(manga) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .centerCrop() + .into(view.thumbnail) + + view.title.text = if (manga.title.isBlank()) { + view.context.getString(R.string.unknown) + } else { + manga.title + } + + view.gradient.visible() + view.manga_source_label.text = /*if (source.id == MERGED_SOURCE_ID) { + MergedSource.MangaConfig.readFromUrl(gson, manga.url).children.map { + sourceManager.getOrStub(it.source).toString() + }.distinct().joinToString() + } else {*/ + source.toString() + // } + + val mangaChapters = db.getChapters(manga).executeAsBlocking() + view.manga_chapters.visible() + view.manga_chapters.text = mangaChapters.size.toString() + val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f + + if (latestChapter > 0f) { + view.manga_last_chapter_label.text = view.context.getString(R.string.latest_x, + DecimalFormat("#.#").format(latestChapter)) + } else { + view.manga_last_chapter_label.setText(R.string.unknown) + } + } + + private fun showPopupMenu(view: View) { + val item = adapter.getItem(adapterPosition) ?: return + + // Create a PopupMenu, giving it the clicked view for an anchor + val popup = PopupMenu(view.context, view) + + // Inflate our menu resource into the PopupMenu's Menu + popup.menuInflater.inflate(R.menu.migration_single, popup.menu) + + val mangas = item.manga + + popup.menu.findItem(R.id.action_search_manually).isVisible = true + // Hide download and show delete if the chapter is downloaded + if (mangas.searchResult.content != null) { + popup.menu.findItem(R.id.action_migrate_now).isVisible = true + popup.menu.findItem(R.id.action_copy_now).isVisible = true + } + + // Set a listener so we are notified if a menu item is clicked + popup.setOnMenuItemClickListener { menuItem -> + adapter.menuItemListener.onMenuItemClick(adapterPosition, menuItem) + true + } + + // Finally show the PopupMenu + popup.show() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessItem.kt new file mode 100644 index 0000000000..45bff997fd --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessItem.kt @@ -0,0 +1,48 @@ +package eu.kanade.tachiyomi.ui.migration.manga.process + +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 +import eu.kanade.tachiyomi.R +import exh.ui.migration.manga.process.MigratingManga + +class MigrationProcessItem(val manga: MigratingManga) : + AbstractFlexibleItem() { + + var holder:MigrationProcessHolder? = null + override fun getLayoutRes(): Int { + return R.layout.migration_new_process_item + } + + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): MigrationProcessHolder { + return MigrationProcessHolder(view, adapter as MigrationProcessAdapter) + } + + override fun bindViewHolder(adapter: FlexibleAdapter>, + holder: MigrationProcessHolder, + position: Int, + payloads: MutableList?) { + + this.holder = holder + holder.bind(this) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other is MigrationProcessItem) { + return manga.mangaId == other.manga.mangaId + } + return false + } + + fun showSpinner() { + holder?.showSpinner() + } + + override fun hashCode(): Int { + return manga.mangaId.hashCode() + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt b/app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt index 5146787989..4c99e8e8ba 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/DeferredField.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.sync.withLock class DeferredField { @Volatile - private var content: T? = null + var content: T? = null @Volatile var initialized = false @@ -32,6 +32,14 @@ class DeferredField { mutex.unlock() } + fun set(content: T) { + mutex.tryLock() + this.content = content + initialized = true + // Notify current listeners + mutex.unlock() + } + /** * Will only suspend if !initialized. */ diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml new file mode 100644 index 0000000000..58d1a85f9e --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_right_black_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/migration_design_controller.xml b/app/src/main/res/layout/migration_design_controller.xml index 9b531d356f..a97ce39e90 100644 --- a/app/src/main/res/layout/migration_design_controller.xml +++ b/app/src/main/res/layout/migration_design_controller.xml @@ -114,30 +114,6 @@ android:gravity="start|center_vertical" 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" - android:focusable="true" /> - - - - + app:constraint_referenced_ids="migration_mode,use_smart_search,fuzzy_search,action_copy_manga,extra_search_param_desc,mig_tracking,textView,mig_chapters,copy_manga_desc,textView2,prioritize_chapter_count,mig_categories,extra_search_param" /> \ No newline at end of file diff --git a/app/src/main/res/layout/migration_list_controller.xml b/app/src/main/res/layout/migration_list_controller.xml new file mode 100644 index 0000000000..7abb2eba76 --- /dev/null +++ b/app/src/main/res/layout/migration_list_controller.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/migration_new_manga_card.xml b/app/src/main/res/layout/migration_new_manga_card.xml new file mode 100644 index 0000000000..82e40e0b6e --- /dev/null +++ b/app/src/main/res/layout/migration_new_manga_card.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/migration_new_process_item.xml b/app/src/main/res/layout/migration_new_process_item.xml new file mode 100644 index 0000000000..1905caec63 --- /dev/null +++ b/app/src/main/res/layout/migration_new_process_item.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/migration_list.xml b/app/src/main/res/menu/migration_list.xml new file mode 100644 index 0000000000..b19ff0b7bc --- /dev/null +++ b/app/src/main/res/menu/migration_list.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/migration_single.xml b/app/src/main/res/menu/migration_single.xml new file mode 100644 index 0000000000..af3ab4772c --- /dev/null +++ b/app/src/main/res/menu/migration_single.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cc6e22c46b..18dfd5163d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -104,6 +104,9 @@ Back Forward Auto-check for updates + Search manually + Migrate now + Copy now Deleting… @@ -416,6 +419,10 @@ Download all Download unread Are you sure you want to delete selected chapters? + Migrate %1$d%2$s mangas? + Copy %1$d%2$s mangas? + (skipping %1$d) + No manga migrated Tracking @@ -486,6 +493,7 @@ Migrate Copy Migrating… + Latest: %1$s An error occurred while downloading chapters. You can try again in the downloads section